From: 孙春阳 Date: Tue, 21 Oct 2025 01:47:15 +0000 (+0800) Subject: imsl vad +minimp3 加入了,可以正常检测到语音,但是会延迟识别。当下也就停止说话能好用。 X-Git-Tag: V1.0.9_20251128_194606_LN~193 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=b59285be0ed01270ce9ce2f396e006e8569ea1b1;p=mirrors%2FFD230.git imsl vad +minimp3 加入了,可以正常检测到语音,但是会延迟识别。当下也就停止说话能好用。 --- diff --git a/code/sdk/application/brandy/brandy_standard/service_init.c b/code/sdk/application/brandy/brandy_standard/service_init.c index c09e1b954..0c7b0c804 100644 --- a/code/sdk/application/brandy/brandy_standard/service_init.c +++ b/code/sdk/application/brandy/brandy_standard/service_init.c @@ -113,7 +113,7 @@ #include "ai_V2_5_at_cmd.h" #endif -#ifdef IMSL_VAD +#ifdef ENABLE_VAD_SERVER #include "imsl_at_cmd.h" #endif @@ -273,7 +273,7 @@ static void register_at_service(void) at_cmd_add_moudle(ai_table, ai_cmd_count); #endif -#ifdef IMSL_VAD +#ifdef ENABLE_VAD_SERVER uint32_t imsl_vad_cmd_count; at_cmd_table_t *imsl_vad_table = get_imsl_vad_at_cmd_table(&imsl_vad_cmd_count); at_cmd_add_moudle(imsl_vad_table, imsl_vad_cmd_count); diff --git a/code/sdk/zhihui/imsl/CMakeLists.txt b/code/sdk/zhihui/imsl/CMakeLists.txt index ef684dd95..d5521d382 100644 --- a/code/sdk/zhihui/imsl/CMakeLists.txt +++ b/code/sdk/zhihui/imsl/CMakeLists.txt @@ -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 index 000000000..fe3660348 --- /dev/null +++ b/code/sdk/zhihui/imsl/include/imsl_log.h @@ -0,0 +1,111 @@ +#ifndef IMSL_LOG_H +#define IMSL_LOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * @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 index 000000000..bba4ae3a2 --- /dev/null +++ b/code/sdk/zhihui/imsl/include/vad_server.h @@ -0,0 +1,94 @@ +#ifndef VAD_SERVER_H +#define VAD_SERVER_H + +#include +#include +#include +#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 */ + diff --git a/code/sdk/zhihui/imsl/src/imsl_at_cmd.c b/code/sdk/zhihui/imsl/src/imsl_at_cmd.c index e0e33443f..be9fa7e03 100644 --- a/code/sdk/zhihui/imsl/src/imsl_at_cmd.c +++ b/code/sdk/zhihui/imsl/src/imsl_at_cmd.c @@ -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 \n"); + IMSL_INFO("Usage: AT^VAD_CONFIG \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 \n"); + IMSL_INFO("Usage: AT^VAD_ANALYZE_PCM \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 - 分析已有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 - 分析已有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 index 000000000..f669d7ce0 --- /dev/null +++ b/code/sdk/zhihui/imsl/src/imsl_log.c @@ -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 index 000000000..d9baff784 --- /dev/null +++ b/code/sdk/zhihui/imsl/src/vad_server.c @@ -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 +#include +#include + +#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 + diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h index a3285c10e..7f6809f87 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h @@ -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 diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c index 4d893b94b..3281dfd74 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c @@ -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; } diff --git a/code/sdk/zhihui/minimp3/CMakeLists.txt b/code/sdk/zhihui/minimp3/CMakeLists.txt index dd9f32fdd..7aaeb2b73 100644 --- a/code/sdk/zhihui/minimp3/CMakeLists.txt +++ b/code/sdk/zhihui/minimp3/CMakeLists.txt @@ -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 index 000000000..33e5403eb --- /dev/null +++ b/code/sdk/zhihui/minimp3/include/mp3_log.h @@ -0,0 +1,111 @@ +#ifndef MP3_LOG_H +#define MP3_LOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * @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 */ + diff --git a/code/sdk/zhihui/minimp3/src/mp3_decoder.c b/code/sdk/zhihui/minimp3/src/mp3_decoder.c index 72a887777..463da025d 100644 --- a/code/sdk/zhihui/minimp3/src/mp3_decoder.c +++ b/code/sdk/zhihui/minimp3/src/mp3_decoder.c @@ -9,6 +9,7 @@ #include #include #include +#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 index 000000000..daa6909c5 --- /dev/null +++ b/code/sdk/zhihui/minimp3/src/mp3_log.c @@ -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; +