Avoid C++ layer for device enumeration / selection
This commit is contained in:
parent
89142ba94e
commit
41f5579438
@ -1,4 +1,4 @@
|
||||
webrtc.version=7680b
|
||||
webrtc.version=7680c
|
||||
|
||||
ringrtc.version.major=2
|
||||
ringrtc.version.minor=67
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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(¶ms).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(¶ms).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)]
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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"))]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user