跳转至

音频示例开发指南

本文档以 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:

音频输入经过 AI 后连接 Sound、AENC、SEA 或 ADP 的处理框图

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

音频数据经过 ADEC、Track 和 Sound 后输出的处理框图

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

多个 Track 经 Sound 混音后输出到多个音频端口的框图

操作流程

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

AT AUDIO 命令从参数解析、场景分发到停止并释放资源的流程

通用处理步骤:

  1. AT 框架接收 AT+AUDIO=<子命令> [参数...]
  2. audio_at_cmd_process() 将等号后的字符串拆分为参数数组。
  3. audio_execute_function()g_audio_func 中查找同名场景函数。
  4. 场景函数解析参数并初始化音频系统、端口和处理模块。
  5. 创建数据线程,持续读取或写入音频帧。
  6. 收到 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 中的内置测试数据 适合第一次验证播放通路
flashnull 从示例配置的固定地址读取数据 仅在已经部署对应数据时使用

API 接口列表

本文档涉及的主要音频接口如下:

接口 说明
uapi_ai_inituapi_ai_deinit 初始化和去初始化 AI 模块
uapi_ai_get_default_attr 获取输入端口默认参数
uapi_ai_openuapi_ai_close 打开和关闭音频输入实例
uapi_ai_startuapi_ai_stop 启动和停止音频采集
uapi_snd_inituapi_snd_deinit 初始化和去初始化 Sound 模块
uapi_snd_openuapi_snd_close 打开和关闭 Sound 实例
uapi_snd_create_trackuapi_snd_destroy_track 创建和销毁 Track
uapi_snd_track_startuapi_snd_track_stop 启动和停止 Track
uapi_adp_acquire_frameuapi_adp_release_frame 获取和释放音频帧
uapi_adp_send_frame 向音频数据通路发送一帧数据

完整实现可参考 /src/application/audio 下对应场景源文件。


快速跑通音频 Demo

功能说明

本节通过三个步骤验证音频示例:先查看场景帮助,再播放内置测试音,最后将麦克风数据录制为 PCM 文件。

验证项 硬件或数据要求 观察结果
查看帮助 串口连接正常 输出 usage 和参数示例
播放内置测试音 板载或外接音频输出设备 扬声器播放测试音
录制 PCM 麦克风和可写文件系统 生成 PCM 文件

音频 Demo 的完整验证顺序如下:

从构建固件到停止音频场景的六步验证流程

编译

完成一站式 CLI 环境配置后执行:

# 构建 diting-community 固件
fbb set-target pack_diting_community
fbb build

构建成功后,使用一站式 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 指令时附加回车换行。

查看场景帮助

以下命令不启动音频数据通路,只输出对应场景的参数说明:

AT+AUDIO=sample_ai
AT+AUDIO=sample_ao
AT+AUDIO=sample_decode
AT+AUDIO=sample_encode

播放内置测试音

发送以下命令播放 16 kHz、16 bit、单声道的内置测试数据:

AT+AUDIO=sample_ao array -6 16000 16 1

参数依次为:

参数 示例值 说明
input array 使用代码中的内置 PCM 数据
volume -6 播放音量,单位 dB
rate 16000 采样率,单位 Hz
bits 16 位宽
channels 1 单声道

停止播放:

AT+AUDIO=sample_ao q

录制 PCM

确认 /mnt 已挂载且可写后,发送以下命令录制 1 MB PCM 数据:

AT+AUDIO=sample_ai 1 /mnt/ai.pcm

提前停止录制:

AT+AUDIO=sample_ai q

录制完成后检查 /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_NAMECOMPONENT_SRCCFG_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_encodesample_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 目录重新构建固件:

fbb set-target pack_diting_community
fbb build

依次完成以下验证:

  1. 从应用任务、服务或 UI 事件调用 sample_xxx_start(),确认音频通路按配置启动。
  2. 传入空指针、非法采样率或不支持的声道数,确认接口拒绝无效配置并返回错误。
  3. 在已运行状态重复调用启动接口,确认不会重复创建线程和音频句柄。
  4. 调用 sample_xxx_stop(),确认场景正常退出。
  5. 多次启动和退出,检查线程、文件和音频句柄是否释放。
  6. 与其他音频场景交替运行,检查资源互斥和异常恢复。

可选的 AT 命令运行配置

产品应用应从任务、服务或 app_run 启动入口直接调用新增场景函数,并自行管理音频对象的创建、启动、停止和释放。只有需要复用现有串口调试入口时,才沿用 AT+AUDIO 指令;此时完成以下配置:

  1. 增加一个只负责解析 AT 参数并调用 sample_xxx_start()sample_xxx_stop() 的适配函数。
  2. sample_audio_api.h 声明该适配函数。
  3. g_audio_func 中增加 SAMPLE_ITEM(sample_xxx);按功能宏裁剪场景时,在 sample_audio_weak.c 增加对应弱实现。
  4. 确保场景名与串口中使用的子命令完全一致。

串口回归时可依次检查帮助、正常参数、非法参数、重复启动和退出:

AT+AUDIO=sample_xxx
AT+AUDIO=sample_xxx start
AT+AUDIO=sample_xxx q

上述分发表只负责把调试命令转交给场景函数,不应成为音频业务组件之间的调用接口。

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 分支和错误路径中的资源释放顺序