多媒体 · audio_process
1. 模块概述
audio_process 当前包含 DOA(Direction of Arrival)声源定位组件,位于 components/multimedia/audio_process/doa。该组件基于 GCC-PHAT(Generalized Cross-Correlation with Phase Transform)实现双声道声源定位,输入左右声道音频,输出声源角度、TDOA 和置信度。
算法链路:
双声道输入 -> Hann 窗口 -> FFT -> 互功率谱 -> PHAT 加权
-> 频域上采样 -> IFFT -> 峰值搜索 -> 抛物线插值 -> TDOA -> DOA
输出角度范围为 [0°, 180°],其中 90° 表示正前方,0° 和 180° 表示两侧端火方向。通道物理位置不同可能导致结果表现为补角,ssl_demo 可通过 --flip 输出 180 - doa,例如 0° -> 180°、50° -> 130°。
主要目录:
| 路径 | 说明 |
|---|---|
components/multimedia/audio_process/doa/include/doa_service.h | C++ 对外 API。 |
components/multimedia/audio_process/doa/src/sound_locator.cpp | GCC-PHAT 实现。 |
components/multimedia/audio_process/doa/examples/ssl_demo.cpp | C++ 示例,产物为 ssl_demo。 |
components/multimedia/audio_process/doa/python/ | Python 包 spacemit_audio_process 和示例。 |
2. 环境准备
前置条件
SDK 源码获取和基础编译环境配置统一参考 2.3-构建编译。完成 SDK 初始化后,回到本文继续执行“构建编译”。
后续命令默认在 spacemit_robot SDK 根目录执行。
构建编译
DOA 是纯算法库,不需要模型文件。系统缺少依赖时先安装:
sudo apt install build-essential cmake libfftw3-dev
随语音链路单组件构建时可使用 target/k3-com260-omni-agent.json 目标配置。当前该 target 的全量构建不默认包含 audio_process / DOA;如需使用 DOA,请在 SDK 根目录进入组件目录单独构建:
source build/envsetup.sh
lunch k3-com260-omni-agent
cd components/multimedia/audio_process/doa
mm
构建完成后,ssl_demo 会安装到 output/staging/bin。需要 Python 示例时,可以在 SDK 根目录构建所有 Python wheel:
m -py
也可以只在 DOA 组件目录构建当前组件的 Python wheel:
cd components/multimedia/audio_process/doa
mm -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-process \
--index-url https://git.spacemit.com/api/v4/projects/33/packages/pypi/simple
方式二,回到 SDK 根目录安装本地构建出的 wheel:
python -m pip install output/wheels/components_multimedia_audio_process_doa/spacemit_audio_process-*.whl \
--index-url https://git.spacemit.com/api/v4/projects/33/packages/pypi/simple
Python import 包名为 spacemit_audio_process,安装后可:
python -c "from spacemit_audio_process import SoundLocator; print('OK')"
Python live 模式还依赖 audio 组件,需同样安装 spacemit-audio:
python -m pip install spacemit-audio \
--index-url https://git.spacemit.com/api/v4/projects/33/packages/pypi/simple
如果选择安装本地 audio wheel,先在 audio 组件目录构建 wheel,再回到 SDK 根目录安装:
cd components/multimedia/audio
mm -py
cd ../../..
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
3. 示例使用
3.1 合成信号精度测试
ssl_demo -t -d 0.058
参数 -d 0.058 表示麦克风间距为 0.058m,应按实际硬件修改。该模式会生成不同角度的合成双声道信号,输出估计角度、误差、置信度和 pass/fail。
如果需要翻转输出角度:
ssl_demo -t -d 0.058 --flip
Python 版本:
cd components/multimedia/audio_process/doa/python/examples
python ssl_demo.py -t -d 0.058
Python 版本同样支持 --flip:
python ssl_demo.py -t -d 0.058 --flip
3.2 WAV 文件测试
对自己的双声道 PCM16 WAV 文件运行:
ssl_demo -f <stereo.wav> -d 0.058
开启逐帧输出:
ssl_demo -f <stereo.wav> -d 0.058 -v
如果硬件通道方向与期望相反,可加 --flip 输出补角:
ssl_demo -f <stereo.wav> -d 0.058 --flip
Python 版本:
python ssl_demo.py -f <stereo.wav> -d 0.058 -v
Python WAV 示例也支持翻转输出:
python ssl_demo.py -f <stereo.wav> -d 0.058 --flip
输入文件必须是双声道 WAV。本文档不假设仓库内自带测试 WAV;下方 WAV 结果仅作为 K3 实测数据引用。
3.3 实时采集
ssl_demo 支持 live capture 模式,但需要构建时找到 audio 组件并链接 PortAudio:
ssl_demo -l -d 0.058
如果实时输出角度为期望值的补角:
ssl_demo -l -d 0.058 --flip
Python live 模式依赖 spacemit-audio,安装见“构建编译”:
python ssl_demo.py -l -d 0.058
Python live 模式同样支持 --flip:
python ssl_demo.py -l -d 0.058 --flip
4. 应用开发
本章面向应用开发者,说明如何在自己的 C++ / Python 应用中集成声源定位组件。完整接口以 components/multimedia/audio_process/doa/include/doa_service.h 为准;本节只介绍常用公开接口和典型调用方式。DOA 组件的 C++ 命名空间是 SpacemitAudio::(与 audio 组件一致);Python 入口是 spacemit_audio_process.SoundLocator。
4.1 接口说明
DOA 组件基于 GCC-PHAT 双通道时延估计,核心入口是 SpacemitAudio::SoundLocator。应用侧负责给麦克风间距 mic_distance、传入立体声 PCM、读取定位结果。
4.1.1 常用数据结构
| 类型 | 说明 |
|---|---|
SoundLocatorConfig | 引擎配置。详细字段见下表。mic_distance 必须匹配实际硬件麦克风间距,否则 DOA 不准。 |
SoundLocator | 定位主类,含 Initialize / Reset / 三个 Process 重载 / 一组结果 getter。 |
SoundLocatorConfig 字段:
| 参数 | 默认值 | 说明 |
|---|---|---|
sample_rate | 16000 | 输入立体声采样率。 |
mic_distance | 0.058 | 麦克风间距(米);必须改为实际硬件值,开发板默认 5.8cm 仅作示例。 |
sound_speed | 343.0 | 声速 (m/s),常温常压可保持默认。 |
fft_size | 0 | FFT 长度;0 表示自动 = 2 * frame_size。 |
frame_size | 512 | 每帧样本数;16kHz 下 512 ≈ 32ms。 |
avg_frames | 4 | 累积多少帧后输出一次结果;越大越稳但延迟越高。 |
confidence_threshold | 0.1 | GCC 峰值的归一化置信度阈值;低于此值 IsValid() 返回 false。 |
upsample_factor | 0 | TDOA 上采样倍率;0 = 自动取约 4μs 分辨率。麦克风间距 < 5cm 时建议手动调高(如 8 或 16)。 |
use_fftw_measure | false | 是否在 Initialize() 时用 FFTW_MEASURE 优化 FFT 计划;初始化更慢但运行更快。 |
4.1.2 引擎初始化
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
SoundLocator(SoundLocatorConfig) | 构造定位器;不会自动初始化 FFT 计划。 | config:SoundLocatorConfig。 | 实例。 |
Initialize() | 分配 FFT 计划与内部缓冲;Process 之前必须调用一次。 | 无。 | bool。 |
Reset() | 清空多帧累积器和缓存结果,便于上下文切换或开始新一段测向。 | 无。 | 无。 |
GetConfig() / GetMaxDelaySamples() | 配置查询;GetMaxDelaySamples() 返回 mic_distance / sound_speed * sample_rate 的整数值,用于判断输入延迟范围合理性。 | 无。 | 各自类型。 |
4.1.3 处理 API
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
Process(const float* interleaved, size_t num_frames) | 处理交错立体声 float [L, R, L, R, ...]。 | interleaved:交错样本;num_frames:帧数(每帧 2 float)。 | bool,true 表示已累积够 avg_frames 并产出新结果。 |
Process(const float* ch0, const float* ch1, size_t num_frames) | 处理两个独立 float 通道。 | 各通道指针;帧数。 | 同上。 |
Process(const int16_t* interleaved, size_t num_frames) | 处理交错立体声 PCM16;常配合 AudioCapture 回调直接使用。 | interleaved:PCM16;num_frames:帧数。 | 同上。 |
4.1.4 结果查询
| 接口 | 说明 | 参数 | 返回值 |
|---|---|---|---|
GetDOA() | 上一次累积窗口的方位角。 | 无。 | float,单位度,范围 [0, 180]。90° = broadside(垂直阵列),0° = ch1 侧 endfire,180° = ch0 侧 endfire。 |
GetTDOA() | 上一次累积窗口的时延差。 | 无。 | float,秒。 |
GetConfidence() | GCC 峰值归一化置信度。 | 无。 | float,[0, 1]。 |
IsValid() | GetConfidence() >= confidence_threshold。 | 无。 | bool。 |
GetAverageDOA() | 自上次 Reset() 以来所有 valid 批次的置信度加权 DOA;近 endfire 时不会被对侧异常值拉回 90°。 | 无。 | float,度。无 valid 结果时返回 90。 |
GetResultCount() | 自上次 Reset() 以来产出的 valid 结果数。 | 无。 | int。 |
4.2 C++ 调用示例
DOA 组件编译后会把 doa_service.h 和 libspacemit_audio_process.so 安装到 output/staging。下游组件链接 DOA 库的 CMake 写法:
add_executable(my_app main.cpp)
find_library(DOA_LIB NAMES spacemit_audio_process
PATHS ${CMAKE_INSTALL_PREFIX}/lib NO_DEFAULT_PATH)
find_path(DOA_INCLUDE_DIR NAMES doa_service.h
PATHS ${CMAKE_INSTALL_PREFIX}/include NO_DEFAULT_PATH)
if(NOT DOA_LIB OR NOT DOA_INCLUDE_DIR)
message(FATAL_ERROR "spacemit_audio_process not found. Build components/multimedia/audio_process first.")
endif()
target_include_directories(my_app PRIVATE ${DOA_INCLUDE_DIR})
target_link_libraries(my_app PRIVATE ${DOA_LIB})
包依赖建议在 package.xml 中声明 <depend>audio_process</depend>。
4.2.1 离线 WAV 测向
适用场景:用录好的立体声 WAV 评估 DOA 算法准确度;典型用于回归测试或参数标定。
调用步骤:
- 配置
SoundLocatorConfig,设好mic_distance。 - 构造
SoundLocator并Initialize。 - 把整段立体声 PCM16 一次性送
Process(int16, num_frames),或按frame_size切片循环送。 - 读取
GetDOA()/GetConfidence()/IsValid()。
#include <iostream>
#include <vector>
#include "doa_service.h"
void LocateOnce(const std::vector<int16_t>& interleaved_stereo,
int sample_rate, float mic_distance) {
SpacemitAudio::SoundLocatorConfig cfg;
cfg.sample_rate = sample_rate;
cfg.mic_distance = mic_distance;
cfg.frame_size = 512;
cfg.avg_frames = 4;
SpacemitAudio::SoundLocator loc(cfg);
if (!loc.Initialize()) {
std::cerr << "Initialize 失败" << std::endl;
return;
}
size_t num_frames = interleaved_stereo.size() / 2;
if (loc.Process(interleaved_stereo.data(), num_frames) && loc.IsValid()) {
std::cout << "DOA: " << loc.GetDOA() << "°"
<< " TDOA: " << loc.GetTDOA() * 1e6 << "μs"
<< " conf: " << loc.GetConfidence() << std::endl;
}
}
完整版含合成精度测试、命令行解析、角度翻转选项,见 components/multimedia/audio_process/doa/examples/ssl_demo.cpp。
4.2.2 实时回调流式测向
适用场景:与 AudioCapture 麦克风回调直接串联,每秒输出滑窗 DOA;典型用于机器人朝向声源转头、波束形成方向控制。
调用步骤:
AudioCapture配置为立体声采集(channels=2)。- 在采集回调里把 PCM16 字节强转
int16_t*,按帧数送loc.Process(int16, num_frames);回调里不要做重计算——Process内部 FFT 是轻量的,但仍然建议把热路径限制在送数据 + 读结果。 - 业务线程定期读
GetAverageDOA()输出方位。
#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
#include "audio_base.hpp"
#include "doa_service.h"
int main() {
SpacemitAudio::SoundLocatorConfig cfg;
cfg.sample_rate = 16000;
cfg.mic_distance = 0.058f; // 改为你的硬件值
cfg.frame_size = 512;
cfg.avg_frames = 4;
cfg.confidence_threshold = 0.1f;
SpacemitAudio::SoundLocator loc(cfg);
if (!loc.Initialize()) return 1;
SpacemitAudio::AudioCapture capture(-1);
capture.SetCallback([&](const uint8_t* data, size_t size) {
const auto* pcm = reinterpret_cast<const int16_t*>(data);
size_t num_frames = (size / sizeof(int16_t)) / 2; // 立体声
loc.Process(pcm, num_frames);
});
capture.Start(16000, 2, 4096); // 立体声 4096 字节 ≈ 64ms
for (int i = 0; i < 30; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));
if (loc.GetResultCount() > 0) {
std::cout << "[" << i << "s] avg DOA = "
<< loc.GetAverageDOA() << "°"
<< " results=" << loc.GetResultCount() << std::endl;
}
loc.Reset(); // 每秒切一次窗
}
capture.Stop();
capture.Close();
return 0;
}
Reset() 间隔越短响应越快、越长越稳;典型 200ms–1s。
4.2.3 自定义置信度过滤
适用场景:环境噪声大或说话人不在麦克风正前方,置信度会显著下降;业务侧需要根据具体情况决定是否使用 DOA 结果。
调用步骤:
- 把
confidence_threshold设得比默认0.1更严(如0.3)。 - 麦克风间距小于 5cm 时调大
upsample_factor,提升 TDOA 分辨率。 - 用
IsValid()过滤单帧结果,再结合GetAverageDOA()做时间平滑。
SpacemitAudio::SoundLocatorConfig cfg;
cfg.sample_rate = 16000;
cfg.mic_distance = 0.040f; // 4cm 紧凑阵列
cfg.confidence_threshold = 0.3f;
cfg.upsample_factor = 8; // 提升分辨率到约 0.5μs
cfg.avg_frames = 8; // 加大平均窗
SpacemitAudio::SoundLocator loc(cfg);
loc.Initialize();
// 处理流后
if (loc.GetResultCount() >= 4 && loc.IsValid()) {
float doa = loc.GetAverageDOA();
if (std::abs(doa - 90.0f) > 5.0f) { // 偏离正前方超过 5° 才输出
std::cout << "声源方向: " << doa << "°" << std::endl;
}
}
4.3 Python 示例
Python 包名为 spacemit_audio_process,安装方式见 §2 中 wheel 安装步骤。导入后直接使用:
import spacemit_audio_process
4.3.1 离线测向
import numpy as np
from spacemit_audio_process import SoundLocator, SoundLocatorConfig
cfg = SoundLocatorConfig()
cfg.sample_rate = 16000
cfg.mic_distance = 0.058 # 改为你的硬件值
cfg.frame_size = 512
cfg.avg_frames = 4
loc = SoundLocator(cfg)
loc.initialize()
# 立体声 PCM16,shape: [num_frames * 2]
stereo_int16 = np.zeros(16000 * 2, dtype=np.int16) # 1 秒静音占位
loc.process_int16(stereo_int16)
if loc.is_valid:
print(f"DOA={loc.doa:.1f}°, TDOA={loc.tdoa*1e6:.1f}μs, conf={loc.confidence:.3f}")
print(f"avg_doa={loc.average_doa:.1f}°, count={loc.result_count}")
4.3.2 上下文管理器 + 实时滑窗
SoundLocator 支持 with 语法(__exit__ 调用 reset(),但 __enter__ 不会自动 initialize()),典型实时管线把 process_int16 放在采集线程,业务线程读属性:
import time
import numpy as np
import spacemit_audio
from spacemit_audio_process import SoundLocator, SoundLocatorConfig
cfg = SoundLocatorConfig()
cfg.sample_rate = 16000
cfg.mic_distance = 0.058
cfg.frame_size = 512
# stereo int16 一帧的字节数:frame_size * channels * sizeof(int16)
chunk_size = cfg.frame_size * 2 * 2
with SoundLocator(cfg) as loc:
loc.initialize() # __enter__ 不自动 init,需显式调用
spacemit_audio.init(sample_rate=cfg.sample_rate, channels=2, chunk_size=chunk_size)
cap = spacemit_audio.AudioCapture()
def on_audio(data: bytes):
pcm = np.frombuffer(data, dtype=np.int16)
loc.process_int16(pcm)
cap.set_callback(on_audio)
cap.start(sample_rate=cfg.sample_rate, channels=2, chunk_size=chunk_size)
try:
for i in range(30):
time.sleep(1.0)
if loc.result_count > 0:
print(f"[{i}s] avg DOA = {loc.average_doa:.1f}°"
f" results={loc.result_count}")
finally:
cap.stop()
with SoundLocator(...) 仅负责退出时 reset();AudioCapture 的 __enter__ 同理只 return self,start() / initialize() 都需显式调用。
4.3.3 C++ ↔ Python 接口对照
C++(SpacemitAudio::) | Python(spacemit_audio_process.) | 备注 |
|---|---|---|
SoundLocatorConfig | SoundLocatorConfig() | Python 字段直接同名(sample_rate、mic_distance 等)。 |
SoundLocator(cfg) + Initialize() | SoundLocator(cfg) + loc.initialize() | Python __enter__ 只 return self,不会自动调 initialize();__exit__ 仅做 reset()。 |
Process(const float* interleaved, n) | loc.process(stereo_float_numpy) | 接受 1D 或 2D numpy 数组(自动展平)。 |
Process(const int16_t* interleaved, n) | loc.process_int16(stereo_int16_numpy) | 与 AudioCapture 输出 PCM16 直接对接。 |
Process(const float* ch0, const float* ch1, n) | loc.process_separate(ch0, ch1) | 两个独立 1D float 数组。 |
GetDOA() / GetTDOA() / GetConfidence() / IsValid() / GetAverageDOA() / GetResultCount() / GetMaxDelaySamples() / GetConfig() | loc.doa / loc.tdoa / loc.confidence / loc.is_valid / loc.average_doa / loc.result_count / loc.max_delay_samples / loc.config | Python 全部用属性,无 getter 方法。 |
Reset() | loc.reset() | 名称对应。 |
更多 Python 示例见 components/multimedia/audio_process/doa/python/examples/。
5. 调试指南
调试 DOA 时先固定麦克风间距和通道方向,再比较合成信号、WAV 文件和实时采集结果:
- 合成信号模式
ssl_demo -t -d <mic_distance>用于排除录音链路问题。 - WAV 文件模式用于复现具体硬件样本,建议开启
-v查看逐帧角度和置信度。 - 实时采集前先用 audio 组件确认双声道输入设备可用。
- 角度出现补角时先用
--flip验证通道方向,再决定业务侧交换通道或输出180 - doa。
6. 常见问题
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 角度是期望值的补角 | 左右声道物理位置与算法假设相反 | ssl_demo 加 --flip;集成应用中交换通道,或使用 180 - doa。 |
| 0° / 180° 附近误差大 | 麦克风间距配置不准 | 精确测量 mic_distance;端火角对毫米级误差敏感。 |
| 一直无有效结果 | 置信度低或输入不是双声道 | 检查 WAV 声道数,降低 confidence_threshold 做验证。 |
| 结果抖动 | 单帧噪声或混响影响 | 增大 avg_frames,或对输出做业务侧平滑。 |
附录:K3 实测数据
以下为 K3 平台实测数据。WAV 样本为测试时使用的双声道 2 秒音频,不作为仓库内置资源说明。
| 场景 | 结果 |
|---|---|
Python 合成信号测试 python ssl_demo.py -t | 13/16 passed。 |
| C++ WAV 文件测试 | 2.00s 音频处理约 3.7-3.9ms,RTF 0.0019。 |
| Python WAV 文件测试 | 2.00s 音频处理约 4.5-4.7ms,RTF 0.0023-0.0024。 |
部分 WAV 实测角度表现为补角关系,例如标称 0° 样本输出约 180°、标称 180° 样本输出约 5.6°。这符合通道映射差异的典型表现。使用 ssl_demo 验证时可加 --flip;应用集成时应先确认硬件左右声道定义。
测试方法:在 K3 板卡上单独构建 DOA 组件后,先运行合成信号测试确认算法链路,再使用双声道 WAV 和 live capture 模式验证硬件通道方向、处理耗时、RTF 和置信度。