デスクトップオーディオパイプライン

adk-audio クレートは、desktop-audio機能フラグの背後でクロスプラットフォームのデスクトップオーディオ I/O を提供します。3 つのコンポーネント — AudioCaptureAudioPlaybackVadTurnManager — により、マイク入力、スピーカー再生、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 機能は、vadVadProcessor 用)を含み、依存関係として 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() を呼び出してください — ゼロ値は 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 値を出力します。

2 つのモードがあります。

  • HandsFree — 無音および発話の継続時間について設定可能なしきい値を使用した、自動的な音声境界検出
  • PushToTalk — 自動イベントは発生せず、呼び出し側が外部でゲーティングを制御

VadConfig

フィールド説明
modeVadModeHandsFree または PushToTalk
silence_threshold_msu32SpeechEnded の前の連続した無音
speech_threshold_msu32SpeechStarted 前の連続発話

使用前にvalidate()を呼び出してください — ゼロのしきい値はAudioError::Vadで拒否されます。

音声エージェントの構築

完全な会話型音声エージェントのパターン:

  1. マイクから音声を取り込む
  2. VADで発話の境界を検出する
  3. GeminiStt(または任意のSttProvider)で音声を書き起こす
  4. 推論のために書き起こし結果をLlmAgentへ送信する
  5. GeminiTts(または任意のTtsProvider)で応答を音声合成する
  6. 合成した音声をスピーカーから再生する

実際のGeminiクラウドプロバイダーを使用した、完全に動作する例についてはexamples/desktop_audio/src/voice_agent.rsを参照してください。

スレッドセーフティ

すべてのデスクトップ音声型(AudioCaptureAudioPlaybackVadTurnManager)はSend + Syncであり、Tokioタスク間で安全に共有できます。

エラー処理

デスクトップ音声エラーには、既存のAudioError列挙型を使用します:

コンポーネントエラーの種類発生時
AudioCaptureAudioError::Deviceデバイスが見つからない、ホストが利用できない、設定の検証
AudioPlaybackAudioError::Deviceデバイスが見つからない、ホストが利用できない、オープンまたは書き込みに失敗
VadTurnManagerAudioError::Vad設定の検証(しきい値ゼロ)

プラットフォームサポート

プラットフォームオーディオバックエンド状態
macOSCoreAudio対応
LinuxPipeWire(ALSA 互換経由)対応(libasound2-devをインストール)
LinuxALSA / PulseAudio (legacy)対応(libasound2-dev をインストール)
WindowsWASAPI対応

現代の Linux では、PipeWire がデフォルトのオーディオサーバーであり、PulseAudio と ALSA に取って代わっています。desktop-audio 機能は、ALSA 互換レイヤーを介して PipeWire 上で透過的に動作します。pipewire crate を使用するネイティブな PipeWire バックエンドは、将来のリリースで提供される予定です(現在は redis-protocol における上流依存関係の競合によりブロックされています)。

実際的な 6 つの例(Gemini の実際の STT/TTS を使用した完全な会話型音声エージェントを含む)については、examples/desktop_audio/ を参照してください。