#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 {
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;
.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声道(单声道) */
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)
{
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;
}
/* 周期性打印统计信息(每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;
}
}
}
- /* 喂入 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();
}
}
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;
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
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);
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;
}
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;
}
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");
/* 默认日志等级: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
/* 全局日志等级变量(可在运行时修改) */
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;
}
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,
// 打印第一帧信息
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;
}
// 调用回调函数
}
// 移除已解码的数据
- 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;
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; // 只接受能容纳的数据
}
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;
--- /dev/null
+# 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
+
--- /dev/null
+/**
+ * @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;
+}
+
.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
#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 = {
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;
}
/**
- * @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);
+
}
/**
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);