Avoid C++ layer for device enumeration / selection

This commit is contained in:
Miriam Zimmerman 2026-04-13 17:43:45 -04:00 committed by GitHub
parent 89142ba94e
commit 41f5579438
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 176 additions and 353 deletions

View File

@ -1,4 +1,4 @@
webrtc.version=7680b
webrtc.version=7680c
ringrtc.version.major=2
ringrtc.version.minor=67

View File

@ -1,10 +1,10 @@
{
"android": "77cdda613ba93a5e004c5dd3ac23796b2e0fa49f4c5831ccdf6d381185f0bc44",
"ios": "3bcdd8c7ad561c4ace992e833bc6fccab3b170149308d8179f78913e6c320aff",
"linux-x64": "eb3f2bf3662b1973dd35aebe7dea38b040611c70df36da9238dc6c4319b16cc3",
"linux-arm64": "00295d10fabf9549421615b68dba0708131ce21eb76cb5a264bd01096a2fbd8d",
"mac-x64": "1eec62aaa10b7f986258a24ae85f9e1d639f125875f15f8299d63892377f0692",
"mac-arm64": "a571fb11f99ca861b525babe494782cab9b9b6b0cd997c906916a701d68bc430",
"windows-x64": "b4ceaa19c64fe421aae541e04c091f73f1621c2ed0819c9d1c1d053aa99dd264",
"windows-arm64": "5ca83c56d7a43d4a57d53a36100b939cdd58d1f7e8cccfd0e73985fad76103ab"
"android": "8be76daee99a7ea273683dbcd396f92db4d7aa0a7a0aac61631cd53693322cf0",
"ios": "665f1f19f3de74444c7953f70d599a94c57113374ac62d558f9bce11dc21d1e8",
"linux-x64": "74b2e7f9e36b24e81eb06df067e9459d97a9f4d89e1d10dc81c766fb823d1f51",
"linux-arm64": "77ee9067a80db4556b02f1f8d97a363ddd2f53499c7e0b1c945bc753d840458d",
"mac-x64": "217bc4868e2751b51cd05e332fc87f219b314c8312e1cf02ec75d3be20eefdcc",
"mac-arm64": "60536d4de2926fe5a71dae57d671f8d43e1b6cbbb55e555e3cfd79b40c6e6f48",
"windows-x64": "c28a52dc851655b45bcc90251b2cc32aa0741e5b34c9ec6ec3ecefbe970f9ce6",
"windows-arm64": "0cafb1d30500fe4f3dca465202d6bffb3c54e2ae5dc6e05ce638d600d132b60a"
}

View File

