音频示例开发指南
本文档以 src/application/audio 中的音频示例为例,带你在 HiDiTing V100 开发板上快速完成音频命令调用、内置测试音播放和 PCM 录制,并了解如何基于现有场景开发自己的音频功能。
音频驱动背景知识
音频工作原理
HiDiTing V100 音频框架将输入、输出、编解码和算法处理拆分为不同模块。示例通过音频句柄连接这些模块,组成录音、播放、回环、编解码和语音算法链路。
| 模块 | 作用 | 典型场景 |
|---|---|---|
| AI(Audio Input) | 从麦克风、ADC 或 I2S 等输入端口采集 PCM 数据 | 录音、语音识别、通话上行 |
| ADP(Audio Data Path) | 在音频模块之间传递帧数据 | AI 数据读取、AO 数据写入、模块连接 |
| AENC | 将 PCM 数据编码为 AAC、MP3 等码流 | 录音压缩、网络传输 |
| ADEC | 将音频码流解码为 PCM 数据 | 音乐播放、提示音播放 |
| TRACK | 接收一路 PCM 数据并送入 Sound | 单路播放、音量控制 |
| SOUND | 对多路 Track 进行混音、重采样并选择输出端口 | 多音轨播放、系统声音混音 |
| SEA/AEF | 执行降噪、回声消除、均衡等算法处理 | 通话、语音识别、音效处理 |
音频输入先进入 AI,再根据场景送往 Sound、编码器、SEA 或 ADP:

播放场景通常由应用提供文件或码流,经 ADEC、Track 和 Sound 后输出到 ADAC、I2S 或 Cast:

多个 Track 可以同时连接到 Sound,由 Sound 完成混音、重采样和输出分发:

操作流程
所有音频子命令共用统一的 AT 入口,整体处理流程如下:

