데스크톱 오디오 파이프라인

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.0cookie-factory =0.3.2을 고정하는 반면 pipewirelibspa0.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_rateu3216000Hz 단위의 샘플 레이트
channelsu81채널 수 (1=모노, 2=스테레오)
frame_duration_msu3220각 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

필드유형설명
modeVadModeHandsFree 또는 PushToTalk
silence_threshold_msu32SpeechEnded 이전의 연속적인 침묵
speech_threshold_msu32SpeechStarted 이전의 연속 발화

사용하기 전에 validate()을 호출하세요 — 0 임계값을 AudioError::Vad로 거부합니다.

음성 에이전트 구축

전체 대화형 음성 에이전트 패턴:

  1. 마이크에서 오디오 캡처
  2. VAD로 음성 경계 감지
  3. GeminiStt(또는 모든 SttProvider)으로 음성 텍스트 변환
  4. 추론을 위해 트랜스크립트를 LlmAgent로 전송
  5. GeminiTts(또는 모든 TtsProvider)으로 응답 합성
  6. 스피커를 통해 합성된 오디오 재생

실제 Gemini 클라우드 공급자를 사용하는 완전한 작동 예제는 examples/desktop_audio/src/voice_agent.rs을 참조하세요.

스레드 안전성

모든 데스크톱 오디오 타입(AudioCapture, AudioPlayback, VadTurnManager)은 Send + Sync이므로 Tokio 작업 간에 안전하게 공유할 수 있습니다.

오류 처리

데스크톱 오디오 오류는 기존 AudioError 열거형을 사용합니다:

구성 요소오류 변형발생 조건
AudioCaptureAudioError::Device장치를 찾을 수 없음, 호스트를 사용할 수 없음, 구성 검증
AudioPlaybackAudioError::Device장치를 찾을 수 없음, 호스트를 사용할 수 없음, 열기/쓰기 실패
VadTurnManagerAudioError::Vad구성 검증(임계값 0)

플랫폼 지원

플랫폼오디오 백엔드상태
macOSCoreAudio지원됨
LinuxPipeWire (ALSA 호환을 통해)지원됨 (libasound2-dev 설치)
LinuxALSA / PulseAudio (레거시)지원됨 (libasound2-dev 설치)
WindowsWASAPI지원됨

최신 Linux에서는 PipeWire가 기본 오디오 서버이며 PulseAudio 및 ALSA를 대체했습니다. desktop-audio 기능은 ALSA 호환성 계층을 통해 PipeWire에서 투명하게 작동합니다. pipewire crate를 사용하는 네이티브 PipeWire 백엔드는 향후 릴리스에 제공될 예정입니다(현재 redis-protocol의 업스트림 종속성 충돌로 인해 진행이 차단된 상태입니다).

예제

실제 Gemini STT/TTS를 사용하는 완전한 대화형 음성 에이전트를 포함한 6개의 실용적인 예제는 examples/desktop_audio/을 참조하세요.