跳到主要内容

多媒体 · 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° 表示正前方,180° 表示两侧端火方向。通道物理位置不同可能导致结果表现为补角,ssl_demo 可通过 --flip 输出 180 - doa,例如 0° -> 180°50° -> 130°

主要目录:

路径说明
components/multimedia/audio_process/doa/include/doa_service.hC++ 对外 API。
components/multimedia/audio_process/doa/src/sound_locator.cppGCC-PHAT 实现。
components/multimedia/audio_process/doa/examples/ssl_demo.cppC++ 示例,产物为 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_rate16000输入立体声采样率。
mic_distance0.058麦克风间距(米);必须改为实际硬件值,开发板默认 5.8cm 仅作示例。
sound_speed343.0声速 (m/s),常温常压可保持默认。
fft_size0FFT 长度;0 表示自动 = 2 * frame_size
frame_size512每帧样本数;16kHz 下 512 ≈ 32ms。
avg_frames4累积多少帧后输出一次结果;越大越稳但延迟越高。
confidence_threshold0.1GCC 峰值的归一化置信度阈值;低于此值 IsValid() 返回 false。
upsample_factor0TDOA 上采样倍率;0 = 自动取约 4μs 分辨率。麦克风间距 < 5cm 时建议手动调高(如 8 或 16)。
use_fftw_measurefalse是否在 Initialize() 时用 FFTW_MEASURE 优化 FFT 计划;初始化更慢但运行更快。

4.1.2 引擎初始化

接口说明参数返回值
SoundLocator(SoundLocatorConfig)构造定位器;不会自动初始化 FFT 计划。configSoundLocatorConfig实例。
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(垂直阵列), = 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.hlibspacemit_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 算法准确度;典型用于回归测试或参数标定。

调用步骤:

  1. 配置 SoundLocatorConfig设好 mic_distance
  2. 构造 SoundLocatorInitialize
  3. 把整段立体声 PCM16 一次性送 Process(int16, num_frames),或按 frame_size 切片循环送。
  4. 读取 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;典型用于机器人朝向声源转头、波束形成方向控制。

调用步骤:

  1. AudioCapture 配置为立体声采集(channels=2)。
  2. 在采集回调里把 PCM16 字节强转 int16_t*,按帧数送 loc.Process(int16, num_frames)回调里不要做重计算——Process 内部 FFT 是轻量的,但仍然建议把热路径限制在送数据 + 读结果。
  3. 业务线程定期读 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 结果。

调用步骤:

  1. confidence_threshold 设得比默认 0.1 更严(如 0.3)。
  2. 麦克风间距小于 5cm 时调大 upsample_factor,提升 TDOA 分辨率。
  3. 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 selfstart() / initialize() 都需显式调用

4.3.3 C++ ↔ Python 接口对照

C++(SpacemitAudio::Python(spacemit_audio_process.备注
SoundLocatorConfigSoundLocatorConfig()Python 字段直接同名(sample_ratemic_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.configPython 全部用属性,无 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 -t13/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 和置信度。