#include "ai_V2_5_at_cmd.h"\r
#endif\r
\r
+#ifdef IMSL_VAD\r
+#include "imsl_at_cmd.h"\r
+#endif\r
+\r
static void register_at_service(void)\r
{\r
#ifdef SUPPORT_AT_GRAPHIC\r
at_cmd_add_moudle(ai_table, ai_cmd_count);\r
#endif\r
\r
+#ifdef IMSL_VAD\r
+ uint32_t imsl_vad_cmd_count;\r
+ at_cmd_table_t *imsl_vad_table = get_imsl_vad_at_cmd_table(&imsl_vad_cmd_count);\r
+ at_cmd_add_moudle(imsl_vad_table, imsl_vad_cmd_count);\r
+#endif\r
+\r
}\r
\r
#ifdef CONFIG_OTA_UPDATE_SUPPORT\r
"ai_stub",
#音频相关配置
"audio_manager",
+
+ "imsl",
+ "minimp3",
# "CustomUIcomponentTool",
"nativeabilityfwk",
"custom_utils",
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)
--- /dev/null
+#===============================================================================
+# @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
--- /dev/null
+#ifndef IMSL_AT_CMD_H
+#define IMSL_AT_CMD_H
+
+#include <stdint.h>
+#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 */
+
--- /dev/null
+#include "at_cmd_api.h"
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#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 <sil_threshold> <energy_threshold> <min_speech_length> <silence_detect_length> <rule1_min_tailing_silence> <rule2_min_tailing_silence> <rule3_min_tailing_silence>\n");
+ return ERRCODE_FAIL;
+ }
+
+ // 解析参数
+ 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 <pcm_path>
+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 <pcm_path>\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 <in.pcm> - 分析已有PCM\n");
+ printf("AT^VAD_RECORD_START - 开始录音测试\n");
+ printf("AT^VAD_RECORD_STOP - 停止录音测试\n");
+ printf("AT^VAD_CLEANUP - 清理 VAD 资源\n");
+ printf("AT^VAD_HELP - 显示此帮助\n");
+ printf("\n=== 使用示例 ===\n");
+ printf("AT^VAD_INIT # 初始化\n");
+ printf("AT^VAD_CONFIG=50 5000 120 300 480 960 960 # 配置参数\n");
+ printf("AT^VAD_STATUS # 查看状态\n");
+ printf("AT^VAD_ANALYZE_PCM=/user/vad_live.pcm # 分析已有PCM\n");
+ printf("AT^VAD_RECORD_START # 开始录音测试\n");
+ printf("AT^VAD_RECORD_STOP # 停止录音测试\n");
+ printf("AT^VAD_CLEANUP # 清理资源\n");
+ printf("=====================\n");
+
+ 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;
+}
+
# 源文件:系统适配层实现
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
# 公共头文件路径(供其他模块引用)
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
${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
-
)
# 公共宏定义(可选)
# 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
#include <sys/stat.h>
#include <errno.h>
#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);
/* 内存查看辅助函数 - 使用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)
{
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';
pos = 0;
if (*argc >= LN_MAX_ARGC)
{
- return VAD_FAILED;
+ return LN_FAILED;
}
}
else
pos++;
if (pos >= LN_MAX_ARGV_LEN)
{
- return VAD_FAILED;
+ return LN_FAILED;
}
}
}
*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 <sil_threshold> <energy_threshold> <min_speech_length> <silence_detect_length> <rule1_min_tailing_silence> <rule2_min_tailing_silence> <rule3_min_tailing_silence>\n");
- return ERRCODE_FAIL;
- }
-
- // 解析参数
- 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 <pcm_path>
-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 <pcm_path>\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 <in.pcm> - 分析已有PCM\n");
- printf("AT^VAD_RECORD_START - 开始录音测试\n");
- printf("AT^VAD_RECORD_STOP - 停止录音测试\n");
- printf("AT^VAD_CLEANUP - 清理 VAD 资源\n");
- printf("AT^VAD_HELP - 显示此帮助\n");
- printf("\n=== 使用示例 ===\n");
- printf("AT^VAD_INIT # 初始化\n");
- printf("AT^VAD_CONFIG=50 5000 120 300 480 960 960 # 配置参数\n");
- printf("AT^VAD_STATUS # 查看状态\n");
- printf("AT^VAD_ANALYZE_PCM=/user/vad_live.pcm # 分析已有PCM\n");
- printf("AT^VAD_RECORD_START # 开始录音测试\n");
- printf("AT^VAD_RECORD_STOP # 停止录音测试\n");
- printf("AT^VAD_CLEANUP # 清理资源\n");
- printf("=====================\n");
-
- 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;
}
//清除设备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;
}
// 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;
}
/* 故事-选择故事: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;
}
/* 故事-随机故事: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 : "<random>");
/* 故事-获取退出哄睡询问: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;
/* 故事-确认结束哄睡模式: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;
/* 设备-获取角色信息: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;
/* 设备-获取绑定用户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;
}
/* 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);
*/
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;
/* 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)
/* 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)
/* AT^LN_WS_SEND=<text> */
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);
/* 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)
/* 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;
/* 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; // 默认值
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) {
/* 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)
/* 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 = "冒险"; // 默认主题
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];
}
}
/* 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 = "用户主动打断"; // 默认原因
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];
}
}
/* 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)
/* 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)
/* 音频播放: 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;
}
/* 音频停止: 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;
}
/* 播放指定文件: 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)
{
/* 暂停播放: 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;
/* 恢复播放: 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;
/* 哄睡模式开始: 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 = "";
/* 哄睡模式结束: 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);
*/
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;
}
*/
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;
}
/* 恢复出厂设置: 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");
*/
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;
}
*/
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;
}
// 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;
*/
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");
/* 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";
/* 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);
/* 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);
/* 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);
/* 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);
/* 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");
/* 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");
/* 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) {
/* 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]) {
*/
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;
*/
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;
}
/* 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;
}
//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;
}
/* 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;
}
/* 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");
/* 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");
}
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"},
--- /dev/null
+#===============================================================================
+# @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
*/
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 å\90\91 MP3 é\98\9få\88\97å\86\99å\85¥æ\95°æ\8d®
+ * @brief å\96\82å\85¥ MP3 æ\95°æ\8d®å\88°æµ\81ç¼\93å\86²å\8cºå¹¶è\87ªå\8a¨è§£ç \81
* @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 获取解码统计信息
*/
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
}
--- /dev/null
+#define MINIMP3_IMPLEMENTATION
+#include "minimp3.h"
+#include "custom_utils.h"
+#include "thread_init.h"
+#include "osal_msgqueue.h"
+#include "cmsis_os2.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+// 定义缓冲区大小 - 优化后使用流式解码
+#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;
+}
+++ /dev/null
-# 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 ===
-```
+++ /dev/null
-#define MINIMP3_IMPLEMENTATION
-#include "minimp3.h"
-#include "custom_utils.h"
-#include "thread_init.h"
-#include "osal_msgqueue.h"
-#include "cmsis_os2.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdint.h>
-#include <errno.h>
-
-// 定义缓冲区大小
-#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;
-}