Canalización de audio de escritorio

El crate adk-audio proporciona E/S de audio de escritorio multiplataforma mediante la marca de funcionalidad desktop-audio. Tres componentes — AudioCapture, AudioPlayback y VadTurnManager — permiten capturar desde el micrófono, reproducir mediante los altavoces y gestionar los turnos mediante VAD para crear agentes de voz de escritorio.

Descripción general

La canalización de audio de escritorio conecta el hardware de audio del sistema con el sistema de canalización adk-audio existente:

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

Todos los componentes utilizan el crate cpal para el audio multiplataforma (CoreAudio en macOS, PipeWire/ALSA/PulseAudio en Linux, WASAPI en Windows) y producen/consumen el tipo estándar AudioFrame.

Marcas de funcionalidad

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

La funcionalidad desktop-audio implica vad (para VadProcessor) y añade cpal como dependencia. Se excluye intencionadamente de la funcionalidad all para evitar incorporar dependencias de audio específicas de la plataforma en las compilaciones de CI.

Compatibilidad con PipeWire

En las versiones modernas de Linux (Fedora 34+, Ubuntu 22.10+, Arch), PipeWire ha reemplazado a PulseAudio y ALSA como servidor de audio predeterminado. La funcionalidad desktop-audio funciona con PipeWire de forma transparente mediante su capa de compatibilidad con ALSA; no se necesita configuración adicional.

Está previsto para una versión futura un backend nativo de PipeWire (desktop-pipewire) que utilice el crate pipewire (v0.9). PipeWire nativo proporcionaría una latencia menor y una gestión directa de sesiones. Actualmente está bloqueado por un conflicto de dependencias: redis-protocol 6.0.0 fija cookie-factory =0.3.2, mientras que pipewire's libspa requiere 0.3.3. Cuando el proyecto upstream relaje esta fijación, se podrá añadir el backend nativo.

Inicio rápido

Enumerar dispositivos de audio

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 audio del micrófono

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

Reproducir audio mediante el altavoz

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

Gestión de turnos mediante 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

Descriptor de un dispositivo de audio del sistema (entrada o salida). Contiene un id opaco y un name legible para las personas.

CaptureConfig

Configuración para la captura del micrófono:

CampoTipoPredeterminadoDescripción
sample_rateu3216000Frecuencia de muestreo en Hz
channelsu81Cantidad de canales (1=mono, 2=estéreo)
frame_duration_msu3220Duración de cada AudioFrame

Llama a validate() antes de usarlo — rechaza valores cero con AudioError::Device.

AudioCapture

Captura del micrófono mediante cpal. El método start_capture() devuelve un AudioStream (mpsc::Receiver<AudioFrame> acotado con capacidad 64). Los fotogramas se producen a intervalos de frame_duration_ms en formato PCM-16 LE.

AudioPlayback

Reproducción por el altavoz mediante cpal. El método play() pone en cola las muestras de un AudioFrame en un búfer compartido que vacía el callback de salida de cpal. Llama a stop() para liberar el dispositivo.

VadTurnManager

Consume un AudioStream, aplica VadProcessor::is_speech() a cada fotograma y emite valores VoiceActivityEvent mediante un callback registrado.

Dos modos:

  • HandsFree — detección automática de los límites del habla mediante umbrales configurables de silencio y duración del habla
  • PushToTalk — sin eventos automáticos; quien realiza la llamada controla la activación externamente

VadConfig

CampoTipoDescripción
modeVadModeHandsFree o PushToTalk
silence_threshold_msu32Silencio consecutivo antes de SpeechEnded
speech_threshold_msu32Discurso consecutivo antes de SpeechStarted

Llama a validate() antes de usarlo — rechaza umbrales cero con AudioError::Vad.

Creación de un agente de voz

El patrón completo de un agente de voz conversacional:

  1. Capturar audio del micrófono
  2. Detectar los límites del habla con VAD
  3. Transcribir el habla con GeminiStt (o cualquier SttProvider)
  4. Enviar la transcripción a un LlmAgent para el razonamiento
  5. Sintetizar la respuesta con GeminiTts (o cualquier TtsProvider)
  6. Reproducir el audio sintetizado a través del altavoz

Consulta examples/desktop_audio/src/voice_agent.rs para ver un ejemplo completo y funcional que utiliza proveedores reales de Gemini en la nube.

Seguridad de los hilos

Todos los tipos de audio de escritorio (AudioCapture, AudioPlayback, VadTurnManager) son Send + Sync, por lo que es seguro compartirlos entre tareas de Tokio.

Gestión de errores

Los errores de audio de escritorio utilizan el enum AudioError existente:

ComponenteVariante de errorCuándo
AudioCaptureAudioError::DeviceDispositivo no encontrado, host no disponible, validación de configuración
AudioPlaybackAudioError::DeviceDispositivo no encontrado, host no disponible, fallo al abrir/escribir
VadTurnManagerAudioError::VadValidación de la configuración (umbrales cero)

Compatibilidad de la plataforma

PlataformaBackend de audioEstado
macOSCoreAudioCompatible
LinuxPipeWire (mediante compatibilidad con ALSA)Compatible (instalar libasound2-dev)
LinuxALSA / PulseAudio (heredado)Compatible (instala libasound2-dev)
WindowsWASAPICompatible

En los sistemas Linux modernos, PipeWire es el servidor de audio predeterminado y ha sustituido a PulseAudio y ALSA. La funcionalidad desktop-audio funciona de forma transparente en PipeWire mediante su capa de compatibilidad con ALSA. Está previsto un backend PipeWire nativo que utilice el crate pipewire para una versión futura (actualmente bloqueado por un conflicto de dependencias ascendente en redis-protocol).

Ejemplos

Consulta examples/desktop_audio/ para ver 6 ejemplos prácticos, incluido un agente de voz conversacional completo con STT/TTS real de Gemini.

Canalización de audio de escritorio - Documentación ADK-Rust | ADK-Rust