修复vad检测, 默认使能Ring 中断
author孙春阳 <snickersun@163.com>
Fri, 24 Oct 2025 11:13:56 +0000 (19:13 +0800)
committer孙春阳 <snickersun@163.com>
Fri, 24 Oct 2025 11:13:56 +0000 (19:13 +0800)
code/sdk/zhihui/imsl/src/vad_server.c
code/sdk/zhihui/minimp3/include/mp3_log.h
code/sdk/zhihui/minimp3/src/mp3_decoder.c
code/sdk/zhihui/minimp3/test/Makefile [new file with mode: 0644]
code/sdk/zhihui/minimp3/test/mp3_to_wav.c [new file with mode: 0644]
code/sdk/zhihui/ppp/src/ppp_api.c
code/sdk/zhihui/ppp/src/ppp_power_ctl.c
code/sdk/zhihui/ppp/src/ppp_serial.c

index d9baff7..a93835a 100644 (file)
@@ -24,9 +24,9 @@
 #define VAD_QUEUE_SIZE 10
 #define VAD_QUEUE_MSG_SIZE sizeof(vad_process_msg_t)
 
-/* 音频数据缓冲区大小 */
-#define VAD_AUDIO_BUFFER_SIZE (64 * 1024)  /* 64KB,用于存储待处理的音频数据 */
-#define VAD_PCM_FRAME_SIZE 160  /* 10ms @ 16kHz = 160 samples */
+/* VAD 处理参数 */
+// ⚠️ 注意:VAD 库支持可变长度输入,推荐每次 < 100ms(imsl_vad.h 第 93 行)
+// 因此无需固定帧大小和缓冲区,直接传入 MP3 解码后的 PCM 数据即可
 
 /* VAD 服务器状态 */
 typedef struct {
@@ -36,10 +36,6 @@ typedef struct {
     bool is_initialized;
     vad_status_callback_t status_callback;
     
-    /* 音频缓冲区 */
-    uint8_t *audio_buffer;
-    size_t buffer_used;
-    
 #ifdef MINI_MP3
     /* MP3 解码相关 */
     bool mp3_decoder_initialized;
@@ -55,8 +51,6 @@ static vad_server_t g_vad_server = {
     .is_running = false,
     .is_initialized = false,
     .status_callback = NULL,
-    .audio_buffer = NULL,
-    .buffer_used = 0,
 #ifdef MINI_MP3
     .mp3_decoder_initialized = false,
     .mp3_detected_channels = 1,      /* 默认:1声道(单声道) */
@@ -95,22 +89,47 @@ static void vad_mp3_pcm_callback(const int16_t *pcm_data, int samples, int chann
             IMSL_INFO("[VAD_SERVER] Info: MP3 has %d channels, will convert to mono\n", channels);
         }
     }
-    
-    /* 处理 PCM 数据(如果是立体声,每隔一个样本取一个) */
+    IMSL_INFO("[VAD_SERVER] vad_mp3_pcm_callback: channels=%d, samples=%d\n", channels, samples);   
+    /* 处理 PCM 数据(如果是立体声,需要转换为单声道) */
     if (channels == 1) {
+        /* 单声道:直接处理 */
         vad_process_pcm_data(pcm_data, samples);
+        // AI_SendAudioDataToQueue(pcm_data, samples * sizeof(int16_t));
     } else {
-        /* 立体声转单声道:只取左声道 */
+        /* ⚠️ 立体声/多声道转单声道:批量转换后处理 */
+        /* 分配临时缓冲区存储单声道数据 */
+        int16_t *mono_buffer = (int16_t *)malloc(samples * sizeof(int16_t));
+        if (!mono_buffer) {
+            IMSL_ERROR("[VAD_SERVER] Error: Failed to allocate mono buffer for %d samples\n", samples);
+            return;
+        }
+        
+        /* 批量转换:只取左声道(交错存储的第一个声道) */
         for (int i = 0; i < samples; i++) {
-            int16_t mono_sample = pcm_data[i * channels];
-            vad_process_pcm_data(&mono_sample, 1);
+            mono_buffer[i] = pcm_data[i * channels];  // 取第一个声道
+        }
+        
+        /* 批量处理转换后的单声道数据 */
+        vad_process_pcm_data(mono_buffer, samples);
+        // AI_SendAudioDataToQueue(mono_buffer, samples * sizeof(int16_t));
+        
+        /* 释放临时缓冲区 */
+        free(mono_buffer);
+        
+        /* 打印转换信息(前3次) */
+        static int stereo_convert_count = 0;
+        if (stereo_convert_count < 3) {
+            IMSL_INFO("[VAD_SERVER] Converted %d-channel audio to mono (%d samples)\n", 
+                     channels, samples);
+            stereo_convert_count++;
         }
     }
 }
 #endif
 
 /**
- * @brief 处理 PCM 数据,按帧(160 samples)送给 VAD
+ * @brief 处理 PCM 数据,直接送给 VAD(简化版)
+ * @note VAD 库支持可变长度输入(推荐 < 100ms),无需固定切分
  */
 static errcode_t vad_process_pcm_data(const int16_t *pcm_data, size_t sample_count)
 {
@@ -120,84 +139,50 @@ static errcode_t vad_process_pcm_data(const int16_t *pcm_data, size_t sample_cou
         return ERRCODE_FAIL;
     }
     
-    /* 检查缓冲区是否有效 */
-    if (!g_vad_server.audio_buffer) {
-        IMSL_ERROR("[VAD_SERVER] Error: Audio buffer is NULL\n");
-        return ERRCODE_FAIL;
-    }
-    
-    /* 周期性打印缓冲区状态(前10次,之后每100次) */
     pcm_call_count++;
-    if (pcm_call_count <= 10 || pcm_call_count % 100 == 0) {
-        IMSL_DEBUG("[VAD_SERVER] vad_process_pcm_data #%d: samples=%zu, buffer_before=%zu\n",
-               pcm_call_count, sample_count, g_vad_server.buffer_used);
+    
+    /* 检查数据长度是否合理(VAD 推荐 < 100ms)*/
+    const size_t MAX_SAMPLES_PER_CALL = 1600;  // 100ms @ 16kHz
+    if (sample_count > MAX_SAMPLES_PER_CALL) {
+        IMSL_WARN("[VAD_SERVER] Warning: PCM data too long (%zu samples > %zu), consider splitting\n",
+                 sample_count, MAX_SAMPLES_PER_CALL);
     }
     
-    /* 将 PCM 数据追加到缓冲区 */
-    size_t bytes_to_copy = sample_count * sizeof(int16_t);
-    if (g_vad_server.buffer_used + bytes_to_copy > VAD_AUDIO_BUFFER_SIZE) {
-        IMSL_ERROR("[VAD_SERVER] Error: Audio buffer overflow (used=%zu, to_copy=%zu, max=%d)\n",
-               g_vad_server.buffer_used, bytes_to_copy, VAD_AUDIO_BUFFER_SIZE);
-        return ERRCODE_FAIL;
+    /* 周期性打印处理信息 */
+    if (pcm_call_count <= 10 || pcm_call_count % 100 == 0) {
+        IMSL_DEBUG("[VAD_SERVER] vad_process_pcm_data #%d: samples=%zu (%.1f ms @ 16kHz)\n",
+               pcm_call_count, sample_count, (float)sample_count / 16.0f);
     }
     
-    memcpy(g_vad_server.audio_buffer + g_vad_server.buffer_used, 
-           pcm_data, bytes_to_copy);
-    g_vad_server.buffer_used += bytes_to_copy;
+    /* ⚠️ 简化:直接传给 VAD,无需缓冲区切分 */
+    run_vad(pcm_data, (int)sample_count);
     
-    /* 按 VAD 帧大小(160 samples = 320 bytes)处理 */
-    size_t frame_bytes = VAD_PCM_FRAME_SIZE * sizeof(int16_t);
-    int16_t *buffer_ptr = (int16_t *)g_vad_server.audio_buffer;
+    /* 检测 VAD 状态 */
+    int status = vad_detect();
     
-    static int frame_count = 0;
-    static int debug_print_count = 0;  /* 用于控制周期性日志 */
+    if (pcm_call_count <= 10 || pcm_call_count % 100 == 0) {
+        IMSL_DEBUG("[VAD_SERVER] VAD status: %d\n", status);
+    }
     
-    while (g_vad_server.buffer_used >= frame_bytes) {
-        /* 调试:打印前几帧的处理信息,之后每100帧打印一次 */
-        bool should_print = (frame_count < 10) || (frame_count % 100 == 0);
-        if (should_print) {
-            IMSL_DEBUG("[VAD_SERVER] Processing VAD frame #%d (buffer_used=%zu, frame_bytes=%zu)\n",
-                   frame_count, g_vad_server.buffer_used, frame_bytes);
-        }
-        
-        /* 运行 VAD */
-        run_vad(buffer_ptr, VAD_PCM_FRAME_SIZE);
+    /* 状态变化时打印日志 */
+    static int last_status = -1;
+    if (status != last_status) {
+        IMSL_INFO("======== [VAD_SERVER] *** VAD status changed: %d -> %d *** ========\n", 
+                 last_status, status);
+        last_status = status;
         
-        /* 检测 VAD 状态 */
-        int status = vad_detect();
-        
-        if (should_print) {
-            IMSL_DEBUG("[VAD_SERVER] Frame #%d VAD status: %d\n", frame_count, status);
-        }
-        frame_count++;
-        
-        /* 状态变化时打印日志 */
-        static int last_status = -1;
-        if (status != last_status) {
-            IMSL_INFO("======== [VAD_SERVER] *** VAD status changed: %d -> %d *** ========\n", last_status, status);
-            last_status = status;
-            
-            /* 如果有回调,通知状态变化 */
-            if (g_vad_server.status_callback) {
-                g_vad_server.status_callback(status);
-            }
-        }
-        
-        /* 如果检测到语音结束,清除 VAD 状态 */
-        if (status == 2) {
-            IMSL_INFO("[VAD_SERVER] Speech ended, clearing VAD state\n");
-            vad_clear();
-        }
-        
-        /* 移动缓冲区数据 */
-        g_vad_server.buffer_used -= frame_bytes;
-        if (g_vad_server.buffer_used > 0) {
-            memmove(g_vad_server.audio_buffer, 
-                   g_vad_server.audio_buffer + frame_bytes,
-                   g_vad_server.buffer_used);
+        /* 如果有回调,通知状态变化 */
+        if (g_vad_server.status_callback) {
+            g_vad_server.status_callback(status);
         }
     }
     
+    /* 如果检测到语音结束,清除 VAD 状态 */
+    if (status == 2) {
+        IMSL_INFO("[VAD_SERVER] Speech ended, clearing VAD state\n");
+        vad_clear();
+    }
+    
     return ERRCODE_SUCC;
 }
 
@@ -220,8 +205,7 @@ static void vad_process_task(void *arg)
         /* 周期性打印统计信息(每10秒) */
         uint32_t current_time = osKernelGetTickCount();
         if (last_stat_time == 0 || (current_time - last_stat_time) > 10000) {
-            IMSL_INFO("[VAD_SERVER] Stats: processed_msgs=%u, buffer_used=%zu\n",
-                   msg_count, g_vad_server.buffer_used);
+            IMSL_INFO("[VAD_SERVER] Stats: processed_msgs=%u\n", msg_count);
             last_stat_time = current_time;
         }
         
@@ -282,11 +266,32 @@ static void vad_process_task(void *arg)
                             }
                         }
                         
-                        /* 喂入 MP3 数据进行解码 */
-                        mp3_decoder_feed_data(msg.data, msg.data_size);
+                        /* ⚠️ 喂入 MP3 数据进行解码(检查返回值) */
+                        mp3_decode_error_t mp3_feed_ret = mp3_decoder_feed_data(msg.data, msg.data_size);
+                        
+                        if (mp3_feed_ret == MP3_DECODE_ERROR_MEMORY) {
+                            /* 缓冲区满,需要等待解码消耗 */
+                            IMSL_WARN("[VAD_SERVER] ⚠️ MP3 decoder buffer full, data may be lost (size=%zu)\n", 
+                                     msg.data_size);
+                            IMSL_WARN("[VAD_SERVER] Consider increasing MP3_STREAM_BUFFER_SIZE or reducing input rate\n");
+                            
+                            /* 尝试延迟后重试一次 */
+                            osDelay(10);  // 等待 10ms 让解码器消耗数据
+                            mp3_feed_ret = mp3_decoder_feed_data(msg.data, msg.data_size);
+                            
+                            if (mp3_feed_ret == MP3_DECODE_ERROR_MEMORY) {
+                                IMSL_ERROR("[VAD_SERVER] ✗ MP3 decoder buffer still full after retry, dropping %zu bytes\n",
+                                          msg.data_size);
+                            } else {
+                                IMSL_INFO("[VAD_SERVER] ✓ MP3 data accepted after retry\n");
+                            }
+                        } else if (mp3_feed_ret != MP3_DECODE_SUCCESS) {
+                            IMSL_ERROR("[VAD_SERVER] ✗ MP3 decoder feed error: %d\n", mp3_feed_ret);
+                        }
                         
                         /* 如果是最后一块数据,刷新解码器 */
                         if (msg.is_last) {
+                            IMSL_INFO("[VAD_SERVER] Last MP3 chunk, flushing decoder\n");
                             mp3_decoder_flush();
                         }
                     }
@@ -303,7 +308,6 @@ static void vad_process_task(void *arg)
             case VAD_MSG_RESET:
                 IMSL_INFO("[VAD_SERVER] Resetting VAD state\n");
                 vad_clear();
-                g_vad_server.buffer_used = 0;
 #ifdef MINI_MP3
                 /* 重置 MP3 格式检测状态 */
                 g_vad_server.mp3_format_detected = false;
@@ -365,16 +369,9 @@ errcode_t vad_server_init(vad_status_callback_t callback)
         return ERRCODE_SUCC;
     }
     
-    IMSL_INFO("[VAD_SERVER] Initializing VAD server...\n");
+    IMSL_INFO("[VAD_SERVER] Initializing VAD server (optimized - no buffer)...\n");
     
-    /* 分配音频缓冲区 */
-    g_vad_server.audio_buffer = (uint8_t *)malloc(VAD_AUDIO_BUFFER_SIZE);
-    if (!g_vad_server.audio_buffer) {
-        IMSL_ERROR("[VAD_SERVER] Error: Failed to allocate audio buffer\n");
-        return ERRCODE_FAIL;
-    }
-    
-    g_vad_server.buffer_used = 0;
+    /* 初始化状态 */
     g_vad_server.status_callback = callback;
     g_vad_server.is_running = true;
 #ifdef MINI_MP3
@@ -396,8 +393,6 @@ errcode_t vad_server_init(vad_status_callback_t callback)
     int vad_ret = init_vad(&vad_config);
     if (vad_ret == 0) {
         IMSL_ERROR("[VAD_SERVER] Error: Failed to initialize VAD library\n");
-        free(g_vad_server.audio_buffer);
-        g_vad_server.audio_buffer = NULL;
         return ERRCODE_FAIL;
     }
     IMSL_INFO("[VAD_SERVER] VAD library initialized (mode=%d)\n", vad_ret);
@@ -419,8 +414,6 @@ errcode_t vad_server_init(vad_status_callback_t callback)
     errcode_t ret = uapi_create_osThread_custom_with_args(&task_cfg);
     if (ret != ERRCODE_SUCC) {
         IMSL_ERROR("[VAD_SERVER] Error: Failed to create VAD task: %d\n", ret);
-        free(g_vad_server.audio_buffer);
-        g_vad_server.audio_buffer = NULL;
         return ret;
     }
     
@@ -434,8 +427,6 @@ errcode_t vad_server_init(vad_status_callback_t callback)
     if (g_vad_server.queue_id == 0xFFFFFFFF) {
         IMSL_ERROR("[VAD_SERVER] Error: Queue creation timeout\n");
         g_vad_server.is_running = false;
-        free(g_vad_server.audio_buffer);
-        g_vad_server.audio_buffer = NULL;
         return ERRCODE_FAIL;
     }
     
@@ -485,12 +476,6 @@ errcode_t vad_server_deinit(void)
         g_vad_server.queue_id = 0;
     }
     
