diff --git a/run_device_tests.sh b/run_device_tests.sh index b1a8f1e9..45bfb4bc 100755 --- a/run_device_tests.sh +++ b/run_device_tests.sh @@ -24,10 +24,8 @@ cargo test test_suspend_input_stream_by_unplugging_a_nondefault_input_device -- cargo test test_destroy_input_stream_after_unplugging_a_default_input_device -- --ignored --nocapture cargo test test_reinit_input_stream_by_unplugging_a_default_input_device -- --ignored --nocapture -# FIXME: We don't monitor the alive-status for output device currently -# cargo test test_destroy_output_stream_after_unplugging_a_nondefault_output_device -- --ignored --nocapture -# FIXME: We don't monitor the alive-status for output device currently -# cargo test test_suspend_output_stream_by_unplugging_a_nondefault_output_device -- --ignored --nocapture +cargo test test_destroy_output_stream_after_unplugging_a_nondefault_output_device -- --ignored --nocapture +cargo test test_suspend_output_stream_by_unplugging_a_nondefault_output_device -- --ignored --nocapture cargo test test_destroy_output_stream_after_unplugging_a_default_output_device -- --ignored --nocapture cargo test test_reinit_output_stream_by_unplugging_a_default_output_device -- --ignored --nocapture @@ -35,10 +33,8 @@ cargo test test_reinit_output_stream_by_unplugging_a_default_output_device -- -- cargo test test_destroy_duplex_stream_after_unplugging_a_nondefault_input_device -- --ignored --nocapture cargo test test_suspend_duplex_stream_by_unplugging_a_nondefault_input_device -# FIXME: We don't monitor the alive-status for output device currently -# cargo test test_destroy_duplex_stream_after_unplugging_a_nondefault_output_device -- --ignored --nocapture -# FIXME: We don't monitor the alive-status for output device currently -# cargo test test_suspend_duplex_stream_by_unplugging_a_nondefault_output_device -- --ignored --nocapture +cargo test test_destroy_duplex_stream_after_unplugging_a_nondefault_output_device -- --ignored --nocapture +cargo test test_suspend_duplex_stream_by_unplugging_a_nondefault_output_device -- --ignored --nocapture cargo test test_destroy_duplex_stream_after_unplugging_a_default_input_device -- --ignored --nocapture cargo test test_reinit_duplex_stream_by_unplugging_a_default_input_device -- --ignored --nocapture diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 07f504a1..c7063103 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -38,6 +38,7 @@ use cubeb_backend::{ }; use mach::mach_time::{mach_absolute_time, mach_timebase_info}; use std::cmp; +use std::collections::HashMap; use std::ffi::{CStr, CString}; use std::mem; use std::os::raw::c_void; @@ -726,7 +727,7 @@ extern "C" fn audiounit_property_listener_callback( } stm.switching_device.store(true, Ordering::SeqCst); - let mut input_device_dead = false; + let mut device_dead = false; // Log the events cubeb_log!( @@ -740,13 +741,13 @@ extern "C" fn audiounit_property_listener_callback( cubeb_log!("Event #{}: {}", i, p); assert_ne!(p, PropertySelector::Unknown); if p == PropertySelector::DeviceIsAlive { - input_device_dead = true; + device_dead = true; } } // Handle the events - if input_device_dead { - cubeb_log!("The user-selected input device is dead, enter error state"); + if device_dead { + cubeb_log!("The user-selected device {} is dead, enter error state", id); stm.stopped.store(true, Ordering::SeqCst); stm.core_stream_data.stop_audiounits(); stm.close_on_error(); @@ -2233,9 +2234,9 @@ struct CoreStreamData<'ctx> { // Listeners indicating what system events are monitored. default_input_listener: Option, default_output_listener: Option, - input_alive_listener: Option, input_source_listener: Option, output_source_listener: Option, + device_alive_listeners: HashMap, } impl<'ctx> Default for CoreStreamData<'ctx> { @@ -2268,9 +2269,9 @@ impl<'ctx> Default for CoreStreamData<'ctx> { input_buffer_manager: None, default_input_listener: None, default_output_listener: None, - input_alive_listener: None, input_source_listener: None, output_source_listener: None, + device_alive_listeners: HashMap::new(), } } } @@ -2310,9 +2311,9 @@ impl<'ctx> CoreStreamData<'ctx> { input_buffer_manager: None, default_input_listener: None, default_output_listener: None, - input_alive_listener: None, input_source_listener: None, output_source_listener: None, + device_alive_listeners: HashMap::new(), } } @@ -2876,14 +2877,6 @@ impl<'ctx> CoreStreamData<'ctx> { } } - if let Err(r) = self.install_system_changed_callback() { - cubeb_log!( - "({:p}) Could not install the device change callback.", - self.stm_ptr - ); - return Err(r); - } - if let Err(r) = self.install_device_changed_callback() { cubeb_log!( "({:p}) Could not install all device change callback.", @@ -2892,13 +2885,25 @@ impl<'ctx> CoreStreamData<'ctx> { return Err(r); } - // We have either default_input_listener or input_alive_listener. + // We have either default_*put_listener or the device_alive_listener on *put side. // We cannot have both of them at the same time. + assert!( + !self.has_output() + || (self.default_output_listener.is_some() + != self + .device_alive_listeners + .contains_key(&self.output_device.id)) + ); assert!( !self.has_input() - || ((self.default_input_listener.is_some() != self.input_alive_listener.is_some()) - && (self.default_input_listener.is_some() - || self.input_alive_listener.is_some())) + || ((self.output_device.id == self.input_device.id + && self + .device_alive_listeners + .contains_key(&self.output_device.id)) + || self.default_input_listener.is_some() + != self + .device_alive_listeners + .contains_key(&self.input_device.id)) ); Ok(()) @@ -2921,13 +2926,6 @@ impl<'ctx> CoreStreamData<'ctx> { self.mixer = None; self.aggregate_device = None; - if self.uninstall_system_changed_callback().is_err() { - cubeb_log!( - "({:p}) Could not uninstall the system changed callback", - self.stm_ptr - ); - } - if self.uninstall_device_changed_callback().is_err() { cubeb_log!( "({:p}) Could not uninstall all device change listeners", @@ -2957,10 +2955,78 @@ impl<'ctx> CoreStreamData<'ctx> { )); let rv = stm.add_device_listener(self.output_source_listener.as_ref().unwrap()); if rv != NO_ERR { + cubeb_log!( + "({:p}) Failed to monitor data source of output device {}. Error: {}", + self.stm_ptr, + self.output_source_listener.as_ref().unwrap().device, + rv + ); self.output_source_listener = None; - cubeb_log!("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv={}, device id={}", rv, self.output_device.id); return Err(Error::error()); } + + if self + .output_device + .flags + .contains(device_flags::DEV_SELECTED_DEFAULT) + { + assert!( + self.default_output_listener.is_none(), + "register default_output_listener without unregistering the one in use" + ); + + // Get the notification when the default output audio changes, e.g., when the user + // plugs in a USB headset and the system chooses it automatically as the default, or + // when another device is chosen in the dropdown list. + self.default_output_listener = Some(device_property_listener::new( + kAudioObjectSystemObject, + get_property_address( + Property::HardwareDefaultOutputDevice, + DeviceType::INPUT | DeviceType::OUTPUT, + ), + audiounit_property_listener_callback, + )); + let rv = stm.add_device_listener(self.default_output_listener.as_ref().unwrap()); + if rv != NO_ERR { + cubeb_log!( + "({:p}) Failed to monitor default output device. Error: {}", + self.stm_ptr, + rv + ); + self.default_output_listener = None; + return Err(Error::error()); + } + } else { + assert!( + !self + .device_alive_listeners + .contains_key(&self.output_device.id), + "register output alive listener without unregistering the one in use" + ); + + // Get the notification when the output device is going away + // if the output doesn't follow the system default. + let listener = device_property_listener::new( + self.output_device.id, + get_property_address( + Property::DeviceIsAlive, + DeviceType::INPUT | DeviceType::OUTPUT, + ), + audiounit_property_listener_callback, + ); + let rv = stm.add_device_listener(&listener); + if rv != NO_ERR { + cubeb_log!( + "({:p}) Failed to monitor alive status of output device {}. Error: {}", + self.stm_ptr, + listener.device, + rv + ); + return Err(Error::error()); + } + self.device_alive_listeners + .insert(self.output_device.id, listener); + } } if !self.input_unit.is_null() { @@ -2970,10 +3036,6 @@ impl<'ctx> CoreStreamData<'ctx> { self.input_source_listener.is_none(), "register input_source_listener without unregistering the one in use" ); - assert!( - self.input_alive_listener.is_none(), - "register input_alive_listener without unregistering the one in use" - ); // Get the notification when the data source on the same device changes, // e.g., when the user plugs in a TRRS mic into the headphone jack. @@ -2984,94 +3046,82 @@ impl<'ctx> CoreStreamData<'ctx> { )); let rv = stm.add_device_listener(self.input_source_listener.as_ref().unwrap()); if rv != NO_ERR { + cubeb_log!( + "({:p}) Failed to monitor data source of input device {}. Error: {}", + self.stm_ptr, + self.input_source_listener.as_ref().unwrap().device, + rv + ); self.input_source_listener = None; - cubeb_log!("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource rv={}, device id={}", rv, self.input_device.id); return Err(Error::error()); } - // Get the notification when the input device is going away - // if the input doesn't follow the system default. - if !self + if self .input_device .flags .contains(device_flags::DEV_SELECTED_DEFAULT) { - self.input_alive_listener = Some(device_property_listener::new( - self.input_device.id, + assert!( + self.default_input_listener.is_none(), + "register default_input_listener without unregistering the one in use" + ); + + // Get the notification when the default intput audio changes, e.g., when the user + // plugs in a USB mic and the system chooses it automatically as the default, or + // when another device is chosen in the system preference. + self.default_input_listener = Some(device_property_listener::new( + kAudioObjectSystemObject, get_property_address( - Property::DeviceIsAlive, + Property::HardwareDefaultInputDevice, DeviceType::INPUT | DeviceType::OUTPUT, ), audiounit_property_listener_callback, )); - let rv = stm.add_device_listener(self.input_alive_listener.as_ref().unwrap()); + let rv = stm.add_device_listener(self.default_input_listener.as_ref().unwrap()); if rv != NO_ERR { - self.input_alive_listener = None; - cubeb_log!("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv={}, device id ={}", rv, self.input_device.id); + cubeb_log!( + "({:p}) Failed to monitor default input device. Error: {}", + self.stm_ptr, + rv + ); + self.default_input_listener = None; return Err(Error::error()); } - } - } - - Ok(()) - } - - fn install_system_changed_callback(&mut self) -> Result<()> { - assert!(!self.stm_ptr.is_null()); - let stm = unsafe { &(*self.stm_ptr) }; - - if !self.output_unit.is_null() { - assert!( - self.default_output_listener.is_none(), - "register default_output_listener without unregistering the one in use" - ); - - // Get the notification when the default output audio changes, e.g., - // when the user plugs in a USB headset and the system chooses it automatically as the default, - // or when another device is chosen in the dropdown list. - self.default_output_listener = Some(device_property_listener::new( - kAudioObjectSystemObject, - get_property_address( - Property::HardwareDefaultOutputDevice, - DeviceType::INPUT | DeviceType::OUTPUT, - ), - audiounit_property_listener_callback, - )); - let r = stm.add_device_listener(self.default_output_listener.as_ref().unwrap()); - if r != NO_ERR { - self.default_output_listener = None; - cubeb_log!("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv={}", r); - return Err(Error::error()); - } - } - - if !self.input_unit.is_null() - && self - .input_device - .flags - .contains(device_flags::DEV_SELECTED_DEFAULT) - { - assert!( - self.default_input_listener.is_none(), - "register default_input_listener without unregistering the one in use" - ); + } else if self.input_device.id == self.output_device.id { + assert!(self + .device_alive_listeners + .contains_key(&self.input_device.id)); + cubeb_log!("({:p}) Same device for input and output. We've monitor the alive status for device {}", self.stm_ptr, self.input_device.id); + } else { + assert!( + !self + .device_alive_listeners + .contains_key(&self.input_device.id), + "register input alive listener without unregistering the one in use" + ); - // Get the notification when the default intput audio changes, e.g., - // when the user plugs in a USB mic and the system chooses it automatically as the default, - // or when another device is chosen in the system preference. - self.default_input_listener = Some(device_property_listener::new( - kAudioObjectSystemObject, - get_property_address( - Property::HardwareDefaultInputDevice, - DeviceType::INPUT | DeviceType::OUTPUT, - ), - audiounit_property_listener_callback, - )); - let r = stm.add_device_listener(self.default_input_listener.as_ref().unwrap()); - if r != NO_ERR { - self.default_input_listener = None; - cubeb_log!("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv={}", r); - return Err(Error::error()); + // Get the notification when the input device is going away + // if the input doesn't follow the system default. + let listener = device_property_listener::new( + self.input_device.id, + get_property_address( + Property::DeviceIsAlive, + DeviceType::INPUT | DeviceType::OUTPUT, + ), + audiounit_property_listener_callback, + ); + let rv = stm.add_device_listener(&listener); + if rv != NO_ERR { + cubeb_log!( + "({:p}) Failed to monitor alive status of input device {}. Error: {}", + self.stm_ptr, + listener.device, + rv + ); + return Err(Error::error()); + } + self.device_alive_listeners + .insert(self.input_device.id, listener); } } @@ -3083,7 +3133,7 @@ impl<'ctx> CoreStreamData<'ctx> { assert!( self.output_source_listener.is_none() && self.input_source_listener.is_none() - && self.input_alive_listener.is_none() + && self.device_alive_listeners.is_empty() ); return Ok(()); } @@ -3093,10 +3143,41 @@ impl<'ctx> CoreStreamData<'ctx> { // Failing to uninstall listeners is not a fatal error. let mut r = Ok(()); + if self.default_input_listener.is_some() { + let rv = stm.remove_device_listener(self.default_input_listener.as_ref().unwrap()); + if rv != NO_ERR { + cubeb_log!( + "({:p}) Failed to cancel monitoring default input device. Error: {}", + self.stm_ptr, + rv + ); + r = Err(Error::error()); + } + self.default_input_listener = None; + } + + if self.default_output_listener.is_some() { + let rv = stm.remove_device_listener(self.default_output_listener.as_ref().unwrap()); + if rv != NO_ERR { + cubeb_log!( + "({:p}) Failed to cancel monitoring default output device. Error: {}", + self.stm_ptr, + rv + ); + r = Err(Error::error()); + } + self.default_output_listener = None; + } + if self.output_source_listener.is_some() { let rv = stm.remove_device_listener(self.output_source_listener.as_ref().unwrap()); if rv != NO_ERR { - cubeb_log!("AudioObjectRemovePropertyListener/output/kAudioDevicePropertyDataSource rv={}, device id={}", rv, self.output_device.id); + cubeb_log!( + "({:p}) Failed to cancel monitoring data source of output device {}. Error: {}", + self.stm_ptr, + self.output_source_listener.as_ref().unwrap().device, + rv + ); r = Err(Error::error()); } self.output_source_listener = None; @@ -3105,52 +3186,33 @@ impl<'ctx> CoreStreamData<'ctx> { if self.input_source_listener.is_some() { let rv = stm.remove_device_listener(self.input_source_listener.as_ref().unwrap()); if rv != NO_ERR { - cubeb_log!("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDataSource rv={}, device id={}", rv, self.input_device.id); + cubeb_log!( + "({:p}) Failed to cancel monitoring data source of input device {}. Error: {}", + self.stm_ptr, + self.input_source_listener.as_ref().unwrap().device, + rv + ); r = Err(Error::error()); } self.input_source_listener = None; } - if self.input_alive_listener.is_some() { - let rv = stm.remove_device_listener(self.input_alive_listener.as_ref().unwrap()); + for listener in self.device_alive_listeners.values() { + let rv = stm.remove_device_listener(listener); if rv != NO_ERR { - cubeb_log!("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv={}, device id={}", rv, self.input_device.id); + cubeb_log!( + "({:p}) Failed to cancel monitoring alive status of device {}. Error: {}", + self.stm_ptr, + listener.device, + rv + ); r = Err(Error::error()); } - self.input_alive_listener = None; } + self.device_alive_listeners.clear(); r } - - fn uninstall_system_changed_callback(&mut self) -> Result<()> { - if self.stm_ptr.is_null() { - assert!( - self.default_output_listener.is_none() && self.default_input_listener.is_none() - ); - return Ok(()); - } - - let stm = unsafe { &(*self.stm_ptr) }; - - if self.default_output_listener.is_some() { - let r = stm.remove_device_listener(self.default_output_listener.as_ref().unwrap()); - if r != NO_ERR { - return Err(Error::error()); - } - self.default_output_listener = None; - } - - if self.default_input_listener.is_some() { - let r = stm.remove_device_listener(self.default_input_listener.as_ref().unwrap()); - if r != NO_ERR { - return Err(Error::error()); - } - self.default_input_listener = None; - } - - Ok(()) - } } impl<'ctx> Drop for CoreStreamData<'ctx> { @@ -3428,17 +3490,6 @@ impl<'ctx> AudioUnitStream<'ctx> { } fn destroy(&mut self) { - if self - .core_stream_data - .uninstall_system_changed_callback() - .is_err() - { - cubeb_log!( - "({:p}) Could not uninstall the system changed callback", - self as *const AudioUnitStream - ); - } - if self .core_stream_data .uninstall_device_changed_callback() diff --git a/src/backend/tests/device_change.rs b/src/backend/tests/device_change.rs index a0897a81..9f953e12 100644 --- a/src/backend/tests/device_change.rs +++ b/src/backend/tests/device_change.rs @@ -467,14 +467,12 @@ fn test_reinit_input_stream_by_unplugging_a_default_input_device() { // Unplug the non-default output device for an output stream // ------------------------------------------------------------------------------------------------ -// FIXME: We don't monitor the alive-status for output device currently #[ignore] #[test] fn test_destroy_output_stream_after_unplugging_a_nondefault_output_device() { test_unplug_a_device_on_an_active_stream(StreamType::OUTPUT, Scope::Output, false, 0); } -// FIXME: We don't monitor the alive-status for output device currently #[ignore] #[test] fn test_suspend_output_stream_by_unplugging_a_nondefault_output_device() { @@ -524,14 +522,12 @@ fn test_suspend_duplex_stream_by_unplugging_a_nondefault_input_device() { // Unplug the non-default output device for a duplex stream // ------------------------------------------------------------------------------------------------ -// FIXME: We don't monitor the alive-status for output device currently #[ignore] #[test] fn test_destroy_duplex_stream_after_unplugging_a_nondefault_output_device() { test_unplug_a_device_on_an_active_stream(StreamType::DUPLEX, Scope::Output, false, 0); } -// FIXME: We don't monitor the alive-status for output device currently #[ignore] #[test] fn test_suspend_duplex_stream_by_unplugging_a_nondefault_output_device() {