imsl vad +minimp3 加入了,可以正常检测到语音,但是会延迟识别。当下也就停止说话能好用。 2573/1
author孙春阳 <snickersun@163.com>
Tue, 21 Oct 2025 01:47:15 +0000 (09:47 +0800)
committer孙春阳 <snickersun@163.com>
Tue, 21 Oct 2025 01:47:15 +0000 (09:47 +0800)
13 files changed:
code/sdk/application/brandy/brandy_standard/service_init.c
code/sdk/zhihui/imsl/CMakeLists.txt
code/sdk/zhihui/imsl/include/imsl_log.h [new file with mode: 0644]
code/sdk/zhihui/imsl/include/vad_server.h [new file with mode: 0644]
code/sdk/zhihui/imsl/src/imsl_at_cmd.c
code/sdk/zhihui/imsl/src/imsl_log.c [new file with mode: 0644]
code/sdk/zhihui/imsl/src/vad_server.c [new file with mode: 0644]
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h
code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c
code/sdk/zhihui/minimp3/CMakeLists.txt
code/sdk/zhihui/minimp3/include/mp3_log.h [new file with mode: 0644]
code/sdk/zhihui/minimp3/src/mp3_decoder.c
code/sdk/zhihui/minimp3/src/mp3_log.c [new file with mode: 0644]

index c09e1b9..0c7b0c8 100644 (file)
 #include "ai_V2_5_at_cmd.h"\r
 #endif\r
 \r
-#ifdef IMSL_VAD\r
+#ifdef ENABLE_VAD_SERVER\r
 #include "imsl_at_cmd.h"\r
 #endif\r
 \r
@@ -273,7 +273,7 @@ static void register_at_service(void)
     at_cmd_add_moudle(ai_table, ai_cmd_count);\r
 #endif\r
 \r
-#ifdef IMSL_VAD\r
+#ifdef ENABLE_VAD_SERVER\r
     uint32_t imsl_vad_cmd_count;\r
     at_cmd_table_t *imsl_vad_table = get_imsl_vad_at_cmd_table(&imsl_vad_cmd_count);\r
     at_cmd_add_moudle(imsl_vad_table, imsl_vad_cmd_count);\r
index ef684dd..d5521d3 100644 (file)
@@ -8,6 +8,8 @@ set(COMPONENT_NAME "imsl")
 set(SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/src/imsl_system_adapter.c
     ${CMAKE_CURRENT_SOURCE_DIR}/src/imsl_at_cmd.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/vad_server.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/imsl_log.c
 )
 
 # 公共头文件路径(供其他模块引用)