@ -31,7 +31,6 @@ use crate::{
audio_device_module_utils::{
DeviceCollectionWrapper, copy_and_truncate_string, redact_by_regex, redact_for_logging,
},
ffi::audio_device_module::RffiAudioTransport,
peer_connection_factory::{AudioDevice, AudioDeviceObserver},
},
};
@ -82,7 +81,6 @@ type OutFrame = StereoFrame<i16>;
#[derive(Debug)]
enum Event {
RefreshCache(DeviceType),
SetCallback(Arc<Mutex<RffiAudioTransport>>),
SetPlayoutDevice(u16),
SetPlayoutDeviceById(String),
SetRecordingDevice(u16),
@ -123,7 +121,6 @@ struct Worker {
// These may outlive the ctx
input_device_names: Arc<Mutex<Vec<Option<AudioDevice>>>>,
output_device_names: Arc<Mutex<Vec<Option<AudioDevice>>>>,
audio_transport: Arc<Mutex<RffiAudioTransport>>,
audio_device_observer: Option<Box<dyn AudioDeviceObserver>>,
send_to_webrtc: Arc<AtomicBool>,
}
@ -279,7 +276,6 @@ impl Worker {
.prefs(StreamPrefs::VOICE)
.take();
let mut builder = cubeb::StreamBuilder::<OutFrame>::new();
let transport = Arc::clone(&self.audio_transport);
let min_latency = self.ctx.min_latency(&params).unwrap_or_else(|e| {
warn!(
"Could not get min latency for playout; using default: {:?}",
@ -321,12 +317,8 @@ impl Worker {
// Then, request more data from WebRTC.
while written < output.len() {
let play_data = Worker::need_more_play_data(
Arc::clone(&transport),
WEBRTC_WINDOW,
NUM_CHANNELS,
SAMPLE_FREQUENCY,
);
let play_data =
Worker::need_more_play_data(WEBRTC_WINDOW, NUM_CHANNELS, SAMPLE_FREQUENCY);
if play_data.success < 0 {
// C function failed; propagate error and don't continue.
return play_data.success as isize;
@ -420,7 +412,6 @@ impl Worker {
}
.take();
let mut builder = cubeb::StreamBuilder::<Frame>::new();
let transport = Arc::clone(&self.audio_transport);
let min_latency = self.ctx.min_latency(&params).unwrap_or_else(|e| {
warn!(
"Could not get min latency for recording; using default: {:?}",
@ -463,7 +454,6 @@ impl Worker {
break;
}
let (ret, _new_mic_level) = Worker::recorded_data_is_available(
Arc::clone(&transport),
chunk.to_vec(),
NUM_CHANNELS,
SAMPLE_FREQUENCY,
@ -551,10 +541,6 @@ impl Worker {
for received in receiver {
if let Err(e) = match received {
Event::RefreshCache(d) => self.refresh_device_cache(d),
Event::SetCallback(ref transport) => {
self.audio_transport = transport.clone();
Ok(())
}
Event::SetPlayoutDevice(index) => {
if let Some(d) = self.output_device_cache.get(index.into()) {
self.playout_device = Some(d.devid);
@ -646,7 +632,6 @@ impl Worker {
#[allow(clippy::too_many_arguments)]
fn recorded_data_is_available(
rffi_audio_transport: Arc<Mutex<RffiAudioTransport>>,
samples: Vec<i16>,
channels: u32,
samples_per_sec: u32,
@ -659,23 +644,13 @@ impl Worker {
let mut new_mic_level = 0u32;
let estimated_capture_time_ns = estimated_capture_time.map_or(-1, |d| d.as_nanos() as i64);
let guard = match rffi_audio_transport.lock() {
Ok(g) => g,
Err(e) => {
error!("Failed to get mutex: {:?}", e);
return (-1, 0);
}
};
// Safety:
// * self.audio_transport is within self, and will remain valid while this function is running
// because we enforce that the callback cannot change while playing or recording.
// * The vector has sizeof(i16) * samples bytes allocated, and we pass both of these
// to the C layer, which should not read beyond that bound.
// * The local new_mic_level pointer is valid and this function is synchronous, so it'll
// remain valid while it runs.
let ret = unsafe {
crate::webrtc::ffi::audio_device_module::Rust_recordedDataIsAvailable(
guard.callback,
samples.as_ptr() as *const c_void,
samples.len(),
std::mem::size_of::<i16>(),
@ -692,39 +667,19 @@ impl Worker {
(ret, new_mic_level)
}
fn need_more_play_data(
rffi_audio_transport: Arc<Mutex<RffiAudioTransport>>,
samples: usize,
channels: u32,
samples_per_sec: u32,
) -> PlayData {
fn need_more_play_data(samples: usize, channels: u32, samples_per_sec: u32) -> PlayData {
let mut data = vec![0i16; samples];
let mut samples_out = 0usize;
let mut elapsed_time_ms = 0i64;
let mut ntp_time_ms = 0i64;
let guard = match rffi_audio_transport.lock() {
Ok(g) => g,
Err(e) => {
error!("Failed to get mutex: {:?}", e);
return PlayData {
success: -1,
data: Vec::new(),
elapsed_time: None,
ntp_time: None,
};
}
};
// Safety:
// * rffi_audio_transport will remain valid while this function is running
// because we enforce that the callback cannot change while playing or recording.
// * The vector has sizeof(i16) * samples bytes allocated, and we pass both of these
// to the C layer, which should not write beyond that bound.
// * The local variable pointers are all valid and this function is synchronous, so they'll
// remain valid while it runs.
let ret = unsafe {
crate::webrtc::ffi::audio_device_module::Rust_needMorePlayData(
guard.callback,
samples,
std::mem::size_of::<i16>(),
channels.try_into().unwrap(), // constant, so unwrap is safe
@ -811,9 +766,6 @@ impl Worker {
output_device_cache: Default::default(),
input_device_names,
output_device_names,
audio_transport: Arc::new(Mutex::new(RffiAudioTransport {
callback: std::ptr::null(),
})),
audio_device_observer: None,
send_to_webrtc: Arc::new(AtomicBool::new(true)),
};
@ -1075,27 +1027,6 @@ impl AudioDeviceModule {
-1
}
pub fn register_audio_callback(&mut self, audio_transport: *const c_void) -> i32 {
// It is unsafe to change this callback while playing or recording, as
// the change might then race with invocations of the callback, which
// need not be serialized.
if self.playing() || self.recording() {
return -1;
}
if let Err(e) = self
.mpsc_sender
.send(Event::SetCallback(std::sync::Arc::new(Mutex::new(
RffiAudioTransport {
callback: audio_transport,
},
))))
{
error!("Failed to request SetCallback: {}", e);
return -1;
}
0
}
// Main initialization and termination
pub fn init(&mut self) -> i32 {
// Don't bother re-initializing -- new handled it.
@ -1465,14 +1396,6 @@ impl AudioDeviceModule {
0
}
// Not a chromium interface function
pub fn warmup_recording(&mut self) -> anyhow::Result<()> {
if let Err(e) = self.mpsc_sender.send(Event::WarmupRecording) {
return Err(anyhow!("Failed to request WarmupRecording: {}", e));
}
Ok(())
}
pub fn stop_recording(&mut self) -> i32 {
self.attempted_recording_init = false;
self.attempted_recording_start = false;
@ -1664,6 +1587,8 @@ impl AudioDeviceModule {
}
}
// The below are inaccessible to C++, so they can use more complex rust types
// Register a new callback to be notified of audio device changes, **replacing any existing one**
pub fn register_audio_device_callback(
&mut self,
@ -1677,6 +1602,21 @@ impl AudioDeviceModule {
}
Ok(())
}
pub fn get_audio_playout_devices(&mut self) -> anyhow::Result<Vec<Option<AudioDevice>>> {
self.enumerate_devices(DeviceType::OUTPUT)
}
pub fn get_audio_recording_devices(&mut self) -> anyhow::Result<Vec<Option<AudioDevice>>> {
self.enumerate_devices(DeviceType::INPUT)
}
pub fn warmup_recording(&mut self) -> anyhow::Result<()> {
if let Err(e) = self.mpsc_sender.send(Event::WarmupRecording) {
return Err(anyhow!("Failed to request WarmupRecording: {}", e));
}
Ok(())
}
}
#[cfg(test)]

View File

@ -14,18 +14,9 @@ use libc::size_t;
use crate::{
webrtc,
webrtc::audio_device_module::{AudioDeviceModule, AudioLayer, WindowsDeviceType},
webrtc::audio_device_module::{AudioDeviceModule, AudioLayer},
};
/// Wrapper type for C++ AudioTransport.
#[derive(Debug, Copy, Clone)]
pub struct RffiAudioTransport {
pub callback: *const c_void,
}
// Safety: managed by not allowing changes to the transport while playout or recording are in progress.
unsafe impl Send for RffiAudioTransport {}
/// all_adm_functions is a higher-level macro that enables "tt muncher" macros
/// The list of functions MUST be kept in sync with AudioDeviceCallbacks in webrtc C++, and
/// in particular the order must match.
@ -34,8 +25,6 @@ macro_rules! all_adm_functions {
$macro!(
active_audio_layer(audio_layer: webrtc::ptr::Borrowed<AudioLayer>) -> i32;
register_audio_callback(audio_callback: *const c_void) -> i32;
// Main initialization and termination
init() -> i32;
terminate() -> i32;
@ -47,13 +36,6 @@ macro_rules! all_adm_functions {
playout_device_name(index: u16, name: webrtc::ptr::Borrowed<c_uchar>, guid: webrtc::ptr::Borrowed<c_uchar>) -> i32;
recording_device_name(index: u16, name: webrtc::ptr::Borrowed<c_uchar>, guid: webrtc::ptr::Borrowed<c_uchar>) -> i32;
// Device selection
set_playout_device(index: u16) -> i32;
set_playout_device_win(device: WindowsDeviceType) -> i32;
set_recording_device(index: u16) -> i32;
set_recording_device_win(device: WindowsDeviceType) -> i32;
// Audio transport initialization
playout_is_available(available: webrtc::ptr::Borrowed<bool>) -> i32;
init_playout() -> i32;
@ -243,7 +225,6 @@ pub unsafe extern "C" fn decrement_adm_ref_count(adm_borrowed: webrtc::ptr::Borr
unsafe extern "C" {
pub fn Rust_recordedDataIsAvailable(
audio_transport: *const c_void,
audio_samples: *const c_void,
n_samples: size_t,
n_bytes_per_sample: size_t,
@ -258,7 +239,6 @@ unsafe extern "C" {
) -> i32;
pub fn Rust_needMorePlayData(
audio_transport: *const c_void,
n_samples: size_t,
n_bytes_per_sample: size_t,
n_channels: size_t,

View File

@ -72,36 +72,4 @@ unsafe extern "C" {
factory: webrtc::ptr::BorrowedRc<RffiPeerConnectionFactoryOwner>,
source: webrtc::ptr::BorrowedRc<RffiVideoSource>,
) -> webrtc::ptr::OwnedRc<RffiVideoTrack>;
#[cfg(feature = "native")]
pub fn Rust_getAudioPlayoutDevices(
factory: webrtc::ptr::BorrowedRc<RffiPeerConnectionFactoryOwner>,
) -> i16;
#[cfg(feature = "native")]
pub fn Rust_getAudioPlayoutDeviceName(
factory: webrtc::ptr::BorrowedRc<RffiPeerConnectionFactoryOwner>,
index: u16,
name_out: *mut c_char,
uuid_out: *mut c_char,
) -> i32;
#[cfg(feature = "native")]
pub fn Rust_setAudioPlayoutDevice(
factory: webrtc::ptr::BorrowedRc<RffiPeerConnectionFactoryOwner>,
index: u16,
) -> bool;
#[cfg(feature = "native")]
pub fn Rust_getAudioRecordingDevices(
factory: webrtc::ptr::BorrowedRc<RffiPeerConnectionFactoryOwner>,
) -> i16;
#[cfg(feature = "native")]
pub fn Rust_getAudioRecordingDeviceName(
factory: webrtc::ptr::BorrowedRc<RffiPeerConnectionFactoryOwner>,
index: u16,
name_out: *mut c_char,
uuid_out: *mut c_char,
) -> i32;
#[cfg(feature = "native")]
pub fn Rust_setAudioRecordingDevice(
factory: webrtc::ptr::BorrowedRc<RffiPeerConnectionFactoryOwner>,
index: u16,
) -> bool;
}

View File

@ -5,8 +5,6 @@
//! WebRTC Peer Connection
#[cfg(feature = "native")]
use std::ffi::CStr;
#[cfg(all(not(feature = "sim"), feature = "native"))]
use std::ffi::c_void;
#[cfg(all(not(feature = "sim"), feature = "native"))]
@ -38,11 +36,6 @@ use crate::{
},
};
#[cfg(feature = "native")]
const ADM_MAX_DEVICE_NAME_SIZE: usize = 128;
#[cfg(feature = "native")]
const ADM_MAX_DEVICE_UUID_SIZE: usize = 128;
#[repr(C)]
pub struct RffiIceServer {
pub username: webrtc::ptr::Borrowed<c_char>,
@ -278,8 +271,8 @@ impl AudioJitterBufferConfig {
#[cfg(feature = "native")]
#[derive(Clone, Debug, Default)]
pub struct DeviceCounts {
playout: Option<u16>,
recording: Option<u16>,
playout: Option<usize>,
recording: Option<usize>,
}
/// Rust wrapper around WebRTC C++ PeerConnectionFactory object.
@ -458,99 +451,43 @@ impl PeerConnectionFactory {
Ok(VideoTrack::new(rffi, Some(self.rffi.clone())))
}
#[cfg(feature = "native")]
fn get_audio_playout_device(&self, index: u16) -> Result<AudioDevice> {
let mut name_buf = [0; ADM_MAX_DEVICE_NAME_SIZE];
let mut unique_id_buf = [0; ADM_MAX_DEVICE_UUID_SIZE];
let rc = unsafe {
pcf::Rust_getAudioPlayoutDeviceName(
self.rffi.as_borrowed(),
index,
name_buf.as_mut_ptr(),
unique_id_buf.as_mut_ptr(),
)
};
if rc != 0 {
error!("getAudioPlayoutDeviceName({}) failed: {}", index, rc);
return Err(RingRtcError::QueryAudioDevices.into());
}
// SAFETY: the buffer pointers will be valid until the end of the scope,
// and they should contain valid C strings if the return code indicated success.
let name = unsafe { CStr::from_ptr(name_buf.as_ptr()) }
.to_string_lossy()
.into_owned();
let unique_id = unsafe { CStr::from_ptr(unique_id_buf.as_ptr()) }
.to_string_lossy()
.into_owned();
Ok(AudioDevice {
name,
unique_id,
i18n_key: "".to_string(),
})
}
#[cfg(feature = "native")]
pub fn get_audio_playout_devices(&mut self) -> Result<Vec<AudioDevice>> {
let device_count = unsafe { pcf::Rust_getAudioPlayoutDevices(self.rffi.as_borrowed()) };
if device_count < 0 {
error!("getAudioPlayoutDevices() returned {}", device_count);
return Err(RingRtcError::QueryAudioDevices.into());
}
let device_count = device_count as u16;
let mut devices = Vec::<AudioDevice>::new();
let devices = self
.adm
.as_ref()
.and_then(|adm| adm.lock().ok())
.map_or(Err(anyhow!("couldn't access ADM")), |mut adm| {
adm.get_audio_playout_devices()
})?;
#[cfg(target_os = "windows")]
// If there is at least one real device, add slots for the "default" and
// "default communications" device. When setting, the ADM already has them,
// but doesn't include them in the count.
let device_count = if device_count > 0 {
device_count + 2
#[allow(unused_mut)] // Only need mut on windows
if let Some(mut devices) = devices.into_iter().collect::<Option<Vec<_>>>() {
if self.device_counts.playout != Some(devices.len()) {
info!(
"PeerConnectionFactory::get_audio_playout_devices(): device_count: {}",
devices.len()
);
self.device_counts.playout = Some(devices.len());
}
#[cfg(target_os = "windows")]
if devices.len() > 1 {
// Swap the first two devices, so that the "default communications" device
// is first and the "default" device is second. The UI treats the first
// index as the default, which for VoIP we prefer communications devices.
devices.swap(0, 1);
// Also, give both of those artificial slots unique ids so that
// the UI can manage them correctly.
devices[0].unique_id.push_str("-0");
devices[1].unique_id.push_str("-1");
}
Ok(devices)
} else {
0
};
if self.device_counts.playout != Some(device_count) {
info!(
"PeerConnectionFactory::get_audio_playout_devices(): device_count: {}",
device_count
);
self.device_counts.playout = Some(device_count);
Err(RingRtcError::QueryAudioDevices.into())
}
for i in 0..device_count {
match self.get_audio_playout_device(i) {
Ok(dev) => devices.push(dev),
Err(fail) => {
error!("getAudioPlayoutDevice({}) failed: {}", i, fail);
return Err(fail);
}
}
}
// For devices missing unique_id, populate them with name + index
for i in 0..devices.len() {
if devices[i].unique_id.is_empty() {
let same_name_count = devices[..i]
.iter()
.filter(|d| d.name == devices[i].name)
.count() as u16;
devices[i].unique_id = format!("{}-{}", devices[i].name, same_name_count);
}
}
#[cfg(target_os = "windows")]
if devices.len() > 1 {
// Swap the first two devices, so that the "default communications" device
// is first and the "default" device is second. The UI treats the first
// index as the default, which for VoIP we prefer communications devices.
devices.swap(0, 1);
// Also, give both of those artificial slots unique ids so that
// the UI can manage them correctly.
devices[0].unique_id.push_str("-0");
devices[1].unique_id.push_str("-1");
}
Ok(devices)
}
#[cfg(feature = "native")]
@ -565,118 +502,89 @@ impl PeerConnectionFactory {
info!("PeerConnectionFactory::set_audio_playout_device({})", index);
let ok = unsafe { pcf::Rust_setAudioPlayoutDevice(self.rffi.as_borrowed(), index) };
if ok {
Ok(())
} else {
error!("setAudioPlayoutDevice({}) failed", index);
Err(RingRtcError::SetAudioDevice.into())
}
self.adm.as_ref().and_then(|adm| adm.lock().ok()).map_or(
Err(anyhow!("couldn't access ADM")),
|mut adm| {
// We need to stop and restart playout if it's already in progress.
let was_initialized = adm.playout_is_initialized();
let was_playing = adm.playing();
if was_initialized && adm.stop_playout() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
if adm.set_playout_device(index) != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
if was_initialized && adm.init_playout() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
if was_playing && adm.start_playout() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
Ok(())
},
)
}
#[cfg(all(not(feature = "sim"), feature = "native"))]
pub fn set_audio_playout_device_by_id(&mut self, device_id: &str) -> Result<()> {
self.adm
.as_ref()
.and_then(|adm| adm.lock().ok())
.map_or(Err(anyhow!("couldn't access ADM")), |mut adm| {
adm.set_playout_device_by_id(device_id)
})
}
#[cfg(feature = "native")]
fn get_audio_recording_device(&self, index: u16) -> Result<AudioDevice> {
let mut name_buf = [0; ADM_MAX_DEVICE_NAME_SIZE];
let mut unique_id_buf = [0; ADM_MAX_DEVICE_UUID_SIZE];
let rc = unsafe {
pcf::Rust_getAudioRecordingDeviceName(
self.rffi.as_borrowed(),
index,
name_buf.as_mut_ptr(),
unique_id_buf.as_mut_ptr(),
)
};
if rc != 0 {
error!("getAudioRecordingDeviceName({}) failed: {}", index, rc);
return Err(RingRtcError::QueryAudioDevices.into());
}
// SAFETY: the buffer pointers will be valid until the end of the scope,
// and they should contain valid C strings if the return code indicated success.
let name = unsafe { CStr::from_ptr(name_buf.as_ptr()) }
.to_string_lossy()
.into_owned();
let unique_id = unsafe { CStr::from_ptr(unique_id_buf.as_ptr()) }
.to_string_lossy()
.into_owned();
Ok(AudioDevice {
name,
unique_id,
i18n_key: "".to_string(),
})
self.adm.as_ref().and_then(|adm| adm.lock().ok()).map_or(
Err(anyhow!("couldn't access ADM")),
|mut adm| {
// We need to stop and restart playout if it's already in progress.
let was_initialized = adm.playout_is_initialized();
let was_playing = adm.playing();
if was_initialized && adm.stop_playout() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
adm.set_playout_device_by_id(device_id)?;
if was_initialized && adm.init_playout() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
if was_playing && adm.start_playout() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
Ok(())
},
)
}
#[cfg(feature = "native")]
pub fn get_audio_recording_devices(&mut self) -> Result<Vec<AudioDevice>> {
let device_count = unsafe { pcf::Rust_getAudioRecordingDevices(self.rffi.as_borrowed()) };
if device_count < 0 {
error!("getAudioRecordingDevices() returned {}", device_count);
return Err(RingRtcError::QueryAudioDevices.into());
}
let device_count = device_count as u16;
let mut devices = Vec::<AudioDevice>::new();
let devices = self
.adm
.as_ref()
.and_then(|adm| adm.lock().ok())
.map_or(Err(anyhow!("couldn't access ADM")), |mut adm| {
adm.get_audio_recording_devices()
})?;
#[cfg(target_os = "windows")]
// If there is at least one real device, add slots for the "default" and
// "default communications" device. When setting, the ADM already has them,
// but doesn't include them in the count.
let device_count = if device_count > 0 {
device_count + 2
#[allow(unused_mut)] // Only need mut on windows
if let Some(mut devices) = devices.into_iter().collect::<Option<Vec<_>>>() {
if self.device_counts.recording != Some(devices.len()) {
info!(
"PeerConnectionFactory::get_audio_recording_devices(): device_count: {}",
devices.len()
);
self.device_counts.recording = Some(devices.len());
}
#[cfg(target_os = "windows")]
if devices.len() > 1 {
// Swap the first two devices, so that the "default communications" device
// is first and the "default" device is second. The UI treats the first
// index as the default, which for VoIP we prefer communications devices.
devices.swap(0, 1);
// Also, give both of those artificial slots unique ids so that
// the UI can manage them correctly.
devices[0].unique_id.push_str("-0");
devices[1].unique_id.push_str("-1");
}
Ok(devices)
} else {
0
};
if self.device_counts.recording != Some(device_count) {
info!(
"PeerConnectionFactory::get_audio_recording_devices(): device_count: {}",
device_count
);
self.device_counts.recording = Some(device_count);
Err(RingRtcError::QueryAudioDevices.into())
}
for i in 0..device_count {
match self.get_audio_recording_device(i) {
Ok(dev) => devices.push(dev),
Err(fail) => {
error!("getAudioRecordingDevice({}) failed: {}", i, fail);
return Err(fail);
}
}
}
// For devices missing unique_id, populate them with name + index
for i in 0..devices.len() {
if devices[i].unique_id.is_empty() {
let same_name_count = devices[..i]
.iter()
.filter(|d| d.name == devices[i].name)
.count() as u16;
devices[i].unique_id = format!("{}-{}", devices[i].name, same_name_count);
}
}
#[cfg(target_os = "windows")]
if devices.len() > 1 {
// Swap the first two devices, so that the "default communications" device
// is first and the "default" device is second. The UI treats the first
// index as the default, which for VoIP we prefer communications devices.
devices.swap(0, 1);
// Also, give both of those artificial slots unique ids so that
// the UI can manage them correctly.
devices[0].unique_id.push_str("-0");
devices[1].unique_id.push_str("-1");
}
Ok(devices)
}
#[cfg(feature = "native")]
@ -694,23 +602,50 @@ impl PeerConnectionFactory {
index
);
let ok = unsafe { pcf::Rust_setAudioRecordingDevice(self.rffi.as_borrowed(), index) };
if ok {
Ok(())
} else {
error!("setAudioRecordingDevice({}) failed", index);
Err(RingRtcError::SetAudioDevice.into())
}
self.adm.as_ref().and_then(|adm| adm.lock().ok()).map_or(
Err(anyhow!("couldn't access ADM")),
|mut adm| {
// We need to stop and restart recording if it is already in progress.
let was_initialized = adm.recording_is_initialized();
let was_recording = adm.recording();
if was_initialized && adm.stop_recording() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
if adm.set_recording_device(index) != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
if was_initialized && adm.init_recording() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
if was_recording && adm.start_recording() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
Ok(())
},
)
}
#[cfg(all(not(feature = "sim"), feature = "native"))]
pub fn set_audio_recording_device_by_id(&mut self, device_id: &str) -> Result<()> {
self.adm
.as_ref()
.and_then(|adm| adm.lock().ok())
.map_or(Err(anyhow!("couldn't access ADM")), |mut adm| {
adm.set_recording_device_by_id(device_id)
})
self.adm.as_ref().and_then(|adm| adm.lock().ok()).map_or(
Err(anyhow!("couldn't access ADM")),
|mut adm| {
// We need to stop and restart recording if it is already in progress.
let was_initialized = adm.recording_is_initialized();
let was_recording = adm.recording();
if was_initialized && adm.stop_recording() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
adm.set_recording_device_by_id(device_id)?;
if was_initialized && adm.init_recording() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
if was_recording && adm.start_recording() != 0 {
return Err(RingRtcError::SetAudioDevice.into());
}
Ok(())
},
)
}
#[cfg(all(not(feature = "sim"), feature = "native"))]