mod error;
mod processing;
pub use error::{AudioError, AudioResult};
pub use processing::{AudioProcessingOptions, AudioProcessingType};
pub use libwebrtc::audio_source::RtcAudioSource;
use std::fmt;
use std::sync::{Arc, Weak};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RecordingDeviceId(String);
impl RecordingDeviceId {
#[doc(hidden)]
pub fn from_unchecked_guid(guid: &str) -> Self {
Self(guid.to_string())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for RecordingDeviceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PlayoutDeviceId(String);
impl PlayoutDeviceId {
#[doc(hidden)]
pub fn from_unchecked_guid(guid: &str) -> Self {
Self(guid.to_string())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for PlayoutDeviceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone)]
pub struct RecordingDeviceInfo {
pub id: RecordingDeviceId,
pub name: String,
pub index: usize,
}
#[derive(Debug, Clone)]
pub struct PlayoutDeviceInfo {
pub id: PlayoutDeviceId,
pub name: String,
pub index: usize,
}
use lazy_static::lazy_static;
use parking_lot::Mutex;
use crate::rtc_engine::lk_runtime::LkRuntime;
lazy_static! {
static ref PLATFORM_ADM_HANDLE: Mutex<Weak<PlatformAdmHandle>> = Mutex::new(Weak::new());
}
struct PlatformAdmHandle {
runtime: Arc<LkRuntime>,
}
impl Drop for PlatformAdmHandle {
fn drop(&mut self) {
log::debug!("PlatformAdmHandle dropped - releasing Platform ADM");
self.runtime.release_platform_adm();
log::info!(
"PlatformAdmHandle: released Platform ADM (ref_count now: {})",
self.runtime.platform_adm_ref_count()
);
}
}
#[derive(Clone)]
pub struct PlatformAudio {
handle: Arc<PlatformAdmHandle>,
}
impl PlatformAudio {
pub fn new() -> AudioResult<Self> {
let mut handle_ref = PLATFORM_ADM_HANDLE.lock();
if let Some(handle) = handle_ref.upgrade() {
log::debug!(
"PlatformAudio: reusing existing handle (ref_count: {})",
handle.runtime.platform_adm_ref_count()
);
return Ok(Self { handle });
}
log::debug!("PlatformAudio: creating new handle");
let runtime = LkRuntime::instance();
if !runtime.acquire_platform_adm() {
log::error!("PlatformAudio: failed to acquire Platform ADM");
return Err(AudioError::PlatformInitFailed);
}
log::info!(
"PlatformAudio: acquired Platform ADM (ref_count: {})",
runtime.platform_adm_ref_count()
);
runtime.set_adm_recording_enabled(true);
log::info!("PlatformAudio: enabled ADM recording for microphone capture");
runtime.set_adm_playout_enabled(true);
log::info!("PlatformAudio: enabled ADM playout for platform speakers");
let recording_count = runtime.recording_devices();
let playout_count = runtime.playout_devices();
log::info!(
"PlatformAudio: {} recording devices, {} playout devices",
recording_count,
playout_count
);
let handle = Arc::new(PlatformAdmHandle { runtime });
*handle_ref = Arc::downgrade(&handle);
let audio = Self { handle };
if let Err(e) = audio.configure_audio_processing(AudioProcessingOptions::default()) {
log::warn!("PlatformAudio: failed to configure audio processing: {}", e);
}
Ok(audio)
}
pub fn rtc_source(&self) -> RtcAudioSource {
RtcAudioSource::Device
}
pub fn recording_devices(&self) -> impl Iterator<Item = RecordingDeviceInfo> + '_ {
let count = self.recording_device_count();
(0..count).filter_map(move |index| self.recording_device_info(index))
}
pub fn playout_devices(&self) -> impl Iterator<Item = PlayoutDeviceInfo> + '_ {
let count = self.playout_device_count();
(0..count).filter_map(move |index| self.playout_device_info(index))
}
fn recording_device_count(&self) -> usize {
self.handle.runtime.recording_devices() as usize
}
fn playout_device_count(&self) -> usize {
self.handle.runtime.playout_devices() as usize
}
fn recording_device_info(&self, index: usize) -> Option<RecordingDeviceInfo> {
if index >= self.recording_device_count() {
return None;
}
let index = index as u16;
Some(RecordingDeviceInfo {
id: RecordingDeviceId::from_unchecked_guid(
&self.handle.runtime.recording_device_guid(index),
),
name: self.handle.runtime.recording_device_name(index),
index: index as usize,
})
}
fn playout_device_info(&self, index: usize) -> Option<PlayoutDeviceInfo> {
if index >= self.playout_device_count() {
return None;
}
let index = index as u16;
Some(PlayoutDeviceInfo {
id: PlayoutDeviceId::from_unchecked_guid(
&self.handle.runtime.playout_device_guid(index),
),
name: self.handle.runtime.playout_device_name(index),
index: index as usize,
})
}
pub fn set_recording_device(&self, id: &RecordingDeviceId) -> AudioResult<()> {
if self.handle.runtime.set_recording_device_by_guid(id.as_str()) {
Ok(())
} else {
Err(AudioError::DeviceNotFound)
}
}
pub fn set_playout_device(&self, id: &PlayoutDeviceId) -> AudioResult<()> {
let runtime = &self.handle.runtime;
if !runtime.set_playout_device_by_guid(id.as_str()) {
return Err(AudioError::DeviceNotFound);
}
Ok(())
}
pub fn switch_recording_device(&self, id: &RecordingDeviceId) -> AudioResult<()> {
let runtime = &self.handle.runtime;
let was_initialized = runtime.recording_is_initialized();
if was_initialized {
if !runtime.stop_recording() {
return Err(AudioError::OperationFailed("stop_recording failed".to_string()));
}
}
if !runtime.set_recording_device_by_guid(id.as_str()) {
return Err(AudioError::DeviceNotFound);
}
if was_initialized {
if !runtime.init_recording() {
return Err(AudioError::OperationFailed("init_recording failed".to_string()));
}
if !runtime.start_recording() {
return Err(AudioError::OperationFailed("start_recording failed".to_string()));
}
}
Ok(())
}
pub fn switch_playout_device(&self, id: &PlayoutDeviceId) -> AudioResult<()> {
let runtime = &self.handle.runtime;
let was_initialized = runtime.playout_is_initialized();
if was_initialized {
if !runtime.stop_playout() {
return Err(AudioError::OperationFailed("stop_playout failed".to_string()));
}
}
if !runtime.set_playout_device_by_guid(id.as_str()) {
return Err(AudioError::DeviceNotFound);
}
if was_initialized {
if !runtime.init_playout() {
return Err(AudioError::OperationFailed("init_playout failed".to_string()));
}
if !runtime.start_playout() {
return Err(AudioError::OperationFailed("start_playout failed".to_string()));
}
}
Ok(())
}
pub fn start_recording(&self) -> AudioResult<()> {
let runtime = &self.handle.runtime;
if !runtime.recording_is_initialized() {
if !runtime.init_recording() {
return Err(AudioError::OperationFailed("init_recording failed".to_string()));
}
}
if runtime.start_recording() {
log::info!("PlatformAudio: started recording");
Ok(())
} else {
Err(AudioError::OperationFailed("start_recording failed".to_string()))
}
}
pub fn stop_recording(&self) -> AudioResult<()> {
let runtime = &self.handle.runtime;
if runtime.stop_recording() {
log::info!("PlatformAudio: stopped recording");
Ok(())
} else {
Err(AudioError::OperationFailed("stop_recording failed".to_string()))
}
}
pub fn is_recording_initialized(&self) -> bool {
self.handle.runtime.recording_is_initialized()
}
pub fn ref_count(&self) -> usize {
Arc::strong_count(&self.handle)
}
pub fn release(self) {
drop(self);
}
pub fn is_hardware_aec_available(&self) -> bool {
self.handle.runtime.builtin_aec_is_available()
}
pub fn is_hardware_agc_available(&self) -> bool {
self.handle.runtime.builtin_agc_is_available()
}
pub fn is_hardware_ns_available(&self) -> bool {
self.handle.runtime.builtin_ns_is_available()
}
pub fn active_aec_type(&self) -> AudioProcessingType {
if self.is_hardware_aec_available() {
AudioProcessingType::Hardware
} else {
AudioProcessingType::Software
}
}
pub fn active_agc_type(&self) -> AudioProcessingType {
if self.is_hardware_agc_available() {
AudioProcessingType::Hardware
} else {
AudioProcessingType::Software
}
}
pub fn active_ns_type(&self) -> AudioProcessingType {
if self.is_hardware_ns_available() {
AudioProcessingType::Hardware
} else {
AudioProcessingType::Software
}
}
pub fn configure_audio_processing(&self, options: AudioProcessingOptions) -> AudioResult<()> {
let runtime = &self.handle.runtime;
let use_hardware = options.prefer_hardware_processing;
if runtime.builtin_aec_is_available() {
let enable_hw = use_hardware && options.echo_cancellation;
if !runtime.enable_builtin_aec(enable_hw) {
log::warn!("enable_builtin_aec({}) failed", enable_hw);
}
}
if runtime.builtin_agc_is_available() {
let enable_hw = use_hardware && options.auto_gain_control;
if !runtime.enable_builtin_agc(enable_hw) {
log::warn!("enable_builtin_agc({}) failed", enable_hw);
}
}
if runtime.builtin_ns_is_available() {
let enable_hw = use_hardware && options.noise_suppression;
if !runtime.enable_builtin_ns(enable_hw) {
log::warn!("enable_builtin_ns({}) failed", enable_hw);
}
}
log::info!(
"Audio processing configured: AEC={}, AGC={}, NS={}, prefer_hw={}",
options.echo_cancellation,
options.auto_gain_control,
options.noise_suppression,
options.prefer_hardware_processing
);
Ok(())
}
pub fn set_echo_cancellation(&self, enable: bool, prefer_hardware: bool) -> AudioResult<()> {
if self.is_hardware_aec_available() {
let enable_hw = enable && prefer_hardware;
if !self.handle.runtime.enable_builtin_aec(enable_hw) {
return Err(AudioError::OperationFailed("enable_builtin_aec failed".to_string()));
}
}
Ok(())
}
pub fn set_auto_gain_control(&self, enable: bool, prefer_hardware: bool) -> AudioResult<()> {
if self.is_hardware_agc_available() {
let enable_hw = enable && prefer_hardware;
if !self.handle.runtime.enable_builtin_agc(enable_hw) {
return Err(AudioError::OperationFailed("enable_builtin_agc failed".to_string()));
}
}
Ok(())
}
pub fn set_noise_suppression(&self, enable: bool, prefer_hardware: bool) -> AudioResult<()> {
if self.is_hardware_ns_available() {
let enable_hw = enable && prefer_hardware;
if !self.handle.runtime.enable_builtin_ns(enable_hw) {
return Err(AudioError::OperationFailed("enable_builtin_ns failed".to_string()));
}
}
Ok(())
}
}
impl fmt::Debug for PlatformAudio {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PlatformAudio")
.field("ref_count", &self.ref_count())
.field("recording_device_count", &self.recording_device_count())
.field("playout_device_count", &self.playout_device_count())
.finish()
}
}
pub fn reset_platform_audio() {
let mut handle_ref = PLATFORM_ADM_HANDLE.lock();
*handle_ref = Weak::new();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rtc_audio_source_device_variant() {
let source = RtcAudioSource::Device;
assert!(matches!(source, RtcAudioSource::Device));
}
}