@@ -18,9 +20,15 @@ set(PUBLIC_HEADER
 # 公共宏定义(可选)
 set(PUBLIC_DEFINES
     # 例如:IMSL_LN_ENABLE=1
-    IMSL_VAD=1
+    ENABLE_VAD_SERVER=1
 )
 
+# 根据 MINI_MP3 宏添加 MP3 解码支持
+if(DEFINED MINI_MP3)
+    list(APPEND PUBLIC_DEFINES MINI_MP3)
+    message(STATUS "[IMSL] MP3 decoding support enabled")
+endif()
+
 # 私有头文件路径(仅当前组件内部使用)
 set(PRIVATE_HEADER
     # 如无特殊私有头文件,可留空
diff --git a/code/sdk/zhihui/imsl/include/imsl_log.h b/code/sdk/zhihui/imsl/include/imsl_log.h
new file mode 100644 (file)
index 0000000..fe36603
--- /dev/null
@@ -0,0 +1,111 @@
+#ifndef IMSL_LOG_H
+#define IMSL_LOG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/**
+ * @file imsl_log.h
+ * @brief IMSL 模块日志系统
+ * 
+ * 提供可控等级的日志打印宏,支持 ERROR、WARN、INFO、DEBUG 四个等级
+ */
+
+/* 日志等级定义 */
+typedef enum {
+    IMSL_LOG_LEVEL_NONE  = 0,   /* 关闭所有日志 */
+    IMSL_LOG_LEVEL_ERROR = 1,   /* 仅错误信息 */
+    IMSL_LOG_LEVEL_WARN  = 2,   /* 错误+警告信息 */
+    IMSL_LOG_LEVEL_INFO  = 3,   /* 错误+警告+一般信息 */
+    IMSL_LOG_LEVEL_DEBUG = 4    /* 所有信息包括调试 */
+} imsl_log_level_t;
+
+/* 默认日志等级:INFO */
+#ifndef IMSL_DEFAULT_LOG_LEVEL
+#define IMSL_DEFAULT_LOG_LEVEL IMSL_LOG_LEVEL_INFO
+#endif
+
+/* 全局日志等级变量(可在运行时修改) */
+extern imsl_log_level_t g_imsl_log_level;
+
+/* 日志等级字符串 */
+static const char *g_imsl_log_level_str[] = {
+    "",         /* NONE */
+    "[ERROR]",  /* ERROR */
+    "[WARN ]",  /* WARN */
+    "[INFO ]",  /* INFO */
+    "[DEBUG]"   /* DEBUG */
+};
+
+/**
+ * @brief 基础日志宏(内部使用)
+ * @param level 日志等级
+ * @param fmt 格式化字符串
+ * @param ... 可变参数
+ */
+#define IMSL_LOG(level, level_str, fmt, ...) do { \
+    if ((level) <= g_imsl_log_level) { \
+        printf("[IMSL]%s " fmt, level_str, ##__VA_ARGS__); \
+    } \
+} while(0)
+
+/**
+ * @brief 错误日志宏
+ * @param fmt 格式化字符串
+ * @param ... 可变参数
+ * @note 用于打印错误信息,如初始化失败、资源分配失败等
+ */
+#define IMSL_ERROR(fmt, ...) IMSL_LOG(IMSL_LOG_LEVEL_ERROR, "[ERROR]", fmt, ##__VA_ARGS__)
+
+/**
+ * @brief 警告日志宏
+ * @param fmt 格式化字符串
+ * @param ... 可变参数
+ * @note 用于打印警告信息,如参数异常、状态异常但可继续运行
+ */
+#define IMSL_WARN(fmt, ...) IMSL_LOG(IMSL_LOG_LEVEL_WARN, "[WARN ]", fmt, ##__VA_ARGS__)
+
+/**
+ * @brief 信息日志宏
+ * @param fmt 格式化字符串
+ * @param ... 可变参数
+ * @note 用于打印一般信息,如初始化成功、状态变化等
+ */
+#define IMSL_INFO(fmt, ...) IMSL_LOG(IMSL_LOG_LEVEL_INFO, "[INFO ]", fmt, ##__VA_ARGS__)
+
+/**
+ * @brief 调试日志宏
+ * @param fmt 格式化字符串
+ * @param ... 可变参数
+ * @note 用于打印调试信息,如详细的执行流程、数据内容等
+ */
+#define IMSL_DEBUG(fmt, ...) IMSL_LOG(IMSL_LOG_LEVEL_DEBUG, "[DEBUG]", fmt, ##__VA_ARGS__)
+
+/**
+ * @brief 设置 IMSL 日志等级
+ * @param level 日志等级
+ */
+static inline void imsl_set_log_level(imsl_log_level_t level)
+{
+    g_imsl_log_level = level;
+    printf("[IMSL][INFO ] Log level set to %d\n", level);
+}
+
+/**
+ * @brief 获取当前 IMSL 日志等级
+ * @return 当前日志等级
+ */
+static inline imsl_log_level_t imsl_get_log_level(void)
+{
+    return g_imsl_log_level;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IMSL_LOG_H */
+
diff --git a/code/sdk/zhihui/imsl/include/vad_server.h b/code/sdk/zhihui/imsl/include/vad_server.h
new file mode 100644 (file)
index 0000000..bba4ae3
--- /dev/null
@@ -0,0 +1,94 @@
+#ifndef VAD_SERVER_H
+#define VAD_SERVER_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "errcode.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 音频格式类型 */
+typedef enum {
+    VAD_AUDIO_FORMAT_PCM = 0,   /* PCM 格式(16kHz, 16bit) */
+    VAD_AUDIO_FORMAT_MP3 = 1    /* MP3 格式 */
+} vad_audio_format_t;
+
+/* VAD 处理消息类型 */
+typedef enum {
+    VAD_MSG_PROCESS_AUDIO = 1,  /* 处理音频数据 */
+    VAD_MSG_RESET = 2,          /* 重置 VAD 状态 */
+    VAD_MSG_STOP = 3            /* 停止 VAD 处理 */
+} vad_msg_type_t;
+
+/* VAD 处理消息结构 */
+typedef struct {
+    vad_msg_type_t type;        /* 消息类型 */
+    vad_audio_format_t format;  /* 音频格式 */
+    uint8_t *data;              /* 音频数据指针 */
+    size_t data_size;           /* 数据大小(字节) */
+    bool is_last;               /* 是否为最后一块数据 */
+} vad_process_msg_t;
+
+/* VAD 状态回调函数类型 */
+typedef void (*vad_status_callback_t)(int status);
+
+/**
+ * @brief 初始化 VAD 处理任务
+ * @param callback VAD 状态回调函数(可选)
+ * @return errcode_t 错误码
+ * @note 创建 VAD 处理线程和消息队列,线程使用扩展内存(PSRAM)
+ */
+errcode_t vad_server_init(vad_status_callback_t callback);
+
+/**
+ * @brief 销毁 VAD 处理任务
+ * @return errcode_t 错误码
+ */
+errcode_t vad_server_deinit(void);
+
+/**
+ * @brief 发送音频数据到 VAD 处理队列
+ * @param format 音频格式(PCM 或 MP3)
+ * @param data 音频数据
+ * @param data_size 数据大小(字节)
+ * @param is_last 是否为最后一块数据
+ * @return errcode_t 错误码
+ * @note 数据会被复制到队列中,调用者可以在函数返回后释放 data
+ */
+errcode_t vad_server_send_audio(vad_audio_format_t format, 
+                                const uint8_t *data, 
+                                size_t data_size, 
+                                bool is_last);
+
+/**
+ * @brief 重置 VAD 状态
+ * @return errcode_t 错误码
+ */
+errcode_t vad_server_reset(void);
+
+/**
+ * @brief 获取 VAD 处理任务运行状态
+ * @return bool true=运行中,false=已停止
+ */
+bool vad_server_is_running(void);
+
+#ifdef MINI_MP3
+/**
+ * @brief 获取检测到的 MP3 格式信息
+ * @param channels 输出声道数(可为NULL)
+ * @param sample_rate 输出采样率(可为NULL)
+ * @return bool true=已从MP3流检测到格式, false=使用默认值(1ch, 16kHz)
+ * @note 如果MP3流有格式信息,第一帧解码后会自动检测并覆盖默认值
+ */
+bool vad_server_get_mp3_format(int *channels, int *sample_rate);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* VAD_SERVER_H */
+
index e0e3344..be9fa7e 100644 (file)
@@ -10,6 +10,7 @@
 #include "errcode.h"
 #include "imsl_vad.h"
 #include "media_thread_adapt.h"
+#include "imsl_log.h"
 
 #define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0]))
 #define VAD_MAX_ARGC 8
@@ -86,7 +87,7 @@ static int AnalyzePcmFileWithVad(const char *pcm_path)
     FILE *fp = fopen(pcm_path, "rb");
     if (fp == NULL)
     {
-        printf("open pcm failed: %s\n", pcm_path);
+        IMSL_ERROR("open pcm failed: %s\n", pcm_path);
         return VAD_FAILED;
     }
 
@@ -107,7 +108,7 @@ static int AnalyzePcmFileWithVad(const char *pcm_path)
         int st = vad_detect();
         if (st != prev)
         {
-            printf("VAD状态变化: %d -> %d 于帧%zu\n", prev, st, total_frames);
+            IMSL_INFO("VAD状态变化: %d -> %d 于帧%zu\n", prev, st, total_frames);
             prev = st;
         }
         if (st == 1)
@@ -122,7 +123,7 @@ static int AnalyzePcmFileWithVad(const char *pcm_path)
         total_frames++;
     }
     fclose(fp);
-    printf("总帧数:%zu, 语音帧:%d, 结束帧:%d\n", total_frames, count_speech, count_end);
+    IMSL_INFO("总帧数:%zu, 语音帧:%d, 结束帧:%d\n", total_frames, count_speech, count_end);
     return VAD_SUCCESS;
 }
 
@@ -143,7 +144,7 @@ static void EnsureVadLivePathInitialized(void)
 // 尾随线程,持续从文件中读取数据并喂给 VAD
 static void *VadTailThread(void *arg)
 {
-    printf("VAD TailThread\n");
+    IMSL_INFO("VAD TailThread\n");
     (void)arg;
     FILE *fp = NULL;
     int16_t frame[160];
@@ -180,11 +181,11 @@ static void *VadTailThread(void *arg)
                 int st = vad_detect();
                 if (st != g_vad_prev_status)
                 {
-                    printf("[VAD-LIVE] 状态变化: %d -> %d\n", g_vad_prev_status, st);
+                    IMSL_INFO("[VAD-LIVE] 状态变化: %d -> %d\n", g_vad_prev_status, st);
                     g_vad_prev_status = st;
                     if (st == 2)
                     {
-                        printf("[VAD-LIVE] 检测到语音结束,vad_clear()\n");
+                        IMSL_INFO("[VAD-LIVE] 检测到语音结束,vad_clear()\n");
                         vad_clear();
                     }
                 }
@@ -205,7 +206,7 @@ static void *VadTailThread(void *arg)
 
 static void *StartRecordThread(void *arg)
 {
-    printf("VAD StartRecordThread\n");
+    IMSL_INFO("VAD StartRecordThread\n");
     (void)arg;
     EnsureVadLivePathInitialized();
     const char *startArgs[6] = {"01", "pcm", "16000", "1", "16", g_vad_live_path ? g_vad_live_path : "/user/vad_live.pcm"};
@@ -251,22 +252,22 @@ static uint32_t vad_init(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
     int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
     if (ret != VAD_SUCCESS)
     {
-        printf("VADTEST ATCMD Convert Command Failed\n");
+        IMSL_ERROR("VADTEST ATCMD Convert Command Failed\n");
         return ERRCODE_FAIL;
     }
 
     if (g_vad_initialized)
     {
-        printf("VAD already initialized\n");
+        IMSL_INFO("VAD already initialized\n");
         return ERRCODE_SUCC;
     }
 
-    printf("=== VAD AT 指令测试 ===\n");
+    IMSL_INFO("=== VAD AT 指令测试 ===\n");
 
     // 设置日志回调
     extern imsl_nn_printf_func imsl_nn_printf;
     imsl_nn_printf = printf;
-    printf("✓ 日志回调设置成功\n");
+    IMSL_INFO("✓ 日志回调设置成功\n");
 
     // 初始化 VAD 配置
     g_vad_config.sil_threshold = 63;
@@ -281,11 +282,11 @@ static uint32_t vad_init(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
     int init_result = init_vad(&g_vad_config);
     if (init_result == 0)
     {
-        printf("❌ VAD 初始化失败\n");
+        IMSL_INFO("❌ VAD 初始化失败\n");
         return ERRCODE_FAIL;
     }
 
-    printf("✓ VAD 初始化成功 (状态: %d)\n", init_result);
+    IMSL_INFO("✓ VAD 初始化成功 (状态: %d)\n", init_result);
     g_vad_initialized = true;
     g_vad_status = 0;
 
@@ -302,19 +303,19 @@ static uint32_t vad_config(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
     int ret = ConvertCmdToParam(para, paraLen, argv, &argc);
     if (ret != VAD_SUCCESS)
     {
-        printf("VADTEST ATCMD Convert Command Failed\n");
+        IMSL_ERROR("VADTEST ATCMD Convert Command Failed\n");
         return ERRCODE_FAIL;
     }
 
     if (!g_vad_initialized)
     {
-        printf("VAD not initialized, call AT^VAD_INIT first\n");
+        IMSL_INFO("VAD not initialized, call AT^VAD_INIT first\n");
         return ERRCODE_FAIL;
     }
 
     if (argc != 7)
     {
-        printf("Usage: AT^VAD_CONFIG <sil_threshold> <energy_threshold> <min_speech_length> <silence_detect_length> <rule1_min_tailing_silence> <rule2_min_tailing_silence> <rule3_min_tailing_silence>\n");
+        IMSL_INFO("Usage: AT^VAD_CONFIG <sil_threshold> <energy_threshold> <min_speech_length> <silence_detect_length> <rule1_min_tailing_silence> <rule2_min_tailing_silence> <rule3_min_tailing_silence>\n");
         return ERRCODE_FAIL;
     }
 
@@ -327,10 +328,10 @@ static uint32_t vad_config(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
     g_vad_config.rule2_min_tailing_silence = atoi((char *)argv[5]);
     g_vad_config.rule3_min_tailing_silence = atoi((char *)argv[6]);
 
-    printf("✓ VAD 配置更新成功\n");
-    printf("  sil_threshold: %d\n", g_vad_config.sil_threshold);
-    printf("  energy_threshold: %d\n", g_vad_config.energy_threshold);
-    printf("  min_speech_length: %d\n", g_vad_config.min_speech_length);
+    IMSL_INFO("✓ VAD 配置更新成功\n");
+    IMSL_INFO("  sil_threshold: %d\n", g_vad_config.sil_threshold);
+    IMSL_INFO("  energy_threshold: %d\n", g_vad_config.energy_threshold);
+    IMSL_INFO("  min_speech_length: %d\n", g_vad_config.min_speech_length);
 
     return ERRCODE_SUCC;
 }
@@ -344,17 +345,17 @@ static uint32_t vad_status(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
 
     if (!g_vad_initialized)
     {
-        printf("VAD not initialized, call AT^VAD_INIT first\n");
+        IMSL_INFO("VAD not initialized, call AT^VAD_INIT first\n");
         return ERRCODE_FAIL;
     }
 
-    printf("=== VAD 状态信息 ===\n");
-    printf("初始化状态: %s\n", g_vad_initialized ? "已初始化" : "未初始化");
-    printf("当前状态: %d (0=静音, 1=语音, 2=结束)\n", g_vad_status);
-    printf("配置参数:\n");
-    printf("  sil_threshold: %d\n", g_vad_config.sil_threshold);
-    printf("  energy_threshold: %d\n", g_vad_config.energy_threshold);
-    printf("  min_speech_length: %d\n", g_vad_config.min_speech_length);
+    IMSL_INFO("=== VAD 状态信息 ===\n");
+    IMSL_INFO("初始化状态: %s\n", g_vad_initialized ? "已初始化" : "未初始化");
+    IMSL_INFO("当前状态: %d (0=静音, 1=语音, 2=结束)\n", g_vad_status);
+    IMSL_INFO("配置参数:\n");
+    IMSL_INFO("  sil_threshold: %d\n", g_vad_config.sil_threshold);
+    IMSL_INFO("  energy_threshold: %d\n", g_vad_config.energy_threshold);
+    IMSL_INFO("  min_speech_length: %d\n", g_vad_config.min_speech_length);
 
     return ERRCODE_SUCC;
 }
@@ -365,18 +366,18 @@ static uint32_t vad_analyze_pcm(uint8_t *para, uint32_t paraLen, char *strCommon
     VAD_UNUSED(strCommonCmd);
     if (!g_vad_initialized)
     {
-        printf("VAD not initialized, call AT^VAD_INIT first\n");
+        IMSL_INFO("VAD not initialized, call AT^VAD_INIT first\n");
         return ERRCODE_FAIL;
     }
     uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0};
     uint32_t argc = 0;
     if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS || argc != 1)
     {
-        printf("Usage: AT^VAD_ANALYZE_PCM <pcm_path>\n");
+        IMSL_INFO("Usage: AT^VAD_ANALYZE_PCM <pcm_path>\n");
         return ERRCODE_FAIL;
     }
     const char *pcm_path = (const char *)argv[0];
-    printf("VAD 分析现有 PCM: %s\n", pcm_path);
+    IMSL_INFO("VAD 分析现有 PCM: %s\n", pcm_path);
     int ret = AnalyzePcmFileWithVad(pcm_path);
     return (ret == VAD_SUCCESS) ? ERRCODE_SUCC : ERRCODE_FAIL;
 }
@@ -387,13 +388,13 @@ static uint32_t vad_record_start(uint8_t *para, uint32_t paraLen, char *strCommo
     VAD_UNUSED(strCommonCmd);
     if (!g_vad_initialized)
     {
-        printf("VAD not initialized, call AT^VAD_INIT first\n");
+        IMSL_INFO("VAD not initialized, call AT^VAD_INIT first\n");
         return ERRCODE_FAIL;
     }
 
     if (g_vad_live_running)
     {
-        printf("VAD live already running\n");
+        IMSL_INFO("VAD live already running\n");
         return ERRCODE_SUCC;
     }
 
@@ -412,8 +413,8 @@ static uint32_t vad_record_start(uint8_t *para, uint32_t paraLen, char *strCommo
         }
     }
 
-    printf("=== VAD 实时录音开始 ===\n");
-    printf("PCM 输出: %s\n", out_path);
+    IMSL_INFO("=== VAD 实时录音开始 ===\n");
+    IMSL_INFO("PCM 输出: %s\n", out_path);
 
     /* 1) 确保目录存在并异步启动系统录音保存为 PCM(避免在 AT 线程上堆栈溢出) */
     EnsureParentDir(out_path);
@@ -432,7 +433,7 @@ static uint32_t vad_record_start(uint8_t *para, uint32_t paraLen, char *strCommo
         g_vad_tail_thread = MediaThreadCreate(VadTailThread, NULL, &attr);
         if (g_vad_tail_thread == NULL)
         {
-            printf("create tail thread failed\n");
+            IMSL_ERROR("create tail thread failed\n");
             g_vad_live_running = false;
             return ERRCODE_FAIL;
         }
@@ -447,21 +448,21 @@ static uint32_t vad_record_stop(uint8_t *para, uint32_t paraLen, char *strCommon
     VAD_UNUSED(paraLen);
     VAD_UNUSED(strCommonCmd);
 
-    printf("=== 停止 VAD 实时录音 ===\n");
+    IMSL_INFO("=== 停止 VAD 实时录音 ===\n");
 
     /* 1) 先停止系统录音,减少文件追加,便于尾随线程退出 */
-    printf("VAD 停止系统录音\n");
+    IMSL_INFO("VAD 停止系统录音\n");
     {
         const char *stopArg = "stop";
         (void)AudioCaptureSample(1, &stopArg);
     }
 
     /* 2) 通知尾随线程退出(不阻塞等待) */
-    printf("VAD 停止尾随线程\n");
+    IMSL_INFO("VAD 停止尾随线程\n");
     g_vad_live_running = false;
     g_vad_tail_thread = NULL;
 
-    printf("VAD 实时录音已停止\n");
+    IMSL_INFO("VAD 实时录音已停止\n");
     return ERRCODE_SUCC;
 }
 
@@ -474,12 +475,12 @@ static uint32_t vad_cleanup(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
 
     if (!g_vad_initialized)
     {
-        printf("VAD not initialized\n");
+        IMSL_INFO("VAD not initialized\n");
         return ERRCODE_SUCC;
     }
 
     free_vad();
-    printf("✓ VAD 资源释放成功\n");
+    IMSL_INFO("✓ VAD 资源释放成功\n");
     g_vad_initialized = false;
     g_vad_status = -1;
 
@@ -493,24 +494,24 @@ static uint32_t vad_help(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
     VAD_UNUSED(paraLen);
     VAD_UNUSED(strCommonCmd);
 
-    printf("=== VAD AT 指令帮助 ===\n");
-    printf("AT^VAD_INIT         - 初始化 VAD\n");
-    printf("AT^VAD_CONFIG       - 配置 VAD 参数\n");
-    printf("AT^VAD_STATUS       - 查询 VAD 状态\n");
-    printf("AT^VAD_ANALYZE_PCM <in.pcm>   - 分析已有PCM\n");
-    printf("AT^VAD_RECORD_START - 开始录音测试\n");
-    printf("AT^VAD_RECORD_STOP  - 停止录音测试\n");
-    printf("AT^VAD_CLEANUP      - 清理 VAD 资源\n");
-    printf("AT^VAD_HELP         - 显示此帮助\n");
-    printf("\n=== 使用示例 ===\n");
-    printf("AT^VAD_INIT                                 # 初始化\n");
-    printf("AT^VAD_CONFIG=50 5000 120 300 480 960 960   # 配置参数\n");
-    printf("AT^VAD_STATUS                               # 查看状态\n");
-    printf("AT^VAD_ANALYZE_PCM=/user/vad_live.pcm       # 分析已有PCM\n");
-    printf("AT^VAD_RECORD_START                         # 开始录音测试\n");
-    printf("AT^VAD_RECORD_STOP                          # 停止录音测试\n");
-    printf("AT^VAD_CLEANUP                              # 清理资源\n");
-    printf("=====================\n");
+    IMSL_INFO("=== VAD AT 指令帮助 ===\n");
+    IMSL_INFO("AT^VAD_INIT         - 初始化 VAD\n");
+    IMSL_INFO("AT^VAD_CONFIG       - 配置 VAD 参数\n");
+    IMSL_INFO("AT^VAD_STATUS       - 查询 VAD 状态\n");
+    IMSL_INFO("AT^VAD_ANALYZE_PCM <in.pcm>   - 分析已有PCM\n");
+    IMSL_INFO("AT^VAD_RECORD_START - 开始录音测试\n");
+    IMSL_INFO("AT^VAD_RECORD_STOP  - 停止录音测试\n");
+    IMSL_INFO("AT^VAD_CLEANUP      - 清理 VAD 资源\n");
+    IMSL_INFO("AT^VAD_HELP         - 显示此帮助\n");
+    IMSL_INFO("\n=== 使用示例 ===\n");
+    IMSL_DEBUG("AT^VAD_INIT                                 # 初始化\n");
+    IMSL_DEBUG("AT^VAD_CONFIG=50 5000 120 300 480 960 960   # 配置参数\n");
+    IMSL_DEBUG("AT^VAD_STATUS                               # 查看状态\n");
+    IMSL_DEBUG("AT^VAD_ANALYZE_PCM=/user/vad_live.pcm       # 分析已有PCM\n");
+    IMSL_DEBUG("AT^VAD_RECORD_START                         # 开始录音测试\n");
+    IMSL_DEBUG("AT^VAD_RECORD_STOP                          # 停止录音测试\n");
+    IMSL_DEBUG("AT^VAD_CLEANUP                              # 清理资源\n");
+    IMSL_INFO("=====================\n");
 
     return ERRCODE_SUCC;
 }
diff --git a/code/sdk/zhihui/imsl/src/imsl_log.c b/code/sdk/zhihui/imsl/src/imsl_log.c
new file mode 100644 (file)
index 0000000..f669d7c
--- /dev/null
@@ -0,0 +1,10 @@
+#include "imsl_log.h"
+
+/**
+ * @file imsl_log.c
+ * @brief IMSL 模块日志系统实现
+ */
+
+/* 全局日志等级变量定义 */
+imsl_log_level_t g_imsl_log_level = IMSL_DEFAULT_LOG_LEVEL;
+
diff --git a/code/sdk/zhihui/imsl/src/vad_server.c b/code/sdk/zhihui/imsl/src/vad_server.c
new file mode 100644 (file)
index 0000000..d9baff7
--- /dev/null
@@ -0,0 +1,629 @@
+#include "vad_server.h"
+#include "imsl_vad.h"
+#include "thread_init.h"
+#include "osal_msgqueue.h"
+#include "soc_osal.h"
+#include "custom_utils.h"
+#include "cmsis_os2.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef MINI_MP3
+#include "mp3_decoder.h"
+#include "imsl_log.h"
+#endif
+
+/* VAD 处理任务配置 */
+#define VAD_TASK_NAME "vad_process"
+#define VAD_TASK_STACK_SIZE (16 * 1024)  /* 16KB 栈 */
+#define VAD_TASK_PRIORITY TASK_PRIORITY_APP
+
+/* 消息队列配置 */
+#define VAD_QUEUE_NAME "vad_queue"
+#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 服务器状态 */
+typedef struct {
+    uint32_t thread_handle;
+    unsigned long queue_id;
+    bool is_running;
+    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;
+    int mp3_detected_channels;    /* 检测到的声道数,默认1 */
+    int mp3_detected_sample_rate; /* 检测到的采样率,默认16000 */
+    bool mp3_format_detected;     /* 是否已检测到格式 */
+#endif
+} vad_server_t;
+
+/* 全局 VAD 服务器实例 */
+static vad_server_t g_vad_server = {
+    .queue_id=0xFFFFFFFF, // 初始化队列为无效值,避免误用 
+    .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声道(单声道) */
+    .mp3_detected_sample_rate = 16000, /* 默认:16kHz采样率 */
+    .mp3_format_detected = false      /* 默认:未检测到格式 */
+#endif
+};
+/* 前向声明 */
+static void vad_process_task(void *arg);
+static void vad_queue_create(void);
+static errcode_t vad_process_pcm_data(const int16_t *pcm_data, size_t sample_count);
+
+#ifdef MINI_MP3
+/* MP3 解码回调:将解码后的 PCM 数据送给 VAD 处理 */
+static void vad_mp3_pcm_callback(const int16_t *pcm_data, int samples, int channels, int sample_rate)
+{
+    if (!pcm_data || samples <= 0) {
+        return;
+    }
+    
+    /* 第一次检测到格式时,记录实际格式(覆盖默认值) */
+    if (!g_vad_server.mp3_format_detected) {
+        g_vad_server.mp3_detected_channels = channels;
+        g_vad_server.mp3_detected_sample_rate = sample_rate;
+        g_vad_server.mp3_format_detected = true;
+        
+        IMSL_INFO("[VAD_SERVER] MP3 format detected: %d channels, %d Hz\n", channels, sample_rate);
+        
+        /* 检查是否符合 VAD 要求 */
+        if (sample_rate != 16000) {
+            IMSL_WARN("[VAD_SERVER] Warning: MP3 sample rate %d Hz (VAD requires 16000 Hz)\n", sample_rate);
+            IMSL_INFO("[VAD_SERVER] Using actual sample rate, VAD results may be affected\n");
+        }
+        
+        if (channels != 1) {
+            IMSL_INFO("[VAD_SERVER] Info: MP3 has %d channels, will convert to mono\n", channels);
+        }
+    }
+    
+    /* 处理 PCM 数据(如果是立体声,每隔一个样本取一个) */
+    if (channels == 1) {
+        vad_process_pcm_data(pcm_data, samples);
+    } else {
+        /* 立体声转单声道:只取左声道 */
+        for (int i = 0; i < samples; i++) {
+            int16_t mono_sample = pcm_data[i * channels];
+            vad_process_pcm_data(&mono_sample, 1);
+        }
+    }
+}
+#endif
+
+/**
+ * @brief 处理 PCM 数据,按帧(160 samples)送给 VAD
+ */
+static errcode_t vad_process_pcm_data(const int16_t *pcm_data, size_t sample_count)
+{
+    static int pcm_call_count = 0;
+    
+    if (!pcm_data || sample_count == 0) {
+        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);
+    }
+    
+    /* 将 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;
+    }
+    
+    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 帧大小(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;
+    
+    static int frame_count = 0;
+    static int debug_print_count = 0;  /* 用于控制周期性日志 */
+    
+    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);
+        
+        /* 检测 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);
+        }
+    }
+    
+    return ERRCODE_SUCC;
+}
+
+/**
+ * @brief VAD 处理任务主函数
+ */
+static void vad_process_task(void *arg)
+{
+    (void)arg;
+    
+    IMSL_DEBUG("[VAD_SERVER] VAD process task started\n");
+    
+    vad_process_msg_t msg;
+    unsigned int msg_size;
+    
+    static uint32_t msg_count = 0;
+    static uint32_t last_stat_time = 0;
+    
+    while (g_vad_server.is_running) {
+        /* 周期性打印统计信息(每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);
+            last_stat_time = current_time;
+        }
+        
+        /* 从队列读取消息(超时 1 秒) */
+        msg_size = VAD_QUEUE_MSG_SIZE;
+        int ret = osal_msg_queue_read_copy(g_vad_server.queue_id, &msg, 
+                                          &msg_size, 1000);
+        
+        if (ret != OSAL_SUCCESS) {
+            /* 超时或错误,继续等待 */
+            continue;
+        }
+        
+        msg_count++;
+        
+        /* 验证消息类型是否有效 */
+        if (msg.type < VAD_MSG_PROCESS_AUDIO || msg.type > VAD_MSG_STOP) {
+            IMSL_ERROR("[VAD_SERVER] Error: Invalid message (type=%d, format=%d, data=%p, size=%zu, is_last=%d)\n",
+                   msg.type, msg.format, msg.data, msg.data_size, msg.is_last);
+            IMSL_ERROR("[VAD_SERVER] Error: Expected message type 1-3, queue_id=0x%lx, msg_size=%zu\n",
+                   g_vad_server.queue_id, sizeof(vad_process_msg_t));
+            /* 如果有数据指针,需要释放(但要小心,因为数据可能已损坏) */
+            if (msg.data && msg.data != (void*)0x1 && msg.data_size > 0 && msg.data_size < 1024*1024) {
+                free(msg.data);
+            }
+            continue;
+        }
+        
+        /* 处理消息 */
+        switch (msg.type) {
+            case VAD_MSG_PROCESS_AUDIO:
+                if (msg.data && msg.data_size > 0) {
+                    IMSL_DEBUG("[VAD_SERVER] Processing audio: format=%d, size=%zu, is_last=%d\n",
+                           msg.format, msg.data_size, msg.is_last);
+                    
+                    if (msg.format == VAD_AUDIO_FORMAT_PCM) {
+                        /* PCM 格式:直接处理 */
+                        vad_process_pcm_data((const int16_t *)msg.data, 
+                                            msg.data_size / sizeof(int16_t));
+                    }
+#ifdef MINI_MP3
+                    else if (msg.format == VAD_AUDIO_FORMAT_MP3) {
+                        /* MP3 格式:先解码为 PCM,再处理 */
+                        if (!g_vad_server.mp3_decoder_initialized) {
+                            mp3_decode_error_t mp3_ret = mp3_decoder_create();
+                            if (mp3_ret == MP3_DECODE_SUCCESS) {
+                                mp3_decoder_set_callback(vad_mp3_pcm_callback);
+                                g_vad_server.mp3_decoder_initialized = true;
+                                /* 重置格式检测状态,准备检测新的 MP3 流格式 */
+                                g_vad_server.mp3_format_detected = false;
+                                g_vad_server.mp3_detected_channels = 1;
+                                g_vad_server.mp3_detected_sample_rate = 16000;
+                                IMSL_INFO("[VAD_SERVER] MP3 decoder initialized (default: 1ch, 16kHz)\n");
+                            } else {
+                                IMSL_ERROR("[VAD_SERVER] Error: Failed to initialize MP3 decoder\n");
+                                free(msg.data);
+                                continue;
+                            }
+                        }
+                        
+                        /* 喂入 MP3 数据进行解码 */
+                        mp3_decoder_feed_data(msg.data, msg.data_size);
+                        
+                        /* 如果是最后一块数据,刷新解码器 */
+                        if (msg.is_last) {
+                            mp3_decoder_flush();
+                        }
+                    }
+#endif
+                    else {
+                        IMSL_ERROR("[VAD_SERVER] Error: Unsupported audio format: %d\n", msg.format);
+                    }
+                    
+                    /* 释放消息数据 */
+                    free(msg.data);
+                }
+                break;
+                
+            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;
+                g_vad_server.mp3_detected_channels = 1;
+                g_vad_server.mp3_detected_sample_rate = 16000;
+#endif
+                break;
+                
+            case VAD_MSG_STOP:
+                IMSL_INFO("[VAD_SERVER] Stopping VAD task\n");
+                g_vad_server.is_running = false;
+                break;
+                
+            default:
+                IMSL_WARN("[VAD_SERVER] Warning: Unknown message type: %d\n", msg.type);
+                break;
+        }
+    }
+    
+#ifdef MINI_MP3
+    /* 清理 MP3 解码器 */
+    if (g_vad_server.mp3_decoder_initialized) {
+        mp3_decoder_destroy();
+        g_vad_server.mp3_decoder_initialized = false;
+        IMSL_INFO("[VAD_SERVER] MP3 decoder destroyed\n");
+    }
+#endif
+    
+    IMSL_DEBUG("[VAD_SERVER] VAD process task stopped\n");
+}
+
+/**
+ * @brief 创建 VAD 消息队列
+ */
+static void vad_queue_create(void)
+{
+    IMSL_INFO("[VAD_SERVER] Creating message queue (size=%d, msg_size=%zu)...\n",
+           VAD_QUEUE_SIZE, VAD_QUEUE_MSG_SIZE);
+    
+    int ret = osal_msg_queue_create(VAD_QUEUE_NAME, VAD_QUEUE_SIZE,
+                                   &g_vad_server.queue_id, 0,
+                                   VAD_QUEUE_MSG_SIZE);
+    if (ret != OSAL_SUCCESS) {
+        IMSL_ERROR("[VAD_SERVER] Error: Failed to create message queue (ret=%d)\n", ret);
+        g_vad_server.queue_id = 0xFFFFFFFF;  /* 保持无效值 */
+    } else {
+        IMSL_INFO("[VAD_SERVER] Message queue created successfully, ID: 0x%x, size: %zu bytes\n",
+               g_vad_server.queue_id, VAD_QUEUE_MSG_SIZE);
+    }
+}
+
+/**
+ * @brief 初始化 VAD 处理任务
+ */
+errcode_t vad_server_init(vad_status_callback_t callback)
+{
+    if (g_vad_server.is_initialized) {
+        IMSL_INFO("[VAD_SERVER] Already initialized\n");
+        return ERRCODE_SUCC;
+    }
+    
+    IMSL_INFO("[VAD_SERVER] Initializing VAD server...\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
+    g_vad_server.mp3_decoder_initialized = false;
+#endif
+    
+    /* 初始化 VAD 库 */
+    struct VadConfig vad_config = {
+        .vad_log_level = 1,                   /* INFO 日志 */
+        .sil_threshold = 63,                  /* 静音阈值 */
+        .energy_threshold = 10000,            /* 能量阈值 */
+        .min_speech_length = 240,             /* 最短语音长度 (ms) */
+        .silence_detect_length = 300,         /* 静音检测长度 (ms) */
+        .rule1_min_tailing_silence = 480,     /* 规则1静音时间 (ms) */
+        .rule2_min_tailing_silence = 960,     /* 规则2静音时间 (ms) */
+        .rule3_min_tailing_silence = 960      /* 规则3静音时间 (ms) */
+    };
+    
+    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);
+    
+    /* 创建 VAD 处理任务(使用扩展内存) */
+    app_task_definition_ext_t task_cfg = {
+        .attr = {
+            .name = VAD_TASK_NAME,
+            .stack_size = VAD_TASK_STACK_SIZE,
+            .priority = VAD_TASK_PRIORITY,
+        },
+        .func = vad_process_task,
+        .task_handle = &g_vad_server.thread_handle,
+        .create_queue_handle = vad_queue_create,
+        .use_mem = USE_EXTEND_MEM,  /* 使用扩展内存(PSRAM) */
+        .inst_args = NULL
+    };
+    
+    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;
+    }
+    
+    /* 等待队列创建完成(最多等待 100ms) */
+    int wait_count = 0;
+    while (g_vad_server.queue_id == 0xFFFFFFFF && wait_count < 10) {
+        osDelay(10);  /* 延迟 10ms */
+        wait_count++;
+    }
+    
+    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.is_initialized = true;
+    
+    IMSL_INFO("[VAD_SERVER] VAD server initialized successfully\n");
+    IMSL_INFO("[VAD_SERVER] Thread handle: 0x%x, Queue ID: 0x%x\n",
+           g_vad_server.thread_handle, g_vad_server.queue_id);
+    
+    return ERRCODE_SUCC;
+}
+
+/**
+ * @brief 销毁 VAD 处理任务
+ */
+errcode_t vad_server_deinit(void)
+{
+    if (!g_vad_server.is_initialized) {
+        IMSL_INFO("[VAD_SERVER] Not initialized\n");
+        return ERRCODE_SUCC;
+    }
+    
+    IMSL_INFO("[VAD_SERVER] Deinitializing VAD server...\n");
+    
+    /* 发送停止消息 */
+    vad_process_msg_t stop_msg = {
+        .type = VAD_MSG_STOP,
+        .format = VAD_AUDIO_FORMAT_PCM,
+        .data = NULL,
+        .data_size = 0,
+        .is_last = false
+    };
+    
+    osal_msg_queue_write_copy(g_vad_server.queue_id, &stop_msg,
+                              VAD_QUEUE_MSG_SIZE, 1000);
+    
+    /* 等待任务停止 */
+    int timeout = 50;  /* 5 秒超时 */
+    while (g_vad_server.is_running && timeout > 0) {
+        osDelay(100);
+        timeout--;
+    }
+    
+    /* 删除消息队列 */
+    if (g_vad_server.queue_id) {
+        osal_msg_queue_delete(g_vad_server.queue_id);
+        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");
+    
+    g_vad_server.is_initialized = false;
+    
+    IMSL_INFO("[VAD_SERVER] VAD server deinitialized\n");
+    
+    return ERRCODE_SUCC;
+}
+
+/**
+ * @brief 发送音频数据到 VAD 处理队列
+ */
+errcode_t vad_server_send_audio(vad_audio_format_t format,
+                                const uint8_t *data,
+                                size_t data_size,
+                                bool is_last)
+{
+    if (!g_vad_server.is_initialized || !g_vad_server.is_running) {
+        IMSL_ERROR("[VAD_SERVER] Error: VAD server not running (initialized=%d, running=%d)\n",
+               g_vad_server.is_initialized, g_vad_server.is_running);
+        return ERRCODE_FAIL;
+    }
+    
+    if (!data || data_size == 0) {
+        IMSL_ERROR("[VAD_SERVER] Error: Invalid data (data=%p, size=%zu)\n", data, data_size);
+        return ERRCODE_FAIL;
+    }
+
+    if (g_vad_server.queue_id == 0xFFFFFFFF || g_vad_server.queue_id == 0) {
+        IMSL_ERROR("[VAD_SERVER] Error: Queue ID is invalid (0x%x)\n", g_vad_server.queue_id);
+        return ERRCODE_FAIL;
+    }
+    
+    /* 验证格式参数 */
+    if (format != VAD_AUDIO_FORMAT_PCM && format != VAD_AUDIO_FORMAT_MP3) {
+        IMSL_ERROR("[VAD_SERVER] Error: Invalid audio format: %d\n", format);
+        return ERRCODE_FAIL;
+    }
+    
+    /* 复制数据(由 VAD 任务负责释放) */
+    uint8_t *data_copy = (uint8_t *)malloc(data_size);
+    if (!data_copy) {
+        IMSL_ERROR("[VAD_SERVER] Error: Failed to allocate memory for data\n");
+        return ERRCODE_FAIL;
+    }
+    
+    memcpy(data_copy, data, data_size);
+    
+    /* 构造消息 */
+    vad_process_msg_t msg = {
+        .type = VAD_MSG_PROCESS_AUDIO,
+        .format = format,
+        .data = data_copy,
+        .data_size = data_size,
+        .is_last = is_last
+    };
+    
+    /* 调试:打印发送前的消息内容 */
+    static int send_count = 0;
+    if (send_count < 3) {  /* 只打印前3条消息 */
+        IMSL_DEBUG("[VAD_SERVER] Sending message #%d: type=%d, format=%d, data=%p, size=%zu, is_last=%d\n",
+               send_count, msg.type, msg.format, msg.data, msg.data_size, msg.is_last);
+        IMSL_INFO("[VAD_SERVER] Message struct size: %zu, VAD_QUEUE_MSG_SIZE: %zu\n",
+               sizeof(vad_process_msg_t), VAD_QUEUE_MSG_SIZE);
+        send_count++;
+    }
+    
+    /* 发送到队列 */
+    int ret = osal_msg_queue_write_copy(g_vad_server.queue_id, &msg,
+                                       VAD_QUEUE_MSG_SIZE, 1000);
+    if (ret != OSAL_SUCCESS) {
+        IMSL_ERROR("[VAD_SERVER] Error: Failed to send message to queue (ret=%d)\n", ret);
+        free(data_copy);
+        return ERRCODE_FAIL;
+    }
+    
+    return ERRCODE_SUCC;
+}
+
+/**
+ * @brief 重置 VAD 状态
+ */
+errcode_t vad_server_reset(void)
+{
+    if (!g_vad_server.is_initialized || !g_vad_server.is_running) {
+        IMSL_ERROR("[VAD_SERVER] Error: VAD server not running\n");
+        return ERRCODE_FAIL;
+    }
+    
+    vad_process_msg_t msg = {
+        .type = VAD_MSG_RESET,
+        .format = VAD_AUDIO_FORMAT_PCM,
+        .data = NULL,
+        .data_size = 0,
+        .is_last = false
+    };
+    
+    int ret = osal_msg_queue_write_copy(g_vad_server.queue_id, &msg,
+                                       VAD_QUEUE_MSG_SIZE, 1000);
+    if (ret != OSAL_SUCCESS) {
+        IMSL_ERROR("[VAD_SERVER] Error: Failed to send reset message\n");
+        return ERRCODE_FAIL;
+    }
+    
+    return ERRCODE_SUCC;
+}
+
+/**
+ * @brief 获取 VAD 处理任务运行状态
+ */
+bool vad_server_is_running(void)
+{
+    return g_vad_server.is_initialized && g_vad_server.is_running;
+}
+
+#ifdef MINI_MP3
+/**
+ * @brief 获取检测到的 MP3 格式信息
+ * @param channels 输出声道数
+ * @param sample_rate 输出采样率
+ * @return true=已检测到格式, false=使用默认值
+ */
+bool vad_server_get_mp3_format(int *channels, int *sample_rate)
+{
+    if (channels) {
+        *channels = g_vad_server.mp3_detected_channels;
+    }
+    if (sample_rate) {
+        *sample_rate = g_vad_server.mp3_detected_sample_rate;
+    }
+    return g_vad_server.mp3_format_detected;
+}
+#endif
+
index a3285c1..7f6809f 100644 (file)
@@ -424,6 +424,12 @@ void ln_ws_print_status_info(void);
 /* 回调函数注册接口 */
 void ln_ws_register_message_callback(ws_message_callback_t_ln callback);
 
+/* VAD 服务器控制接口(开机时控制) */
+int ln_ws_enable_vad_server(void);      /* 启用 VAD 服务器 */
+int ln_ws_disable_vad_server(void);     /* 禁用 VAD 服务器 */
+bool ln_ws_is_vad_server_enabled(void); /* 获取 VAD 服务器启用状态 */
+bool ln_ws_is_vad_server_initialized(void); /* 获取 VAD 服务器初始化状态 */
+
 #ifdef __cplusplus
 }
 #endif
index 4d893b9..3281dfd 100644 (file)
@@ -12,6 +12,9 @@
 #include "mbedtls/base64.h"
 #include "ai_media.h" // Added for AI_RegisterWriteAudioDataCallback
 #include "audio_base_type.h"
+#if ENABLE_VAD_SERVER
+#include "vad_server.h" // Added for VAD processing
+#endif
 // 功能开关宏定义
 #define ENABLE_BINARY_DATA_INTERRUPT_CHECK 1  // 启用二进制数据间隔检查
 
@@ -93,6 +96,10 @@ static ln_audio_chat_command_t g_audio_chat_command_type = LN_AUDIO_CHAT_VOICE_C
 /* 音频初始化状态标志位 */
 static volatile bool g_audio_initialized = false;
 
+/* VAD 服务器状态标志位 */
+static volatile bool g_vad_server_enabled = false;
+static volatile bool g_vad_server_initialized = false;
+
 /* 回调函数结构体 */
 static ws_call_backs_t g_ws_callbacks = {0};
 
@@ -474,6 +481,30 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f
         return -1;
     }
     
+    // ========== VAD 处理:在录音接口层推送 ==========
+    // 优势:1. 更上层更清晰  2. 天然只处理录音数据  3. finished即is_last
+#if ENABLE_VAD_SERVER
+    if (g_vad_server_enabled && g_vad_server_initialized && vad_server_is_running()) {
+        vad_audio_format_t format = VAD_AUDIO_FORMAT_MP3;  // 默认 MP3
+        
+        if (audio_data && audio_size > 0) {
+            errcode_t ret = vad_server_send_audio(format, audio_data, audio_size, finished);
+            if (ret != ERRCODE_SUCC) {
+                /* 静默失败,避免过多日志 */
+            } else {
+                LN_WS_DEBUG("VAD: Sent %u bytes (format=%d, is_last=%d)\n", 
+                           audio_size, format, finished);
+            }
+        }
+        
+        // 如果是结束标志,打印日志
+        if (finished) {
+            LN_WS_INFO("VAD: Audio recording ended\n");
+        }
+    }
+#endif
+    // ================================================
+    
     // 检查连接状态
     if (g_ws_connected == WS_STATE_RECONNECTING) {
         // 重连中,缓存音频数据
@@ -689,8 +720,15 @@ const char* ln_ws_get_current_chat_id(void)
 
 static void AIVoiceDataCallback_ln(const uint8_t* data, uint32_t size, void* userData)
 {
+    (void)userData;
+    
     // MEDIA_INFO_LOG("Received audio data: size=%u bytes", size);
-    if( g_audio_chat_ctx.is_active ==true)
+    
+    // ========== VAD 处理已移到 ln_ws_send_audio_data (录音接口层) ==========
+    // 更上层、更清晰、天然只处理录音数据
+    
+    // WebSocket 发送逻辑(如果需要同时发送到服务器)
+    if (g_audio_chat_ctx.is_active == true)
     {
          if (g_audio_chat_ending) {
             LN_WS_DEBUG("Sending audio chat end message\n");
@@ -701,15 +739,11 @@ static void AIVoiceDataCallback_ln(const uint8_t* data, uint32_t size, void* use
             g_audio_chat_ending = false;
         }
         else
-
         {
             // 使用新的音频发送接口
             ln_ws_send_audio_data(data, size, false);
         }
     }
-    
-    // 检查是否设置了结束标志
-   
 }
 
 // "audioBase64":
@@ -893,6 +927,13 @@ int ln_ws_start(ws_message_callback_t_ln callback, uint32_t audio_timeout_ticks)
     // 注册音频数据写入回调
     AI_RegisterWriteAudioDataCallback(ln_ws_audio_write_callback);
 
+#if ENABLE_VAD_SERVER
+    // 启动时自动启用 VAD 服务器
+    if (!g_vad_server_enabled) {
+        LN_WS_INFO("Enabling VAD server on WebSocket start\n");
+        g_vad_server_enabled = true;
+    }
+#endif
 
     return 0;
 }
@@ -927,6 +968,20 @@ void ln_ws_audio_init(void)
     // 设置初始化标志位
     g_audio_initialized = true;
     LN_WS_INFO("Audio initialization completed successfully\n");
+    
+#if ENABLE_VAD_SERVER
+    // 如果 VAD 服务器已启用,则初始化 VAD 服务器
+    if (g_vad_server_enabled && !g_vad_server_initialized) {
+        LN_WS_INFO("Initializing VAD server...\n");
+        errcode_t ret = vad_server_init(NULL);  // 使用 NULL 回调,也可以传入自定义回调
+        if (ret == ERRCODE_SUCC) {
+            g_vad_server_initialized = true;
+            LN_WS_INFO("VAD server initialized successfully\n");
+        } else {
+            LN_WS_ERROR("Failed to initialize VAD server: %d\n", ret);
+        }
+    }
+#endif
 }
 
 
@@ -938,6 +993,20 @@ void ln_ws_audio_deinit(void)
         return;
     }
     
+#if ENABLE_VAD_SERVER
+    // 如果 VAD 服务器已初始化,则停止 VAD 服务器
+    if (g_vad_server_initialized) {
+        LN_WS_INFO("Deinitializing VAD server...\n");
+        errcode_t ret = vad_server_deinit();
+        if (ret == ERRCODE_SUCC) {
+            g_vad_server_initialized = false;
+            LN_WS_INFO("VAD server deinitialized successfully\n");
+        } else {
+            LN_WS_ERROR("Failed to deinitialize VAD server: %d\n", ret);
+        }
+    }
+#endif
+    
     StopAIVoice();
 
     #ifdef SUPPORT_CAT1_SERVICE
@@ -956,6 +1025,86 @@ bool ln_ws_is_audio_initialized(void)
     return g_audio_initialized;
 }
 
+#if ENABLE_VAD_SERVER
+/**
+ * @brief 启用 VAD 服务器(开机时调用)
+ * @return 0成功,-1失败
+ * @note 此函数应在开机时调用,用于启用 VAD 功能
+ */
+int ln_ws_enable_vad_server(void)
+{
+    if (g_vad_server_enabled) {
+        LN_WS_INFO("VAD server already enabled\n");
+        return 0;
+    }
+    
+    LN_WS_INFO("Enabling VAD server\n");
+    g_vad_server_enabled = true;
+    
+    // 如果音频已经初始化,立即初始化 VAD 服务器
+    if (g_audio_initialized && !g_vad_server_initialized) {
+        errcode_t ret = vad_server_init(NULL);
+        if (ret == ERRCODE_SUCC) {
+            g_vad_server_initialized = true;
+            LN_WS_INFO("VAD server initialized successfully\n");
+            return 0;
+        } else {
+            LN_WS_ERROR("Failed to initialize VAD server: %d\n", ret);
+            return -1;
+        }
+    }
+    
+    return 0;
+}
+
+/**
+ * @brief 禁用 VAD 服务器
+ * @return 0成功,-1失败
+ */
+int ln_ws_disable_vad_server(void)
+{
+    if (!g_vad_server_enabled) {
+        LN_WS_INFO("VAD server already disabled\n");
+        return 0;
+    }
+    
+    LN_WS_INFO("Disabling VAD server\n");
+    
+    // 如果 VAD 服务器已初始化,先停止它
+    if (g_vad_server_initialized) {
+        errcode_t ret = vad_server_deinit();
+        if (ret == ERRCODE_SUCC) {
+            g_vad_server_initialized = false;
+            LN_WS_INFO("VAD server deinitialized\n");
+        } else {
+            LN_WS_ERROR("Failed to deinitialize VAD server: %d\n", ret);
+            return -1;
+        }
+    }
+    
+    g_vad_server_enabled = false;
+    return 0;
+}
+
+/**
+ * @brief 获取 VAD 服务器启用状态
+ * @return true=已启用,false=未启用
+ */
+bool ln_ws_is_vad_server_enabled(void)
+{
+    return g_vad_server_enabled;
+}
+
+/**
+ * @brief 获取 VAD 服务器初始化状态
+ * @return true=已初始化,false=未初始化
+ */
+bool ln_ws_is_vad_server_initialized(void)
+{
+    return g_vad_server_initialized;
+}
+#endif /* ENABLE_VAD_SERVER */
+
 
 
 
@@ -1160,6 +1309,15 @@ int ln_ws_stop(void)
     g_audio_playback_start_tick = 0;
     g_last_audio_write_tick = 0;
     
+#if ENABLE_VAD_SERVER
+    // 停止时自动禁用 VAD 服务器
+    if (g_vad_server_enabled) {
+        LN_WS_INFO("Disabling VAD server on WebSocket stop\n");
+        g_vad_server_enabled = false;
+        // 注意:VAD 服务器的实际清理已在 ln_ws_audio_deinit() 中完成
+    }
+#endif
+    
     return ret;
 }
 