-    /* 释放音频缓冲区 */
-    if (g_vad_server.audio_buffer) {
-        free(g_vad_server.audio_buffer);
-        g_vad_server.audio_buffer = NULL;
-    }
-    
     /* 释放 VAD 库资源 */
     free_vad();
     IMSL_INFO("[VAD_SERVER] VAD library released\n");
index 33e5403..14196b3 100644 (file)
@@ -25,7 +25,7 @@ typedef enum {
 
 /* 默认日志等级:INFO */
 #ifndef MP3_DEFAULT_LOG_LEVEL
-#define MP3_DEFAULT_LOG_LEVEL MP3_LOG_LEVEL_INFO
+#define MP3_DEFAULT_LOG_LEVEL MP3_LOG_LEVEL_WARN
 #endif
 
 /* 全局日志等级变量(可在运行时修改) */
index 463da02..61fdb17 100644 (file)
@@ -61,6 +61,7 @@ static mp3_decoder_t *g_mp3_decoder = NULL;
 static int mp3_decoder_try_decode_frame(void)
 {
     if (!g_mp3_decoder || !g_mp3_decoder->is_initialized) {
+        MP3_ERROR("[MP3_DECODE] Decoder not initialized\n");
         return -1;
     }
     
@@ -68,6 +69,13 @@ static int mp3_decoder_try_decode_frame(void)
         return 0;  // 需要更多数据
     }
     
+    // ⚠️ 诊断:检查缓冲区状态
+    static int decode_attempt = 0;
+    decode_attempt++;
+    
+    MP3_DEBUG("[MP3_DECODE] Attempt #%d: buffer_used=%zu bytes\n", 
+             decode_attempt, g_mp3_decoder->stream_buffer_used);
+    
     int samples = mp3dec_decode_frame(&g_mp3_decoder->decoder,
                                      g_mp3_decoder->stream_buffer,
                                      g_mp3_decoder->stream_buffer_used,
@@ -81,11 +89,46 @@ static int mp3_decoder_try_decode_frame(void)
         
         // 打印第一帧信息
         if (g_mp3_decoder->total_frames_decoded == 1) {
-            MP3_DEBUG("[MP3] First frame decoded:\n");
+            MP3_INFO("[MP3_DECODE] ✓ First frame decoded successfully:\n");
             MP3_INFO("  Channels: %d\n", g_mp3_decoder->frame_info.channels);
             MP3_INFO("  Sample Rate: %d Hz\n", g_mp3_decoder->frame_info.hz);
             MP3_INFO("  Bitrate: %d kbps\n", g_mp3_decoder->frame_info.bitrate_kbps);
             MP3_INFO("  Layer: %d\n", g_mp3_decoder->frame_info.layer);
+            MP3_INFO("  Frame bytes: %d\n", g_mp3_decoder->frame_info.frame_bytes);
+        }
+        
+        // ⚠️ 诊断:定期打印解码信息
+        if (g_mp3_decoder->total_frames_decoded % 100 == 0) {
+            MP3_DEBUG("[MP3_DECODE] Frame #%d: samples=%d, frame_bytes=%d, buffer_remaining=%zu\n",
+                     g_mp3_decoder->total_frames_decoded, samples, 
+                     g_mp3_decoder->frame_info.frame_bytes,
+                     g_mp3_decoder->stream_buffer_used);
+        }
+        
+        // ⚠️ 关键修复:检查 frame_bytes 合法性
+        size_t frame_bytes = g_mp3_decoder->frame_info.frame_bytes;
+        
+        if (frame_bytes == 0) {
+            MP3_WARN("[MP3_DECODE] ⚠️ Warning: frame_bytes=0, samples=%d (frame #%d)\n", 
+                    samples, g_mp3_decoder->total_frames_decoded);
+            // frame_bytes 为 0 但有 samples,这是异常情况
+            // 不移除数据,等待更多输入
+            return samples;
+        }
+        
+        if (frame_bytes > g_mp3_decoder->stream_buffer_used) {
+            MP3_ERROR("[MP3_DECODE] ✗ Error: frame_bytes(%zu) > buffer_used(%zu) (frame #%d)\n",
+                     frame_bytes, g_mp3_decoder->stream_buffer_used,
+                     g_mp3_decoder->total_frames_decoded);
+            // 数据不一致,跳过一些字节尝试重新同步
+            size_t skip = 1;
+            g_mp3_decoder->stream_buffer_used -= skip;
+            if (g_mp3_decoder->stream_buffer_used > 0) {
+                memmove(g_mp3_decoder->stream_buffer,
+                       g_mp3_decoder->stream_buffer + skip,
+                       g_mp3_decoder->stream_buffer_used);
+            }
+            return -1;
         }
         
         // 调用回调函数
@@ -96,25 +139,52 @@ static int mp3_decoder_try_decode_frame(void)
         }
         
         // 移除已解码的数据
-        size_t frame_bytes = g_mp3_decoder->frame_info.frame_bytes;
-        if (frame_bytes > 0 && frame_bytes <= g_mp3_decoder->stream_buffer_used) {
-            g_mp3_decoder->stream_buffer_used -= frame_bytes;
-            if (g_mp3_decoder->stream_buffer_used > 0) {
-                memmove(g_mp3_decoder->stream_buffer,
-                       g_mp3_decoder->stream_buffer + frame_bytes,
-                       g_mp3_decoder->stream_buffer_used);
-            }
+        g_mp3_decoder->stream_buffer_used -= frame_bytes;
+        if (g_mp3_decoder->stream_buffer_used > 0) {
+            memmove(g_mp3_decoder->stream_buffer,
+                   g_mp3_decoder->stream_buffer + frame_bytes,
+                   g_mp3_decoder->stream_buffer_used);
         }
+        
+        MP3_DEBUG("[MP3_DECODE] ✓ Consumed %zu bytes, remaining=%zu bytes\n",
+                 frame_bytes, g_mp3_decoder->stream_buffer_used);
+        
     } else if (samples == 0) {
-        // 需要更多数据,尝试跳过一些字节
+        // ⚠️ 修复:需要更多数据或需要同步
+        // 检查是否有足够的数据来判断
+        if (g_mp3_decoder->stream_buffer_used < 4) {
+            // 数据太少,等待更多输入
+            MP3_DEBUG("[MP3_DECODE] Need more data (only %zu bytes)\n", 
+                     g_mp3_decoder->stream_buffer_used);
+            return 0;
+        }
+        
+        // ⚠️ 诊断:寻找帧同步失败次数
+        static int sync_fail_count = 0;
+        sync_fail_count++;
+        
+        if (sync_fail_count % 10 == 1) {
+            MP3_WARN("[MP3_DECODE] ⚠️ Sync failed %d times, buffer_used=%zu\n",
+                    sync_fail_count, g_mp3_decoder->stream_buffer_used);
+            // 打印缓冲区头部数据用于诊断
+            MP3_DEBUG("[MP3_DECODE] Buffer head: %02X %02X %02X %02X\n",
+                     g_mp3_decoder->stream_buffer[0],
+                     g_mp3_decoder->stream_buffer[1],
+                     g_mp3_decoder->stream_buffer[2],
+                     g_mp3_decoder->stream_buffer[3]);
+        }
+        
+        // 跳过 1 字节尝试重新同步
+        g_mp3_decoder->stream_buffer_used--;
         if (g_mp3_decoder->stream_buffer_used > 0) {
-            g_mp3_decoder->stream_buffer_used--;
-            if (g_mp3_decoder->stream_buffer_used > 0) {
-                memmove(g_mp3_decoder->stream_buffer,
-                       g_mp3_decoder->stream_buffer + 1,
-                       g_mp3_decoder->stream_buffer_used);
-            }
+            memmove(g_mp3_decoder->stream_buffer,
+                   g_mp3_decoder->stream_buffer + 1,
+                   g_mp3_decoder->stream_buffer_used);
         }
+    } else {
+        // samples < 0,解码错误
+        MP3_ERROR("[MP3_DECODE] ✗ Decode error: samples=%d, frame #%d\n",
+                 samples, g_mp3_decoder->total_frames_decoded);
     }
     
     return samples;
@@ -206,18 +276,32 @@ void mp3_decoder_destroy(void)
 mp3_decode_error_t mp3_decoder_feed_data(const uint8_t *data, size_t data_size)
 {
     if (!g_mp3_decoder || !g_mp3_decoder->is_initialized) {
-        MP3_ERROR("[MP3] Error: Decoder not initialized\n");
+        MP3_ERROR("[MP3_FEED] Error: Decoder not initialized\n");
         return MP3_DECODE_ERROR_UNKNOWN;
     }
     
     if (!data || data_size == 0) {
+        MP3_DEBUG("[MP3_FEED] No data to feed (data=%p, size=%zu)\n", data, data_size);
         return MP3_DECODE_SUCCESS;
     }
     
+    // ⚠️ 诊断:记录输入数据
+    static size_t total_fed = 0;
+    static int feed_count = 0;
+    feed_count++;
+    total_fed += data_size;
+    
+    MP3_DEBUG("[MP3_FEED] Feed #%d: size=%zu bytes, total_fed=%zu, buffer_before=%zu\n",
+             feed_count, data_size, total_fed, g_mp3_decoder->stream_buffer_used);
+    
     // 检查缓冲区空间
     size_t available = MP3_STREAM_BUFFER_SIZE - g_mp3_decoder->stream_buffer_used;
+    size_t original_data_size = data_size;
+    
     if (data_size > available) {
-        MP3_WARN("[MP3] Warning: Stream buffer full, processing data in chunks\n");
+        MP3_WARN("[MP3_FEED] ⚠️ Warning: Stream buffer full (used=%zu, available=%zu, request=%zu)\n",
+                g_mp3_decoder->stream_buffer_used, available, data_size);
+        MP3_WARN("[MP3_FEED] ⚠️ Truncating input from %zu to %zu bytes\n", data_size, available);
         data_size = available;  // 只接受能容纳的数据
     }
     
@@ -226,23 +310,55 @@ mp3_decode_error_t mp3_decoder_feed_data(const uint8_t *data, size_t data_size)
            data, data_size);
     g_mp3_decoder->stream_buffer_used += data_size;
     
+    MP3_DEBUG("[MP3_FEED] Buffer after copy: %zu bytes\n", g_mp3_decoder->stream_buffer_used);
+    
+    // ⚠️ 诊断:打印缓冲区头部(第一次喂数据时)
+    if (feed_count == 1) {
+        MP3_DEBUG("[MP3_FEED] First feed buffer head: %02X %02X %02X %02X\n",
+                 g_mp3_decoder->stream_buffer[0],
+                 g_mp3_decoder->stream_buffer[1],
+                 g_mp3_decoder->stream_buffer[2],
+                 g_mp3_decoder->stream_buffer[3]);
+    }
+    
     // 尝试解码所有可能的帧
     int decoded_frames = 0;
+    int decode_errors = 0;
+    
     while (1) {
         int samples = mp3_decoder_try_decode_frame();
         if (samples > 0) {
             decoded_frames++;
         } else if (samples == 0) {
             // 需要更多数据
+            MP3_DEBUG("[MP3_FEED] Need more data to decode (decoded %d frames so far)\n", decoded_frames);
             break;
         } else {
             // 解码错误
-            break;
+            decode_errors++;
+            MP3_WARN("[MP3_FEED] ⚠️ Decode error (decoded %d frames, %d errors)\n", 
+                    decoded_frames, decode_errors);
+            // 继续尝试,可能只是暂时的同步问题
+            if (decode_errors > 10) {
+                MP3_ERROR("[MP3_FEED] ✗ Too many decode errors (%d), stopping\n", decode_errors);
+                break;
+            }
         }
     }
     
     if (decoded_frames > 0) {
-        MP3_DEBUG("[MP3] Decoded %d frames from %zu bytes input\n", decoded_frames, data_size);
+        MP3_INFO("[MP3_FEED] ✓ Feed #%d: decoded %d frames from %zu bytes input (buffer_after=%zu)\n",
+                feed_count, decoded_frames, data_size, g_mp3_decoder->stream_buffer_used);
+    } else {
+        MP3_DEBUG("[MP3_FEED] Feed #%d: no frames decoded (buffer=%zu bytes)\n",
+                 feed_count, g_mp3_decoder->stream_buffer_used);
+    }
+    
+    // ⚠️ 警告:如果数据被截断
+    if (original_data_size > data_size) {
+        MP3_ERROR("[MP3_FEED] ✗ DATA LOSS: %zu bytes dropped due to buffer full\n",
+                 original_data_size - data_size);
+        return MP3_DECODE_ERROR_MEMORY;  // 返回错误,提示上层处理
     }
     
     return MP3_DECODE_SUCCESS;
diff --git a/code/sdk/zhihui/minimp3/test/Makefile b/code/sdk/zhihui/minimp3/test/Makefile
new file mode 100644 (file)
index 0000000..b2eccc2
--- /dev/null
@@ -0,0 +1,94 @@
+# Makefile for MP3 to WAV converter (PC test program)
+# Usage:
+#   make          - Build the test program
+#   make clean    - Clean build files
+#   make test     - Run a test conversion (requires test.mp3)
+#   make help     - Show this help
+
+# Compiler settings
+CC := gcc
+CFLAGS := -Wall -Wextra -O2 -g
+LDFLAGS := -lm
+
+# Directories
+SRC_DIR := .
+INC_DIR := ../include
+BUILD_DIR := build
+
+# Target
+TARGET := $(BUILD_DIR)/mp3_to_wav
+
+# Source files (only test program, minimp3.h is header-only)
+SOURCES := mp3_to_wav.c
+
+# Object files
+OBJECTS := $(SOURCES:%.c=$(BUILD_DIR)/%.o)
+
+# Include paths
+INCLUDES := -I$(INC_DIR)
+
+# Default target
+.PHONY: all
+all: $(BUILD_DIR) $(TARGET)
+       @echo ""
+       @echo "✓ Build complete!"
+       @echo "  Executable: $(TARGET)"
+       @echo ""
+       @echo "Usage: $(TARGET) input.mp3 output.wav"
+       @echo ""
+
+# Create build directory
+$(BUILD_DIR):
+       @mkdir -p $(BUILD_DIR)
+
+# Build target
+$(TARGET): $(OBJECTS)
+       @echo "Linking $@..."
+       @$(CC) $(OBJECTS) -o $@ $(LDFLAGS)
+
+# Compile source files
+$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
+       @echo "Compiling $<..."
+       @$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
+
+# Clean build files
+.PHONY: clean
+clean:
+       @echo "Cleaning build files..."
+       @rm -rf $(BUILD_DIR)
+       @echo "✓ Clean complete!"
+
+# Run test (requires test.mp3 in current directory)
+.PHONY: test
+test: all
+       @echo "Running test conversion..."
+       @if [ -f test.mp3 ]; then \
+               $(TARGET) test.mp3 test.wav; \
+       else \
+               echo "Error: test.mp3 not found"; \
+               echo "Please provide a test.mp3 file in the test directory"; \
+               exit 1; \
+       fi
+
+# Help
+.PHONY: help
+help:
+       @echo "MP3 to WAV Converter - Makefile Help"
+       @echo ""
+       @echo "Available targets:"
+       @echo "  make          - Build the test program"
+       @echo "  make clean    - Clean build files"
+       @echo "  make test     - Run test conversion (requires test.mp3)"
+       @echo "  make help     - Show this help"
+       @echo ""
+       @echo "Usage example:"
+       @echo "  make"
+       @echo "  ./build/mp3_to_wav input.mp3 output.wav"
+       @echo ""
+       @echo "Compiler: $(CC)"
+       @echo "Flags: $(CFLAGS)"
+       @echo ""
+
+# Phony targets
+.PHONY: all clean test help
+
diff --git a/code/sdk/zhihui/minimp3/test/mp3_to_wav.c b/code/sdk/zhihui/minimp3/test/mp3_to_wav.c
new file mode 100644 (file)
index 0000000..4a035df
--- /dev/null
@@ -0,0 +1,277 @@
+/**
+ * @file mp3_to_wav.c
+ * @brief MP3 to WAV converter using minimp3 library
+ * @note PC test program - no embedded system dependencies
+ * 
+ * Usage: ./mp3_to_wav input.mp3 output.wav
+ */
+
+#define MINIMP3_IMPLEMENTATION
+#include "../include/minimp3.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#define MP3_READ_BUFFER_SIZE (128 * 1024)  // 128KB MP3 read buffer
+#define PCM_FRAME_BUFFER_SIZE (MINIMP3_MAX_SAMPLES_PER_FRAME * 2)  // Per-frame PCM buffer
+
+/**
+ * @brief Write WAV file header
+ */
+static int write_wav_header(FILE *file, int sample_rate, int channels, int samples) {
+    int data_size = samples * channels * sizeof(int16_t);
+    int file_size = 36 + data_size;
+    
+    // RIFF header
+    fwrite("RIFF", 1, 4, file);
+    fwrite(&file_size, 4, 1, file);
+    fwrite("WAVE", 1, 4, file);
+    
+    // fmt chunk
+    fwrite("fmt ", 1, 4, file);
+    int fmt_size = 16;
+    fwrite(&fmt_size, 4, 1, file);
+    int16_t audio_format = 1; // PCM
+    int16_t num_channels = (int16_t)channels;
+    fwrite(&audio_format, 2, 1, file);
+    fwrite(&num_channels, 2, 1, file);
+    fwrite(&sample_rate, 4, 1, file);
+    int byte_rate = sample_rate * channels * sizeof(int16_t);
+    fwrite(&byte_rate, 4, 1, file);
+    int16_t block_align = (int16_t)(channels * sizeof(int16_t));
+    fwrite(&block_align, 2, 1, file);
+    int16_t bits_per_sample = 16;
+    fwrite(&bits_per_sample, 2, 1, file);
+    
+    // data chunk
+    fwrite("data", 1, 4, file);
+    fwrite(&data_size, 4, 1, file);
+    
+    return 0;
+}
+
+/**
+ * @brief Convert MP3 file to WAV file (streaming mode)
+ * @note Decodes and writes PCM data frame by frame to minimize memory usage
+ */
+int mp3_to_wav(const char *mp3_file, const char *wav_file) {
+    FILE *mp3_fp = NULL;
+    FILE *wav_fp = NULL;
+    uint8_t *mp3_buffer = NULL;
+    int16_t *pcm_frame_buffer = NULL;
+    int ret = -1;
+    
+    printf("[MP3→WAV] Converting: %s -> %s\n", mp3_file, wav_file);
+    printf("[INFO ] Using streaming mode (low memory usage)\n");
+    
+    // Open MP3 file
+    mp3_fp = fopen(mp3_file, "rb");
+    if (!mp3_fp) {
+        fprintf(stderr, "[ERROR] Cannot open MP3 file: %s\n", mp3_file);
+        goto cleanup;
+    }
+    
+    // Get MP3 file size
+    fseek(mp3_fp, 0, SEEK_END);
+    long mp3_size = ftell(mp3_fp);
+    fseek(mp3_fp, 0, SEEK_SET);
+    
+    printf("[INFO ] MP3 file size: %ld bytes (%.2f MB)\n", 
+           mp3_size, mp3_size / 1024.0 / 1024.0);
+    
+    // Allocate buffers (minimal memory usage)
+    mp3_buffer = (uint8_t*)malloc(MP3_READ_BUFFER_SIZE);
+    pcm_frame_buffer = (int16_t*)malloc(PCM_FRAME_BUFFER_SIZE * sizeof(int16_t));
+    
+    if (!mp3_buffer || !pcm_frame_buffer) {
+        fprintf(stderr, "[ERROR] Memory allocation failed\n");
+        goto cleanup;
+    }
+    
+    printf("[INFO ] Allocated buffers: MP3=%d KB, PCM=%zu KB\n",
+           MP3_READ_BUFFER_SIZE / 1024,
+           (PCM_FRAME_BUFFER_SIZE * sizeof(int16_t)) / 1024);
+    
+    // Open WAV file and reserve space for header
+    wav_fp = fopen(wav_file, "wb");
+    if (!wav_fp) {
+        fprintf(stderr, "[ERROR] Cannot create WAV file: %s\n", wav_file);
+        goto cleanup;
+    }
+    
+    // Write dummy header (44 bytes) - will be updated later
+    uint8_t dummy_header[44] = {0};
+    if (fwrite(dummy_header, 1, 44, wav_fp) != 44) {
+        fprintf(stderr, "[ERROR] Failed to write WAV header placeholder\n");
+        goto cleanup;
+    }
+    
+    // Initialize decoder
+    mp3dec_t mp3d;
+    mp3dec_init(&mp3d);
+    
+    // Streaming decode and write
+    int total_samples = 0;
+    int total_frames = 0;
+    int sample_rate = 0;
+    int channels = 0;
+    size_t mp3_buffer_used = 0;
+    size_t total_mp3_read = 0;
+    
+    printf("[INFO ] Decoding and writing PCM data...\n");
+    
+    while (1) {
+        // Read more MP3 data if buffer is running low
+        if (mp3_buffer_used < MP3_READ_BUFFER_SIZE / 2 && total_mp3_read < (size_t)mp3_size) {
+            // Move remaining data to beginning of buffer
+            if (mp3_buffer_used > 0) {
+                memmove(mp3_buffer, mp3_buffer + (MP3_READ_BUFFER_SIZE - mp3_buffer_used), 
+                       mp3_buffer_used);
+            }
+            
+            // Read more data
+            size_t to_read = MP3_READ_BUFFER_SIZE - mp3_buffer_used;
+            size_t bytes_read = fread(mp3_buffer + mp3_buffer_used, 1, to_read, mp3_fp);
+            mp3_buffer_used += bytes_read;
+            total_mp3_read += bytes_read;
+        }
+        
+        // Try to decode one frame
+        if (mp3_buffer_used == 0) {
+            break;  // No more data
+        }
+        
+        mp3dec_frame_info_t info;
+        int samples = mp3dec_decode_frame(&mp3d, mp3_buffer, mp3_buffer_used, 
+                                         pcm_frame_buffer, &info);
+        
+        if (samples == 0) {
+            // Need more data or end of stream
+            if (total_mp3_read >= (size_t)mp3_size) {
+                // Already read all MP3 data, stop
+                break;
+            }
+            // Skip 1 byte and try again
+            if (mp3_buffer_used > 0) {
+                memmove(mp3_buffer, mp3_buffer + 1, mp3_buffer_used - 1);
+                mp3_buffer_used--;
+            }
+            continue;
+        }
+        
+        if (samples < 0) {
+            fprintf(stderr, "[ERROR] Decode error\n");
+            break;
+        }
+        
+        // Save first frame info
+        if (total_frames == 0) {
+            sample_rate = info.hz;
+            channels = info.channels;
+            printf("[INFO ] First frame decoded:\n");
+            printf("        - Sample rate: %d Hz\n", sample_rate);
+            printf("        - Channels: %d\n", channels);
+            printf("        - Bitrate: %d kbps\n", info.bitrate_kbps);
+            printf("        - Layer: %d\n", info.layer);
+        }
+        
+        // Write PCM data to WAV file immediately
+        size_t pcm_bytes = samples * channels * sizeof(int16_t);
+        size_t written = fwrite(pcm_frame_buffer, 1, pcm_bytes, wav_fp);
+        if (written != pcm_bytes) {
+            fprintf(stderr, "[ERROR] Failed to write PCM data to WAV file\n");
+            goto cleanup;
+        }
+        
+        total_samples += samples;
+        total_frames++;
+        
+        // Progress indicator
+        if (total_frames % 100 == 0) {
+            float progress = (float)total_mp3_read / mp3_size * 100.0f;
+            printf("\r[INFO ] Progress: %.1f%% | Frames: %d | Samples: %d", 
+                   progress, total_frames, total_samples);
+            fflush(stdout);
+        }
+        
+        // Move to next frame
+        if (info.frame_bytes > 0 && (size_t)info.frame_bytes <= mp3_buffer_used) {
+            memmove(mp3_buffer, mp3_buffer + info.frame_bytes, 
+                   mp3_buffer_used - info.frame_bytes);
+            mp3_buffer_used -= info.frame_bytes;
+        } else {
+            // Skip 1 byte if frame size is unknown
+            if (mp3_buffer_used > 0) {
+                memmove(mp3_buffer, mp3_buffer + 1, mp3_buffer_used - 1);
+                mp3_buffer_used--;
+            }
+        }
+    }
+    
+    printf("\n[INFO ] Decoding complete: %d frames, %d samples\n", 
+           total_frames, total_samples);
+    
+    if (total_samples == 0 || sample_rate == 0 || channels == 0) {
+        fprintf(stderr, "[ERROR] No valid audio data decoded\n");
+        goto cleanup;
+    }
+    
+    // Calculate duration
+    float duration_sec = (float)total_samples / sample_rate;
+    printf("[INFO ] Duration: %.2f seconds\n", duration_sec);
+    
+    // Update WAV header with correct information
+    printf("[INFO ] Updating WAV header...\n");
+    fseek(wav_fp, 0, SEEK_SET);
+    write_wav_header(wav_fp, sample_rate, channels, total_samples);
+    
+    // Calculate final file size
+    long wav_size = 44 + (long)(total_samples * channels * sizeof(int16_t));
+    printf("[INFO ] WAV file created successfully\n");
+    printf("[INFO ] Total size: %ld bytes (%.2f MB)\n", 
+           wav_size, wav_size / 1024.0 / 1024.0);
+    
+    ret = 0;
+    
+cleanup:
+    if (mp3_fp) fclose(mp3_fp);
+    if (wav_fp) fclose(wav_fp);
+    if (mp3_buffer) free(mp3_buffer);
+    if (pcm_frame_buffer) free(pcm_frame_buffer);
+    
+    return ret;
+}
+
+/**
+ * @brief Main function
+ */
+int main(int argc, char *argv[]) {
+    printf("╔════════════════════════════════════════╗\n");
+    printf("║   MP3 to WAV Converter (minimp3)      ║\n");
+    printf("╚════════════════════════════════════════╝\n\n");
+    
+    if (argc != 3) {
+        printf("Usage: %s <input.mp3> <output.wav>\n", argv[0]);
+        printf("\nExample:\n");
+        printf("  %s music.mp3 music.wav\n", argv[0]);
+        return 1;
+    }
+    
+    const char *mp3_file = argv[1];
+    const char *wav_file = argv[2];
+    
+    int result = mp3_to_wav(mp3_file, wav_file);
+    
+    if (result == 0) {
+        printf("\n✓ Conversion successful!\n");
+        printf("  Input:  %s\n", mp3_file);
+        printf("  Output: %s\n", wav_file);
+    } else {
+        printf("\n✗ Conversion failed!\n");
+        return 1;
+    }
+    
+    return 0;
+}
+
index 7565a40..a46ffe6 100644 (file)
@@ -71,8 +71,8 @@ errcode_t ppp_api_power_init(void)
         .pwr_pin_en_value = 1,
         .reset_pin = S_AGPIO_L24,
         .reset_pin_en_value = 1, //电路中 控制gpio拉高,reset才拉低
-        .ring_pin = PIN_NONE, //CTS cat1 wakeup mcu modem还没实现,暂时不使用
-        // .ring_pin = S_MGPIO21, //CTS cat1 wakeup mcu
+        // .ring_pin = PIN_NONE, //CTS cat1 wakeup mcu modem还没实现,暂时不使用
+        .ring_pin = S_MGPIO21, //CTS cat1 wakeup mcu
         .ring_pin_en_value = 2, //2 双边沿中断 , 1 上升沿中断 , 0 下降沿中断
         .wakemodem_pin = S_MGPIO22, //RTS mcu wakeup cat1
         .wakemodem_pin_en_value = 0 //0 唤醒cat1 , 1 休眠cat1
index c9af10a..b80ffdd 100644 (file)
@@ -12,6 +12,7 @@
 #include "pinctrl_porting.h"
 #include "osal_debug.h"
 #include "securec.h"
+#include "tcxo.h"  // 用于获取系统时间(去抖用)
 
 // PPP模块的4G控制引脚配置
 static ppp_ctl_pin_4g_t g_ppp_cat1_ctl_pins = {
@@ -116,15 +117,18 @@ void ppp_api_gpio_init(void)
         switch (g_ppp_cat1_ctl_pins.ring_pin_en_value)
         {
         case 0:
+            uapi_pin_set_pull(gpiopin, (pin_pull_t)HAL_PIO_PULL_UP);
             uapi_gpio_register_isr_func(gpiopin, GPIO_INTERRUPT_FALLING_EDGE, ppp_ring_isr_callback);
             PPP_DEBUG_PRINT("PPP: Register ring pin %d falling edge interrupt\n", gpiopin);
             break;
         case 1:
+            uapi_pin_set_pull(gpiopin, (pin_pull_t)HAL_PIO_PULL_DOWN);
             uapi_gpio_register_isr_func(gpiopin, GPIO_INTERRUPT_RISING_EDGE, ppp_ring_isr_callback);
             PPP_DEBUG_PRINT("PPP: Register ring pin %d rising edge interrupt\n", gpiopin);
             break;
         case 2:
         default:
+            uapi_pin_set_pull(gpiopin, (pin_pull_t)HAL_PIO_PULL_MAX);
             uapi_gpio_register_isr_func(gpiopin, GPIO_INTERRUPT_DEDGE, ppp_ring_isr_callback);
             PPP_DEBUG_PRINT("PPP: Register ring pin %d both edge interrupt\n", gpiopin);
             break;
@@ -391,22 +395,57 @@ errcode_t ppp_register_ctl_pin(ppp_ctl_pin_4g_t *ctl_pin)
 }
 
 /**
- * @brief PPP模块Cat1唤醒中断回调函数
+ * @brief PPP模块Cat1唤醒中断回调函数(带去抖处理)
  */
 void ppp_ring_isr_callback(pin_t pin, uintptr_t param)
 {
     (void)param;
-
+    
+    // ⚠️ 去抖处理:防止信号抖动导致频繁中断
+    static uint64_t last_interrupt_time = 0;
+    static gpio_level_t last_level = GPIO_LEVEL_LOW;
+    static uint32_t same_level_count = 0;
+    
+    uint64_t current_time = uapi_tcxo_get_ms();  // 获取当前时间(毫秒)
     gpio_level_t val = uapi_gpio_get_val(pin);
+    
+    // 去抖时间窗口:50ms(可根据实际情况调整)
+    #define RING_DEBOUNCE_TIME_MS 50
+    
+    // 检查是否在去抖时间内
+    if ((current_time - last_interrupt_time) < RING_DEBOUNCE_TIME_MS) {
+        // 在去抖时间内,检查电平是否一致
+        if (val == last_level) {
+            same_level_count++;
+            // 如果同一电平连续触发超过5次,打印警告
+            if (same_level_count == 5) {
+                PPP_DEBUG_WARN("PPP: ⚠️ Ring pin %d signal bouncing (level=%d, count=%u)\n", 
+                              pin, val, same_level_count);
+            }
+        } else {
+            same_level_count = 1;
+            last_level = val;
+        }
+        
+        // 清除中断标志,但不处理
+        // uapi_gpio_clear_interrupt(pin);
+        // return;
+    }
+    
+    // 去抖时间已过,处理中断
+    last_interrupt_time = current_time;
+    last_level = val;
+    same_level_count = 1;
+    
+    cat1_api_add_cat1_vote();
     if (val == GPIO_LEVEL_HIGH) {
-        PPP_DEBUG_PRINT("PPP: Cat1 ring interrupt on pin %d high \n", pin);
-        cat1_api_add_cat1_vote();
+        PPP_DEBUG_INFO("PPP: Cat1 ring interrupt on pin %d → HIGH \n", pin);
     } else {
-        PPP_DEBUG_PRINT("PPP: Cat1 ring interrupt on pin %d low\n", pin);
+        PPP_DEBUG_INFO("PPP: Cat1 ring interrupt on pin %d → LOW\n", pin);
         //idle后 remove
-        // cat1_api_remove_cat1_vote();
     }
     uapi_gpio_clear_interrupt(pin);
+    
 }
 
 /**
index f53c399..ba094fc 100644 (file)
@@ -200,7 +200,7 @@ ppp_error_t ppp_serial_init(const ppp_serial_config_t *config)
         g_serial_ctx.rx_buffer = NULL;
         return PPP_ERR_UART;
     }
-    PPP_DEBUG_INFO("UART initialized successfully (error code = %d)", ret);
+    PPP_DEBUG_INFO("UART initialized successfully ");
 
     // 使能UART_BUS_0 (UART_L) 的唤醒
     hal_pmu_lpm_wakeup_action_enable(LPM_EVT_ACTION, TURN_ON, PMU_LPM_MCPU_UART_L0_RX_WAKEUP);