跳到主要内容

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 优化路径。
APIC++ audio_base.hppaudio_duplex.hppaudio_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.hppAudioCaptureAudioPlayer 和全局配置。
components/multimedia/audio/include/audio_resampler.hppResampler C++/C API。
components/multimedia/audio/include/audio_duplex.hpp全双工音频接口。
components/multimedia/audio/examples/audio_demo.cppC++ 示例程序。
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 DevicesOutput 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=16000channels=1chunk_size=3200(采集每次回调字节数)、capture_device=-1player_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_rateoutput_sample_ratechannelsmethodLINEAR_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() 之前调用。cbstd::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、网络发送等)。

调用步骤:

  1. 构造 AudioCapture(-1) 或指定设备索引。
  2. SetCallback(...) 注册收数据的回调;回调里只做轻量动作(拷贝、入队),不要做推理。
  3. Start(sample_rate, channels, chunk_size) 启动;chunk_size 是字节数,例如 16kHz mono PCM16 下 3200 字节 = 100ms。
  4. 退出前 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 参考信号。

调用步骤:

  1. 构造 AudioDuplexSetCallback(...)
  2. 回调里:把 input 和"准备播给扬声器的 reference"送 WebRTC AEC3,得到去回声的 mic 信号;同时把要播的内容写到 output
  3. 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未暴露 AudioDuplexResampler——全双工与重采样请用 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=...) 再构造。回调拿到的 databytes(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/CloseAudioCapture() 上下文管理器 + set_callback(fn) + start() / stop() / close()Python 端 start() / stop() 等是方法(不是属性);is_running() 也是方法。
AudioCapture::ListDevices()AudioCapture.list_devices()(静态)返回 [(index, name), ...]
AudioPlayer(device) + Start / Write / PlayFile / Stop / CloseAudioPlayer() 上下文管理器 + 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 listarecord -laplay -l 对照确认设备索引。
  • 录音问题先保存 WAV 并回放,确认输入不是静音、削波或声道数不符。
  • 播放问题先查看 demo 日志中的通道转换和重采样信息。
  • Python 问题先确认已激活 ~/.comm-env,并能 import spacemit_audio

6. 常见问题

现象可能原因处理
找不到设备声卡未枚举或权限不足audio_demo listarecord -laplay -l 检查设备。
打开流时报 invalid sample rate声卡不支持该采样率使用设备支持的 48kHz,或通过 Resampler 转换。
Python import spacemit_audio 失败未激活虚拟环境,或未安装 Python 包激活 ~/.comm-env,再安装 spacemit-audiooutput/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 日志中的采样率/声道转换信息和实际播放结果确认。