Pipeline de Áudio para Desktop

O crate adk-audio fornece E/S de áudio para desktop multiplataforma por trás da flag de recurso desktop-audio. Três componentes — AudioCapture, AudioPlayback e VadTurnManager — habilitam a captura pelo microfone, a reprodução pelos alto-falantes e o gerenciamento de turnos orientado por VAD para a criação de agentes de voz para desktop.

Visão geral

O pipeline de áudio para desktop conecta o hardware de áudio do sistema ao sistema de pipeline adk-audio existente:

Microphone → AudioCapture → AudioStream → [VAD → STT → Agent → TTS] → AudioPlayback → Speaker

Todos os componentes usam o crate cpal para áudio multiplataforma (CoreAudio no macOS, PipeWire/ALSA/PulseAudio no Linux, WASAPI no Windows) e produzem/consomem o tipo AudioFrame padrão.

Flags de recurso

[dependencies]
# Cross-platform (macOS, Linux, Windows) via cpal
adk-audio = { version = "2.1.0", features = ["desktop-audio"] }

O recurso desktop-audio implica vad (para VadProcessor) e adiciona cpal como dependência. Ele é intencionalmente excluído do recurso all para evitar incluir dependências de áudio específicas da plataforma nas compilações de CI.

Suporte a PipeWire

Nas versões modernas do Linux (Fedora 34+, Ubuntu 22.10+, Arch), PipeWire substituiu PulseAudio e ALSA como servidor de áudio padrão. O recurso desktop-audio funciona de forma transparente em PipeWire por meio de sua camada de compatibilidade com ALSA — nenhuma configuração adicional é necessária.

Um backend nativo de PipeWire (desktop-pipewire), usando o crate pipewire (v0.9), está planejado para uma versão futura. O PipeWire nativo forneceria menor latência e gerenciamento direto de sessões. Atualmente, ele está bloqueado por um conflito de dependências: redis-protocol 6.0.0 fixa cookie-factory =0.3.2, enquanto pipewire's libspa requer 0.3.3. Quando o upstream flexibilizar essa fixação, o backend nativo poderá ser adicionado.

Início rápido

Listar dispositivos de áudio

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());
}

Capturar áudio do microfone

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();

Reproduzir áudio pelos alto-falantes

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();

Gerenciamento de turnos com 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)");
        }
    }
});

Componentes

AudioDevice

Descritor de um dispositivo de áudio do sistema (entrada ou saída). Contém um id opaco e um name legível.

CaptureConfig

Configuração para captura de microfone:

CampoTipoPadrãoDescrição
sample_rateu3216000Taxa de amostragem em Hz
channelsu81Contagem de canais (1=mono, 2=estéreo)
frame_duration_msu3220Duração de cada AudioFrame

Chame validate() antes de usar — rejeita valores zero com AudioError::Device.

AudioCapture

Captura do microfone via cpal. O método start_capture() retorna um AudioStream (mpsc::Receiver<AudioFrame> limitado com capacidade 64). Os frames são produzidos em intervalos de frame_duration_ms no formato PCM-16 LE.

AudioPlayback

Reprodução pelo alto-falante via cpal. O método play() enfileira as amostras de um AudioFrame em um buffer compartilhado que o callback de saída do cpal drena. Chame stop() para liberar o dispositivo.

VadTurnManager

Consome um AudioStream, aplica VadProcessor::is_speech() a cada frame e emite valores VoiceActivityEvent por meio de um callback registrado.

Dois modos:

  • HandsFree — detecção automática dos limites da fala usando limiares configuráveis de duração do silêncio e da fala
  • PushToTalk — nenhum evento automático; o chamador controla o gating externamente

VadConfig

CampoTipoDescrição
modeVadModeHandsFree ou PushToTalk
silence_threshold_msu32Silêncio consecutivo antes de SpeechEnded
speech_threshold_msu32Fala consecutiva antes de SpeechStarted

Chame validate() antes do uso — rejeita limites zero com AudioError::Vad.

Criando um agente de voz

O padrão completo de agente de voz conversacional:

  1. Capture áudio do microfone
  2. Detecte os limites da fala com VAD
  3. Transcreva a fala com GeminiStt (ou qualquer SttProvider)
  4. Envie a transcrição para um LlmAgent para raciocínio
  5. Sintetize a resposta com GeminiTts (ou qualquer TtsProvider)
  6. Reproduza o áudio sintetizado pelo alto-falante

Consulte examples/desktop_audio/src/voice_agent.rs para obter um exemplo completo e funcional usando provedores reais do Gemini na nuvem.

Segurança de threads

Todos os tipos de áudio para desktop (AudioCapture, AudioPlayback, VadTurnManager) são Send + Sync, o que os torna seguros para compartilhar entre tarefas do Tokio.

Tratamento de erros

Os erros de áudio para desktop usam o enum AudioError existente:

ComponenteVariante de erroQuando
AudioCaptureAudioError::DeviceDispositivo não encontrado, host indisponível, validação da configuração
AudioPlaybackAudioError::DeviceDispositivo não encontrado, host indisponível, falha ao abrir/gravar
VadTurnManagerAudioError::VadValidação da configuração (limiares zero)

Suporte à plataforma

PlataformaBackend de áudioStatus
macOSCoreAudioCompatível
LinuxPipeWire (via compatibilidade com ALSA)Compatível (instale libasound2-dev)
LinuxALSA / PulseAudio (legado)Compatível (instale libasound2-dev)
WindowsWASAPICompatível

No Linux moderno, PipeWire é o servidor de áudio padrão e substituiu PulseAudio e ALSA. O recurso desktop-audio funciona em PipeWire de forma transparente por meio de sua camada de compatibilidade com ALSA. Um backend PipeWire nativo usando o crate pipewire está planejado para uma versão futura (atualmente bloqueado por um conflito de dependência upstream em redis-protocol).

Exemplos

Consulte examples/desktop_audio/ para ver 6 exemplos práticos, incluindo um agente de voz conversacional completo com STT/TTS reais do Gemini.