From: 孙春阳 Date: Fri, 24 Oct 2025 11:13:56 +0000 (+0800) Subject: 修复vad检测, 默认使能Ring 中断 X-Git-Tag: V1.0.9_20251128_194606_LN~173^2~2 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=03e82890c08ca9757498b264f1924f0d989229f7;p=mirrors%2FFD230.git 修复vad检测, 默认使能Ring 中断 --- diff --git a/code/sdk/zhihui/imsl/src/vad_server.c b/code/sdk/zhihui/imsl/src/vad_server.c index d9baff784..a93835a7d 100644 --- a/code/sdk/zhihui/imsl/src/vad_server.c +++ b/code/sdk/zhihui/imsl/src/vad_server.c @@ -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"); diff --git a/code/sdk/zhihui/minimp3/include/mp3_log.h b/code/sdk/zhihui/minimp3/include/mp3_log.h index 33e5403eb..14196b36d 100644 --- a/code/sdk/zhihui/minimp3/include/mp3_log.h +++ b/code/sdk/zhihui/minimp3/include/mp3_log.h @@ -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 /* 全局日志等级变量(可在运行时修改) */ diff --git a/code/sdk/zhihui/minimp3/src/mp3_decoder.c b/code/sdk/zhihui/minimp3/src/mp3_decoder.c index 463da025d..61fdb17c5 100644 --- a/code/sdk/zhihui/minimp3/src/mp3_decoder.c +++ b/code/sdk/zhihui/minimp3/src/mp3_decoder.c @@ -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 index 000000000..b2eccc206 --- /dev/null +++ b/code/sdk/zhihui/minimp3/test/Makefile @@ -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 index 000000000..4a035df70 --- /dev/null +++ b/code/sdk/zhihui/minimp3/test/mp3_to_wav.c @@ -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 +#include +#include +#include + +#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 \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; +} + diff --git a/code/sdk/zhihui/ppp/src/ppp_api.c b/code/sdk/zhihui/ppp/src/ppp_api.c index 7565a405d..a46ffe610 100644 --- a/code/sdk/zhihui/ppp/src/ppp_api.c +++ b/code/sdk/zhihui/ppp/src/ppp_api.c @@ -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 diff --git a/code/sdk/zhihui/ppp/src/ppp_power_ctl.c b/code/sdk/zhihui/ppp/src/ppp_power_ctl.c index c9af10afc..b80ffdde3 100644 --- a/code/sdk/zhihui/ppp/src/ppp_power_ctl.c +++ b/code/sdk/zhihui/ppp/src/ppp_power_ctl.c @@ -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); + } /** diff --git a/code/sdk/zhihui/ppp/src/ppp_serial.c b/code/sdk/zhihui/ppp/src/ppp_serial.c index f53c399b6..ba094fcd2 100644 --- a/code/sdk/zhihui/ppp/src/ppp_serial.c +++ b/code/sdk/zhihui/ppp/src/ppp_serial.c @@ -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);