桌面音频管道
adk-audio crate 在 desktop-audio 功能标志后提供跨平台桌面音频 I/O。三个组件——AudioCapture、AudioPlayback 和 VadTurnManager——支持麦克风采集、扬声器播放以及由 VAD 驱动的轮流发言,用于构建桌面语音代理。
概览
桌面音频管道将系统音频硬件连接到现有的 adk-audio 管道系统:
Microphone → AudioCapture → AudioStream → [VAD → STT → Agent → TTS] → AudioPlayback → Speaker
所有组件都使用 cpal crate 实现跨平台音频(macOS 上使用 CoreAudio,Linux 上使用 PipeWire/ALSA/PulseAudio,Windows 上使用 WASAPI),并生成/使用标准的 AudioFrame 类型。
功能标志
[dependencies]
# Cross-platform (macOS, Linux, Windows) via cpal
adk-audio = { version = "2.1.0", features = ["desktop-audio"] }
desktop-audio 功能包含 vad(用于 VadProcessor),并添加 cpal 作为依赖项。它有意排除在 all 功能之外,以避免在 CI 构建中引入特定平台的音频依赖。
PipeWire 支持
在现代 Linux(Fedora 34+、Ubuntu 22.10+、Arch)上,PipeWire 已取代 PulseAudio 和 ALSA,成为默认音频服务器。desktop-audio 功能通过其 ALSA 兼容层透明地运行于 PipeWire 之上——无需额外配置。
未来版本计划提供原生 PipeWire 后端(desktop-pipewire),使用 pipewire crate(v0.9)。原生 PipeWire 将提供更低的延迟和直接的会话管理。目前,该功能受依赖冲突阻碍:redis-protocol 6.0.0 固定使用 cookie-factory =0.3.2,而 pipewire 的 libspa 要求使用 0.3.3。一旦上游放宽此固定版本限制,即可添加原生后端。
快速开始
列出音频设备
use adk_audio::{AudioCapture, AudioPlayback};
let inputs = AudioCapture::list_input_devices()?;
for device in &inputs {
println!("Mic: {} ({})", device.name(), device.id());
}
let outputs = AudioPlayback::list_output_devices()?;
for device in &outputs {
println!("Speaker: {} ({})", device.name(), device.id());
}
采集麦克风音频
use adk_audio::{AudioCapture, CaptureConfig};
use std::time::{Duration, Instant};
let mut capture = AudioCapture::new();
let devices = AudioCapture::list_input_devices()?;
let device = devices.first().expect("no input device");
let config = CaptureConfig::default(); // 16kHz, mono, 20ms frames
let mut stream = capture.start_capture(device.id(), &config)?;
let start = Instant::now();
while start.elapsed() < Duration::from_secs(3) {
if let Some(frame) = stream.recv().await {
// frame.data: PCM-16 LE bytes
// frame.sample_rate: 16000
// frame.channels: 1
// frame.duration_ms: 20
}
}
capture.stop_capture();
通过扬声器播放音频
use adk_audio::{AudioFrame, AudioPlayback};
let mut playback = AudioPlayback::new();
let devices = AudioPlayback::list_output_devices()?;
let device = devices.first().expect("no output device");
let frame = AudioFrame::silence(16000, 1, 1000); // 1 second
playback.play(device.id(), &frame).await?;
playback.stop();
VAD 轮流发言
use std::sync::Arc;
use adk_audio::{
AudioCapture, CaptureConfig, VadConfig, VadMode,
VadTurnManager, VoiceActivityEvent, VadProcessor,
};
let vad: Arc<dyn VadProcessor> = /* your VadProcessor impl */;
let config = VadConfig {
mode: VadMode::HandsFree,
silence_threshold_ms: 500,
speech_threshold_ms: 200,
};
let mut manager = VadTurnManager::new(vad, config)?;
let mut capture = AudioCapture::new();
let stream = capture.start_capture(device_id, &CaptureConfig::default())?;
manager.start(stream, |event| {
match event {
VoiceActivityEvent::SpeechStarted => println!("Speech started"),
VoiceActivityEvent::SpeechEnded { duration_ms } => {
println!("Speech ended ({duration_ms}ms)");
}
}
});
组件
AudioDevice
系统音频设备(输入或输出)的描述符。包含不透明的 id 和人类可读的 name。
CaptureConfig
麦克风采集的配置:
| 字段 | 类型 | 默认值 | 描述 |
|---|---|---|---|
sample_rate | u32 | 16000 | 采样率(单位:Hz) |
channels | u8 | 1 | 声道数(1=单声道,2=立体声) |
frame_duration_ms | u32 | 20 | 每个 AudioFrame 的持续时间 |
使用前调用 validate() — 使用 AudioError::Device 拒绝零值。
AudioCapture
通过 cpal 采集麦克风音频。start_capture() 方法返回一个 AudioStream(容量为 64 的有界 mpsc::Receiver<AudioFrame>)。帧以 frame_duration_ms 间隔生成,格式为 PCM-16 LE。
AudioPlayback
通过 cpal 播放扬声器音频。play() 方法将 AudioFrame 的采样写入共享缓冲区,该缓冲区由 cpal 输出回调排空。调用 stop() 以释放设备。
VadTurnManager
消费一个 AudioStream,对每个帧应用 VadProcessor::is_speech(),并通过已注册的回调发出 VoiceActivityEvent 值。
两种模式:
- HandsFree — 使用可配置的静音时长和语音时长阈值自动检测语音边界
- PushToTalk — 不生成自动事件;由调用方在外部控制门控
VadConfig
| 字段 | 类型 | 描述 |
|---|---|---|
mode | VadMode | HandsFree 或 PushToTalk |
silence_threshold_ms | u32 | SpeechEnded 之前的连续静默 |
speech_threshold_ms | u32 | SpeechStarted 之前的连续语音 |
使用前调用 validate() — 使用 AudioError::Vad 拒绝零阈值。
构建语音代理
完整的对话式语音代理模式:
- 从麦克风捕获音频
- 使用 VAD 检测语音边界
- 使用 GeminiStt(或任何
SttProvider)转录语音 - 将转录文本发送给 LlmAgent 进行推理
- 使用 GeminiTts(或任何
TtsProvider)合成响应 - 通过扬声器播放合成的音频
请参阅 examples/desktop_audio/src/voice_agent.rs,了解使用真实 Gemini 云提供商的完整可运行示例。
线程安全
所有桌面音频类型(AudioCapture、AudioPlayback、VadTurnManager)均为 Send + Sync,因此可以安全地在 Tokio 任务之间共享。
错误处理
桌面音频错误使用现有的 AudioError 枚举:
| 组件 | 错误变体 | 适用情况 |
|---|---|---|
| AudioCapture | AudioError::Device | 未找到设备、主机不可用、配置验证 |
| AudioPlayback | AudioError::Device | 未找到设备、主机不可用、打开/写入失败 |
| VadTurnManager | AudioError::Vad | 配置验证(零阈值) |
平台支持
| 平台 | 音频后端 | 状态 |
|---|---|---|
| macOS | CoreAudio | 支持 |
| Linux | PipeWire(通过 ALSA 兼容层) | 支持(安装 libasound2-dev) |
| Linux | ALSA / PulseAudio(旧版) | 支持(安装 libasound2-dev) |
| Windows | WASAPI | 支持 |
在现代 Linux 上,PipeWire 是默认的音频服务器,已取代 PulseAudio 和 ALSA。desktop-audio 功能通过其 ALSA 兼容层透明地运行于 PipeWire 之上。计划在未来版本中提供使用 pipewire crate 的原生 PipeWire 后端(目前因 redis-protocol 中的上游依赖冲突而受阻)。
示例
请参阅 examples/desktop_audio/,其中包含 6 个实用示例,包括一个使用真实 Gemini STT/TTS 的完整对话式语音代理。