데스크톱 오디오 파이프라인
adk-audio 크레이트는 desktop-audio 기능 플래그를 통해 크로스 플랫폼 데스크톱 오디오 I/O를 제공합니다. 세 가지 구성 요소인 AudioCapture, AudioPlayback, VadTurnManager은 마이크 캡처, 스피커 재생, VAD 기반 발화 전환을 지원하여 데스크톱 음성 에이전트를 구축할 수 있도록 합니다.
개요
데스크톱 오디오 파이프라인은 시스템 오디오 하드웨어를 기존 adk-audio 파이프라인 시스템에 연결합니다.
Microphone → AudioCapture → AudioStream → [VAD → STT → Agent → TTS] → AudioPlayback → Speaker
모든 구성 요소는 크로스 플랫폼 오디오를 위해 cpal 크레이트를 사용하며(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을 종속성으로 추가합니다. CI 빌드에 플랫폼별 오디오 종속성이 포함되는 것을 방지하기 위해 all 기능에서는 의도적으로 제외되어 있습니다.
PipeWire 지원
최신 Linux(Fedora 34+, Ubuntu 22.10+, Arch)에서는 PipeWire이 기본 오디오 서버로 PulseAudio 및 ALSA를 대체했습니다. desktop-audio 기능은 ALSA 호환성 계층을 통해 PipeWire에서 투명하게 작동하므로 추가 구성이 필요하지 않습니다.
pipewire 크레이트(v0.9)를 사용하는 네이티브 PipeWire 백엔드(desktop-pipewire)는 향후 릴리스에서 추가될 예정입니다. 네이티브 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()을 호출하세요 — 0 값을 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()을 호출하세요 — 0 임계값을 AudioError::Vad로 거부합니다.
음성 에이전트 구축
전체 대화형 음성 에이전트 패턴:
- 마이크에서 오디오 캡처
- VAD로 음성 경계 감지
- GeminiStt(또는 모든
SttProvider)으로 음성 텍스트 변환 - 추론을 위해 트랜스크립트를 LlmAgent로 전송
- GeminiTts(또는 모든
TtsProvider)으로 응답 합성 - 스피커를 통해 합성된 오디오 재생
실제 Gemini 클라우드 공급자를 사용하는 완전한 작동 예제는 examples/desktop_audio/src/voice_agent.rs을 참조하세요.
스레드 안전성
모든 데스크톱 오디오 타입(AudioCapture, AudioPlayback, VadTurnManager)은 Send + Sync이므로 Tokio 작업 간에 안전하게 공유할 수 있습니다.
오류 처리
데스크톱 오디오 오류는 기존 AudioError 열거형을 사용합니다:
| 구성 요소 | 오류 변형 | 발생 조건 |
|---|---|---|
| AudioCapture | AudioError::Device | 장치를 찾을 수 없음, 호스트를 사용할 수 없음, 구성 검증 |
| AudioPlayback | AudioError::Device | 장치를 찾을 수 없음, 호스트를 사용할 수 없음, 열기/쓰기 실패 |
| VadTurnManager | AudioError::Vad | 구성 검증(임계값 0) |
플랫폼 지원
| 플랫폼 | 오디오 백엔드 | 상태 |
|---|---|---|
| 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의 업스트림 종속성 충돌로 인해 진행이 차단된 상태입니다).
예제
실제 Gemini STT/TTS를 사용하는 완전한 대화형 음성 에이전트를 포함한 6개의 실용적인 예제는 examples/desktop_audio/을 참조하세요.