index dd9f32f..7aaeb2b 100644 (file)
@@ -7,6 +7,7 @@ set(COMPONENT_NAME "minimp3")
 # 源文件:系统适配层实现
 set(SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/src/mp3_decoder.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/src/mp3_log.c
 )
 
 # 公共头文件路径(供其他模块引用)
diff --git a/code/sdk/zhihui/minimp3/include/mp3_log.h b/code/sdk/zhihui/minimp3/include/mp3_log.h
new file mode 100644 (file)
index 0000000..33e5403
--- /dev/null
@@ -0,0 +1,111 @@
+#ifndef MP3_LOG_H
+#define MP3_LOG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/**
+ * @file mp3_log.h
+ * @brief minimp3 模块日志系统
+ * 
+ * 提供可控等级的日志打印宏,支持 ERROR、WARN、INFO、DEBUG 四个等级
+ */
+
+/* 日志等级定义 */
+typedef enum {
+    MP3_LOG_LEVEL_NONE  = 0,   /* 关闭所有日志 */
+    MP3_LOG_LEVEL_ERROR = 1,   /* 仅错误信息 */
+    MP3_LOG_LEVEL_WARN  = 2,   /* 错误+警告信息 */
+    MP3_LOG_LEVEL_INFO  = 3,   /* 错误+警告+一般信息 */
+    MP3_LOG_LEVEL_DEBUG = 4    /* 所有信息包括调试 */
+} mp3_log_level_t;
+
+/* 默认日志等级:INFO */
+#ifndef MP3_DEFAULT_LOG_LEVEL
+#define MP3_DEFAULT_LOG_LEVEL MP3_LOG_LEVEL_INFO
+#endif
+
+/* 全局日志等级变量(可在运行时修改) */
+extern mp3_log_level_t g_mp3_log_level;
+
+/* 日志等级字符串 */
+static const char *g_mp3_log_level_str[] = {
+    "",         /* NONE */
+    "[ERROR]",  /* ERROR */
+    "[WARN ]",  /* WARN */
+    "[INFO ]",  /* INFO */
+    "[DEBUG]"   /* DEBUG */
+};
+
+/**
+ * @brief 基础日志宏(内部使用)
+ * @param level 日志等级
+ * @param fmt 格式化字符串
+ * @param ... 可变参数
+ */
+#define MP3_LOG(level, level_str, fmt, ...) do { \
+    if ((level) <= g_mp3_log_level) { \
+        printf("[MP3]%s " fmt, level_str, ##__VA_ARGS__); \
+    } \
+} while(0)
+
+/**
+ * @brief 错误日志宏
+ * @param fmt 格式化字符串
+ * @param ... 可变参数
+ * @note 用于打印错误信息,如解码失败、内存分配失败等
+ */
+#define MP3_ERROR(fmt, ...) MP3_LOG(MP3_LOG_LEVEL_ERROR, "[ERROR]", fmt, ##__VA_ARGS__)
+
+/**
+ * @brief 警告日志宏
+ * @param fmt 格式化字符串
+ * @param ... 可变参数
+ * @note 用于打印警告信息,如格式异常、数据截断等
+ */
+#define MP3_WARN(fmt, ...) MP3_LOG(MP3_LOG_LEVEL_WARN, "[WARN ]", fmt, ##__VA_ARGS__)
+
+/**
+ * @brief 信息日志宏
+ * @param fmt 格式化字符串
+ * @param ... 可变参数
+ * @note 用于打印一般信息,如解码器创建、格式检测等
+ */
+#define MP3_INFO(fmt, ...) MP3_LOG(MP3_LOG_LEVEL_INFO, "[INFO ]", fmt, ##__VA_ARGS__)
+
+/**
+ * @brief 调试日志宏
+ * @param fmt 格式化字符串
+ * @param ... 可变参数
+ * @note 用于打印调试信息,如详细的解码流程、帧信息等
+ */
+#define MP3_DEBUG(fmt, ...) MP3_LOG(MP3_LOG_LEVEL_DEBUG, "[DEBUG]", fmt, ##__VA_ARGS__)
+
+/**
+ * @brief 设置 MP3 日志等级
+ * @param level 日志等级
+ */
+static inline void mp3_set_log_level(mp3_log_level_t level)
+{
+    g_mp3_log_level = level;
+    printf("[MP3][INFO ] Log level set to %d\n", level);
+}
+
+/**
+ * @brief 获取当前 MP3 日志等级
+ * @return 当前日志等级
+ */
+static inline mp3_log_level_t mp3_get_log_level(void)
+{
+    return g_mp3_log_level;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MP3_LOG_H */
+
index 72a8877..463da02 100644 (file)
@@ -9,6 +9,7 @@
 #include <string.h>
 #include <stdint.h>
 #include <errno.h>
+#include "mp3_log.h"
 
 // 定义缓冲区大小 - 优化后使用流式解码
 #define MP3_STREAM_BUFFER_SIZE (16 * 1024)  // 16KB 流式缓冲区(优化:从1MB降至16KB)
@@ -80,11 +81,11 @@ static int mp3_decoder_try_decode_frame(void)
         
         // 打印第一帧信息
         if (g_mp3_decoder->total_frames_decoded == 1) {
-            printf("[MP3] First frame decoded:\n");
-            printf("  Channels: %d\n", g_mp3_decoder->frame_info.channels);
-            printf("  Sample Rate: %d Hz\n", g_mp3_decoder->frame_info.hz);
-            printf("  Bitrate: %d kbps\n", g_mp3_decoder->frame_info.bitrate_kbps);
-            printf("  Layer: %d\n", g_mp3_decoder->frame_info.layer);
+            MP3_DEBUG("[MP3] First frame decoded:\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);
         }
         
         // 调用回调函数
@@ -125,18 +126,18 @@ static int mp3_decoder_try_decode_frame(void)
  */
 mp3_decode_error_t mp3_decoder_create(void)
 {
-    printf("[MP3] Creating stream decoder...\n");
+    MP3_INFO("[MP3] Creating stream decoder...\n");
     
     // 检查是否已经创建
     if (g_mp3_decoder && g_mp3_decoder->is_initialized) {
-        printf("[MP3] Decoder already initialized\n");
+        MP3_INFO("[MP3] Decoder already initialized\n");
         return MP3_DECODE_SUCCESS;
     }
     
     // 分配解码器结构体内存(包含16KB流缓冲区)
     g_mp3_decoder = (mp3_decoder_t*)malloc(sizeof(mp3_decoder_t));
     if (!g_mp3_decoder) {
-        printf("[MP3] Error: Failed to allocate decoder memory (%zu bytes)\n", sizeof(mp3_decoder_t));
+        MP3_ERROR("[MP3] Error: Failed to allocate decoder memory (%zu bytes)\n", sizeof(mp3_decoder_t));
         return MP3_DECODE_ERROR_MEMORY;
     }
     
@@ -149,7 +150,7 @@ mp3_decode_error_t mp3_decoder_create(void)
     // 分配 PCM 输出缓冲区(4.6KB)
     g_mp3_decoder->pcm_buffer = (int16_t*)malloc(PCM_BUFFER_SIZE * sizeof(int16_t));
     if (!g_mp3_decoder->pcm_buffer) {
-        printf("[MP3] Error: Failed to allocate PCM buffer (%zu bytes)\n", PCM_BUFFER_SIZE * sizeof(int16_t));
+        MP3_ERROR("[MP3] Error: Failed to allocate PCM buffer (%zu bytes)\n", PCM_BUFFER_SIZE * sizeof(int16_t));
         free(g_mp3_decoder);
         g_mp3_decoder = NULL;
         return MP3_DECODE_ERROR_MEMORY;
@@ -162,8 +163,8 @@ mp3_decode_error_t mp3_decoder_create(void)
     g_mp3_decoder->pcm_callback = NULL;
     g_mp3_decoder->is_initialized = true;
     
-    printf("[MP3] Decoder created successfully\n");
-    printf("[MP3] Memory usage: ~%zu KB (structure=%zu, PCM buffer=%zu)\n",
+    MP3_INFO("[MP3] Decoder created successfully\n");
+    MP3_DEBUG("[MP3] Memory usage: ~%zu KB (structure=%zu, PCM buffer=%zu)\n",
            (sizeof(mp3_decoder_t) + PCM_BUFFER_SIZE * sizeof(int16_t)) / 1024,
            sizeof(mp3_decoder_t) / 1024,
            (PCM_BUFFER_SIZE * sizeof(int16_t)) / 1024);
@@ -177,11 +178,11 @@ mp3_decode_error_t mp3_decoder_create(void)
 void mp3_decoder_destroy(void)
 {
     if (!g_mp3_decoder) {
-        printf("[MP3] Decoder not initialized\n");
+        MP3_INFO("[MP3] Decoder not initialized\n");
         return;
     }
     
-    printf("[MP3] Destroying decoder...\n");
+    MP3_INFO("[MP3] Destroying decoder...\n");
     
     // 释放 PCM 缓冲区
     if (g_mp3_decoder->pcm_buffer) {
@@ -193,7 +194,7 @@ void mp3_decoder_destroy(void)
     free(g_mp3_decoder);
     g_mp3_decoder = NULL;
     
-    printf("[MP3] Decoder destroyed\n");
+    MP3_INFO("[MP3] Decoder destroyed\n");
 }
 
 /**
@@ -205,7 +206,7 @@ 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) {
-        printf("[MP3] Error: Decoder not initialized\n");
+        MP3_ERROR("[MP3] Error: Decoder not initialized\n");
         return MP3_DECODE_ERROR_UNKNOWN;
     }
     
@@ -216,7 +217,7 @@ mp3_decode_error_t mp3_decoder_feed_data(const uint8_t *data, size_t data_size)
     // 检查缓冲区空间
     size_t available = MP3_STREAM_BUFFER_SIZE - g_mp3_decoder->stream_buffer_used;
     if (data_size > available) {
-        printf("[MP3] Warning: Stream buffer full, processing data in chunks\n");
+        MP3_WARN("[MP3] Warning: Stream buffer full, processing data in chunks\n");
         data_size = available;  // 只接受能容纳的数据
     }
     
@@ -241,7 +242,7 @@ mp3_decode_error_t mp3_decoder_feed_data(const uint8_t *data, size_t data_size)
     }
     
     if (decoded_frames > 0) {
-        printf("[MP3] Decoded %d frames from %zu bytes input\n", decoded_frames, data_size);
+        MP3_DEBUG("[MP3] Decoded %d frames from %zu bytes input\n", decoded_frames, data_size);
     }
     
     return MP3_DECODE_SUCCESS;
@@ -257,13 +258,13 @@ mp3_decode_error_t mp3_decoder_flush(void)
         return MP3_DECODE_ERROR_UNKNOWN;
     }
     
-    printf("[MP3] Flushing decoder, remaining buffer: %zu bytes\n", 
+    MP3_DEBUG("[MP3] Flushing decoder, remaining buffer: %zu bytes\n", 
            g_mp3_decoder->stream_buffer_used);
     
     // 清空缓冲区
     g_mp3_decoder->stream_buffer_used = 0;
     
-    printf("[MP3] Total decoded: %d frames, %d samples\n",
+    MP3_DEBUG("[MP3] Total decoded: %d frames, %d samples\n",
            g_mp3_decoder->total_frames_decoded,
            g_mp3_decoder->total_samples_decoded);
     
@@ -278,7 +279,7 @@ void mp3_decoder_set_callback(void (*callback)(const int16_t *pcm_data, int samp
 {
     if (g_mp3_decoder && g_mp3_decoder->is_initialized) {
         g_mp3_decoder->pcm_callback = callback;
-        printf("[MP3] PCM callback set\n");
+        MP3_INFO("[MP3] PCM callback set\n");
     }
 }
 
@@ -315,11 +316,11 @@ mp3_decode_error_t mp3_decoder_decode_file_stream(
     // 打开文件
     FILE *file = fopen(filename, "rb");
     if (!file) {
-        printf("[MP3] Error: Cannot open file %s: %s\n", filename, strerror(errno));
+        MP3_ERROR("[MP3] Error: Cannot open file %s: %s\n", filename, strerror(errno));
         return MP3_DECODE_ERROR_FILE_OPEN;
     }
     
-    printf("[MP3] Streaming decode: %s\n", filename);
+    MP3_INFO("[MP3] Streaming decode: %s\n", filename);
     
     // 流式读取并解码
     uint8_t chunk[MP3_FILE_CHUNK_SIZE];
@@ -336,8 +337,8 @@ mp3_decode_error_t mp3_decoder_decode_file_stream(
     // 刷新剩余数据
     mp3_decoder_flush();
     
-    printf("[MP3] File decoded successfully\n");
-    printf("[MP3] Total read: %zu bytes, decoded: %d frames, %d samples\n",
+    MP3_INFO("[MP3] File decoded successfully\n");
+    MP3_DEBUG("[MP3] Total read: %zu bytes, decoded: %d frames, %d samples\n",
            total_bytes, g_mp3_decoder->total_frames_decoded, g_mp3_decoder->total_samples_decoded);
     
     return MP3_DECODE_SUCCESS;
@@ -375,7 +376,7 @@ int save_pcm_as_wav(const char *filename, const int16_t *pcm_data, int samples,
 {
     FILE *file = fopen(filename, "wb");
     if (!file) {
-        printf("Error: Cannot create WAV file %s\n", filename);
+        MP3_ERROR("Error: Cannot create WAV file %s\n", filename);
         return -1;
     }
     
@@ -409,7 +410,7 @@ int save_pcm_as_wav(const char *filename, const int16_t *pcm_data, int samples,
     fwrite(pcm_data, sizeof(int16_t), samples * channels, file);
     
     fclose(file);
-    printf("WAV file saved: %s (%d samples)\n", filename, samples);
+    MP3_INFO("WAV file saved: %s (%d samples)\n", filename, samples);
     return 0;
 }
 
@@ -420,7 +421,7 @@ void pcm_callback_example(const int16_t *pcm_data, int samples, int channels, in
     call_count++;
     
     if (call_count % 100 == 1) { // 每100帧打印一次
-        printf("Frame %d: %d samples, %d channels, %d Hz\n", 
+        MP3_DEBUG("Frame %d: %d samples, %d channels, %d Hz\n", 
                call_count, samples, channels, sample_rate);
     }
 }
@@ -434,17 +435,17 @@ void pcm_callback_save_to_file(const int16_t *pcm_data, int samples, int channel
     if (!pcm_file) {
         pcm_file = fopen("output.pcm", "wb");
         if (!pcm_file) {
-            printf("Error: Cannot create PCM file\n");
+            MP3_ERROR("Error: Cannot create PCM file\n");
             return;
         }
-        printf("PCM data will be saved to output.pcm\n");
+        MP3_DEBUG("PCM data will be saved to output.pcm\n");
     }
     
     fwrite(pcm_data, sizeof(int16_t), samples * channels, pcm_file);
     frame_count++;
     
     if (frame_count % 100 == 0) {
-        printf("Saved %d frames to PCM file\n", frame_count);
+        MP3_DEBUG("Saved %d frames to PCM file\n", frame_count);
     }
 }
 
@@ -482,6 +483,6 @@ mp3_decode_error_t mp3_decoder_reset(void)
     g_mp3_decoder->total_samples_decoded = 0;
     g_mp3_decoder->total_frames_decoded = 0;
     
-    printf("[MP3] Decoder reset\n");
+    MP3_INFO("[MP3] Decoder reset\n");
     return MP3_DECODE_SUCCESS;
 }
diff --git a/code/sdk/zhihui/minimp3/src/mp3_log.c b/code/sdk/zhihui/minimp3/src/mp3_log.c
new file mode 100644 (file)
index 0000000..daa6909
--- /dev/null
@@ -0,0 +1,10 @@
+#include "mp3_log.h"
+
+/**
+ * @file mp3_log.c
+ * @brief minimp3 模块日志系统实现
+ */
+
+/* 全局日志等级变量定义 */
+mp3_log_level_t g_mp3_log_level = MP3_DEFAULT_LOG_LEVEL;
+