خط أنابيب الصوت لسطح المكتب

توفر حزمة adk-audio إدخال/إخراج الصوت عبر سطح المكتب على مختلف المنصات خلف علامة الميزات desktop-audio. وتتيح ثلاثة مكونات — AudioCapture وAudioPlayback وVadTurnManager — التقاط الصوت من الميكروفون، وتشغيل الصوت عبر مكبرات الصوت، وتناوب الأدوار المعتمد على VAD لبناء وكلاء صوتيين لسطح المكتب.

نظرة عامة

يربط خط أنابيب الصوت لسطح المكتب أجهزة الصوت في النظام بنظام خطوط الأنابيب adk-audio الحالي:

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

تستخدم جميع المكونات حزمة cpal للصوت عبر مختلف المنصات (CoreAudio على macOS، وPipeWire/ALSA/PulseAudio على Linux، وWASAPI على Windows)، وتنتج/تستهلك النوع القياسي AudioFrame.

علامات الميزات

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

تتضمن ميزة desktop-audio ضمنيًا vad (من أجل VadProcessor)، وتضيف cpal باعتبارها تبعية. وقد استُبعدت عمدًا من ميزة all لتجنب جلب تبعيات الصوت الخاصة بالمنصة إلى عمليات إنشاء CI.

دعم PipeWire

في أنظمة Linux الحديثة (Fedora 34+ وUbuntu 22.10+ وArch)، حلّ PipeWire محل PulseAudio وALSA باعتباره خادم الصوت الافتراضي. تعمل ميزة desktop-audio على PipeWire بشفافية عبر طبقة التوافق مع ALSA، من دون الحاجة إلى إعداد إضافي.

من المخطط إضافة واجهة خلفية أصلية لـ PipeWire (desktop-pipewire) تستخدم حزمة pipewire (v0.9) في إصدار مستقبلي. سيوفر PipeWire الأصلي زمن استجابة أقل وإدارة مباشرة للجلسات. وهو محجوب حاليًا بسبب تعارض في التبعيات: إذ تثبّت redis-protocol 6.0.0 الإصدار cookie-factory =0.3.2، بينما تتطلب libspa الخاصة بـpipewire الإصدار 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_rateu3216000معدل أخذ العينات بالهرتز
channelsu81عدد القنوات (1=أحادي، 2=استريو)
frame_duration_msu3220مدة كل AudioFrame

استدعِ validate() قبل الاستخدام — يرفض القيم الصفرية باستخدام AudioError::Device.

AudioCapture

التقاط الصوت من الميكروفون عبر cpal. تُرجع طريقة start_capture() قيمة AudioStream (وهي mpsc::Receiver<AudioFrame> محدودة السعة بسعة 64). تُنتَج الإطارات بفواصل زمنية قدرها 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_msu32الصمت المتواصل قبل SpeechEnded
speech_threshold_msu32الكلام المتتالي قبل SpeechStarted

استدعِ validate() قبل الاستخدام — إذ يرفض العتبات الصفرية باستخدام AudioError::Vad.

بناء وكيل صوتي

النمط الكامل لوكيل صوتي تحاوري:

  1. التقاط الصوت من الميكروفون
  2. اكتشاف حدود الكلام باستخدام VAD
  3. تحويل الكلام إلى نص باستخدام GeminiStt (أو أي SttProvider)
  4. إرسال النص إلى LlmAgent للاستدلال
  5. توليف الاستجابة باستخدام GeminiTts (أو أي TtsProvider)
  6. تشغيل الصوت المُولَّف عبر مكبر الصوت

راجع examples/desktop_audio/src/voice_agent.rs للحصول على مثال عملي كامل يستخدم موفّري Gemini السحابيين الفعليين.

أمان الخيوط

جميع أنواع الصوت على سطح المكتب (AudioCapture وAudioPlayback وVadTurnManager) هي Send + Sync، مما يجعل مشاركتها آمنة عبر مهام Tokio.

معالجة الأخطاء

تستخدم أخطاء الصوت على سطح المكتب تعداد AudioError الموجود:

المكوّنصيغة الخطأمتى
AudioCaptureAudioError::Deviceالجهاز غير موجود، المضيف غير متاح، التحقق من صحة الإعدادات
AudioPlaybackAudioError::Deviceالجهاز غير موجود، المضيف غير متاح، فشل الفتح/الكتابة
VadTurnManagerAudioError::Vadالتحقق من الإعدادات (الحدود الدنيا الصفرية)

دعم المنصة

المنصةالواجهة الخلفية للصوتالحالة
macOSCoreAudioمدعوم
LinuxPipeWire (عبر توافق ALSA)مدعوم (ثبّت libasound2-dev)
LinuxALSA / PulseAudio (قديم)مدعوم (ثبّت libasound2-dev)
WindowsWASAPIمدعوم

في أنظمة Linux الحديثة، يُعدّ PipeWire خادم الصوت الافتراضي، وقد حلّ محلّ PulseAudio وALSA. تعمل ميزة desktop-audio على PipeWire بشفافية من خلال طبقة التوافق مع ALSA الخاصة بها. ومن المخطط توفير خلفية PipeWire أصلية تستخدم crate ‏pipewire في إصدار مستقبلي (وهي محجوبة حاليًا بسبب تعارض في التبعيات upstream داخل redis-protocol).

أمثلة

راجع examples/desktop_audio/ للاطلاع على 6 أمثلة عملية، بما في ذلك وكيل صوتي حواري متكامل باستخدام Gemini STT/TTS الحقيقي.

خط أنابيب الصوت لسطح المكتب - وثائق ADK-Rust | ADK-Rust