通用处理步骤:
- AT 框架接收
AT+AUDIO=<子命令> [参数...]。 audio_at_cmd_process()将等号后的字符串拆分为参数数组。audio_execute_function()在g_audio_func中查找同名场景函数。- 场景函数解析参数并初始化音频系统、端口和处理模块。
- 创建数据线程,持续读取或写入音频帧。
- 收到
q后停止线程,并关闭文件、端口和音频句柄。
各场景的主要流程:
| 场景 | 处理流程 |
|---|---|
| PCM 录音 | 初始化系统 → 打开 AI 和 ADP → 启动 AI → 获取音频帧 → 写入文件 → 停止并释放 |
| PCM 播放 | 初始化系统 → 打开 Sound、Track 和 ADP → 读取 PCM → 发送音频帧 → 停止并释放 |
| 音频编码 | 打开输入 → 创建 AENC → 设置编码参数 → 发送 PCM → 获取码流 → 写入文件 |
| 音频解码 | 打开码流 → 创建 ADEC → 设置解码参数 → 发送码流 → 连接 Track → 输出 PCM |
| 音频算法 | 创建输入输出通路 → 加载算法或参数 → 发送音频帧 → 获取处理结果 → 释放算法资源 |
音频场景之间可能共用 AI、Sound、DSP 和文件系统资源。启动新场景时,示例框架会先退出仍在运行的其他场景。
通道与参数说明
AT+AUDIO 使用一个字符串参数承载子命令及其参数,子命令与参数之间使用空格分隔。
| 命令 | 参数 | 说明 |
|---|---|---|
AT+AUDIO=sample_ai |
无 | 显示 PCM 录音用法 |
AT+AUDIO=sample_ai <size> <file> |
size:文件大小,单位 MB;file:输出文件 |
采集 PCM 并写入文件 |
AT+AUDIO=sample_ao |
无 | 显示 PCM 播放用法 |
AT+AUDIO=sample_ao <input> <volume> <rate> <bits> <channels> |
输入源、音量、采样率、位宽、声道数 | 播放一条 PCM 音轨 |
AT+AUDIO=sample_decode |
无 | 显示解码用法 |
AT+AUDIO=sample_decode -i <file> [options] |
输入文件及可选参数 | 解码并播放音频文件 |
AT+AUDIO=sample_encode |
无 | 显示编码用法 |
AT+AUDIO=sample_encode <pcm> <codec> <rate> <bits> <channels> |
PCM 文件、编码格式和 PCM 参数 | 将 PCM 数据编码为音频码流 |
AT+AUDIO=sample_proc <module> |
音频模块名 | 查看模块运行信息 |
AT+AUDIO=<subcommand> q |
无 | 停止指定场景 |
sample_ao 的输入源支持以下形式:
| 输入源 | 说明 | 使用建议 |
|---|---|---|
| 文件路径 | 从文件系统读取 PCM 数据 | 参数必须与文件格式一致 |
array |
使用 sample_data.c 中的内置测试数据 | 适合第一次验证播放通路 |
flash 或 null |
从示例配置的固定地址读取数据 | 仅在已经部署对应数据时使用 |
API 接口列表
本文档涉及的主要音频接口如下:
| 接口 | 说明 |
|---|---|
| uapi_ai_init、uapi_ai_deinit | 初始化和去初始化 AI 模块 |
| uapi_ai_get_default_attr | 获取输入端口默认参数 |
| uapi_ai_open、uapi_ai_close | 打开和关闭音频输入实例 |
| uapi_ai_start、uapi_ai_stop | 启动和停止音频采集 |
| uapi_snd_init、uapi_snd_deinit | 初始化和去初始化 Sound 模块 |
| uapi_snd_open、uapi_snd_close | 打开和关闭 Sound 实例 |
| uapi_snd_create_track、uapi_snd_destroy_track | 创建和销毁 Track |
| uapi_snd_track_start、uapi_snd_track_stop | 启动和停止 Track |
| uapi_adp_acquire_frame、uapi_adp_release_frame | 获取和释放音频帧 |
| uapi_adp_send_frame | 向音频数据通路发送一帧数据 |
完整实现可参考 /src/application/audio 下对应场景源文件。
快速跑通音频 Demo
功能说明
本节通过三个步骤验证音频示例:先查看场景帮助,再播放内置测试音,最后将麦克风数据录制为 PCM 文件。
| 验证项 | 硬件或数据要求 | 观察结果 |
|---|---|---|
| 查看帮助 | 串口连接正常 | 输出 usage 和参数示例 |
| 播放内置测试音 | 板载或外接音频输出设备 | 扬声器播放测试音 |
| 录制 PCM | 麦克风和可写文件系统 | 生成 PCM 文件 |
音频 Demo 的完整验证顺序如下:

编译
完成一站式 CLI 环境配置后执行:
构建成功后,使用一站式 CLI 烧写固件并打开 UART2 串口监视器。以下为 Windows USB DFU 示例;将 COM3 替换为实际日志串口,其他平台和串口烧写参数参见一站式 CLI 开发环境使用指南。
fbb flash -f "$env:FBB_SDK_DIR\output\3322\fwpkg\diting-community.fwpkg" --chip 3322 -d --timeout 180
fbb monitor --port COM3 --baud 750000
构建完成后,使用所选开发环境的烧录功能写入完整固件包,并复位开发板。
使用方式
选择开发环境
| 开发环境 | 适用场景 | 使用指南 |
|---|---|---|
| 一站式 CLI(推荐) | 快速完成构建、烧录和串口验证 | 一站式 CLI 开发环境使用指南 |
| HiSpark Studio for VS Code | 图形化编辑、编译、烧录和调试 | HiSpark Studio for VS Code 开发环境使用指南 |
| WSL 与 Docker | 在一致的 Linux 容器环境中构建 | WSL 与 Docker 环境使用指南 |
使用任一环境完成固件构建和烧录后,通过串口工具发送音频指令。串口参数使用 115200-8-N-1,发送 AT 指令时附加回车换行。
查看场景帮助
以下命令不启动音频数据通路,只输出对应场景的参数说明:
播放内置测试音
发送以下命令播放 16 kHz、16 bit、单声道的内置测试数据:
参数依次为:
| 参数 | 示例值 | 说明 |
|---|---|---|
input |
array |
使用代码中的内置 PCM 数据 |
volume |
-6 |
播放音量,单位 dB |
rate |
16000 |
采样率,单位 Hz |
bits |
16 |
位宽 |
channels |
1 |
单声道 |
停止播放:
录制 PCM
确认 /mnt 已挂载且可写后,发送以下命令录制 1 MB PCM 数据:
提前停止录制:
录制完成后检查 /mnt/ai.pcm 是否存在,并核对文件大小是否符合预期。
预期结果
查看帮助
串口应输出以 usage 开头的命令说明。例如 sample_ao 会列出输入文件、音量、采样率、位宽和声道参数。
播放内置测试音
- 扬声器或外接音频输出设备播放测试音。
- 串口输出 Sound、Track 或 PCM 格式信息。
- 发送
AT+AUDIO=sample_ao q后声音停止,场景退出。
录制 PCM
- 文件系统中生成
/mnt/ai.pcm。 - 文件大小随采集过程增加,达到指定大小后停止。
- 发送
AT+AUDIO=sample_ai q可以提前结束录音。
如果串口显示命令执行完成但没有声音或录音数据,应继续检查板级端口、功放、麦克风、文件系统和 PCM 参数,不能只根据命令返回判断音频通路是否正常。
文件结构与代码走读
文件结构
src/application/audio/
├── CMakeLists.txt # 音频示例构建配置
├── sample_audio.c # 子命令表和统一分发入口
├── sample_audio.h # 统一入口声明
├── sample_audio_api.h # 场景函数声明
├── sample_audio_utils.c # 端口、格式和场景辅助函数
├── sample_audio_fops.c # 文件操作适配
├── sample_audio_weak.c # 可选场景的弱实现
├── sample_ai/ # 音频采集、编码和回环
├── sample_ao/ # PCM 播放和 AEF
├── sample_encode/ # 音频编码
├── sample_decode/ # 音频解码
├── sample_asr/ # ASR 和 VAD
├── sample_tts/ # TTS
├── sample_effect/ # 音效处理
├── sample_sea/ # SEA 算法示例
├── sample_phone/ # 通话场景
├── sample_lpwk/ # 低功耗音频场景
└── tools/ # proc、dump、RMS 和测试数据
各文件职责
| 文件或目录 | 职责 | 关键内容 |
|---|---|---|
CMakeLists.txt |
收集源文件、头文件和音频配置 | COMPONENT_NAME、COMPONENT_SRC、CFG_SAP_* |
sample_audio.c |
维护命令表并分发场景 | g_audio_func[]、audio_execute_function() |
sample_audio_api.h |
声明各场景入口 | sample_ai()、sample_ao()、sample_decode() 等 |
sample_audio_utils.c |
转换端口名、格式和音频场景 | sample_audio_get_ai_port()、sample_audio_get_ao_port() |
sample_audio_fops.c |
封装示例文件读写 | 输入输出流的打开、读取和写入 |
sample_audio_weak.c |
为可裁剪场景提供弱符号 | define_sample_audio_weak_function() |
sample_ai/ |
AI 创建、启动、采集和释放 | sample_ai()、sample_ai_ao() |
sample_ao/ |
Sound、Track 和 PCM 播放 | sample_ao()、sample_aef() |
sample_encode/、sample_decode/ |
编码器、解码器和数据线程 | sample_encode()、sample_decode() |
tools/ |
运行信息和测试数据 | sample_proc()、sample_dump()、sample_rms() |
代码走读
CMakeLists.txt 解析
音频示例组件名为 audio_sample。通用场景直接加入 COMPONENT_SRC,依赖算法或产品能力的场景通过 CFG_SAP_* 条件加入:
set(COMPONENT_NAME "audio_sample")
set(COMPONENT_SRC
${CMAKE_CURRENT_SOURCE_DIR}/sample_ai/sample_ai.c
${CMAKE_CURRENT_SOURCE_DIR}/sample_ao/sample_ao.c
${CMAKE_CURRENT_SOURCE_DIR}/sample_decode/sample_adec.c
${CMAKE_CURRENT_SOURCE_DIR}/sample_decode/sample_decode.c
${CMAKE_CURRENT_SOURCE_DIR}/sample_encode/sample_encode.c
${CMAKE_CURRENT_SOURCE_DIR}/sample_audio.c
${CMAKE_CURRENT_SOURCE_DIR}/sample_audio_utils.c
)
if("${CFG_SAP_EFFECT_ADJUSTMENT_SUPPORT}" STREQUAL "y")
set(COMPONENT_SRC ${COMPONENT_SRC}
${CMAKE_CURRENT_SOURCE_DIR}/sample_effect/sample_effect.c
)
endif()
新增场景时,应同时补充源文件、头文件目录和必要的条件开关。
sample_audio_api.h 解析
sample_audio_api.h 统一声明可由命令分发器调用的场景函数。场景入口使用相同的函数签名:
td_s32 sample_ai(td_s32 argc, td_char *argv[]);
td_s32 sample_ao(td_s32 argc, td_char *argv[]);
td_s32 sample_encode(td_s32 argc, td_char *argv[]);
td_s32 sample_decode(td_s32 argc, td_char *argv[]);
td_s32 sample_proc(td_s32 argc, td_char *argv[]);
统一签名使 g_audio_func 可以通过函数指针分发不同命令。
命令注册函数
at_audio_cmd_parse_table 注册 AUDIO 指令,并将等号后的字符串交给 audio_at_cmd_process():
const at_cmd_entry_t at_audio_cmd_parse_table[] = {
{
"AUDIO",
0x2401,
0,
audio_at_cmd_syntax,
NULL,
(at_set_func_t)audio_at_cmd_process,
NULL,
NULL,
},
};
audio_para_to_args() 使用空格拆分子命令和参数,因此发送命令时不能使用逗号分隔音频参数。
audio_execute_function —— 分发音频场景
g_audio_func 将命令名称映射到对应场景函数:
static functions g_audio_func[] = {
SAMPLE_ITEM(sample_set_vol),
SAMPLE_ITEM(sample_set_config),
SAMPLE_ITEM(sample_ai),
SAMPLE_ITEM(sample_ao),
SAMPLE_ITEM(sample_proc),
SAMPLE_ITEM(sample_encode),
SAMPLE_ITEM(sample_decode),
SAMPLE_ITEM(sample_asr),
SAMPLE_ITEM(sample_tts),
};
audio_execute_function() 遍历该数组并执行同名函数。除寄存器操作和 sample_proc 外,分发器还会处理 DSP 工作模式和场景退出顺序。
sample_ai —— 获取并处理音频帧
sample_ai_thread() 从 ADP 获取音频帧,交给录音、回环或其他处理函数,处理完成后释放音频帧:
while (inst->task_enable) {
ret = uapi_adp_acquire_frame(inst->h_adp, &frame);
if (ret != EXT_SUCCESS) {
sap_msleep(THREAD_SLEEP_10MS);
continue;
}
if (inst->sample_ai_process != NULL) {
inst->sample_ai_process(inst, &frame);
}
ret = uapi_adp_release_frame(inst->h_adp, &frame);
if (ret != EXT_SUCCESS) {
sap_err_log_fun(uapi_adp_release_frame, ret);
}
}
录音场景中的 sample_ai_process 指向文件写入函数;回环场景则将音频帧发送到播放通路。
sample_ao —— 读取并发送 PCM 帧
sample_ao_track_thread() 从文件、Flash 或内置数组读取 PCM,再向 ADP 发送帧数据:
while (inst->task_active) {
uapi_adp_query_free(inst->h_adp, &len);
if (len < FILE_READ_LEN_MIN) {
sap_msleep(THREAD_SLEEP_5MS);
continue;
}
frame.bits_bytes = circ_buf_min(len, inst->frame_size);
len = inst->read_frame(inst, &frame);
if (len == 0) {
continue;
}
frame.bits_bytes = len;
while (inst->task_active) {
ret = uapi_adp_send_frame(inst->h_adp, &frame);
if (ret != EXT_SUCCESS) {
sap_msleep(THREAD_SLEEP_10MS);
continue;
}
break;
}
}
sample_ao_track_open_file() 根据输入参数选择 sample_ao_track_read_file()、sample_ao_track_read_flash() 或 sample_ao_track_read_array()。
CFG_SAP 功能宏说明
部分音频场景通过 CFG_SAP_* 控制源文件、头文件或算法依赖。例如:
| 功能宏 | 对应能力 |
|---|---|
CFG_SAP_ASR_SUPPORT |
ASR 场景 |
CFG_SAP_TTS_SUPPORT |
TTS 场景 |
CFG_SAP_EFFECT_ADJUSTMENT_SUPPORT |
音效调节场景 |
CFG_SAP_WAKEUP_SUPPORT |
语音唤醒场景 |
CFG_SAP_DPM_SUPPORT |
DPM 场景 |
CFG_SAP_LIVE_MIC_SUPPORT |
实时麦克风场景 |
新场景依赖算法或专用硬件时,应复用相应功能宏,避免在不具备依赖的配置中直接引用实现。
基于音频 Demo 开发自己的应用
以新增 sample_xxx 音频场景为例,建议选择最接近的现有场景复制和修改。播放类功能参考 sample_ao,录音类功能参考 sample_ai,编解码功能参考 sample_encode 或 sample_decode。产品代码应先提供可由任务、服务或 UI 直接调用的启动和停止接口,再按需增加串口调试适配层。
代码清单
src/application/audio/
├── sample_xxx/
│ ├── sample_xxx.c # 业务初始化、运行、停止和资源释放
│ └── sample_xxx.h # 业务配置、状态及公开入口
├── sample_audio_api.h # 可选:声明 AT 场景适配函数
├── sample_audio.c # 可选:增加 AT 子命令映射
├── sample_audio_weak.c # 可选:按功能宏增加弱实现
└── CMakeLists.txt # 增加源文件和头文件目录
CMakeLists.txt 修改示例
在音频组件的 CMakeLists.txt 中增加源文件和头文件目录:
set(COMPONENT_SRC ${COMPONENT_SRC}
${CMAKE_CURRENT_SOURCE_DIR}/sample_xxx/sample_xxx.c
)
set(COMPONENT_INC ${COMPONENT_INC}
${CMAKE_CURRENT_SOURCE_DIR}/sample_xxx
)
如果场景只在特定配置下使用,应增加功能宏:
if("${CFG_SAP_XXX_SUPPORT}" STREQUAL "y")
set(COMPONENT_SRC ${COMPONENT_SRC}
${CMAKE_CURRENT_SOURCE_DIR}/sample_xxx/sample_xxx.c
)
endif()
关键代码片段
sample_xxx.h —— 业务配置和公开入口
typedef struct {
td_u32 sample_rate;
td_u32 channels;
} sample_xxx_config;
td_s32 sample_xxx_start(const sample_xxx_config *config);
td_s32 sample_xxx_stop(td_void);
sample_xxx.c —— 场景启动和退出处理
#include "sample_xxx.h"
static td_bool g_sample_xxx_running = TD_FALSE;
td_s32 sample_xxx_start(const sample_xxx_config *config)
{
if (config == TD_NULL) {
sap_printf("sample_xxx config is null\n");
return EXT_FAILURE;
}
if (g_sample_xxx_running == TD_TRUE) {
sap_printf("sample_xxx is already running\n");
return EXT_FAILURE;
}
/* 1. 校验采样率、声道数和目标端口。 */
/* 2. 初始化音频系统并打开所需句柄。 */
/* 3. 创建数据处理线程并启动音频通路。 */
/* 4. 任一步失败时按已完成步骤的逆序释放资源。 */
g_sample_xxx_running = TD_TRUE;
return EXT_SUCCESS;
}
td_s32 sample_xxx_stop(td_void)
{
if (g_sample_xxx_running == TD_FALSE) {
return EXT_SUCCESS;
}
/* 按初始化的逆序停止线程并释放音频资源。 */
g_sample_xxx_running = TD_FALSE;
return EXT_SUCCESS;
}
测试验证
在 src 目录重新构建固件:
依次完成以下验证:
- 从应用任务、服务或 UI 事件调用
sample_xxx_start(),确认音频通路按配置启动。 - 传入空指针、非法采样率或不支持的声道数,确认接口拒绝无效配置并返回错误。
- 在已运行状态重复调用启动接口,确认不会重复创建线程和音频句柄。
- 调用
sample_xxx_stop(),确认场景正常退出。 - 多次启动和退出,检查线程、文件和音频句柄是否释放。
- 与其他音频场景交替运行,检查资源互斥和异常恢复。
可选的 AT 命令运行配置
产品应用应从任务、服务或 app_run 启动入口直接调用新增场景函数,并自行管理音频对象的创建、启动、停止和释放。只有需要复用现有串口调试入口时,才沿用 AT+AUDIO 指令;此时完成以下配置:
- 增加一个只负责解析 AT 参数并调用
sample_xxx_start()、sample_xxx_stop()的适配函数。 - 在
sample_audio_api.h声明该适配函数。 - 在
g_audio_func中增加SAMPLE_ITEM(sample_xxx);按功能宏裁剪场景时,在sample_audio_weak.c增加对应弱实现。 - 确保场景名与串口中使用的子命令完全一致。
串口回归时可依次检查帮助、正常参数、非法参数、重复启动和退出:
上述分发表只负责把调试命令转交给场景函数,不应成为音频业务组件之间的调用接口。
AT 参数总长度不要超过音频 AT 指令允许的字符串长度;适配函数必须实现退出分支,使分发器能够主动结束当前场景。
注意事项
音频资源:
- AI、AO、Sound、Track、ADP、编解码器和算法句柄使用后必须释放。
- 启动新场景前确认旧场景已经退出,避免端口和 DSP 资源冲突。
- 初始化过程中任一步骤失败,都应释放此前已经创建的资源。
- 线程退出后再释放线程使用的缓冲区和音频句柄,避免并发访问已释放内存。
PCM 参数:
- 输入文件的采样率、位宽和声道数必须与命令参数一致。
sample_ao array使用 16 kHz、16 bit、单声道数据。- PCM 参数不匹配会导致播放速度、音调或声道异常。
- 音量应从较低值开始调整,避免突然输出过大音量。
文件读写:
- 播放前确认输入文件存在且路径可读。
- 录音前确认目标目录已经挂载、空间充足且具有写权限。
- 使用
q正常结束录音,确保缓存数据写入并关闭文件。 - 文件读写不应长期阻塞音频线程,产品代码可增加独立缓存和写盘线程。
算法场景:
- ASR、TTS、AEF、SEA 和唤醒等场景可能依赖模型或参数文件。
- 算法输入格式必须满足对应场景源文件中的采样率、位宽和声道要求。
- 调整算法或 DSP 配置后,应同时验证内存、实时性和功耗。
AT 命令:
- 主命令为大写
AUDIO,子命令名称与源码中的g_audio_func一致。 - 子命令参数使用空格分隔,不使用逗号分隔。
- 停止命令中的
q为小写。
常见错误
| 错误现象 | 原因 | 解决方法 |
|---|---|---|
AT+AUDIO 无法识别 |
音频 AT 指令未注册 | 检查 audio_at 组件和 AT 初始化日志 |
只打印 usage |
参数数量不足或格式错误 | 根据帮助信息补齐参数,参数之间使用空格分隔 |
open file error |
文件不存在、路径不可读或文件系统未挂载 | 检查文件路径和挂载状态;播放验证可先使用 array |
| 播放速度或音调异常 | PCM 参数与数据格式不一致 | 核对采样率、位宽和声道数 |
| 串口有执行日志但没有声音 | 功放、输出端口、音量或板级连接异常 | 检查音频输出硬件和 AO 配置,使用 sample_proc 查看通路 |
| 录音文件为空 | 麦克风端口未打开、路径不可写或采集提前失败 | 检查 AI 端口、文件权限和采集日志 |
| 录音出现丢帧 | 文件写入过慢或处理线程阻塞 | 减少同步日志,增加缓存,检查存储介质性能 |
| 第二个场景启动后第一个退出 | 示例默认对音频场景进行互斥处理 | 先停止当前场景;并发业务需设计资源仲裁机制 |
| 算法子命令立即失败 | 模型、参数文件或算法输入不满足要求 | 检查场景依赖、输入格式和相关功能配置 |
| 退出后再次运行失败 | 线程或音频句柄没有完全释放 | 检查 q 分支和错误路径中的资源释放顺序 |