From c4d182bba5bfa762a98cf507beb213dab3af7f24 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E5=AD=99=E6=98=A5=E9=98=B3?= Date: Mon, 20 Oct 2025 19:12:09 +0800 Subject: [PATCH] =?utf8?q?imsl,=20minimp3=20=E7=8B=AC=E7=AB=8B=E6=88=90?= =?utf8?q?=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../brandy/brandy_standard/service_init.c | 10 + .../config/target_config/brandy/config.py | 3 + code/sdk/zhihui/CMakeLists.txt | 2 + code/sdk/zhihui/imsl/CMakeLists.txt | 67 ++ code/sdk/zhihui/imsl/include/imsl_at_cmd.h | 23 + .../imsl_ln => imsl}/include/imsl_kws.h | 0 .../imsl_ln => imsl}/include/imsl_vad.h | 0 .../imsl_ln => imsl}/lib/libimslkws.a | Bin .../{leiniao/imsl_ln => imsl}/lib/libimslnn.a | Bin .../imsl_ln => imsl}/lib/libimslvad.a | Bin code/sdk/zhihui/imsl/src/imsl_at_cmd.c | 536 +++++++++++++ .../src}/imsl_system_adapter.c | 0 .../sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt | 10 - .../zhihui/leiniao/imsl_ln/leiniao_api_at.c | 716 +++--------------- code/sdk/zhihui/minimp3/CMakeLists.txt | 57 ++ .../{utils => minimp3/include}/minimp3.h | 0 .../{utils => minimp3/include}/mp3_decoder.h | 90 +-- code/sdk/zhihui/minimp3/src/mp3_decoder.c | 487 ++++++++++++ code/sdk/zhihui/utils/README_MP3_DECODER.md | 359 --------- code/sdk/zhihui/utils/mp3_decoder.c | 712 ----------------- 20 files changed, 1346 insertions(+), 1726 deletions(-) create mode 100644 code/sdk/zhihui/imsl/CMakeLists.txt create mode 100644 code/sdk/zhihui/imsl/include/imsl_at_cmd.h rename code/sdk/zhihui/{leiniao/imsl_ln => imsl}/include/imsl_kws.h (100%) rename code/sdk/zhihui/{leiniao/imsl_ln => imsl}/include/imsl_vad.h (100%) rename code/sdk/zhihui/{leiniao/imsl_ln => imsl}/lib/libimslkws.a (100%) rename code/sdk/zhihui/{leiniao/imsl_ln => imsl}/lib/libimslnn.a (100%) rename code/sdk/zhihui/{leiniao/imsl_ln => imsl}/lib/libimslvad.a (100%) create mode 100644 code/sdk/zhihui/imsl/src/imsl_at_cmd.c rename code/sdk/zhihui/{leiniao/imsl_ln => imsl/src}/imsl_system_adapter.c (100%) create mode 100644 code/sdk/zhihui/minimp3/CMakeLists.txt rename code/sdk/zhihui/{utils => minimp3/include}/minimp3.h (100%) rename code/sdk/zhihui/{utils => minimp3/include}/mp3_decoder.h (52%) create mode 100644 code/sdk/zhihui/minimp3/src/mp3_decoder.c delete mode 100644 code/sdk/zhihui/utils/README_MP3_DECODER.md delete mode 100644 code/sdk/zhihui/utils/mp3_decoder.c diff --git a/code/sdk/application/brandy/brandy_standard/service_init.c b/code/sdk/application/brandy/brandy_standard/service_init.c index 9b7fedc2c..c09e1b954 100644 --- a/code/sdk/application/brandy/brandy_standard/service_init.c +++ b/code/sdk/application/brandy/brandy_standard/service_init.c @@ -113,6 +113,10 @@ #include "ai_V2_5_at_cmd.h" #endif +#ifdef IMSL_VAD +#include "imsl_at_cmd.h" +#endif + static void register_at_service(void) { #ifdef SUPPORT_AT_GRAPHIC @@ -269,6 +273,12 @@ static void register_at_service(void) at_cmd_add_moudle(ai_table, ai_cmd_count); #endif +#ifdef IMSL_VAD + 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); +#endif + } #ifdef CONFIG_OTA_UPDATE_SUPPORT diff --git a/code/sdk/build/config/target_config/brandy/config.py b/code/sdk/build/config/target_config/brandy/config.py index 8bad25f6a..879ab857d 100644 --- a/code/sdk/build/config/target_config/brandy/config.py +++ b/code/sdk/build/config/target_config/brandy/config.py @@ -1494,6 +1494,9 @@ target = { "ai_stub", #音频相关配置 "audio_manager", + + "imsl", + "minimp3", # "CustomUIcomponentTool", "nativeabilityfwk", "custom_utils", diff --git a/code/sdk/zhihui/CMakeLists.txt b/code/sdk/zhihui/CMakeLists.txt index b6c40a308..6a1ca3e75 100644 --- a/code/sdk/zhihui/CMakeLists.txt +++ b/code/sdk/zhihui/CMakeLists.txt @@ -28,3 +28,5 @@ add_subdirectory_if_exist(websocket) add_subdirectory_if_exist(4gTestcpp) add_subdirectory_if_exist(ppp) add_subdirectory_if_exist(ai_new) +add_subdirectory_if_exist(imsl) +add_subdirectory_if_exist(minimp3) diff --git a/code/sdk/zhihui/imsl/CMakeLists.txt b/code/sdk/zhihui/imsl/CMakeLists.txt new file mode 100644 index 000000000..ef684dd95 --- /dev/null +++ b/code/sdk/zhihui/imsl/CMakeLists.txt @@ -0,0 +1,67 @@ +#=============================================================================== +# @brief imsl_ln component cmake file +# Copyright (c) @CompanyNameMagicTag 2023-2023. All rights reserved. +#=============================================================================== +set(COMPONENT_NAME "imsl") + +# 源文件:系统适配层实现 +set(SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/src/imsl_system_adapter.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/imsl_at_cmd.c +) + +# 公共头文件路径(供其他模块引用) +set(PUBLIC_HEADER + ${CMAKE_CURRENT_SOURCE_DIR}/include # 包含imsl_vad.h和imsl_kws.h的目录 +) + +# 公共宏定义(可选) +set(PUBLIC_DEFINES + # 例如:IMSL_LN_ENABLE=1 + IMSL_VAD=1 +) + +# 私有头文件路径(仅当前组件内部使用) +set(PRIVATE_HEADER + # 如无特殊私有头文件,可留空 +) + +# 私有宏定义(可选) +set(PRIVATE_DEFINES + # 如无特殊私有宏,可留空 +) + +# 公共编译选项(可选) +set(COMPONENT_PUBLIC_CCFLAGS +) + +# 组件专属编译选项 +set(COMPONENT_CCFLAGS + -Wno-incompatible-function-pointer-types + -Wno-unused-function # 屏蔽未使用函数的警告(根据实际情况调整) +) + +# 是否启用全量链接(静态库通常需要) +set(WHOLE_LINK + true +) + +# 是否为主组件(通常为false,主组件一般是app) +set(MAIN_COMPONENT + false +) + +# 安装SDK时包含的文件(头文件和库文件) +# install_sdk(${CMAKE_CURRENT_SOURCE_DIR}/include "*.h") # 安装头文件 +# install_sdk(${CMAKE_CURRENT_SOURCE_DIR}/lib "*.a") # 安装静态库(假设.a在lib目录) +install_sdk(${CMAKE_CURRENT_SOURCE_DIR} "*") + +# 直接指定静态库全路径(目录下现有文件名为 libimslvad.a、libimslkws.a、libimslnn.a) +set(LIBS + ${CMAKE_CURRENT_SOURCE_DIR}/lib/libimslvad.a + ${CMAKE_CURRENT_SOURCE_DIR}/lib/libimslkws.a + ${CMAKE_CURRENT_SOURCE_DIR}/lib/libimslnn.a +) + +# 构建组件 +build_component() \ No newline at end of file diff --git a/code/sdk/zhihui/imsl/include/imsl_at_cmd.h b/code/sdk/zhihui/imsl/include/imsl_at_cmd.h new file mode 100644 index 000000000..95c421432 --- /dev/null +++ b/code/sdk/zhihui/imsl/include/imsl_at_cmd.h @@ -0,0 +1,23 @@ +#ifndef IMSL_AT_CMD_H +#define IMSL_AT_CMD_H + +#include +#include "at_cmd_api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief 获取 IMSL VAD AT 命令表 + * @param cmd_count 输出参数,返回命令数量 + * @return AT 命令表指针 + */ +at_cmd_table_t *get_imsl_vad_at_cmd_table(uint32_t *cmd_count); + +#ifdef __cplusplus +} +#endif + +#endif /* IMSL_AT_CMD_H */ + diff --git a/code/sdk/zhihui/leiniao/imsl_ln/include/imsl_kws.h b/code/sdk/zhihui/imsl/include/imsl_kws.h similarity index 100% rename from code/sdk/zhihui/leiniao/imsl_ln/include/imsl_kws.h rename to code/sdk/zhihui/imsl/include/imsl_kws.h diff --git a/code/sdk/zhihui/leiniao/imsl_ln/include/imsl_vad.h b/code/sdk/zhihui/imsl/include/imsl_vad.h similarity index 100% rename from code/sdk/zhihui/leiniao/imsl_ln/include/imsl_vad.h rename to code/sdk/zhihui/imsl/include/imsl_vad.h diff --git a/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslkws.a b/code/sdk/zhihui/imsl/lib/libimslkws.a similarity index 100% rename from code/sdk/zhihui/leiniao/imsl_ln/lib/libimslkws.a rename to code/sdk/zhihui/imsl/lib/libimslkws.a diff --git a/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslnn.a b/code/sdk/zhihui/imsl/lib/libimslnn.a similarity index 100% rename from code/sdk/zhihui/leiniao/imsl_ln/lib/libimslnn.a rename to code/sdk/zhihui/imsl/lib/libimslnn.a diff --git a/code/sdk/zhihui/leiniao/imsl_ln/lib/libimslvad.a b/code/sdk/zhihui/imsl/lib/libimslvad.a similarity index 100% rename from code/sdk/zhihui/leiniao/imsl_ln/lib/libimslvad.a rename to code/sdk/zhihui/imsl/lib/libimslvad.a diff --git a/code/sdk/zhihui/imsl/src/imsl_at_cmd.c b/code/sdk/zhihui/imsl/src/imsl_at_cmd.c new file mode 100644 index 000000000..e0e33443f --- /dev/null +++ b/code/sdk/zhihui/imsl/src/imsl_at_cmd.c @@ -0,0 +1,536 @@ +#include "at_cmd_api.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "errcode.h" +#include "imsl_vad.h" +#include "media_thread_adapt.h" + +#define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) +#define VAD_MAX_ARGC 8 +#define VAD_MAX_ARGV_LEN 64 +#define VAD_FAILED (-1) +#define VAD_SUCCESS (0) + +/* Indicate that a local variable is not used */ +#define VAD_UNUSED(a) ((void)(a)) + +// 全局 VAD 状态 +static bool g_vad_initialized = false; +static int g_vad_status = -1; +static struct VadConfig g_vad_config = {0}; + +// 音频缓冲区(用于真实语音测试) +#define VAD_AUDIO_BUFFER_SIZE 3200 // 200ms @ 16kHz +static int16_t *g_audio_buffer = NULL; /* heap allocated */ +static int g_audio_buffer_pos = 0; + +/* 录音封装(由 C++ 提供,C 接口声明) */ +extern int32_t AudioCaptureSample(int32_t argc, const char *argv[]); + +// 实时录音相关 +static volatile bool g_vad_live_running = false; +static MediaThreadIdHandle g_vad_tail_thread = NULL; +#define LN_VAD_PATH_CAP 256 +static char *g_vad_live_path = NULL; /* heap allocated, cap LN_VAD_PATH_CAP */ +static int g_vad_prev_status = -1; + +/* 工具函数:参数解析 */ +static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN], uint32_t *argc) +{ + uint32_t pos = 0; + *argc = 0; + + for (uint32_t i = 0; i < paraLen; i++) + { + /* 以空格/换行/逗号/等号作为分隔符,兼容 "AT^CMD=arg1,arg2" 或 "AT^CMD arg1 arg2" */ + if ((para[i] == ' ' || para[i] == '\n' || para[i] == ',' || para[i] == '=') && pos != 0) + { + argv[*argc][pos] = '\0'; + *argc += 1; + pos = 0; + if (*argc >= VAD_MAX_ARGC) + { + return VAD_FAILED; + } + } + else + { + if (para[i] == ' ' || para[i] == '\n' || para[i] == ',' || para[i] == '=') { + continue; /* 连续分隔符跳过 */ + } + argv[*argc][pos] = para[i]; + pos++; + if (pos >= VAD_MAX_ARGV_LEN) + { + return VAD_FAILED; + } + } + } + if (pos != 0) + { + *argc += 1; + } + + return VAD_SUCCESS; +} + +/* 将 PCM 文件按 10ms(160点) 分帧喂给 VAD 并打印结果 */ +static int AnalyzePcmFileWithVad(const char *pcm_path) +{ + FILE *fp = fopen(pcm_path, "rb"); + if (fp == NULL) + { + printf("open pcm failed: %s\n", pcm_path); + return VAD_FAILED; + } + + int16_t frame[160]; + size_t total_frames = 0; + int prev = -1; + int count_speech = 0; + int count_end = 0; + + while (1) + { + size_t n = fread(frame, sizeof(int16_t), 160, fp); + if (n < 160) + { + break; + } + run_vad(frame, 160); + int st = vad_detect(); + if (st != prev) + { + printf("VAD状态变化: %d -> %d 于帧%zu\n", prev, st, total_frames); + prev = st; + } + if (st == 1) + { + count_speech++; + } + else if (st == 2) + { + count_end++; + vad_clear(); + } + total_frames++; + } + fclose(fp); + printf("总帧数:%zu, 语音帧:%d, 结束帧:%d\n", total_frames, count_speech, count_end); + return VAD_SUCCESS; +} + +static void EnsureVadLivePathInitialized(void) +{ + if (g_vad_live_path == NULL) + { + g_vad_live_path = (char *)malloc(LN_VAD_PATH_CAP); + if (g_vad_live_path) + { + g_vad_live_path[0] = '\0'; + strncpy(g_vad_live_path, "/user/vad_live.pcm", LN_VAD_PATH_CAP - 1); + g_vad_live_path[LN_VAD_PATH_CAP - 1] = '\0'; + } + } +} + +// 尾随线程,持续从文件中读取数据并喂给 VAD +static void *VadTailThread(void *arg) +{ + printf("VAD TailThread\n"); + (void)arg; + FILE *fp = NULL; + int16_t frame[160]; + size_t filled = 0; + + /* 等待文件可读 */ + while (g_vad_live_running && fp == NULL) + { + EnsureVadLivePathInitialized(); + fp = fopen(g_vad_live_path ? g_vad_live_path : "/user/vad_live.pcm", "rb"); + if (fp == NULL) + { + usleep(50 * 1000); + } + } + if (!g_vad_live_running) + { + if (fp) + fclose(fp); + return NULL; + } + + /* 从头开始读,持续尾随追加的数据 */ + while (g_vad_live_running) + { + int16_t sample; + size_t n = fread(&sample, sizeof(int16_t), 1, fp); + if (n == 1) + { + frame[filled++] = sample; + if (filled == 160) + { + run_vad(frame, 160); + int st = vad_detect(); + if (st != g_vad_prev_status) + { + printf("[VAD-LIVE] 状态变化: %d -> %d\n", g_vad_prev_status, st); + g_vad_prev_status = st; + if (st == 2) + { + printf("[VAD-LIVE] 检测到语音结束,vad_clear()\n"); + vad_clear(); + } + } + filled = 0; + } + } + else + { + /* 到达当前 EOF,短暂等待数据追加 */ + clearerr(fp); + usleep(10 * 1000); + } + } + if (fp) + fclose(fp); + return NULL; +} + +static void *StartRecordThread(void *arg) +{ + printf("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"}; + (void)AudioCaptureSample(6, startArgs); + return NULL; +} + +/* 确保父目录存在 */ +static void EnsureParentDir(const char *file_path) +{ + if (file_path == NULL) + { + return; + } + const char *slash = strrchr(file_path, '/'); + if (slash == NULL || slash == file_path) + { + return; // no parent or root + } + char dir[256]; + size_t len = (size_t)(slash - file_path); + if (len >= sizeof(dir)) + { + return; + } + memcpy(dir, file_path, len); + dir[len] = '\0'; + struct stat st; + if (stat(dir, &st) == 0) + { + return; // exists + } + (void)mkdir(dir, 0777); +} + +// AT^VAD_INIT - 初始化 VAD +static uint32_t vad_init(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(strCommonCmd); + uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0}; + uint32_t argc = 0; + + int ret = ConvertCmdToParam(para, paraLen, argv, &argc); + if (ret != VAD_SUCCESS) + { + printf("VADTEST ATCMD Convert Command Failed\n"); + return ERRCODE_FAIL; + } + + if (g_vad_initialized) + { + printf("VAD already initialized\n"); + return ERRCODE_SUCC; + } + + printf("=== VAD AT 指令测试 ===\n"); + + // 设置日志回调 + extern imsl_nn_printf_func imsl_nn_printf; + imsl_nn_printf = printf; + printf("✓ 日志回调设置成功\n"); + + // 初始化 VAD 配置 + g_vad_config.sil_threshold = 63; + g_vad_config.energy_threshold = 10000; + g_vad_config.min_speech_length = 240; + g_vad_config.silence_detect_length = 300; + g_vad_config.rule1_min_tailing_silence = 480; + g_vad_config.rule2_min_tailing_silence = 960; + g_vad_config.rule3_min_tailing_silence = 960; + g_vad_config.vad_log_level = 2; + + int init_result = init_vad(&g_vad_config); + if (init_result == 0) + { + printf("❌ VAD 初始化失败\n"); + return ERRCODE_FAIL; + } + + printf("✓ VAD 初始化成功 (状态: %d)\n", init_result); + g_vad_initialized = true; + g_vad_status = 0; + + return ERRCODE_SUCC; +} + +// AT^VAD_CONFIG - 配置 VAD 参数 +static uint32_t vad_config(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(strCommonCmd); + uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0}; + uint32_t argc = 0; + + int ret = ConvertCmdToParam(para, paraLen, argv, &argc); + if (ret != VAD_SUCCESS) + { + printf("VADTEST ATCMD Convert Command Failed\n"); + return ERRCODE_FAIL; + } + + if (!g_vad_initialized) + { + printf("VAD not initialized, call AT^VAD_INIT first\n"); + return ERRCODE_FAIL; + } + + if (argc != 7) + { + printf("Usage: AT^VAD_CONFIG \n"); + return ERRCODE_FAIL; + } + + // 解析参数 + g_vad_config.sil_threshold = atoi((char *)argv[0]); + g_vad_config.energy_threshold = atoi((char *)argv[1]); + g_vad_config.min_speech_length = atoi((char *)argv[2]); + g_vad_config.silence_detect_length = atoi((char *)argv[3]); + g_vad_config.rule1_min_tailing_silence = atoi((char *)argv[4]); + 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); + + return ERRCODE_SUCC; +} + +// AT^VAD_STATUS - 查询 VAD 状态 +static uint32_t vad_status(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(para); + VAD_UNUSED(paraLen); + VAD_UNUSED(strCommonCmd); + + if (!g_vad_initialized) + { + printf("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); + + return ERRCODE_SUCC; +} + +// AT^VAD_ANALYZE_PCM +static uint32_t vad_analyze_pcm(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(strCommonCmd); + if (!g_vad_initialized) + { + printf("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"); + return ERRCODE_FAIL; + } + const char *pcm_path = (const char *)argv[0]; + printf("VAD 分析现有 PCM: %s\n", pcm_path); + int ret = AnalyzePcmFileWithVad(pcm_path); + return (ret == VAD_SUCCESS) ? ERRCODE_SUCC : ERRCODE_FAIL; +} + +// AT^VAD_RECORD_START - 开始录音测试 +static uint32_t vad_record_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(strCommonCmd); + if (!g_vad_initialized) + { + printf("VAD not initialized, call AT^VAD_INIT first\n"); + return ERRCODE_FAIL; + } + + if (g_vad_live_running) + { + printf("VAD live already running\n"); + return ERRCODE_SUCC; + } + + /* 可选自定义输出路径 */ + EnsureVadLivePathInitialized(); + const char *out_path = (const char *)(g_vad_live_path ? g_vad_live_path : "/user/vad_live.pcm"); + uint8_t argvbuf[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0}; + uint32_t argc = 0; + if (para != NULL && paraLen > 0 && ConvertCmdToParam(para, paraLen, argvbuf, &argc) == VAD_SUCCESS && argc >= 1) + { + if (g_vad_live_path) + { + strncpy(g_vad_live_path, (const char *)argvbuf[0], LN_VAD_PATH_CAP - 1); + g_vad_live_path[LN_VAD_PATH_CAP - 1] = '\0'; + out_path = g_vad_live_path; + } + } + + printf("=== VAD 实时录音开始 ===\n"); + printf("PCM 输出: %s\n", out_path); + + /* 1) 确保目录存在并异步启动系统录音保存为 PCM(避免在 AT 线程上堆栈溢出) */ + EnsureParentDir(out_path); + if (g_vad_live_path) + { + strncpy(g_vad_live_path, out_path, LN_VAD_PATH_CAP - 1); + g_vad_live_path[LN_VAD_PATH_CAP - 1] = '\0'; + } + StartRecordThread(NULL); + + /* 2) 启动尾随线程实时喂给 VAD */ + g_vad_prev_status = -1; + g_vad_live_running = true; + { + MediaThreadattr attr = {"VADTail", 0x1000, THREAD_SCHED_INVALID, 0, true}; + g_vad_tail_thread = MediaThreadCreate(VadTailThread, NULL, &attr); + if (g_vad_tail_thread == NULL) + { + printf("create tail thread failed\n"); + g_vad_live_running = false; + return ERRCODE_FAIL; + } + } + return ERRCODE_SUCC; +} + +// AT^VAD_RECORD_STOP - 停止录音测试 +static uint32_t vad_record_stop(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(para); + VAD_UNUSED(paraLen); + VAD_UNUSED(strCommonCmd); + + printf("=== 停止 VAD 实时录音 ===\n"); + + /* 1) 先停止系统录音,减少文件追加,便于尾随线程退出 */ + printf("VAD 停止系统录音\n"); + { + const char *stopArg = "stop"; + (void)AudioCaptureSample(1, &stopArg); + } + + /* 2) 通知尾随线程退出(不阻塞等待) */ + printf("VAD 停止尾随线程\n"); + g_vad_live_running = false; + g_vad_tail_thread = NULL; + + printf("VAD 实时录音已停止\n"); + return ERRCODE_SUCC; +} + +// AT^VAD_CLEANUP - 清理 VAD 资源 +static uint32_t vad_cleanup(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(para); + VAD_UNUSED(paraLen); + VAD_UNUSED(strCommonCmd); + + if (!g_vad_initialized) + { + printf("VAD not initialized\n"); + return ERRCODE_SUCC; + } + + free_vad(); + printf("✓ VAD 资源释放成功\n"); + g_vad_initialized = false; + g_vad_status = -1; + + return ERRCODE_SUCC; +} + +// AT^VAD_HELP - 显示帮助信息 +static uint32_t vad_help(uint8_t *para, uint32_t paraLen, char *strCommonCmd) +{ + VAD_UNUSED(para); + 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"); + + return ERRCODE_SUCC; +} + +/* VAD AT 命令表 */ +static const at_cmd_table_t g_vad_at_cmd[] = { + {(char *)"AT^VAD_INIT", vad_init, (char *)"初始化 VAD"}, + {(char *)"AT^VAD_CONFIG", vad_config, (char *)"配置 VAD 参数"}, + {(char *)"AT^VAD_STATUS", vad_status, (char *)"查询 VAD 状态"}, + {(char *)"AT^VAD_ANALYZE_PCM", vad_analyze_pcm, (char *)"分析现有PCM"}, + {(char *)"AT^VAD_RECORD_START", vad_record_start, (char *)"开始录音测试"}, + {(char *)"AT^VAD_RECORD_STOP", vad_record_stop, (char *)"停止录音测试"}, + {(char *)"AT^VAD_CLEANUP", vad_cleanup, (char *)"清理 VAD 资源"}, + {(char *)"AT^VAD_HELP", vad_help, (char *)"显示帮助信息"}, +}; + +/* 导出函数:获取 VAD AT 命令表 */ +at_cmd_table_t *get_imsl_vad_at_cmd_table(uint32_t *cmd_count) +{ + *cmd_count = ARRAY_COUNT(g_vad_at_cmd); + return (at_cmd_table_t *)g_vad_at_cmd; +} + diff --git a/code/sdk/zhihui/leiniao/imsl_ln/imsl_system_adapter.c b/code/sdk/zhihui/imsl/src/imsl_system_adapter.c similarity index 100% rename from code/sdk/zhihui/leiniao/imsl_ln/imsl_system_adapter.c rename to code/sdk/zhihui/imsl/src/imsl_system_adapter.c diff --git a/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt b/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt index 6ce3f05bb..d8c4e7633 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt +++ b/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt @@ -6,7 +6,6 @@ set(COMPONENT_NAME "thunderbird_toys") # 源文件:系统适配层实现 set(SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/imsl_system_adapter.c ${CMAKE_CURRENT_SOURCE_DIR}/leiniao_api_at.c ${CMAKE_CURRENT_SOURCE_DIR}/ln_api.c ${CMAKE_CURRENT_SOURCE_DIR}/ln_api_token.c @@ -25,7 +24,6 @@ set(SOURCES # 公共头文件路径(供其他模块引用) set(PUBLIC_HEADER - ${CMAKE_CURRENT_SOURCE_DIR}/include # 包含imsl_vad.h和imsl_kws.h的目录 ${ROOT_DIR}/zhihui/4g/cat1Service/include/ ${ROOT_DIR}/zhihui/leiniao/ln_api_cpp/ ${ROOT_DIR}/open_source/openharmony/third_party/lwip/lwip_v2.1.3/src/include @@ -33,7 +31,6 @@ set(PUBLIC_HEADER ${ROOT_DIR}/open_source/openharmony/third_party/cJSON/ ${ROOT_DIR}/open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/websocket/include ${ROOT_DIR}/zhihui/websocket/os_ws - ) # 公共宏定义(可选) @@ -78,12 +75,5 @@ set(MAIN_COMPONENT # install_sdk(${CMAKE_CURRENT_SOURCE_DIR}/lib "*.a") # 安装静态库(假设.a在lib目录) install_sdk(${CMAKE_CURRENT_SOURCE_DIR} "*") -# 直接指定静态库全路径(目录下现有文件名为 libimslvad.a、libimslkws.a、libimslnn.a) -set(LIBS - ${CMAKE_CURRENT_SOURCE_DIR}/lib/libimslvad.a - ${CMAKE_CURRENT_SOURCE_DIR}/lib/libimslkws.a - ${CMAKE_CURRENT_SOURCE_DIR}/lib/libimslnn.a -) - # 构建组件 build_component() \ No newline at end of file diff --git a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c index fc689a76d..8a78fc121 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/leiniao_api_at.c @@ -9,35 +9,19 @@ #include #include #include "errcode.h" -#include "imsl_vad.h" -#include "media_thread_adapt.h" #include "ln_api.h" #include "ln_api_interface.h" #include "cJSON.h" #define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) -#define VAD_MAX_ARGC 8 -#define VAD_MAX_ARGV_LEN 64 #define LN_MAX_ARGC 8 #define LN_MAX_ARGV_LEN 512 -#define VAD_FAILED (-1) -#define VAD_SUCCESS (0) +#define LN_FAILED (-1) +#define LN_SUCCESS (0) /* Indicate that a local variable is not used */ -#define VAD_UNUSED(a) ((void)(a)) +#define LN_UNUSED(a) ((void)(a)) -// 全局 VAD 状态 -static bool g_vad_initialized = false; -static int g_vad_status = -1; -static struct VadConfig g_vad_config = {0}; - -// 音频缓冲区(用于真实语音测试) -#define VAD_AUDIO_BUFFER_SIZE 3200 // 200ms @ 16kHz -static int16_t *g_audio_buffer = NULL; /* heap allocated */ -static int g_audio_buffer_pos = 0; - -/* 录音封装(由 C++ 提供,C 接口声明) */ -extern int32_t AudioCaptureSample(int32_t argc, const char *argv[]); /* 对话控制相关AT命令函数声明 */ static uint32_t ln_conversation_flag_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd); @@ -63,173 +47,6 @@ static uint32_t ln_check_dns_at(uint8_t *para, uint32_t paraLen, char *strCommon /* 内存查看辅助函数 - 使用MemoryManager接口 */ -/* 将 PCM 文件按 10ms(160点) 分帧喂给 VAD 并打印结果 */ -static int AnalyzePcmFileWithVad(const char *pcm_path) -{ - FILE *fp = fopen(pcm_path, "rb"); - if (fp == NULL) - { - printf("open pcm failed: %s\n", pcm_path); - return VAD_FAILED; - } - - int16_t frame[160]; - size_t total_frames = 0; - int prev = -1; - int count_speech = 0; - int count_end = 0; - - while (1) - { - size_t n = fread(frame, sizeof(int16_t), 160, fp); - if (n < 160) - { - break; - } - run_vad(frame, 160); - int st = vad_detect(); - if (st != prev) - { - printf("VAD状态变化: %d -> %d 于帧%zu\n", prev, st, total_frames); - prev = st; - } - if (st == 1) - { - count_speech++; - } - else if (st == 2) - { - count_end++; - vad_clear(); - } - total_frames++; - } - fclose(fp); - printf("总帧数:%zu, 语音帧:%d, 结束帧:%d\n", total_frames, count_speech, count_end); - return VAD_SUCCESS; -} - -/* ---------------- 实时录音并同步喂给 VAD(基于文件尾随) ---------------- */ -static volatile bool g_vad_live_running = false; -static MediaThreadIdHandle g_vad_tail_thread = NULL; -#define LN_VAD_PATH_CAP 256 -static char *g_vad_live_path = NULL; /* heap allocated, cap LN_VAD_PATH_CAP */ -static int g_vad_prev_status = -1; - -static void EnsureVadLivePathInitialized(void) -{ - if (g_vad_live_path == NULL) - { - g_vad_live_path = (char *)malloc(LN_VAD_PATH_CAP); - if (g_vad_live_path) - { - g_vad_live_path[0] = '\0'; - strncpy(g_vad_live_path, "/user/vad_live.pcm", LN_VAD_PATH_CAP - 1); - g_vad_live_path[LN_VAD_PATH_CAP - 1] = '\0'; - } - } -} - -// 尾随线程,持续从文件中读取数据并喂给 VAD -static void *VadTailThread(void *arg) -{ - printf("VAD TailThread\n"); - (void)arg; - FILE *fp = NULL; - int16_t frame[160]; - size_t filled = 0; - - /* 等待文件可读 */ - while (g_vad_live_running && fp == NULL) - { - EnsureVadLivePathInitialized(); - fp = fopen(g_vad_live_path ? g_vad_live_path : "/user/vad_live.pcm", "rb"); - if (fp == NULL) - { - usleep(50 * 1000); - } - } - if (!g_vad_live_running) - { - if (fp) - fclose(fp); - return NULL; - } - - /* 从头开始读,持续尾随追加的数据 */ - while (g_vad_live_running) - { - int16_t sample; - size_t n = fread(&sample, sizeof(int16_t), 1, fp); - if (n == 1) - { - frame[filled++] = sample; - if (filled == 160) - { - run_vad(frame, 160); - int st = vad_detect(); - if (st != g_vad_prev_status) - { - printf("[VAD-LIVE] 状态变化: %d -> %d\n", g_vad_prev_status, st); - g_vad_prev_status = st; - if (st == 2) - { - printf("[VAD-LIVE] 检测到语音结束,vad_clear()\n"); - vad_clear(); - } - } - filled = 0; - } - } - else - { - /* 到达当前 EOF,短暂等待数据追加 */ - clearerr(fp); - usleep(10 * 1000); - } - } - if (fp) - fclose(fp); - return NULL; -} - -static void *StartRecordThread(void *arg) -{ - printf("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"}; - (void)AudioCaptureSample(6, startArgs); - return NULL; -} - -/* 确保父目录存在 */ -static void EnsureParentDir(const char *file_path) -{ - if (file_path == NULL) - { - return; - } - const char *slash = strrchr(file_path, '/'); - if (slash == NULL || slash == file_path) - { - return; // no parent or root - } - char dir[256]; - size_t len = (size_t)(slash - file_path); - if (len >= sizeof(dir)) - { - return; - } - memcpy(dir, file_path, len); - dir[len] = '\0'; - struct stat st; - if (stat(dir, &st) == 0) - { - return; // exists - } - (void)mkdir(dir, 0777); -} static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN], uint32_t *argc) { @@ -238,7 +55,7 @@ static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, uint8_t argv[LN_MA for (uint32_t i = 0; i < paraLen; i++) { - /* 以空格/换行/逗号/等号作为分隔符,兼容 “AT^CMD=arg1,arg2” 或 “AT^CMD arg1 arg2” */ + /* 以空格/换行/逗号/等号作为分隔符,兼容 "AT^CMD=arg1,arg2" 或 "AT^CMD arg1 arg2" */ if ((para[i] == ' ' || para[i] == '\n' || para[i] == ',' || para[i] == '=') && pos != 0) { argv[*argc][pos] = '\0'; @@ -246,7 +63,7 @@ static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, uint8_t argv[LN_MA pos = 0; if (*argc >= LN_MAX_ARGC) { - return VAD_FAILED; + return LN_FAILED; } } else @@ -258,7 +75,7 @@ static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, uint8_t argv[LN_MA pos++; if (pos >= LN_MAX_ARGV_LEN) { - return VAD_FAILED; + return LN_FAILED; } } } @@ -267,290 +84,16 @@ static int ConvertCmdToParam(uint8_t *para, uint32_t paraLen, uint8_t argv[LN_MA *argc += 1; } - return VAD_SUCCESS; -} - -// AT^VAD_INIT - 初始化 VAD -static uint32_t vad_init(uint8_t *para, uint32_t paraLen, char *strCommonCmd) -{ - VAD_UNUSED(strCommonCmd); - uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0}; - uint32_t argc = 0; - - int ret = ConvertCmdToParam(para, paraLen, argv, &argc); - if (ret != VAD_SUCCESS) - { - printf("VADTEST ATCMD Convert Command Failed\n"); - return ERRCODE_FAIL; - } - - if (g_vad_initialized) - { - printf("VAD already initialized\n"); - return ERRCODE_SUCC; - } - - printf("=== VAD AT 指令测试 ===\n"); - - // 设置日志回调 - extern imsl_nn_printf_func imsl_nn_printf; - imsl_nn_printf = printf; - printf("✓ 日志回调设置成功\n"); - - // 初始化 VAD 配置 - g_vad_config.sil_threshold = 63; - g_vad_config.energy_threshold = 10000; - g_vad_config.min_speech_length = 240; - g_vad_config.silence_detect_length = 300; - g_vad_config.rule1_min_tailing_silence = 480; - g_vad_config.rule2_min_tailing_silence = 960; - g_vad_config.rule3_min_tailing_silence = 960; - g_vad_config.vad_log_level = 2; - - int init_result = init_vad(&g_vad_config); - if (init_result == 0) - { - printf("❌ VAD 初始化失败\n"); - return ERRCODE_FAIL; - } - - printf("✓ VAD 初始化成功 (状态: %d)\n", init_result); - g_vad_initialized = true; - g_vad_status = 0; - - return ERRCODE_SUCC; -} - -// AT^VAD_CONFIG - 配置 VAD 参数 -static uint32_t vad_config(uint8_t *para, uint32_t paraLen, char *strCommonCmd) -{ - VAD_UNUSED(strCommonCmd); - uint8_t argv[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0}; - uint32_t argc = 0; - - int ret = ConvertCmdToParam(para, paraLen, argv, &argc); - if (ret != VAD_SUCCESS) - { - printf("VADTEST ATCMD Convert Command Failed\n"); - return ERRCODE_FAIL; - } - - if (!g_vad_initialized) - { - printf("VAD not initialized, call AT^VAD_INIT first\n"); - return ERRCODE_FAIL; - } - - if (argc != 7) - { - printf("Usage: AT^VAD_CONFIG \n"); - return ERRCODE_FAIL; - } - - // 解析参数 - g_vad_config.sil_threshold = atoi((char *)argv[0]); - g_vad_config.energy_threshold = atoi((char *)argv[1]); - g_vad_config.min_speech_length = atoi((char *)argv[2]); - g_vad_config.silence_detect_length = atoi((char *)argv[3]); - g_vad_config.rule1_min_tailing_silence = atoi((char *)argv[4]); - 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); - - return ERRCODE_SUCC; -} - -// AT^VAD_STATUS - 查询 VAD 状态 -static uint32_t vad_status(uint8_t *para, uint32_t paraLen, char *strCommonCmd) -{ - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); - - if (!g_vad_initialized) - { - printf("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); - - return ERRCODE_SUCC; -} - -// AT^VAD_ANALYZE_PCM -static uint32_t vad_analyze_pcm(uint8_t *para, uint32_t paraLen, char *strCommonCmd) -{ - VAD_UNUSED(strCommonCmd); - if (!g_vad_initialized) - { - printf("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"); - return ERRCODE_FAIL; - } - const char *pcm_path = (const char *)argv[0]; - printf("VAD 分析现有 PCM: %s\n", pcm_path); - int ret = AnalyzePcmFileWithVad(pcm_path); - return (ret == VAD_SUCCESS) ? ERRCODE_SUCC : ERRCODE_FAIL; -} - -// AT^VAD_RECORD_START - 开始录音测试 -static uint32_t vad_record_start(uint8_t *para, uint32_t paraLen, char *strCommonCmd) -{ - VAD_UNUSED(strCommonCmd); - if (!g_vad_initialized) - { - printf("VAD not initialized, call AT^VAD_INIT first\n"); - return ERRCODE_FAIL; - } - - if (g_vad_live_running) - { - printf("VAD live already running\n"); - return ERRCODE_SUCC; - } - - /* 可选自定义输出路径 */ - EnsureVadLivePathInitialized(); - const char *out_path = (const char *)(g_vad_live_path ? g_vad_live_path : "/user/vad_live.pcm"); - uint8_t argvbuf[VAD_MAX_ARGC][VAD_MAX_ARGV_LEN] = {0}; - uint32_t argc = 0; - if (para != NULL && paraLen > 0 && ConvertCmdToParam(para, paraLen, argvbuf, &argc) == VAD_SUCCESS && argc >= 1) - { - if (g_vad_live_path) - { - strncpy(g_vad_live_path, (const char *)argvbuf[0], LN_VAD_PATH_CAP - 1); - g_vad_live_path[LN_VAD_PATH_CAP - 1] = '\0'; - out_path = g_vad_live_path; - } - } - - printf("=== VAD 实时录音开始 ===\n"); - printf("PCM 输出: %s\n", out_path); - - /* 1) 确保目录存在并异步启动系统录音保存为 PCM(避免在 AT 线程上堆栈溢出) */ - EnsureParentDir(out_path); - if (g_vad_live_path) - { - strncpy(g_vad_live_path, out_path, LN_VAD_PATH_CAP - 1); - g_vad_live_path[LN_VAD_PATH_CAP - 1] = '\0'; - } - StartRecordThread(NULL); - - /* 2) 启动尾随线程实时喂给 VAD */ - g_vad_prev_status = -1; - g_vad_live_running = true; - { - MediaThreadattr attr = {"VADTail", 0x1000, THREAD_SCHED_INVALID, 0, true}; - g_vad_tail_thread = MediaThreadCreate(VadTailThread, NULL, &attr); - if (g_vad_tail_thread == NULL) - { - printf("create tail thread failed\n"); - g_vad_live_running = false; - return ERRCODE_FAIL; - } - } - return ERRCODE_SUCC; -} - -// AT^VAD_RECORD_STOP - 停止录音测试 -static uint32_t vad_record_stop(uint8_t *para, uint32_t paraLen, char *strCommonCmd) -{ - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); - - printf("=== 停止 VAD 实时录音 ===\n"); - - /* 1) 先停止系统录音,减少文件追加,便于尾随线程退出 */ - printf("VAD 停止系统录音\n"); - { - const char *stopArg = "stop"; - (void)AudioCaptureSample(1, &stopArg); - } - - /* 2) 通知尾随线程退出(不阻塞等待) */ - printf("VAD 停止尾随线程\n"); - g_vad_live_running = false; - g_vad_tail_thread = NULL; - - printf("VAD 实时录音已停止\n"); - return ERRCODE_SUCC; -} - -// AT^VAD_CLEANUP - 清理 VAD 资源 -static uint32_t vad_cleanup(uint8_t *para, uint32_t paraLen, char *strCommonCmd) -{ - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); - - if (!g_vad_initialized) - { - printf("VAD not initialized\n"); - return ERRCODE_SUCC; - } - - free_vad(); - printf("✓ VAD 资源释放成功\n"); - g_vad_initialized = false; - g_vad_status = -1; - - return ERRCODE_SUCC; -} - -// AT^VAD_HELP - 显示帮助信息 -static uint32_t vad_help(uint8_t *para, uint32_t paraLen, char *strCommonCmd) -{ - VAD_UNUSED(para); - 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"); - - return ERRCODE_SUCC; + return LN_SUCCESS; } /* AT^LN_GET_TOKEN[=deviceId,deviceIccid,deviceModel,deviceVersion] */ //获取设备Token static uint32_t ln_get_device_token_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_GET_DEVICE_TOKEN, NULL, 0); return ERRCODE_SUCC; } @@ -559,9 +102,9 @@ static uint32_t ln_get_device_token_at(uint8_t *para, uint32_t paraLen, char *st //清除设备Token static uint32_t ln_clear_device_token_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); uapi_ln_clear_device_token(); return ERRCODE_SUCC; } @@ -569,9 +112,9 @@ static uint32_t ln_clear_device_token_at(uint8_t *para, uint32_t paraLen, char * // AT^LN_SPEECH_CHAT=deviceId,deviceToken[,audioPath] static uint32_t ln_speech_chat_stream_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); - VAD_UNUSED(para); - VAD_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_SPEECH_CHAT_STREAM, NULL, 0); return ERRCODE_SUCC; } @@ -579,9 +122,9 @@ static uint32_t ln_speech_chat_stream_at(uint8_t *para, uint32_t paraLen, char * /* 故事-选择故事:AT^LN_STORY_CHOOSE[=audioPath] */ static uint32_t ln_story_choose_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); - VAD_UNUSED(para); - VAD_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_STORY_CHOOSE, NULL, 0); return ERRCODE_SUCC; } @@ -589,7 +132,7 @@ static uint32_t ln_story_choose_at(uint8_t *para, uint32_t paraLen, char *strCom /* 故事-随机故事:AT^LN_STORY_RANDOM[=storyTheme] */ static uint32_t ln_story_random_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; (void)ConvertCmdToParam(para, paraLen, argv, &argc); ln_story_random_req_t req = {0}; if (argc >= 1) { strncpy(req.story_theme, (const char *)argv[0], sizeof(req.story_theme) - 1); } printf("[LN][STORY_RANDOM] theme=%s\n", req.story_theme[0] ? req.story_theme : ""); @@ -600,9 +143,9 @@ static uint32_t ln_story_random_at(uint8_t *para, uint32_t paraLen, char *strCom /* 故事-获取退出哄睡询问:AT^LN_STORY_EXIT */ static uint32_t ln_story_exit_inquiry_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][STORY_EXIT_INQUIRY]\n"); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_STORY_EXIT_INQUIRY, NULL, 0); return ERRCODE_SUCC; @@ -611,9 +154,9 @@ static uint32_t ln_story_exit_inquiry_at(uint8_t *para, uint32_t paraLen, char * /* 故事-确认结束哄睡模式:AT^LN_STORY_FINISH */ static uint32_t ln_story_finish_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][STORY_FINISH]\n"); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_STORY_FINISH, NULL, 0); return ERRCODE_SUCC; @@ -622,9 +165,9 @@ static uint32_t ln_story_finish_at(uint8_t *para, uint32_t paraLen, char *strCom /* 设备-获取角色信息:AT^LN_DEV_CHARACTER */ static uint32_t ln_device_get_character_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][DEVICE_GET_CHARACTER]\n"); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_GET_CHARACTER, NULL, 0); return ERRCODE_SUCC; @@ -633,21 +176,21 @@ static uint32_t ln_device_get_character_at(uint8_t *para, uint32_t paraLen, char /* 设备-获取绑定用户ID:AT^LN_DEV_USERID */ static uint32_t ln_device_get_userid_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][DEVICE_GET_USER_ID]\n"); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_GET_USER_ID, NULL, 0); return ERRCODE_SUCC; } static uint32_t ln_notify_bind_and_unbind_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; - if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) != LN_SUCCESS) { printf("ERROR: Failed to parse parameters\n"); return ERRCODE_FAIL; } @@ -674,9 +217,9 @@ static uint32_t ln_notify_bind_and_unbind_at(uint8_t *para, uint32_t paraLen, ch /* OTA-获取升级任务:AT^LN_OTA_GET_TASK */ static uint32_t ln_ota_get_task_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][OTA_GET_TASK]\n"); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_GET_TASK, NULL, 0); @@ -689,9 +232,9 @@ static uint32_t ln_ota_get_task_at(uint8_t *para, uint32_t paraLen, char *strCom */ static uint32_t ln_ota_download_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][OTA_DOWNLOAD]\n"); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD, NULL, 0); return ERRCODE_SUCC; @@ -700,9 +243,9 @@ static uint32_t ln_ota_download_at(uint8_t *para, uint32_t paraLen, char *strCom /* AT^LN_WS_START */ static uint32_t ln_ws_start_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][WS_START]\n"); int ret = ln_ws_start(NULL,2000); if (ret != 0) @@ -743,7 +286,7 @@ static uint32_t ln_ws_start_with_callback_at(uint8_t *para, uint32_t paraLen, ch /* AT^LN_WS_STOP */ static uint32_t ln_ws_stop_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); VAD_UNUSED(paraLen); VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); LN_UNUSED(paraLen); LN_UNUSED(strCommonCmd); printf("[LN][WS_STOP]\n"); int ret = ln_ws_stop(); if (ret != 0) @@ -757,7 +300,7 @@ static uint32_t ln_ws_stop_at(uint8_t *para, uint32_t paraLen, char *strCommonCm /* AT^LN_WS_SEND= */ static uint32_t ln_ws_send_text_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; (void)ConvertCmdToParam(para, paraLen, argv, &argc); @@ -780,9 +323,9 @@ static uint32_t ln_ws_send_text_at(uint8_t *para, uint32_t paraLen, char *strCom /* AT^LN_WS_FORCE_RECONNECT */ static uint32_t ln_ws_force_reconnect_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][WS_FORCE_RECONNECT]\n"); int ret =0;// ln_ws_force_reconnect(); if (ret != 0) @@ -796,9 +339,9 @@ static uint32_t ln_ws_force_reconnect_at(uint8_t *para, uint32_t paraLen, char * /* AT^LN_WS_CLOSE_STATUS */ static uint32_t ln_ws_close_status_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][WS_CLOSE_STATUS]\n"); // ln_ws_print_status_info(); return ERRCODE_SUCC; @@ -807,7 +350,7 @@ static uint32_t ln_ws_close_status_at(uint8_t *para, uint32_t paraLen, char *str /* AT^LN_WS_START_AUDIO_CHAT - 开始音频聊天 */ static uint32_t ln_ws_start_audio_chat_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); ln_audio_chat_command_t command_type = LN_AUDIO_CHAT_VOICE_CHAT_REQUEST; // 默认值 @@ -816,7 +359,7 @@ static uint32_t ln_ws_start_audio_chat_at(uint8_t *para, uint32_t paraLen, char uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; - if (ConvertCmdToParam(para, paraLen, argv, &argc) == VAD_SUCCESS && argc >= 1) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) == LN_SUCCESS && argc >= 1) { const char *param = (const char *)argv[0]; if (strcmp(param, "0") == 0 || strcmp(param, "VoiceChatRequest") == 0) { @@ -851,9 +394,9 @@ static uint32_t ln_ws_start_audio_chat_at(uint8_t *para, uint32_t paraLen, char /* AT^LN_WS_END_AUDIO_CHAT - 结束音频聊天 */ static uint32_t ln_ws_end_audio_chat_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][WS_END_AUDIO_CHAT]\n"); int ret = ln_ws_end_audio_chat(); if (ret != 0) @@ -868,7 +411,7 @@ static uint32_t ln_ws_end_audio_chat_at(uint8_t *para, uint32_t paraLen, char *s /* AT^LN_WS_SEND_RANDOM_STORY - 发送随机故事请求 */ static uint32_t ln_ws_send_random_story_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); const char *story_theme = "冒险"; // 默认主题 @@ -877,7 +420,7 @@ static uint32_t ln_ws_send_random_story_at(uint8_t *para, uint32_t paraLen, char uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; - if (ConvertCmdToParam(para, paraLen, argv, &argc) == VAD_SUCCESS && argc >= 1) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) == LN_SUCCESS && argc >= 1) { story_theme = (const char *)argv[0]; } } @@ -897,7 +440,7 @@ static uint32_t ln_ws_send_random_story_at(uint8_t *para, uint32_t paraLen, char /* AT^LN_WS_SEND_VOICE_CHAT_INTERRUPT - 发送语音聊天打断请求 */ static uint32_t ln_ws_send_voice_chat_interrupt_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); const char *reason = "用户主动打断"; // 默认原因 @@ -906,7 +449,7 @@ static uint32_t ln_ws_send_voice_chat_interrupt_at(uint8_t *para, uint32_t paraL uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; - if (ConvertCmdToParam(para, paraLen, argv, &argc) == VAD_SUCCESS && argc >= 1) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) == LN_SUCCESS && argc >= 1) { reason = (const char *)argv[0]; } } @@ -926,9 +469,9 @@ static uint32_t ln_ws_send_voice_chat_interrupt_at(uint8_t *para, uint32_t paraL /* AT^LN_WS_SLEEP */ static uint32_t ln_ws_sleep_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][WS_SLEEP]\n"); int ret = ln_ws_enter_sleep(); if (ret != 0) @@ -942,9 +485,9 @@ static uint32_t ln_ws_sleep_at(uint8_t *para, uint32_t paraLen, char *strCommonC /* AT^LN_WS_WAKEUP */ static uint32_t ln_ws_wakeup_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][WS_WAKEUP]\n"); int ret = ln_ws_exit_sleep(); if (ret != 0) @@ -958,7 +501,7 @@ static uint32_t ln_ws_wakeup_at(uint8_t *para, uint32_t paraLen, char *strCommon /* 音频播放: AT^LN_STORY_PLAY_NEXT */ static uint32_t ln_audio_play_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); VAD_UNUSED(paraLen); VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); LN_UNUSED(paraLen); LN_UNUSED(strCommonCmd); printf("[LN][AUDIO_PLAY]\n"); return ERRCODE_SUCC; } @@ -966,7 +509,7 @@ static uint32_t ln_audio_play_at(uint8_t *para, uint32_t paraLen, char *strCommo /* 音频停止: AT^LN_STORY_STOP_AT */ static uint32_t ln_audio_stop_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); VAD_UNUSED(paraLen); VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); LN_UNUSED(paraLen); LN_UNUSED(strCommonCmd); printf("[LN][AUDIO_STOP]\n"); return ERRCODE_SUCC; } @@ -974,7 +517,7 @@ static uint32_t ln_audio_stop_at(uint8_t *para, uint32_t paraLen, char *strCommo /* 播放指定文件: AT^LN_AUDIO_PLAY_FILE=file_path */ static uint32_t ln_audio_play_file_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); printf("[LN][AUDIO_PLAY_FILE]\n"); if (para && paraLen > 0) { @@ -992,7 +535,7 @@ static uint32_t ln_audio_play_file_at(uint8_t *para, uint32_t paraLen, char *str /* 暂停播放: AT^LN_AUDIO_PAUSE */ static uint32_t ln_audio_pause_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); VAD_UNUSED(paraLen); VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); LN_UNUSED(paraLen); LN_UNUSED(strCommonCmd); printf("[LN][AUDIO_PAUSE]\n"); // do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_AUDIO_PAUSE, NULL, 0); return ERRCODE_SUCC; @@ -1001,7 +544,7 @@ static uint32_t ln_audio_pause_at(uint8_t *para, uint32_t paraLen, char *strComm /* 恢复播放: AT^LN_AUDIO_RESUME */ static uint32_t ln_audio_resume_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); VAD_UNUSED(paraLen); VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); LN_UNUSED(paraLen); LN_UNUSED(strCommonCmd); printf("[LN][AUDIO_RESUME]\n"); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_AUDIO_RESUME, NULL, 0); return ERRCODE_SUCC; @@ -1010,7 +553,7 @@ static uint32_t ln_audio_resume_at(uint8_t *para, uint32_t paraLen, char *strCom /* 哄睡模式开始: AT^LN_SLEEP_MODE_START[=storyTheme] */ static uint32_t ln_sleep_mode_start_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); const char *deviceId = uapi_ln_get_device_id(); const char *id = (deviceId && deviceId[0]) ? deviceId : "DEVICE_LOCAL"; const char *theme = ""; @@ -1051,9 +594,9 @@ static uint32_t ln_sleep_mode_start_at(uint8_t *para, uint32_t paraLen, char *st /* 哄睡模式结束: AT^LN_SLEEP_MODE_END */ static uint32_t ln_sleep_mode_end_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); const char *deviceId = uapi_ln_get_device_id(); const char *id = (deviceId && deviceId[0]) ? deviceId : "DEVICE_LOCAL"; char *json = (char*)malloc(256); @@ -1094,13 +637,13 @@ static uint32_t ln_sleep_mode_end_at(uint8_t *para, uint32_t paraLen, char *strC */ static uint32_t ln_conversation_flag_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; // 解析参数 - if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) != LN_SUCCESS) { printf("ERROR: Failed to parse parameters\n"); return ERRCODE_FAIL; } @@ -1144,13 +687,13 @@ static uint32_t ln_conversation_flag_at(uint8_t *para, uint32_t paraLen, char *s */ static uint32_t ln_push_text_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; // 解析参数 - if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) != LN_SUCCESS) { printf("ERROR: Failed to parse parameters\n"); return ERRCODE_FAIL; } @@ -1184,7 +727,7 @@ static uint32_t ln_push_text_at(uint8_t *para, uint32_t paraLen, char *strCommon /* 恢复出厂设置: AT^LN_FACTORY */ static uint32_t ln_factory_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); VAD_UNUSED(paraLen); VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); LN_UNUSED(paraLen); LN_UNUSED(strCommonCmd); printf("[LN][FACTORY]\n"); do_restore_factory_setting(); printf("[LN][FACTORY] ok\n"); @@ -1207,13 +750,13 @@ static uint32_t ln_factory_at(uint8_t *para, uint32_t paraLen, char *strCommonCm */ static uint32_t ln_ui_ota_status_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; // 解析参数 - if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) != LN_SUCCESS) { printf("ERROR: Failed to parse parameters\n"); return ERRCODE_FAIL; } @@ -1260,13 +803,13 @@ static uint32_t ln_ui_ota_status_at(uint8_t *para, uint32_t paraLen, char *strCo */ static uint32_t ln_ui_ota_progress_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; // 解析参数 - if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) != LN_SUCCESS) { printf("ERROR: Failed to parse parameters\n"); return ERRCODE_FAIL; } @@ -1306,7 +849,7 @@ static uint32_t ln_ui_ota_progress_at(uint8_t *para, uint32_t paraLen, char *str // AT^LN_SCHEDULE_REMINDER=开会提醒,您有一个会议即将开始 static uint32_t ln_schedule_reminder_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); const char *defaultName = "日程提醒"; const char *defaultContent = "您有一个提醒"; const char *name = defaultName; @@ -1416,9 +959,9 @@ static uint32_t ln_schedule_reminder_cfg_at(uint8_t *para, uint32_t paraLen, cha */ static uint32_t ln_memory_info_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("\r\n=== System Memory Information ===\r\n"); @@ -1485,7 +1028,7 @@ static uint32_t ln_folder_size_at(uint8_t *para, uint32_t paraLen, char *strComm /* AT^LN_CHARACTER_CFG[=characterName,eyesShape,hairColor,eyesColor,hairStyle,screenImageAsEmoji] */ static uint32_t ln_character_config_change_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); const char *deviceId = uapi_ln_get_device_id(); const char *id = (deviceId && deviceId[0]) ? deviceId : "DEVICE_LOCAL"; @@ -1553,9 +1096,9 @@ static uint32_t ln_character_config_change_at(uint8_t *para, uint32_t paraLen, c /* AT^LN_GET_DEVICE_ID */ static uint32_t ln_get_device_id_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][GET_DEVICE_ID]\n"); const char *deviceId = uapi_ln_get_device_id(); printf("deviceId: %s\n", deviceId); @@ -1564,9 +1107,9 @@ static uint32_t ln_get_device_id_at(uint8_t *para, uint32_t paraLen, char *strCo /* AT^LN_GET_DEVICE_ICCID */ static uint32_t ln_get_device_iccid_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][GET_DEVICE_ICCID]\n"); const char *deviceIccid = uapi_ln_get_device_iccid(); printf("deviceIccid: %s\n", deviceIccid); @@ -1575,9 +1118,9 @@ static uint32_t ln_get_device_iccid_at(uint8_t *para, uint32_t paraLen, char *st /* AT^LN_GET_DEVICE_MODEL */ static uint32_t ln_get_device_model_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][GET_DEVICE_MODEL]\n"); const char *deviceModel = uapi_ln_get_device_model(); printf("deviceModel: %s\n", deviceModel); @@ -1586,9 +1129,9 @@ static uint32_t ln_get_device_model_at(uint8_t *para, uint32_t paraLen, char *st /* AT^LN_GET_DEVICE_VERSION */ static uint32_t ln_get_device_version_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][GET_DEVICE_VERSION]\n"); const char *deviceVersion = uapi_ln_get_device_version(); printf("deviceVersion: %s\n", deviceVersion); @@ -1598,7 +1141,7 @@ static uint32_t ln_get_device_version_at(uint8_t *para, uint32_t paraLen, char * /* AT^LN_SET_FPS - 设置帧率 */ static uint32_t ln_set_fps_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); if (para == NULL || paraLen == 0) { printf("[LN][SET_FPS] Error: No parameter provided\n"); @@ -1637,9 +1180,9 @@ static uint32_t ln_set_fps_at(uint8_t *para, uint32_t paraLen, char *strCommonCm /* AT^LN_GET_FPS - 获取当前帧率 */ static uint32_t ln_get_fps_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][GET_FPS] Getting current FPS...\n"); @@ -1652,9 +1195,9 @@ static uint32_t ln_get_fps_at(uint8_t *para, uint32_t paraLen, char *strCommonCm /* AT^LN_DOWNLOAD_EMOJI */ static uint32_t ln_download_emoji_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][DOWNLOAD_EMOJI]\n"); char *json = (char*)malloc(1024); if (json == NULL) { @@ -1703,9 +1246,9 @@ static uint32_t ln_download_emoji_at(uint8_t *para, uint32_t paraLen, char *strC /* AT^LN_GET_CHAT_STAMINA - 获取对话体力与流量信息 */ static uint32_t ln_get_chat_stamina_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); const char *id = uapi_ln_get_device_id(); const char *token = uapi_ln_get_device_token(); if (!id || !id[0] || !token || !token[0]) { @@ -1729,7 +1272,7 @@ static uint32_t ln_get_chat_stamina_at(uint8_t *para, uint32_t paraLen, char *st */ static uint32_t ln_chat_stamina_demo_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); int chatLeft = 0; int flowLeft = 0; const char *chatDesc = NULL; @@ -1779,13 +1322,13 @@ static uint32_t ln_chat_stamina_demo_at(uint8_t *para, uint32_t paraLen, char *s */ static uint32_t ln_switch_expression_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; // 解析参数 - if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) != LN_SUCCESS) { printf("ERROR: Failed to parse parameters\n"); return ERRCODE_FAIL; } @@ -1837,14 +1380,14 @@ static uint32_t ln_switch_expression_at(uint8_t *para, uint32_t paraLen, char *s /* AT^LN_API_ERROR - 推送协议错误 */ static uint32_t ln_api_error_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; const char *message = ""; // 默认空消息 if (para != NULL && paraLen > 0) { - if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) != LN_SUCCESS) { printf("ERROR: Failed to parse parameters\n"); return ERRCODE_FAIL; } @@ -1867,13 +1410,13 @@ static uint32_t ln_api_error_at(uint8_t *para, uint32_t paraLen, char *strCommon //AT^LN_NOTIFY_DOWNLOAD_SUCCESS=0/1 static uint32_t ln_download_success_notification_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); int type = 0; uint8_t argv[LN_MAX_ARGC][LN_MAX_ARGV_LEN] = {0}; uint32_t argc = 0; if (para != NULL && paraLen > 0) { - if (ConvertCmdToParam(para, paraLen, argv, &argc) != VAD_SUCCESS) { + if (ConvertCmdToParam(para, paraLen, argv, &argc) != LN_SUCCESS) { printf("ERROR: Failed to parse parameters\n"); return ERRCODE_FAIL; } @@ -1895,7 +1438,7 @@ static uint32_t ln_download_success_notification_at(uint8_t *para, uint32_t para /* AT^LN_TIMELINE_GET_STATUS - 获取时间线状态 */ static uint32_t ln_timeline_get_status_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(strCommonCmd); + LN_UNUSED(strCommonCmd); do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_TIMELINE_GET_STATUS, NULL, 0); return ERRCODE_SUCC; } @@ -1903,9 +1446,9 @@ static uint32_t ln_timeline_get_status_at(uint8_t *para, uint32_t paraLen, char /* AT^LN_WS_AUDIO_INIT - 初始化WebSocket音频 */ static uint32_t ln_ws_audio_init_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][WS_AUDIO_INIT] Initializing WebSocket audio...\n"); @@ -1918,9 +1461,9 @@ static uint32_t ln_ws_audio_init_at(uint8_t *para, uint32_t paraLen, char *strCo /* AT^LN_WS_AUDIO_DEINIT - 反初始化WebSocket音频 */ static uint32_t ln_ws_audio_deinit_at(uint8_t *para, uint32_t paraLen, char *strCommonCmd) { - VAD_UNUSED(para); - VAD_UNUSED(paraLen); - VAD_UNUSED(strCommonCmd); + LN_UNUSED(para); + LN_UNUSED(paraLen); + LN_UNUSED(strCommonCmd); printf("[LN][WS_AUDIO_DEINIT] Deinitializing WebSocket audio...\n"); @@ -1931,15 +1474,6 @@ static uint32_t ln_ws_audio_deinit_at(uint8_t *para, uint32_t paraLen, char *str } static const at_cmd_table_t g_ln_api_cmd[] = { - /* ----------------------------------- VAD ---------------------------------- */ - {(char *)"AT^VAD_INIT", vad_init, (char *)"初始化 VAD"}, - {(char *)"AT^VAD_CONFIG", vad_config, (char *)"配置 VAD 参数"}, - {(char *)"AT^VAD_STATUS", vad_status, (char *)"查询 VAD 状态"}, - {(char *)"AT^VAD_ANALYZE_PCM", vad_analyze_pcm, (char *)"分析现有PCM"}, - {(char *)"AT^VAD_RECORD_START", vad_record_start, (char *)"开始录音测试"}, - {(char *)"AT^VAD_RECORD_STOP", vad_record_stop, (char *)"停止录音测试"}, - {(char *)"AT^VAD_CLEANUP", vad_cleanup, (char *)"清理 VAD 资源"}, - {(char *)"AT^VAD_HELP", vad_help, (char *)"显示帮助信息"}, /* ---------------------------------- 雷鸟API --------------------------------- */ {(char *)"AT^LN_GET_TOKEN", ln_get_device_token_at, (char *)"获取雷鸟设备Token: AT^LN_GET_TOKEN[=deviceId,deviceIccid,deviceModel,deviceVersion]"}, {(char *)"AT^LN_CLEAR_TOKEN", ln_clear_device_token_at, (char *)"清除雷鸟设备Token: AT^LN_CLEAR_TOKEN"}, diff --git a/code/sdk/zhihui/minimp3/CMakeLists.txt b/code/sdk/zhihui/minimp3/CMakeLists.txt new file mode 100644 index 000000000..dd9f32fdd --- /dev/null +++ b/code/sdk/zhihui/minimp3/CMakeLists.txt @@ -0,0 +1,57 @@ +#=============================================================================== +# @brief imsl_ln component cmake file +# Copyright (c) @CompanyNameMagicTag 2023-2023. All rights reserved. +#=============================================================================== +set(COMPONENT_NAME "minimp3") + +# 源文件:系统适配层实现 +set(SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/src/mp3_decoder.c +) + +# 公共头文件路径(供其他模块引用) +set(PUBLIC_HEADER + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +# 公共宏定义(可选) +set(PUBLIC_DEFINES + MINI_MP3=1 +) + +# 私有头文件路径(仅当前组件内部使用) +set(PRIVATE_HEADER + # 如无特殊私有头文件,可留空 +) + +# 私有宏定义(可选) +set(PRIVATE_DEFINES + # 如无特殊私有宏,可留空 +) + +# 公共编译选项(可选) +set(COMPONENT_PUBLIC_CCFLAGS +) + +# 组件专属编译选项 +set(COMPONENT_CCFLAGS + -Wno-incompatible-function-pointer-types + -Wno-unused-function # 屏蔽未使用函数的警告(根据实际情况调整) +) + +# 是否启用全量链接(静态库通常需要) +set(WHOLE_LINK + true +) + +# 是否为主组件(通常为false,主组件一般是app) +set(MAIN_COMPONENT + false +) + +# 安装SDK时包含的文件(头文件和库文件) +# install_sdk(${CMAKE_CURRENT_SOURCE_DIR}/include "*.h") # 安装头文件 +# install_sdk(${CMAKE_CURRENT_SOURCE_DIR}/lib "*.a") # 安装静态库(假设.a在lib目录) + +# 构建组件 +build_component() \ No newline at end of file diff --git a/code/sdk/zhihui/utils/minimp3.h b/code/sdk/zhihui/minimp3/include/minimp3.h similarity index 100% rename from code/sdk/zhihui/utils/minimp3.h rename to code/sdk/zhihui/minimp3/include/minimp3.h diff --git a/code/sdk/zhihui/utils/mp3_decoder.h b/code/sdk/zhihui/minimp3/include/mp3_decoder.h similarity index 52% rename from code/sdk/zhihui/utils/mp3_decoder.h rename to code/sdk/zhihui/minimp3/include/mp3_decoder.h index f7e0a21a1..62bb8bae2 100644 --- a/code/sdk/zhihui/utils/mp3_decoder.h +++ b/code/sdk/zhihui/minimp3/include/mp3_decoder.h @@ -30,89 +30,77 @@ typedef struct mp3_decoder_s mp3_decoder_t; */ typedef void (*mp3_pcm_callback_t)(const int16_t *pcm_data, int samples, int channels, int sample_rate); +/* ==================== 核心 API ==================== */ + /** - * @brief 创建 MP3 解码器 + * @brief 创建/初始化 MP3 流式解码器(优化版) * @return mp3_decode_error_t 错误码 + * @note 内存占用: ~20KB (16KB流缓冲 + 4.6KB PCM缓冲) */ mp3_decode_error_t mp3_decoder_create(void); /** - * @brief 销毁 MP3 解码器 + * @brief 销毁 MP3 解码器,释放所有资源 */ void mp3_decoder_destroy(void); /** - * @brief 从文件加载 MP3 数据 - * @param filename MP3 文件路径 - * @return mp3_decode_error_t 错误码 + * @brief 设置 PCM 数据回调函数 + * @param callback PCM 回调函数 */ -mp3_decode_error_t mp3_decoder_load_file(const char *filename); +void mp3_decoder_set_callback(mp3_pcm_callback_t callback); -/** - * @brief 异步解码 MP3 数据(使用队列机制) - * @param callback PCM 数据回调函数,可为 NULL - * @return mp3_decode_error_t 错误码 - * @note 此函数启动异步解码,不等待完成。解码在后台线程中进行。 - */ -mp3_decode_error_t mp3_decoder_decode(mp3_pcm_callback_t callback); +/* ==================== 流式解码 API ==================== */ /** - * @brief 向 MP3 队列写入数据 + * @brief 喂入 MP3 数据到流缓冲区并自动解码 * @param data MP3 数据 * @param data_size 数据大小 - * @param is_last_packet 是否为最后一个数据包 * @return mp3_decode_error_t 错误码 + * @note 支持流式解码,边喂数据边解码,无文件大小限制 + * + * 使用示例: + * while (read_chunk_from_source(chunk, size)) { + * mp3_decoder_feed_data(chunk, size); + * } */ -mp3_decode_error_t mp3_decoder_write_data(const uint8_t *data, size_t data_size, bool is_last_packet); - -/** - * @brief 设置 PCM 回调函数 - * @param callback PCM 回调函数 - */ -void mp3_decoder_set_callback(mp3_pcm_callback_t callback); +mp3_decode_error_t mp3_decoder_feed_data(const uint8_t *data, size_t data_size); /** - * @brief 等待解码完成 - * @param timeout_ms 超时时间(毫秒),0表示无限等待 + * @brief 刷新解码器,处理剩余数据 * @return mp3_decode_error_t 错误码 + * @note 在完成数据喂入后调用 */ -mp3_decode_error_t mp3_decoder_wait_finish(uint32_t timeout_ms); +mp3_decode_error_t mp3_decoder_flush(void); /** - * @brief 初始化 MP3 解码器(简化接口) - * @param callback PCM 数据回调函数,可为 NULL - * @return mp3_decode_error_t 错误码 - */ -mp3_decode_error_t mp3_decoder_init_decode(mp3_pcm_callback_t callback); - -/** - * @brief 写入 MP3 数据(简化接口) - * @param data MP3 数据 - * @param data_size 数据大小 - * @param is_last 是否为最后一块数据 + * @brief 流式解码 MP3 文件(优化版 - 边读边解码) + * @param filename MP3 文件路径 + * @param callback PCM 回调函数 * @return mp3_decode_error_t 错误码 + * @note 一键解码整个文件,无文件大小限制,内存占用低 + * + * 使用示例: + * mp3_decoder_decode_file_stream("music.mp3", pcm_callback); */ -mp3_decode_error_t mp3_decoder_write(const uint8_t *data, size_t data_size, bool is_last); +mp3_decode_error_t mp3_decoder_decode_file_stream( + const char *filename, + void (*callback)(const int16_t *pcm_data, int samples, int channels, int sample_rate)); -/** - * @brief 检查解码是否完成(简化接口) - * @return bool true 表示解码完成,false 表示还在进行中 - */ -bool mp3_decoder_is_finished(void); +/* ==================== 辅助 API ==================== */ /** - * @brief 获取解码进度(简化接口) - * @param frames 输出已解码帧数 - * @param samples 输出已解码样本数 + * @brief 初始化 MP3 解码器并设置回调(便捷接口) + * @param callback PCM 数据回调函数,可为 NULL * @return mp3_decode_error_t 错误码 */ -mp3_decode_error_t mp3_decoder_get_progress(int *frames, int *samples); +mp3_decode_error_t mp3_decoder_init_decode(mp3_pcm_callback_t callback); /** - * @brief 停止解码(简化接口) + * @brief 重置解码器状态 * @return mp3_decode_error_t 错误码 */ -mp3_decode_error_t mp3_decoder_stop(void); +mp3_decode_error_t mp3_decoder_reset(void); /** * @brief 获取解码统计信息 @@ -133,12 +121,6 @@ void mp3_decoder_get_stats(int *frames, int *samples, int *duration_ms); */ int save_pcm_as_wav(const char *filename, const int16_t *pcm_data, int samples, int channels, int sample_rate); -/** - * @brief 示例用法函数 - * @param mp3_filename MP3 文件路径 - * @return int 成功返回 0,失败返回 -1 - */ -int mp3_decoder_example(const char *mp3_filename); #ifdef __cplusplus } diff --git a/code/sdk/zhihui/minimp3/src/mp3_decoder.c b/code/sdk/zhihui/minimp3/src/mp3_decoder.c new file mode 100644 index 000000000..72a887777 --- /dev/null +++ b/code/sdk/zhihui/minimp3/src/mp3_decoder.c @@ -0,0 +1,487 @@ +#define MINIMP3_IMPLEMENTATION +#include "minimp3.h" +#include "custom_utils.h" +#include "thread_init.h" +#include "osal_msgqueue.h" +#include "cmsis_os2.h" +#include +#include +#include +#include +#include + +// 定义缓冲区大小 - 优化后使用流式解码 +#define MP3_STREAM_BUFFER_SIZE (16 * 1024) // 16KB 流式缓冲区(优化:从1MB降至16KB) +#define PCM_BUFFER_SIZE MINIMP3_MAX_SAMPLES_PER_FRAME + +// 文件读取块大小 +#define MP3_FILE_CHUNK_SIZE (4 * 1024) // 4KB per read + +// 错误码定义 +typedef enum { + MP3_DECODE_SUCCESS = 0, + MP3_DECODE_ERROR_FILE_OPEN, + MP3_DECODE_ERROR_FILE_READ, + MP3_DECODE_ERROR_INVALID_FORMAT, + MP3_DECODE_ERROR_MEMORY, + MP3_DECODE_ERROR_UNKNOWN +} mp3_decode_error_t; + +// MP3 流式解码器结构体(优化版本) +typedef struct { + mp3dec_t decoder; // 解码器状态 + mp3dec_frame_info_t frame_info; + + // 流式缓冲区(16KB) + uint8_t stream_buffer[MP3_STREAM_BUFFER_SIZE]; + size_t stream_buffer_used; // 当前使用的字节数 + + // PCM 输出缓冲区 + int16_t *pcm_buffer; + + // 统计信息 + int total_samples_decoded; + int total_frames_decoded; + + // 回调函数 + void (*pcm_callback)(const int16_t *pcm_data, int samples, int channels, int sample_rate); + + // 状态标志 + bool is_initialized; +} mp3_decoder_t; + +// 全局解码器实例 +static mp3_decoder_t *g_mp3_decoder = NULL; + +/** + * @brief 尝试从流缓冲区解码一帧MP3数据 + * @return 解码的样本数,0表示需要更多数据,-1表示错误 + */ +static int mp3_decoder_try_decode_frame(void) +{ + if (!g_mp3_decoder || !g_mp3_decoder->is_initialized) { + return -1; + } + + if (g_mp3_decoder->stream_buffer_used == 0) { + return 0; // 需要更多数据 + } + + int samples = mp3dec_decode_frame(&g_mp3_decoder->decoder, + g_mp3_decoder->stream_buffer, + g_mp3_decoder->stream_buffer_used, + g_mp3_decoder->pcm_buffer, + &g_mp3_decoder->frame_info); + + if (samples > 0) { + // 解码成功 + g_mp3_decoder->total_frames_decoded++; + g_mp3_decoder->total_samples_decoded += samples; + + // 打印第一帧信息 + 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); + } + + // 调用回调函数 + if (g_mp3_decoder->pcm_callback) { + g_mp3_decoder->pcm_callback(g_mp3_decoder->pcm_buffer, samples, + g_mp3_decoder->frame_info.channels, + g_mp3_decoder->frame_info.hz); + } + + // 移除已解码的数据 + size_t frame_bytes = g_mp3_decoder->frame_info.frame_bytes; + if (frame_bytes > 0 && frame_bytes <= g_mp3_decoder->stream_buffer_used) { + g_mp3_decoder->stream_buffer_used -= frame_bytes; + if (g_mp3_decoder->stream_buffer_used > 0) { + memmove(g_mp3_decoder->stream_buffer, + g_mp3_decoder->stream_buffer + frame_bytes, + g_mp3_decoder->stream_buffer_used); + } + } + } else if (samples == 0) { + // 需要更多数据,尝试跳过一些字节 + if (g_mp3_decoder->stream_buffer_used > 0) { + g_mp3_decoder->stream_buffer_used--; + if (g_mp3_decoder->stream_buffer_used > 0) { + memmove(g_mp3_decoder->stream_buffer, + g_mp3_decoder->stream_buffer + 1, + g_mp3_decoder->stream_buffer_used); + } + } + } + + return samples; +} + +/** + * @brief 创建/初始化 MP3 流式解码器(优化版) + * @return mp3_decode_error_t 错误码 + */ +mp3_decode_error_t mp3_decoder_create(void) +{ + printf("[MP3] Creating stream decoder...\n"); + + // 检查是否已经创建 + if (g_mp3_decoder && g_mp3_decoder->is_initialized) { + printf("[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)); + return MP3_DECODE_ERROR_MEMORY; + } + + // 清零结构体 + memset(g_mp3_decoder, 0, sizeof(mp3_decoder_t)); + + // 初始化 minimp3 解码器 + mp3dec_init(&g_mp3_decoder->decoder); + + // 分配 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)); + free(g_mp3_decoder); + g_mp3_decoder = NULL; + return MP3_DECODE_ERROR_MEMORY; + } + + // 初始化状态 + g_mp3_decoder->stream_buffer_used = 0; + g_mp3_decoder->total_samples_decoded = 0; + g_mp3_decoder->total_frames_decoded = 0; + 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", + (sizeof(mp3_decoder_t) + PCM_BUFFER_SIZE * sizeof(int16_t)) / 1024, + sizeof(mp3_decoder_t) / 1024, + (PCM_BUFFER_SIZE * sizeof(int16_t)) / 1024); + + return MP3_DECODE_SUCCESS; +} + +/** + * @brief 销毁 MP3 流式解码器 + */ +void mp3_decoder_destroy(void) +{ + if (!g_mp3_decoder) { + printf("[MP3] Decoder not initialized\n"); + return; + } + + printf("[MP3] Destroying decoder...\n"); + + // 释放 PCM 缓冲区 + if (g_mp3_decoder->pcm_buffer) { + free(g_mp3_decoder->pcm_buffer); + g_mp3_decoder->pcm_buffer = NULL; + } + + // 释放解码器结构体 + free(g_mp3_decoder); + g_mp3_decoder = NULL; + + printf("[MP3] Decoder destroyed\n"); +} + +/** + * @brief 喂入 MP3 数据到流缓冲区并尝试解码(流式API) + * @param data MP3 数据 + * @param data_size 数据大小 + * @return mp3_decode_error_t 错误码 + */ +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"); + return MP3_DECODE_ERROR_UNKNOWN; + } + + if (!data || data_size == 0) { + return MP3_DECODE_SUCCESS; + } + + // 检查缓冲区空间 + 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"); + data_size = available; // 只接受能容纳的数据 + } + + // 将数据追加到流缓冲区 + memcpy(g_mp3_decoder->stream_buffer + g_mp3_decoder->stream_buffer_used, + data, data_size); + g_mp3_decoder->stream_buffer_used += data_size; + + // 尝试解码所有可能的帧 + int decoded_frames = 0; + while (1) { + int samples = mp3_decoder_try_decode_frame(); + if (samples > 0) { + decoded_frames++; + } else if (samples == 0) { + // 需要更多数据 + break; + } else { + // 解码错误 + break; + } + } + + if (decoded_frames > 0) { + printf("[MP3] Decoded %d frames from %zu bytes input\n", decoded_frames, data_size); + } + + return MP3_DECODE_SUCCESS; +} + +/** + * @brief 刷新解码器,处理剩余数据 + * @return mp3_decode_error_t 错误码 + */ +mp3_decode_error_t mp3_decoder_flush(void) +{ + if (!g_mp3_decoder || !g_mp3_decoder->is_initialized) { + return MP3_DECODE_ERROR_UNKNOWN; + } + + printf("[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", + g_mp3_decoder->total_frames_decoded, + g_mp3_decoder->total_samples_decoded); + + return MP3_DECODE_SUCCESS; +} + +/** + * @brief 设置 PCM 回调函数 + * @param callback PCM 回调函数 + */ +void mp3_decoder_set_callback(void (*callback)(const int16_t *pcm_data, int samples, int channels, int sample_rate)) +{ + if (g_mp3_decoder && g_mp3_decoder->is_initialized) { + g_mp3_decoder->pcm_callback = callback; + printf("[MP3] PCM callback set\n"); + } +} + +/** + * @brief 流式解码 MP3 文件(优化版 - 边读边解码) + * @param filename MP3 文件路径 + * @param callback PCM 回调函数 + * @return mp3_decode_error_t 错误码 + */ +mp3_decode_error_t mp3_decoder_decode_file_stream( + const char *filename, + void (*callback)(const int16_t *pcm_data, int samples, int channels, int sample_rate)) +{ + if (!filename) { + return MP3_DECODE_ERROR_UNKNOWN; + } + + // 确保解码器已创建 + if (!g_mp3_decoder || !g_mp3_decoder->is_initialized) { + mp3_decode_error_t ret = mp3_decoder_create(); + if (ret != MP3_DECODE_SUCCESS) { + return ret; + } + } + + // 设置回调 + g_mp3_decoder->pcm_callback = callback; + + // 重置统计 + g_mp3_decoder->stream_buffer_used = 0; + g_mp3_decoder->total_samples_decoded = 0; + g_mp3_decoder->total_frames_decoded = 0; + + // 打开文件 + FILE *file = fopen(filename, "rb"); + if (!file) { + printf("[MP3] Error: Cannot open file %s: %s\n", filename, strerror(errno)); + return MP3_DECODE_ERROR_FILE_OPEN; + } + + printf("[MP3] Streaming decode: %s\n", filename); + + // 流式读取并解码 + uint8_t chunk[MP3_FILE_CHUNK_SIZE]; + size_t bytes_read; + size_t total_bytes = 0; + + while ((bytes_read = fread(chunk, 1, MP3_FILE_CHUNK_SIZE, file)) > 0) { + total_bytes += bytes_read; + mp3_decoder_feed_data(chunk, bytes_read); + } + + fclose(file); + + // 刷新剩余数据 + mp3_decoder_flush(); + + printf("[MP3] File decoded successfully\n"); + printf("[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; +} + + +/** + * @brief 获取解码统计信息 + * @param frames 输出帧数 + * @param samples 输出样本数 + * @param duration_ms 输出时长(毫秒) + */ +void mp3_decoder_get_stats(int *frames, int *samples, int *duration_ms) +{ + if (g_mp3_decoder && g_mp3_decoder->is_initialized) { + if (frames) *frames = g_mp3_decoder->total_frames_decoded; + if (samples) *samples = g_mp3_decoder->total_samples_decoded; + if (duration_ms && g_mp3_decoder->frame_info.hz > 0) { + *duration_ms = (g_mp3_decoder->total_samples_decoded * 1000) / + (g_mp3_decoder->frame_info.hz * g_mp3_decoder->frame_info.channels); + } + } +} + +/** + * @brief 将 PCM 数据保存为 WAV 文件 + * @param filename 输出文件名 + * @param pcm_data PCM 数据 + * @param samples 样本数 + * @param channels 声道数 + * @param sample_rate 采样率 + * @return int 成功返回 0,失败返回 -1 + */ +int save_pcm_as_wav(const char *filename, const int16_t *pcm_data, int samples, int channels, int sample_rate) +{ + FILE *file = fopen(filename, "wb"); + if (!file) { + printf("Error: Cannot create WAV file %s\n", filename); + return -1; + } + + // WAV 文件头 + int data_size = samples * channels * sizeof(int16_t); + int file_size = 36 + data_size; + + // RIFF 头 + fwrite("RIFF", 1, 4, file); + fwrite(&file_size, 4, 1, file); + fwrite("WAVE", 1, 4, file); + + // fmt 块 + fwrite("fmt ", 1, 4, file); + int fmt_size = 16; + fwrite(&fmt_size, 4, 1, file); + int16_t audio_format = 1; // PCM + fwrite(&audio_format, 2, 1, file); + fwrite(&channels, 2, 1, file); + fwrite(&sample_rate, 4, 1, file); + int byte_rate = sample_rate * channels * sizeof(int16_t); + fwrite(&byte_rate, 4, 1, file); + int16_t block_align = channels * sizeof(int16_t); + fwrite(&block_align, 2, 1, file); + int16_t bits_per_sample = 16; + fwrite(&bits_per_sample, 2, 1, file); + + // data 块 + fwrite("data", 1, 4, file); + fwrite(&data_size, 4, 1, file); + fwrite(pcm_data, sizeof(int16_t), samples * channels, file); + + fclose(file); + printf("WAV file saved: %s (%d samples)\n", filename, samples); + return 0; +} + +// 示例回调函数:打印 PCM 数据信息 +void pcm_callback_example(const int16_t *pcm_data, int samples, int channels, int sample_rate) +{ + static int call_count = 0; + call_count++; + + if (call_count % 100 == 1) { // 每100帧打印一次 + printf("Frame %d: %d samples, %d channels, %d Hz\n", + call_count, samples, channels, sample_rate); + } +} + +// 示例回调函数:保存 PCM 数据到文件 +void pcm_callback_save_to_file(const int16_t *pcm_data, int samples, int channels, int sample_rate) +{ + static FILE *pcm_file = NULL; + static int frame_count = 0; + + if (!pcm_file) { + pcm_file = fopen("output.pcm", "wb"); + if (!pcm_file) { + printf("Error: Cannot create PCM file\n"); + return; + } + printf("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); + } +} + + +/** + * @brief 初始化 MP3 解码器并设置回调(简化接口) + * @param callback PCM 数据回调函数,可为 NULL + * @return mp3_decode_error_t 错误码 + */ +mp3_decode_error_t mp3_decoder_init_decode(void (*callback)(const int16_t *pcm_data, int samples, int channels, int sample_rate)) +{ + mp3_decode_error_t ret = mp3_decoder_create(); + if (ret != MP3_DECODE_SUCCESS) { + return ret; + } + + if (callback) { + mp3_decoder_set_callback(callback); + } + + return MP3_DECODE_SUCCESS; +} + +/** + * @brief 重置解码器状态(简化接口) + * @return mp3_decode_error_t 错误码 + */ +mp3_decode_error_t mp3_decoder_reset(void) +{ + if (!g_mp3_decoder || !g_mp3_decoder->is_initialized) { + return MP3_DECODE_ERROR_UNKNOWN; + } + + g_mp3_decoder->stream_buffer_used = 0; + g_mp3_decoder->total_samples_decoded = 0; + g_mp3_decoder->total_frames_decoded = 0; + + printf("[MP3] Decoder reset\n"); + return MP3_DECODE_SUCCESS; +} diff --git a/code/sdk/zhihui/utils/README_MP3_DECODER.md b/code/sdk/zhihui/utils/README_MP3_DECODER.md deleted file mode 100644 index 09fb756ab..000000000 --- a/code/sdk/zhihui/utils/README_MP3_DECODER.md +++ /dev/null @@ -1,359 +0,0 @@ -# MP3 解码器使用说明 - -基于 minimp3 库实现的轻量级 MP3 解码器。 - -## 文件说明 - -- `minimp3.h` - MinimP3 库(单头文件库) -- `mp3_decoder.h` - MP3 解码器头文件 -- `mp3_decoder.c` - MP3 解码器实现(支持线程和队列) -- `mp3_thread_example.c` - 线程和队列使用示例 -- `mp3_simple_example.c` - 简化接口使用示例 - -## 编译方法 - -### 方法1:编译简化接口示例程序 -```bash -# 编译简化接口示例程序 -gcc -DMINIMP3_IMPLEMENTATION -DMP3_DECODER_SIMPLE_TEST -o mp3_simple_example mp3_decoder.c mp3_simple_example.c - -# 或者分步编译 -gcc -DMINIMP3_IMPLEMENTATION -c mp3_decoder.c -gcc -DMP3_DECODER_SIMPLE_TEST -c mp3_simple_example.c -gcc -o mp3_simple_example mp3_decoder.o mp3_simple_example.o -``` - -### 方法2:编译线程示例程序 -```bash -# 编译线程示例程序 -gcc -DMINIMP3_IMPLEMENTATION -DMP3_DECODER_THREAD_TEST -o mp3_thread_example mp3_decoder.c mp3_thread_example.c -``` - -### 方法3:编译基本示例程序 -```bash -# 编译基本示例程序(在 mp3_decoder.c 中定义) -gcc -DMINIMP3_IMPLEMENTATION -DMP3_DECODER_TEST -o mp3_example mp3_decoder.c -``` - -## 使用方法 - -### 1. 简化接口示例(推荐) -```bash -# 使用简化接口解码 -./mp3_simple_example test.mp3 simple - -# 流式解码模式 -./mp3_simple_example test.mp3 stream -``` - -### 2. 线程和队列示例 -```bash -# 使用线程和队列机制解码 -./mp3_thread_example test.mp3 thread - -# 流式数据写入队列 -./mp3_thread_example test.mp3 stream -``` - -### 3. 基本示例 -```bash -# 运行基本示例程序 -./mp3_example test.mp3 -``` - -## 编程接口使用 - -### 简化接口用法(推荐) -```c -#include "mp3_decoder.h" - -// PCM 回调函数 -void my_pcm_callback(const int16_t *pcm_data, int samples, int channels, int sample_rate) { - printf("收到 PCM 数据: %d 样本, %d 声道, %d Hz\n", samples, channels, sample_rate); -} - -int main() { - // 1. 创建解码器 - mp3_decoder_t *decoder = mp3_decoder_create(); - if (!decoder) { - printf("创建解码器失败\n"); - return -1; - } - - // 2. 初始化解码 - mp3_decoder_init_decode(decoder, my_pcm_callback); - - // 3. 写入 MP3 数据 - uint8_t mp3_data[] = { /* MP3 数据 */ }; - mp3_decoder_write(decoder, mp3_data, sizeof(mp3_data), true); - - // 4. 等待解码完成 - while (!mp3_decoder_is_finished(decoder)) { - osDelay(100); - - // 检查进度 - int frames, samples; - mp3_decoder_get_progress(decoder, &frames, &samples); - printf("进度: %d 帧, %d 样本\n", frames, samples); - } - - // 5. 清理 - mp3_decoder_destroy(decoder); - return 0; -} -``` - -### 流式解码用法 -```c -#include "mp3_decoder.h" - -int main() { - mp3_decoder_t *decoder = mp3_decoder_create(); - mp3_decoder_init_decode(decoder, my_pcm_callback); - - // 打开文件 - FILE *file = fopen("test.mp3", "rb"); - uint8_t buffer[4096]; - size_t bytes_read; - - // 流式读取并写入 - while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { - bool is_last = (bytes_read < sizeof(buffer)); - mp3_decoder_write(decoder, buffer, bytes_read, is_last); - - // 检查是否完成 - if (mp3_decoder_is_finished(decoder)) { - break; - } - } - - fclose(file); - mp3_decoder_destroy(decoder); - return 0; -} -``` - -### 流式数据写入 -```c -#include "mp3_decoder.h" - -int main() { - // 创建解码器 - mp3_decoder_t *decoder = mp3_decoder_create(); - if (!decoder) { - printf("创建解码器失败\n"); - return -1; - } - - // 设置回调函数 - mp3_decoder_set_callback(decoder, my_pcm_callback); - - // 打开 MP3 文件 - FILE *file = fopen("test.mp3", "rb"); - if (!file) { - printf("无法打开文件\n"); - mp3_decoder_destroy(decoder); - return -1; - } - - // 流式读取并写入队列 - uint8_t buffer[4096]; - size_t bytes_read; - int packet_count = 0; - - while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { - bool is_last = (bytes_read < sizeof(buffer)); - - mp3_decode_error_t ret = mp3_decoder_write_data(decoder, buffer, bytes_read, is_last); - if (ret != MP3_DECODE_SUCCESS) { - printf("写入队列失败: %d\n", ret); - break; - } - - packet_count++; - printf("数据包 %d: %zu 字节%s\n", packet_count, bytes_read, is_last ? " (最后)" : ""); - } - - fclose(file); - - // 等待解码完成 - mp3_decode_error_t ret = mp3_decoder_wait_finish(decoder, 30000); // 30秒超时 - if (ret != MP3_DECODE_SUCCESS) { - printf("解码未完成: %d\n", ret); - } - - // 清理 - mp3_decoder_destroy(decoder); - return 0; -} -``` - -### 自定义回调函数 -```c -// 自定义 PCM 数据处理回调 -void my_pcm_callback(const int16_t *pcm_data, int samples, int channels, int sample_rate) { - printf("收到 PCM 数据: %d 样本, %d 声道, %d Hz\n", samples, channels, sample_rate); - - // 在这里处理 PCM 数据 - // 例如:播放、保存、分析等 -} - -int main() { - mp3_decoder_t *decoder = mp3_decoder_create(); - mp3_decoder_load_file(decoder, "test.mp3"); - - // 使用自定义回调解码 - mp3_decoder_decode(decoder, my_pcm_callback); - - mp3_decoder_destroy(decoder); - return 0; -} -``` - -## API 参考 - -### 主要函数 - -#### `mp3_decoder_create()` -创建 MP3 解码器实例。 -- **返回值**: 解码器指针,失败返回 NULL - -#### `mp3_decoder_destroy(decoder)` -销毁解码器实例。 -- **参数**: `decoder` - 解码器指针 - -#### `mp3_decoder_load_file(decoder, filename)` -从文件加载 MP3 数据。 -- **参数**: - - `decoder` - 解码器指针 - - `filename` - MP3 文件路径 -- **返回值**: 错误码 - -#### `mp3_decoder_decode(decoder, callback)` -使用队列机制异步解码 MP3 数据。 -- **参数**: - - `decoder` - 解码器指针 - - `callback` - PCM 数据回调函数,可为 NULL -- **返回值**: 错误码 -- **注意**: 此函数启动异步解码,不等待完成。解码在后台线程中进行。 - -#### `mp3_decoder_write_data(decoder, data, data_size, is_last_packet)` -向 MP3 队列写入数据。 -- **参数**: - - `decoder` - 解码器指针 - - `data` - MP3 数据 - - `data_size` - 数据大小 - - `is_last_packet` - 是否为最后一个数据包 -- **返回值**: 错误码 - -#### `mp3_decoder_set_callback(decoder, callback)` -设置 PCM 回调函数。 -- **参数**: - - `decoder` - 解码器指针 - - `callback` - PCM 回调函数 - -#### `mp3_decoder_wait_finish(decoder, timeout_ms)` -等待解码完成。 -- **参数**: - - `decoder` - 解码器指针 - - `timeout_ms` - 超时时间(毫秒),0表示无限等待 -- **返回值**: 错误码 - -#### `mp3_decoder_get_stats(decoder, frames, samples, duration_ms)` -获取解码统计信息。 -- **参数**: - - `decoder` - 解码器指针 - - `frames` - 输出帧数(可为 NULL) - - `samples` - 输出样本数(可为 NULL) - - `duration_ms` - 输出时长毫秒(可为 NULL) - -### 错误码 -- `MP3_DECODE_SUCCESS` - 成功 -- `MP3_DECODE_ERROR_FILE_OPEN` - 文件打开失败 -- `MP3_DECODE_ERROR_FILE_READ` - 文件读取失败 -- `MP3_DECODE_ERROR_INVALID_FORMAT` - 无效格式 -- `MP3_DECODE_ERROR_MEMORY` - 内存分配失败 -- `MP3_DECODE_ERROR_UNKNOWN` - 未知错误 - -## 特性 - -- ✅ 支持 MP3 (MPEG-1 Layer 3) 解码 -- ✅ 支持单声道和立体声 -- ✅ 支持多种采样率 (8kHz, 11.025kHz, 12kHz, 16kHz, 22.05kHz, 24kHz, 32kHz, 44.1kHz, 48kHz) -- ✅ 支持多种比特率 -- ✅ **线程安全解码** - 使用独立线程进行解码 -- ✅ **队列机制** - 支持流式数据输入 -- ✅ **异步解码** - 非阻塞式解码,立即返回 -- ✅ **后台处理** - 解码在独立线程中进行 -- ✅ 内存效率高 -- ✅ 支持 WAV 文件输出 -- ✅ 支持原始 PCM 数据输出 -- ✅ **实时流式解码** - 支持边接收边解码 - -## 注意事项 - -1. **内存管理**: 使用完毕后必须调用 `mp3_decoder_destroy()` 释放资源 -2. **线程安全**: 解码器使用独立线程,支持多实例并发使用 -3. **队列大小**: 默认队列大小为 10 个数据包,可根据需要调整 -4. **数据包大小**: 每个队列项最大 4KB,大数据需要分块传输 -5. **超时处理**: 队列操作有超时机制,避免死锁 -6. **格式支持**: 仅支持 MP3 格式,不支持其他音频格式 -7. **编译选项**: 必须定义 `MINIMP3_IMPLEMENTATION` 宏来包含 minimp3 实现 -8. **依赖库**: 需要 `custom_utils.h`、`thread_init.h`、`osal_msgqueue.h` 等系统库 - -## 示例输出 - -### 线程和队列模式 -``` -=== MP3 Decoder Thread Example === -MP3 decoder created successfully -Thread handle: 0x20001234, Queue ID: 0x20005678 -MP3 queue created successfully, ID: 0x20005678 -Loaded MP3 file: test.mp3 (2048576 bytes) -Starting MP3 decoding with queue mechanism... -MP3 data written to queue: 4096 bytes -MP3 data written to queue: 4096 bytes -... -All MP3 data written to queue: 500 packets -MP3 decode thread started -MP3 Info: - Channels: 2 - Sample Rate: 44100 Hz - Bitrate: 128 kbps - Layer: 3 -Decoded packet: 4096 bytes, 1 frames so far -... -MP3 decode thread finished: 1234 total frames, 567890 total samples -Decode finished successfully - -Decoding Statistics: - Total frames: 1234 - Total samples: 567890 - Duration: 12876 ms (12.88 seconds) -=== Thread Example completed === -``` - -### 流式模式 -``` -=== MP3 Decoder Stream Example === -MP3 decoder created successfully -Thread handle: 0x20001234, Queue ID: 0x20005678 -MP3 queue created successfully, ID: 0x20005678 -Streaming MP3 data to queue... -Packet 1: 4096 bytes -Packet 2: 4096 bytes -... -Packet 500: 1024 bytes (last) -Waiting for decode to finish... -MP3 decode thread started -... -MP3 decode thread finished: 1234 total frames, 567890 total samples -Decode finished successfully - -Stream Decoding Statistics: - Total packets: 500 - Total frames: 1234 - Total samples: 567890 - Duration: 12876 ms (12.88 seconds) -=== Stream Example completed === -``` diff --git a/code/sdk/zhihui/utils/mp3_decoder.c b/code/sdk/zhihui/utils/mp3_decoder.c deleted file mode 100644 index a246d50dc..000000000 --- a/code/sdk/zhihui/utils/mp3_decoder.c +++ /dev/null @@ -1,712 +0,0 @@ -#define MINIMP3_IMPLEMENTATION -#include "minimp3.h" -#include "custom_utils.h" -#include "thread_init.h" -#include "osal_msgqueue.h" -#include "cmsis_os2.h" -#include -#include -#include -#include -#include - -// 定义缓冲区大小 -#define MP3_BUFFER_SIZE (1024 * 1024) // 1MB 缓冲区 -#define PCM_BUFFER_SIZE MINIMP3_MAX_SAMPLES_PER_FRAME - -// MP3 队列相关定义 -#define MP3_QUEUE_SIZE 10 -#define MP3_QUEUE_ITEM_SIZE 1024 // 每个队列项的大小(4KB) - -// MP3 数据包结构 -typedef struct { - uint8_t data[MP3_QUEUE_ITEM_SIZE]; - size_t data_size; - bool is_last_packet; -} mp3_queue_item_t; - -// 错误码定义 -typedef enum { - MP3_DECODE_SUCCESS = 0, - MP3_DECODE_ERROR_FILE_OPEN, - MP3_DECODE_ERROR_FILE_READ, - MP3_DECODE_ERROR_INVALID_FORMAT, - MP3_DECODE_ERROR_MEMORY, - MP3_DECODE_ERROR_UNKNOWN -} mp3_decode_error_t; - -// MP3 解码器结构体 -typedef struct { - mp3dec_t decoder; // 直接使用结构体,不使用指针 - mp3dec_frame_info_t frame_info; - uint8_t *mp3_buffer; - size_t mp3_buffer_size; - size_t mp3_data_size; - int16_t *pcm_buffer; - int total_samples_decoded; - int total_frames_decoded; - - // 线程和队列相关 - uint32_t thread_handle; - uint32_t queue_id; - bool thread_running; - bool decode_finished; - void (*pcm_callback)(const int16_t *pcm_data, int samples, int channels, int sample_rate); -} mp3_decoder_t; - -// 全局解码器实例(用于线程回调) -static mp3_decoder_t *g_decoder_instance = NULL; - -// 动态全局解码器实例 -static mp3_decoder_t *g_mp3_decoder = NULL; -static bool g_decoder_initialized = false; -static bool g_decoder_created = false; // 创建标志位 -static bool g_decoder_init_decode = false; // 初始化解码标志位 - -/** - * @brief 创建 MP3 队列 - */ -static void mp3_queue_create(void) -{ - if (g_decoder_initialized && g_mp3_decoder) { - int ret = osal_msg_queue_create("mp3_queue", MP3_QUEUE_SIZE, - &g_mp3_decoder->queue_id, 0, - sizeof(mp3_queue_item_t)); - if (ret != OSAL_SUCCESS) { - printf("Error: Failed to create MP3 queue\n"); - g_mp3_decoder->queue_id = 0; - } else { - printf("MP3 queue created successfully, ID: 0x%x\n", g_mp3_decoder->queue_id); - } - } -} - -/** - * @brief MP3 解码线程主函数 - * @param argument 线程参数(未使用) - */ -static void mp3_decode_thread(void *argument) -{ - // VAD_UNUSED(argument); - - if (!g_decoder_initialized || !g_mp3_decoder || !g_mp3_decoder->queue_id) { - printf("Error: Decoder not initialized or invalid queue in thread\n"); - return; - } - - printf("MP3 decode thread started\n"); - g_mp3_decoder->thread_running = true; - g_mp3_decoder->decode_finished = false; - - mp3_queue_item_t queue_item; - size_t current_offset = 0; - int frame_count = 0; - - while (g_mp3_decoder->thread_running && !g_mp3_decoder->decode_finished) { - // 从队列中读取 MP3 数据 - int ret = osal_msg_queue_read_copy(g_mp3_decoder->queue_id, &queue_item, - sizeof(mp3_queue_item_t), 1000); // 1秒超时 - - printf("osal_msg_queue_read_copy mp3_decode_thread\n"); - if (ret == OSAL_SUCCESS) { - if (queue_item.is_last_packet) { - printf("Received last packet, finishing decode\n"); - g_mp3_decoder->decode_finished = true; - break; - } - - // 解码接收到的数据 - size_t data_offset = 0; - while (data_offset < queue_item.data_size) { - printf("mp3dec_decode_frame\n"); - int samples = mp3dec_decode_frame(&g_mp3_decoder->decoder, - queue_item.data + data_offset, - queue_item.data_size - data_offset, - g_mp3_decoder->pcm_buffer, - &g_mp3_decoder->frame_info); - - if (samples > 0) { - // 解码成功 - frame_count++; - g_mp3_decoder->total_frames_decoded++; - g_mp3_decoder->total_samples_decoded += samples; - - // 打印帧信息(仅第一帧) - if (frame_count == 1) { - printf("MP3 Info:\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); - } - - // 调用回调函数处理 PCM 数据 - if (g_mp3_decoder->pcm_callback) { - g_mp3_decoder->pcm_callback(g_mp3_decoder->pcm_buffer, samples, - g_mp3_decoder->frame_info.channels, g_mp3_decoder->frame_info.hz); - } - - data_offset += g_mp3_decoder->frame_info.frame_bytes; - } else { - // 解码失败,尝试跳过一些字节 - data_offset++; - if (data_offset >= queue_item.data_size) { - break; - } - } - } - - printf("Decoded packet: %zu bytes, %d frames so far\n", - queue_item.data_size, frame_count); - } else { - printf("Error reading from queue: %d\n", ret); - continue; - } - } - - printf("MP3 decode thread finished: %d total frames, %d total samples\n", - g_mp3_decoder->total_frames_decoded, g_mp3_decoder->total_samples_decoded); - g_mp3_decoder->thread_running = false; -} - -/** - * @brief 创建 MP3 解码器 - * @return mp3_decode_error_t 错误码 - */ -mp3_decode_error_t mp3_decoder_create(void) -{ - printf("mp3_decoder_create: Starting create process\n"); - - // 检查是否已经创建 - if (g_decoder_created) { - printf("MP3 decoder already created, skipping creation\n"); - return MP3_DECODE_SUCCESS; - } - - if (g_decoder_initialized) { - printf("MP3 decoder already initialized\n"); - return MP3_DECODE_SUCCESS; - } - - // 分配解码器结构体内存 - g_mp3_decoder = (mp3_decoder_t*)malloc(sizeof(mp3_decoder_t)); - if (!g_mp3_decoder) { - printf("mp3_decoder_create Error: Failed to allocate decoder memory\n"); - return MP3_DECODE_ERROR_MEMORY; - } - - // 初始化解码器 - mp3dec_init(&g_mp3_decoder->decoder); - - // 分配 MP3 缓冲区 - g_mp3_decoder->mp3_buffer = (uint8_t*)malloc(MP3_BUFFER_SIZE); - if (!g_mp3_decoder->mp3_buffer) { - printf("mp3_decoder_create Error: Failed to allocate MP3 buffer\n"); - free(g_mp3_decoder); - g_mp3_decoder = NULL; - return MP3_DECODE_ERROR_MEMORY; - } - - // 分配 PCM 缓冲区 - g_mp3_decoder->pcm_buffer = (int16_t*)malloc(PCM_BUFFER_SIZE * sizeof(int16_t)); - if (!g_mp3_decoder->pcm_buffer) { - printf("mp3_decoder_create Error: Failed to allocate PCM buffer\n"); - free(g_mp3_decoder->mp3_buffer); - free(g_mp3_decoder); - g_mp3_decoder = NULL; - return MP3_DECODE_ERROR_MEMORY; - } - - g_mp3_decoder->mp3_buffer_size = MP3_BUFFER_SIZE; - g_mp3_decoder->mp3_data_size = 0; - g_mp3_decoder->total_samples_decoded = 0; - g_mp3_decoder->total_frames_decoded = 0; - - // 初始化线程和队列相关字段 - g_mp3_decoder->thread_handle = 0; - g_mp3_decoder->queue_id = 0; - g_mp3_decoder->thread_running = false; - g_mp3_decoder->decode_finished = false; - g_mp3_decoder->pcm_callback = NULL; - - // 设置全局实例(用于队列创建回调) - g_decoder_instance = g_mp3_decoder; - g_decoder_initialized = true; - g_decoder_created = true; // 设置创建标志位 - - // 创建线程和队列 - app_task_definition_ext_t task_cfg = { - .attr = { - .name = "mp3_decode_thread", - .stack_size = 8192, - .priority = TASK_PRIORITY_APP, - }, - .func = mp3_decode_thread, - .task_handle = &g_mp3_decoder->thread_handle, - .create_queue_handle = mp3_queue_create, - .use_mem = USE_EXTEND_MEM, - .inst_args = NULL // 不再需要传递参数 - }; - - errcode_t ret = uapi_create_osThread_custom_with_args(&task_cfg); - if (ret != ERRCODE_SUCC) { - printf("mp3_decoder_create Error: Failed to create MP3 decode thread: %d\n", ret); - free(g_mp3_decoder->mp3_buffer); - free(g_mp3_decoder->pcm_buffer); - free(g_mp3_decoder); - g_mp3_decoder = NULL; - g_decoder_instance = NULL; - g_decoder_initialized = false; - return MP3_DECODE_ERROR_UNKNOWN; - } - - printf("MP3 decoder created successfully\n"); - printf("Thread handle: 0x%x, Queue ID: 0x%x\n", g_mp3_decoder->thread_handle, g_mp3_decoder->queue_id); - return MP3_DECODE_SUCCESS; -} - -/** - * @brief 销毁 MP3 解码器 - */ -void mp3_decoder_destroy(void) -{ - if (!g_decoder_initialized || !g_mp3_decoder) { - printf("MP3 decoder not initialized\n"); - return; - } - - // 停止解码线程 - if (g_mp3_decoder->thread_running) { - g_mp3_decoder->thread_running = false; - g_mp3_decoder->decode_finished = true; - - // 等待线程结束 - int timeout = 100; // 10秒超时 - while (g_mp3_decoder->thread_running && timeout > 0) { - osDelay(100); // 等待100ms - timeout--; - } - - if (g_mp3_decoder->thread_running) { - printf("Warning: Thread did not stop gracefully\n"); - } - } - - // 删除队列 - if (g_mp3_decoder->queue_id) { - osal_msg_queue_delete(g_mp3_decoder->queue_id); - printf("MP3 queue deleted\n"); - } - - // 清理内存 - if (g_mp3_decoder->mp3_buffer) { - free(g_mp3_decoder->mp3_buffer); - } - if (g_mp3_decoder->pcm_buffer) { - free(g_mp3_decoder->pcm_buffer); - } - - // 释放解码器结构体 - free(g_mp3_decoder); - g_mp3_decoder = NULL; - - // 清理全局实例 - g_decoder_instance = NULL; - g_decoder_initialized = false; - - printf("MP3 decoder destroyed\n"); -} - -/** - * @brief 从文件加载 MP3 数据 - * @param filename MP3 文件路径 - * @return mp3_decode_error_t 错误码 - */ -mp3_decode_error_t mp3_decoder_load_file(const char *filename) -{ - if (!g_decoder_initialized || !g_mp3_decoder || !filename) { - return MP3_DECODE_ERROR_UNKNOWN; - } - - FILE *file = fopen(filename, "rb"); - if (!file) { - printf("Error: Cannot open file %s: %s\n", filename, strerror(errno)); - return MP3_DECODE_ERROR_FILE_OPEN; - } - - // 获取文件大小 - fseek(file, 0, SEEK_END); - long file_size = ftell(file); - fseek(file, 0, SEEK_SET); - - if (file_size <= 0) { - printf("Error: Invalid file size: %ld\n", file_size); - fclose(file); - return MP3_DECODE_ERROR_FILE_READ; - } - - // 检查文件大小是否超过缓冲区 - if ((size_t)file_size > g_mp3_decoder->mp3_buffer_size) { - printf("Error: File too large (%ld bytes), max supported: %zu bytes\n", - file_size, g_mp3_decoder->mp3_buffer_size); - fclose(file); - return MP3_DECODE_ERROR_FILE_READ; - } - - // 读取文件数据 - size_t bytes_read = fread(g_mp3_decoder->mp3_buffer, 1, file_size, file); - fclose(file); - - if (bytes_read != (size_t)file_size) { - printf("Error: Failed to read complete file\n"); - return MP3_DECODE_ERROR_FILE_READ; - } - - g_mp3_decoder->mp3_data_size = bytes_read; - printf("Loaded MP3 file: %s (%zu bytes)\n", filename, bytes_read); - - return MP3_DECODE_SUCCESS; -} - -/** - * @brief 向 MP3 队列写入数据 - * @param data MP3 数据 - * @param data_size 数据大小 - * @param is_last_packet 是否为最后一个数据包 - * @return mp3_decode_error_t 错误码 - */ -mp3_decode_error_t mp3_decoder_write_data(const uint8_t *data, size_t data_size, bool is_last_packet) -{ - if (!g_decoder_initialized || !g_mp3_decoder || !data || data_size == 0 || !g_mp3_decoder->queue_id) { - return MP3_DECODE_ERROR_UNKNOWN; - } - - mp3_queue_item_t queue_item; - memset(&queue_item, 0, sizeof(queue_item)); - - // 复制数据到队列项 - size_t copy_size = (data_size > MP3_QUEUE_ITEM_SIZE) ? MP3_QUEUE_ITEM_SIZE : data_size; - memcpy(queue_item.data, data, copy_size); - queue_item.data_size = copy_size; - queue_item.is_last_packet = is_last_packet; - - // 写入队列 - int ret = osal_msg_queue_write_copy(g_mp3_decoder->queue_id, &queue_item, - sizeof(mp3_queue_item_t), 1000); // 1秒超时 - - if (ret == OSAL_SUCCESS) { - printf("MP3 data written to queue: %zu bytes%s\n", - copy_size, is_last_packet ? " (last packet)" : ""); - return MP3_DECODE_SUCCESS; - } else { - printf("Error: Failed to write to queue: %d\n", ret); - return MP3_DECODE_ERROR_UNKNOWN; - } -} - -/** - * @brief 设置 PCM 回调函数 - * @param callback PCM 回调函数 - */ -void mp3_decoder_set_callback(void (*callback)(const int16_t *pcm_data, int samples, int channels, int sample_rate)) -{ - if (g_decoder_initialized && g_mp3_decoder) { - g_mp3_decoder->pcm_callback = callback; - printf("PCM callback set\n"); - } -} - -/** - * @brief 等待解码完成 - * @param timeout_ms 超时时间(毫秒),0表示无限等待 - * @return mp3_decode_error_t 错误码 - */ -mp3_decode_error_t mp3_decoder_wait_finish(uint32_t timeout_ms) -{ - if (!g_decoder_initialized || !g_mp3_decoder) { - return MP3_DECODE_ERROR_UNKNOWN; - } - - uint32_t start_time = osKernelGetTickCount(); - uint32_t timeout_ticks = (timeout_ms > 0) ? (timeout_ms / osKernelGetTickFreq()) : UINT32_MAX; - - while (!g_mp3_decoder->decode_finished && g_mp3_decoder->thread_running) { - if (timeout_ms > 0) { - uint32_t current_time = osKernelGetTickCount(); - if ((current_time - start_time) >= timeout_ticks) { - printf("Wait timeout\n"); - return MP3_DECODE_ERROR_UNKNOWN; - } - } - osDelay(10); // 等待10ms - } - - if (g_mp3_decoder->decode_finished) { - printf("Decode finished successfully\n"); - return MP3_DECODE_SUCCESS; - } else { - printf("Decode thread stopped unexpectedly\n"); - return MP3_DECODE_ERROR_UNKNOWN; - } -} - -/** - * @brief 异步解码 MP3 数据(使用队列机制) - * @param callback PCM 数据回调函数,参数为 (pcm_data, samples, channels, sample_rate) - * @return mp3_decode_error_t 错误码 - * @note 此函数启动异步解码,不等待完成。解码在后台线程中进行。 - */ -mp3_decode_error_t mp3_decoder_decode(void (*callback)(const int16_t *pcm_data, int samples, int channels, int sample_rate)) -{ - if (!g_decoder_initialized || !g_mp3_decoder || g_mp3_decoder->mp3_data_size == 0 || !g_mp3_decoder->queue_id) { - return MP3_DECODE_ERROR_UNKNOWN; - } - - // 设置回调函数 - g_mp3_decoder->pcm_callback = callback; - - printf("Starting MP3 decoding with queue mechanism...\n"); - - // 将 MP3 数据分块写入队列 - size_t offset = 0; - int packet_count = 0; - - while (offset < g_mp3_decoder->mp3_data_size) { - size_t chunk_size = MP3_QUEUE_ITEM_SIZE; - if (offset + chunk_size > g_mp3_decoder->mp3_data_size) { - chunk_size = g_mp3_decoder->mp3_data_size - offset; - } - - bool is_last = (offset + chunk_size >= g_mp3_decoder->mp3_data_size); - - mp3_decode_error_t ret = mp3_decoder_write_data(g_mp3_decoder->mp3_buffer + offset, - chunk_size, is_last); - if (ret != MP3_DECODE_SUCCESS) { - printf("Error writing data to queue at offset %zu\n", offset); - return ret; - } - - offset += chunk_size; - packet_count++; - - // 小延迟,避免队列过满 - osDelay(10); - } - - printf("All MP3 data written to queue: %d packets\n", packet_count); - - // 异步解码,不等待完成 - printf("MP3 decoding started in background thread\n"); - return MP3_DECODE_SUCCESS; -} - -/** - * @brief 获取解码统计信息 - * @param frames 输出帧数 - * @param samples 输出样本数 - * @param duration_ms 输出时长(毫秒) - */ -void mp3_decoder_get_stats(int *frames, int *samples, int *duration_ms) -{ - if (g_decoder_initialized && g_mp3_decoder) { - if (frames) *frames = g_mp3_decoder->total_frames_decoded; - if (samples) *samples = g_mp3_decoder->total_samples_decoded; - if (duration_ms && g_mp3_decoder->frame_info.hz > 0) { - *duration_ms = (g_mp3_decoder->total_samples_decoded * 1000) / - (g_mp3_decoder->frame_info.hz * g_mp3_decoder->frame_info.channels); - } - } -} - -/** - * @brief 将 PCM 数据保存为 WAV 文件 - * @param filename 输出文件名 - * @param pcm_data PCM 数据 - * @param samples 样本数 - * @param channels 声道数 - * @param sample_rate 采样率 - * @return int 成功返回 0,失败返回 -1 - */ -int save_pcm_as_wav(const char *filename, const int16_t *pcm_data, int samples, int channels, int sample_rate) -{ - FILE *file = fopen(filename, "wb"); - if (!file) { - printf("Error: Cannot create WAV file %s\n", filename); - return -1; - } - - // WAV 文件头 - int data_size = samples * channels * sizeof(int16_t); - int file_size = 36 + data_size; - - // RIFF 头 - fwrite("RIFF", 1, 4, file); - fwrite(&file_size, 4, 1, file); - fwrite("WAVE", 1, 4, file); - - // fmt 块 - fwrite("fmt ", 1, 4, file); - int fmt_size = 16; - fwrite(&fmt_size, 4, 1, file); - int16_t audio_format = 1; // PCM - fwrite(&audio_format, 2, 1, file); - fwrite(&channels, 2, 1, file); - fwrite(&sample_rate, 4, 1, file); - int byte_rate = sample_rate * channels * sizeof(int16_t); - fwrite(&byte_rate, 4, 1, file); - int16_t block_align = channels * sizeof(int16_t); - fwrite(&block_align, 2, 1, file); - int16_t bits_per_sample = 16; - fwrite(&bits_per_sample, 2, 1, file); - - // data 块 - fwrite("data", 1, 4, file); - fwrite(&data_size, 4, 1, file); - fwrite(pcm_data, sizeof(int16_t), samples * channels, file); - - fclose(file); - printf("WAV file saved: %s (%d samples)\n", filename, samples); - return 0; -} - -// 示例回调函数:打印 PCM 数据信息 -void pcm_callback_example(const int16_t *pcm_data, int samples, int channels, int sample_rate) -{ - static int call_count = 0; - call_count++; - - if (call_count % 100 == 1) { // 每100帧打印一次 - printf("Frame %d: %d samples, %d channels, %d Hz\n", - call_count, samples, channels, sample_rate); - } -} - -// 示例回调函数:保存 PCM 数据到文件 -void pcm_callback_save_to_file(const int16_t *pcm_data, int samples, int channels, int sample_rate) -{ - static FILE *pcm_file = NULL; - static int frame_count = 0; - - if (!pcm_file) { - pcm_file = fopen("output.pcm", "wb"); - if (!pcm_file) { - printf("Error: Cannot create PCM file\n"); - return; - } - printf("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); - } -} - - -/** - * @brief 初始化 MP3 解码器(简化接口) - * @param callback PCM 数据回调函数,可为 NULL - * @return mp3_decode_error_t 错误码 - */ -mp3_decode_error_t mp3_decoder_init_decode(void (*callback)(const int16_t *pcm_data, int samples, int channels, int sample_rate)) -{ - mp3_decoder_create(); - - if (!g_decoder_initialized || !g_mp3_decoder) { - printf("Error: Decoder not initialized\n"); - return MP3_DECODE_ERROR_UNKNOWN; - } - - if (!g_mp3_decoder->queue_id) { - printf("Error: Decoder not properly initialized\n"); - return MP3_DECODE_ERROR_UNKNOWN; - } - - // 设置回调函数 - g_mp3_decoder->pcm_callback = callback; - - // 重置解码状态 - g_mp3_decoder->decode_finished = false; - g_mp3_decoder->total_samples_decoded = 0; - g_mp3_decoder->total_frames_decoded = 0; - - printf("MP3 decoder initialized for decoding\n"); - - return MP3_DECODE_SUCCESS; -} - -/** - * @brief 写入 MP3 数据(简化接口) - * @param data MP3 数据 - * @param data_size 数据大小 - * @param is_last 是否为最后一块数据 - * @return mp3_decode_error_t 错误码 - */ -mp3_decode_error_t mp3_decoder_write(const uint8_t *data, size_t data_size, bool is_last) -{ - if (!g_decoder_initialized || !g_mp3_decoder || !data || data_size == 0) { - printf("Error: Invalid parameters\n"); - return MP3_DECODE_ERROR_UNKNOWN; - } - - if (!g_mp3_decoder->queue_id) { - printf("Error: Decoder not initialized\n"); - return MP3_DECODE_ERROR_UNKNOWN; - } - - // 调用现有的写数据函数 - return mp3_decoder_write_data(data, data_size, is_last); -} - -/** - * @brief 检查解码是否完成(简化接口) - * @return bool true 表示解码完成,false 表示还在进行中 - */ -bool mp3_decoder_is_finished(void) -{ - if (!g_decoder_initialized || !g_mp3_decoder) { - return true; - } - return g_mp3_decoder->decode_finished; -} - -/** - * @brief 获取解码进度(简化接口) - * @param frames 输出已解码帧数 - * @param samples 输出已解码样本数 - * @return mp3_decode_error_t 错误码 - */ -mp3_decode_error_t mp3_decoder_get_progress(int *frames, int *samples) -{ - if (!g_decoder_initialized || !g_mp3_decoder) { - return MP3_DECODE_ERROR_UNKNOWN; - } - - if (frames) { - *frames = g_mp3_decoder->total_frames_decoded; - } - if (samples) { - *samples = g_mp3_decoder->total_samples_decoded; - } - - return MP3_DECODE_SUCCESS; -} - -/** - * @brief 停止解码(简化接口) - * @return mp3_decode_error_t 错误码 - */ -mp3_decode_error_t mp3_decoder_stop(void) -{ - if (!g_decoder_initialized || !g_mp3_decoder) { - return MP3_DECODE_ERROR_UNKNOWN; - } - - g_mp3_decoder->thread_running = false; - g_mp3_decoder->decode_finished = true; - - printf("MP3 decoder stopped\n"); - return MP3_DECODE_SUCCESS; -} -- 2.17.1