6.3.3 audio
1. 模块概述
audio 组件位于 components/multimedia/audio,基于 PortAudio 封装音频采集、播放、全双工 I/O、WAV 文件读写和重采样能力。语音模块的 ASR、TTS、VAD、omni_agent 都依赖它完成麦克风输入、扬声器输出和采样率转换。
功能特性:
| 类别 | 支持 |
|---|---|
| 音频 I/O | 录音、播放、输入/输出设备枚举、全双工流。 |
| 数据格式 | PCM16 little-endian、WAV 文件。 |
| 重采样 | 线性重采样,支持可选 libsamplerate,包含 RISC-V Vector 优化路径。 |
| API | C++ audio_base.hpp、audio_duplex.hpp、audio_resampler.hpp;Python 包 spacemit_audio。 |
| 示例 | C++ audio_demo,Python python/examples/audio_demo.py。 |
典型数据链路:
麦克风/声卡 -> AudioCapture -> PCM/WAV/Resampler -> 业务模块
业务模块/WAV -> Resampler -> AudioPlayer -> 扬声器/声卡
主要目录:
| 路径 | 说明 |
|---|---|
components/multimedia/audio/include/audio_base.hpp | AudioCapture、AudioPlayer 和全局配置。 |
components/multimedia/audio/include/audio_resampler.hpp | Resampler C++/C API。 |
components/multimedia/audio/include/audio_duplex.hpp | 全双工音频接口。 |
components/multimedia/audio/examples/audio_demo.cpp | C++ 示例程序。 |
components/multimedia/audio/python/ | Python 包 spacemit_audio 和示例。 |
2. 环境准备
前置条件
SDK 源码获取和基础编译环境配置统一参考 2.3-构建编译。完成 SDK 初始化后,回到本文继续执行“构建编译”。
后续命令默认在 spacemit_robot SDK 根目录执行。
构建编译
系统缺少依赖时先安装:
sudo apt install portaudio19-dev libsndfile1-dev
sudo apt install libsamplerate0-dev pybind11-dev
随语音链路构建时使用 target/k3-com260-omni-agent.json 目标配置。在 SDK 根目录构建 audio:
source build/envsetup.sh
lunch k3-com260-omni-agent
cd components/multimedia/audio
mm
构建完成后,audio_demo 会安装到 output/staging/bin。需要构建 Python wheel 时可在 SDK 根目录运行:
m -py
注意:m -py / mm -py 是构建 wheel,使用系统 Python 环境执行;不要在 ~/.comm-env 中执行。~/.comm-env 只用于后面的 pip install 和运行 Python 示例。
运行 Python 示例前,先安装虚拟环境依赖,并创建、激活 ~/.comm-env:
sudo apt install python3-venv python-is-python3 python3-pip
python3 -m venv ~/.comm-env
source ~/.comm-env/bin/activate
然后二选一安装 Python 包。方式一,直接安装发布包:
python -m pip install spacemit-audio \
--index-url https://git.spacemit.com/api/v4/projects/33/packages/pypi/simple
方式二,回到 SDK 根目录安装本地构建出的 wheel:
python -m pip install output/wheels/components_multimedia_audio/spacemit_audio-*.whl \
--index-url https://git.spacemit.com/api/v4/projects/33/packages/pypi/simple
安装后可 import spacemit_audio。
3. 示例使用
3.1 列出设备
audio_demo list
输出会分为 Input Devices 和 Output Devices。后续录音、播放命令中的 -d 或 --device 使用这里的设备索引。
3.2 录音
audio_demo record test.wav --duration 5 --rate 16000 --channels 2 --device -1
预期日志包含:
Recording 5s to test.wav...
Config: 16000Hz, 2ch, device=-1
Opened: 16000Hz, 2 channels
Saved ... to test.wav
test.wav 可直接作为 ASR、声纹或 VAD 文件示例的输入。
3.3 播放
audio_demo play ./test.wav -c 2 -d 1
当输入 WAV 与播放设备采样率或声道数不同,demo 会自动转换。K3 实测日志显示 16kHz 单声道输入会转换为 48kHz 双声道后播放:
Converting channels 1ch -> 2ch...
Resampling 16000Hz -> 48000Hz...
Opened: 48000Hz, 2 channels, write mode
Done
3.4 Python 示例
安装 spacemit-audio 后运行:
cd components/multimedia/audio/python/examples
python audio_demo.py record test.wav --duration 5 --rate 16000 --channels 2 --device -1
python audio_demo.py play test.wav --device 1
Python 示例同样支持设备枚举、录音、播放和重采样。
4. 应用开发
本章面向应用开发者,说明如何在自己的 C++ / Python 应用中集成 audio 组件。完整接口以 components/multimedia/audio/include/{audio_base,audio_duplex,audio_resampler}.hpp 为准;本节只介绍常用公开接口和典型调用方式。audio 组件的 C++ 命名空间是 SpacemitAudio::(区别于其它 model_zoo 组件的 SpacemiT::);Python 入口是 spacemit_audio 包。
4.1 接口说明
audio 组件的核心类型分布在三个对外头文件:audio_base.hpp(采集 / 播放 / 全局配置)、audio_duplex.hpp(全双工)、audio_resampler.hpp(重采样,无独立命名空间)。所有回调由 PortAudio 内部线程触发,回调里不要做耗时推理,应把数据丢入队列由业务线程处理。
4.1.1 常用数据结构
| 类型 | 说明 |
|---|---|
SpacemitAudio::AudioConfig | 全局默认配置:sample_rate=16000、channels=1、chunk_size=3200(采集每次回调字节数)、capture_device=-1、player_device=-1(-1 = 系统默认)。 |
SpacemitAudio::AudioCapture::Callback | 录音回调签名 void(const uint8_t* data, size_t size);data 是 PCM16 little-endian 字节流。 |
SpacemitAudio::AudioDuplex::Callback | 全双工回调签名 void(const float* input, float* output, size_t frames, int channels);同步收发 PCM float [-1.0, 1.0],每次 frames * channels 个样本。 |
Resampler::Config | 重采样配置:input_sample_rate、output_sample_rate、channels、method(LINEAR_UPSAMPLE / LINEAR_DOWNSAMPLE / SRC_SINC_* 等)。 |
ResampleMethod | 重采样方法枚举:LINEAR_* 永远可用;SRC_* 系列需编译时启用 USE_LIBSAMPLERATE,否则自动 fallback 到线性。 |
4.1.2 全局配置
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
Init(AudioConfig) / Init(int...) | 设置全局默认配置;AudioCapture / AudioPlayer 之后用 -1 传值时会取这里的值。 | AudioConfig 或具体字段;任意字段为 -1 表示保留当前值。 | 无。 |
GetConfig() | 返回当前全局配置快照。 | 无。 | AudioConfig。 |
4.1.3 采集与播放
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
AudioCapture(device_index=-1) | 构造采集器;-1 取系统默认输入。 | device_index:设备索引。 | 实例。 |
AudioCapture::SetCallback(cb) | 注册回调,必须在 Start() 之前调用。 | cb:std::function<void(const uint8_t*, size_t)>。 | 无。 |
AudioCapture::Start(sample_rate=-1, channels=-1, chunk_size=-1) | 启动采集;任意参数为 -1 时取全局配置的值。 | 各音频参数。 | bool。 |
AudioCapture::Stop() / Close() / IsRunning() | 停止 / 关闭设备 / 状态查询。 | 无。 | 无 / bool。 |
AudioCapture::ListDevices() | 静态方法,列举可用输入设备。 | 无。 | vector<pair<int, string>>,元素为 (索引, 设备名)。 |
AudioPlayer(device_index=-1) | 构造播放器。 | device_index:设备索引。 | 实例。 |
AudioPlayer::Start(sample_rate=-1, channels=-1) | 启动播放流。 | 各音频参数。 | bool。 |
AudioPlayer::Write(vector<uint8_t>) / Write(uint8_t*, size_t) | 写入 PCM16 字节流。 | data:PCM16 LE 字节。 | bool。 |
AudioPlayer::PlayFile(file_path) | 阻塞地播放整个 WAV 文件,内部完成读取、解码、写入;返回时已播完。 | file_path:WAV 路径。 | bool。 |
AudioPlayer::Stop() / Close() / IsRunning() / ListDevices() | 同采集对应接口。 | 无。 | 同上。 |
4.1.4 全双工
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
AudioDuplex(input_device=-1, output_device=-1) | 构造全双工对象,可独立指定输入 / 输出设备。 | 设备索引。 | 实例。 |
AudioDuplex::SetCallback(cb) | 注册同步回调,必须在 Start() 之前。 | cb:四参回调(input PCM float、output buffer、frames、channels)。 | 无。 |
AudioDuplex::Start(sample_rate=48000, channels=1, frames_per_buffer=480) | 启动全双工;默认 48kHz(K3 声卡硬限制),frames_per_buffer=480 即 10ms@48kHz。 | 音频参数。 | bool。 |
AudioDuplex::Stop() / Close() / IsRunning() | 状态控制与查询。 | 无。 | 无 / bool。 |
AudioDuplex::GetSampleRate() / GetChannels() / GetInputDevice() / GetOutputDevice() | 配置查询。 | 无。 | 各自类型。 |
AudioDuplex::ListInputDevices() / ListOutputDevices() | 静态方法分别列输入 / 输出设备。 | 无。 | 同 AudioCapture::ListDevices()。 |
4.1.5 重采样
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
Resampler(Config) | 构造重采样器。 | Config。 | 实例。 |
Resampler::initialize() | 初始化内部缓冲与方法;Start 前必须调用。 | 无。 | bool。 |
Resampler::process(vector<float>) / process(float*, num_samples) | 一次性重采样;适合离线音频块。 | float PCM。 | vector<float>。 |
Resampler::processStreaming(vector<float>, end_of_input=false) | 流式重采样;持续送入分块,最后一块设 end_of_input=true 把内部缓冲冲出来。 | float PCM;是否末块。 | vector<float>。 |
Resampler::reset() | 清空内部状态,复用实例做下一段流式音频。 | 无。 | 无。 |
Resampler::getRatio() / isUpsampling() / isDownsampling() / getConfig() | 工具查询。 | 无。 | 各自类型。 |
4.2 C++ 调用示例
audio 组件编译后会把上述头文件和 libspacemit_audio.so 安装到 output/staging。下游组件链接 audio 库的 CMake 写法:
add_executable(my_app main.cpp)
find_library(AUDIO_LIB NAMES spacemit_audio
PATHS ${CMAKE_INSTALL_PREFIX}/lib NO_DEFAULT_PATH)
find_path(AUDIO_INCLUDE_DIR NAMES audio_base.hpp
PATHS ${CMAKE_INSTALL_PREFIX}/include NO_DEFAULT_PATH)
if(NOT AUDIO_LIB OR NOT AUDIO_INCLUDE_DIR)
message(FATAL_ERROR "spacemit_audio not found. Build components/multimedia/audio first.")
endif()
target_include_directories(my_app PRIVATE ${AUDIO_INCLUDE_DIR})
target_link_libraries(my_app PRIVATE ${AUDIO_LIB})
包依赖建议在 package.xml 中声明 <depend>audio</depend>。
4.2.1 设备枚举与选择
适用场景:列出系统可用音频设备,让用户选择 / 配置文件指定 / CLI 工具的 -l 参数。
#include <iostream>
#include "audio_base.hpp"
int main() {
std::cout << "输入设备:" << std::endl;
for (const auto& [idx, name] : SpacemitAudio::AudioCapture::ListDevices()) {
std::cout << " [" << idx << "] " << name << std::endl;
}
std::cout << "输出设备:" << std::endl;
for (const auto& [idx, name] : SpacemitAudio::AudioPlayer::ListDevices()) {
std::cout << " [" << idx << "] " << name << std::endl;
}
return 0;
}
4.2.2 录音回调
适用场景:从麦克风持续采集,把 PCM 数据丢给上游处理(VAD、ASR、网络发送等)。
调用步骤:
- 构造
AudioCapture(-1)或指定设备索引。 SetCallback(...)注册收数据的回调;回调里只做轻量动作(拷贝、入队),不要做推理。Start(sample_rate, channels, chunk_size)启动;chunk_size是字节数,例如 16kHz mono PCM16 下 3200 字节 = 100ms。- 退出前
Stop()+Close()。
#include <atomic>
#include <chrono>
#include <fstream>
#include <thread>
#include <vector>
#include "audio_base.hpp"
int main() {
std::vector<uint8_t> buffer;
std::atomic<bool> running{true};
SpacemitAudio::AudioCapture capture(-1);
capture.SetCallback([&](const uint8_t* data, size_t size) {
buffer.insert(buffer.end(), data, data + size); // 真实场景请加锁或用 SPSC 队列
});
if (!capture.Start(16000, 1, 3200)) return 1;
std::this_thread::sleep_for(std::chrono::seconds(5));
capture.Stop();
capture.Close();
// 5 秒 16kHz mono PCM16 = 160000 字节
std::ofstream out("recording.pcm", std::ios::binary);
out.write(reinterpret_cast<char*>(buffer.data()), buffer.size());
return 0;
}
完整版含 WAV 头封装、设备列表、命令行参数,见 components/multimedia/audio/examples/audio_demo.cpp。
4.2.3 播放(PCM 与 WAV 双场景)
适用场景:(a) 已有 PCM bytes(例如 TTS 合成结果)直接送扬声器;(b) 直接播放 WAV 文件。
#include "audio_base.hpp"
void PlayPcm(const std::vector<uint8_t>& pcm16, int sample_rate) {
SpacemitAudio::AudioPlayer player(-1);
player.Start(sample_rate, 1);
player.Write(pcm16);
player.Stop();
player.Close();
}
void PlayWav(const std::string& path) {
SpacemitAudio::AudioPlayer player(-1);
player.PlayFile(path); // 阻塞,内部完成 Start/Write/Stop/Close
}
PlayFile 是阻塞接口;用于"播完一段 BGM 再继续"的同步场景。需要异步 / 边合成边播参考 §4.2.5 的流式重采样 + Write 组合,或参考 tts_stream_demo.cpp。
4.2.4 全双工 AEC 用法
适用场景:硬件没有内置回声消除时,需要软件 AEC(例如 omni_agent voice_chat_aec)。AudioDuplex 用同步回调把 input/output 配对,便于把扬声器输出当 AEC 参考信号。
调用步骤:
- 构造
AudioDuplex并SetCallback(...)。 - 回调里:把
input和"准备播给扬声器的 reference"送 WebRTC AEC3,得到去回声的 mic 信号;同时把要播的内容写到output。 Start(48000, 1, 480)启动;K3 声卡硬限制 48kHz,frames_per_buffer=480即 10ms 块。
#include "audio_duplex.hpp"
int main() {
SpacemitAudio::AudioDuplex duplex(-1, -1);
duplex.SetCallback([&](const float* input, float* output,
size_t frames, int channels) {
// 1. 把 input 送 AEC3,参考信号是上次写到 output 的内容
// 2. AEC 输出送 VAD/ASR
// 3. 把待播音频写到 output(这里用静音占位)
for (size_t i = 0; i < frames * channels; ++i) output[i] = 0.0f;
});
if (!duplex.Start(48000, 1, 480)) return 1;
// 业务线程驱动 LLM/TTS 等上层逻辑...
duplex.Stop();
duplex.Close();
return 0;
}
具体 AEC3 集成代码见 application/native/omni_agent/src/aec_duplex_processor.cpp。
4.2.5 重采样(含流式)
适用场景:K3 声卡只支持 48kHz;TTS 输出 22050/16000/24000 Hz,必须先升采样到 48000 才能播;ASR 要 16000Hz 输入,48000Hz 麦克风需要降采样。
#include "audio_resampler.hpp"
// 一次性重采样(已有完整音频块)
Resampler::Config cfg;
cfg.input_sample_rate = 22050;
cfg.output_sample_rate = 48000;
cfg.channels = 1;
cfg.method = ResampleMethod::LINEAR_UPSAMPLE;
Resampler resampler(cfg);
resampler.initialize();
std::vector<float> out = resampler.process(input_float_22k);
// 流式重采样(边到边出,避免端点失真)
resampler.reset();
for (size_t i = 0; i + chunk <= input_float_22k.size(); i += chunk) {
std::vector<float> piece(input_float_22k.begin() + i,
input_float_22k.begin() + i + chunk);
bool last = (i + chunk == input_float_22k.size());
auto resampled = resampler.processStreaming(piece, last);
// resampled 立即可写 AudioPlayer
}
K3 平台 RVV 加速会自动启用(无需额外配置),实测 16k→48k mono 延迟 < 1ms / 1024 样本。
4.3 Python 示例
Python 包名为 spacemit_audio,安装方式见 §2 中 wheel 安装步骤。当前 Python 包仅暴露 AudioCapture / AudioPlayer / init / get_config,未暴露 AudioDuplex 和 Resampler——全双工与重采样请用 C++ API。
import spacemit_audio
4.3.1 录音
from spacemit_audio import AudioCapture
chunks = []
with AudioCapture() as cap:
cap.set_callback(lambda data: chunks.append(data))
cap.start()
import time; time.sleep(5) # 录 5 秒
cap.stop()
print(f"共 {sum(len(c) for c in chunks)} 字节 PCM16")
AudioCapture() 构造时无参数,使用全局默认(16kHz mono);要换设备 / 采样率,先 spacemit_audio.init(sample_rate=..., capture_device=...) 再构造。回调拿到的 data 是 bytes(PCM16 LE)。
4.3.2 播放
from spacemit_audio import AudioPlayer
with AudioPlayer() as player:
player.start(48000, 1)
player.write(pcm16_bytes)
player.stop()
直接播放 WAV 文件(阻塞):
from spacemit_audio import AudioPlayer
with AudioPlayer() as player:
player.play_file("test.wav")
4.3.3 C++ ↔ Python 接口对照
C++(SpacemitAudio::) | Python(spacemit_audio.) | 备注 |
|---|---|---|
Init(AudioConfig) / GetConfig() | init(sample_rate=..., channels=..., chunk_size=..., capture_device=..., player_device=...) / get_config() | Python 用关键字参数;get_config() 返回 dict。 |
AudioCapture(device) + SetCallback + Start/Stop/Close | AudioCapture() 上下文管理器 + set_callback(fn) + start() / stop() / close() | Python 端 start() / stop() 等是方法(不是属性);is_running() 也是方法。 |
AudioCapture::ListDevices() | AudioCapture.list_devices()(静态) | 返回 [(index, name), ...]。 |
AudioPlayer(device) + Start / Write / PlayFile / Stop / Close | AudioPlayer() 上下文管理器 + start(rate, ch) / write(bytes) / play_file(path) / stop() / close() | 同上。 |
AudioDuplex 全双工 | Python 包未暴露 | 需要软件 AEC 时使用 C++ API。 |
Resampler 重采样 | Python 包未暴露 | Python 端可用 numpy + scipy.signal.resample_poly 做替代,或自己写线性插值。spacemit_asr 内置的 Resampler 仅是线性插值版,不用 libsamplerate。 |
更多 Python 示例见 components/multimedia/audio/python/examples/。后续与 ASR / TTS / VAD 串联使用时,参考各模型组件文档的 §4.3。
5. 调试指南
调试 audio 时优先把设备枚举、录音、播放和采样率转换分开验证:
- 用
audio_demo list、arecord -l、aplay -l对照确认设备索引。 - 录音问题先保存 WAV 并回放,确认输入不是静音、削波或声道数不符。
- 播放问题先查看 demo 日志中的通道转换和重采样信息。
- Python 问题先确认已激活
~/.comm-env,并能import spacemit_audio。
6. 常见问题
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 找不到设备 | 声卡未枚举或权限不足 | 用 audio_demo list、arecord -l、aplay -l 检查设备。 |
| 打开流时报 invalid sample rate | 声卡不支持该采样率 | 使用设备支持的 48kHz,或通过 Resampler 转换。 |
Python import spacemit_audio 失败 | 未激活虚拟环境,或未安装 Python 包 | 激活 ~/.comm-env,再安装 spacemit-audio 或 output/wheels/components_multimedia_audio/ 下的 wheel。 |
| 播放声音速度异常 | WAV 采样率与播放配置不一致 | 使用 audio_demo play 的重采样参数,或在应用中显式重采样。 |
附录:K3 实测记录
K3 平台完成了以下验证流程:
| 命令 | 结果 |
|---|---|
audio_demo record test.wav --duration 5 --rate 16000 --channels 2 --device -1 | 成功录制 5 秒 16kHz 双声道 WAV,保存 320000 bytes。 |
audio_demo play ./test.wav -c 2 -d 1 | 成功把 16kHz/1ch 转为 48kHz/2ch 并播放。 |
python audio_demo.py record test.wav --duration 5 --rate 16000 --channels 2 --device -1 | 成功录制 320000 bytes,100 个 chunks。 |
python audio_demo.py play test.wav --device 1 | 成功重采样到 48kHz 并播放。 |
测试方法:在 K3 板卡上构建 audio 后,分别运行 C++ 与 Python 的设备枚举、录音和播放命令;通过输出 WAV 文件大小、demo 日志中的采样率/声道转换信息和实际播放结果确认。