From 59de2818b14df0e6370cfe6507ede4623ca009fe Mon Sep 17 00:00:00 2001 From: Chun-Min Chang Date: Thu, 31 Mar 2022 10:49:07 -0700 Subject: [PATCH 1/4] Merge *_system_changed_callback to *_device_changed_callback --- src/backend/mod.rs | 185 ++++++++++++++++----------------------------- 1 file changed, 65 insertions(+), 120 deletions(-) diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 07f504a1..80e2d928 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -2876,14 +2876,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.", @@ -2921,13 +2913,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", @@ -2961,6 +2946,29 @@ impl<'ctx> CoreStreamData<'ctx> { cubeb_log!("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv={}, device id={}", rv, self.output_device.id); return Err(Error::error()); } + + 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 { + self.default_output_listener = None; + cubeb_log!("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv={}", rv); + return Err(Error::error()); + } } if !self.input_unit.is_null() { @@ -2989,13 +2997,36 @@ impl<'ctx> CoreStreamData<'ctx> { 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) { + 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::HardwareDefaultInputDevice, + DeviceType::INPUT | DeviceType::OUTPUT, + ), + audiounit_property_listener_callback, + )); + let rv = stm.add_device_listener(self.default_input_listener.as_ref().unwrap()); + if rv != NO_ERR { + self.default_input_listener = None; + cubeb_log!("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv={}", rv); + return Err(Error::error()); + } + } else { + // Get the notification when the input device is going away + // if the input doesn't follow the system default. self.input_alive_listener = Some(device_property_listener::new( self.input_device.id, get_property_address( @@ -3016,68 +3047,6 @@ impl<'ctx> CoreStreamData<'ctx> { 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" - ); - - // 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()); - } - } - - Ok(()) - } - fn uninstall_device_changed_callback(&mut self) -> Result<()> { if self.stm_ptr.is_null() { assert!( @@ -3093,6 +3062,22 @@ 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 { + 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 { + 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 { @@ -3122,35 +3107,6 @@ impl<'ctx> CoreStreamData<'ctx> { 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 +3384,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() From 82401c1ce29bbf227da01002be17f653ab1a496e Mon Sep 17 00:00:00 2001 From: Chun-Min Chang Date: Thu, 31 Mar 2022 11:21:23 -0700 Subject: [PATCH 2/4] Revise error messages --- src/backend/mod.rs | 64 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 80e2d928..382b7255 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -2942,8 +2942,13 @@ 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()); } @@ -2965,8 +2970,12 @@ impl<'ctx> CoreStreamData<'ctx> { )); 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; - cubeb_log!("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv={}", rv); return Err(Error::error()); } } @@ -2992,8 +3001,13 @@ 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()); } @@ -3020,8 +3034,12 @@ impl<'ctx> CoreStreamData<'ctx> { )); let rv = stm.add_device_listener(self.default_input_listener.as_ref().unwrap()); if rv != NO_ERR { + cubeb_log!( + "({:p}) Failed to monitor default input device. Error: {}", + self.stm_ptr, + rv + ); self.default_input_listener = None; - cubeb_log!("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv={}", rv); return Err(Error::error()); } } else { @@ -3037,8 +3055,13 @@ impl<'ctx> CoreStreamData<'ctx> { )); let rv = stm.add_device_listener(self.input_alive_listener.as_ref().unwrap()); if rv != NO_ERR { + cubeb_log!( + "({:p}) Failed to monitor alive status of input device {}. Error: {}", + self.stm_ptr, + self.input_alive_listener.as_ref().unwrap().device, + rv + ); self.input_alive_listener = None; - cubeb_log!("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv={}, device id ={}", rv, self.input_device.id); return Err(Error::error()); } } @@ -3065,6 +3088,11 @@ impl<'ctx> CoreStreamData<'ctx> { 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; @@ -3073,6 +3101,11 @@ impl<'ctx> CoreStreamData<'ctx> { 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; @@ -3081,7 +3114,12 @@ impl<'ctx> CoreStreamData<'ctx> { 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; @@ -3090,7 +3128,12 @@ 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; @@ -3099,7 +3142,12 @@ impl<'ctx> CoreStreamData<'ctx> { if self.input_alive_listener.is_some() { let rv = stm.remove_device_listener(self.input_alive_listener.as_ref().unwrap()); 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 input device {}. Error: {}", + self.stm_ptr, + self.input_alive_listener.as_ref().unwrap().device, + rv + ); r = Err(Error::error()); } self.input_alive_listener = None; From 3f287ff73405aad2db7cdcc3e7a8d4a40e9bc79b Mon Sep 17 00:00:00 2001 From: Chun-Min Chang Date: Thu, 31 Mar 2022 11:26:38 -0700 Subject: [PATCH 3/4] Shorten the check --- src/backend/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 382b7255..d0b054a6 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -2888,9 +2888,7 @@ impl<'ctx> CoreStreamData<'ctx> { // We cannot have both of them at the same time. 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.default_input_listener.is_some() != self.input_alive_listener.is_some()) ); Ok(()) From 7b928c35774d30675e0b233bcde3f5a067a9911d Mon Sep 17 00:00:00 2001 From: Chun-Min Chang Date: Thu, 31 Mar 2022 15:09:55 -0700 Subject: [PATCH 4/4] Watching the alive status for all running devices This patch implements the code to monitor the alive status of both input and output devices in use. We used to watch input device only. With the changes here, we are able to watch the alive status of all the running devices when user doesn't follow the system default devices. If the watched device is unplugged before it's destroyed, we will fire an error callback. --- run_device_tests.sh | 12 +-- src/backend/mod.rs | 154 ++++++++++++++++++++--------- src/backend/tests/device_change.rs | 4 - 3 files changed, 111 insertions(+), 59 deletions(-) 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 d0b054a6..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(), } } @@ -2884,11 +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.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(()) @@ -2950,31 +2965,67 @@ impl<'ctx> CoreStreamData<'ctx> { return Err(Error::error()); } - assert!( - self.default_output_listener.is_none(), - "register default_output_listener without unregistering the one in use" - ); + 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 + // 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" ); - self.default_output_listener = None; - return Err(Error::error()); + + // 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); } } @@ -2985,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. @@ -3040,28 +3087,41 @@ impl<'ctx> CoreStreamData<'ctx> { self.default_input_listener = None; return Err(Error::error()); } + } 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 input device is going away // if the input doesn't follow the system default. - self.input_alive_listener = Some(device_property_listener::new( + 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(self.input_alive_listener.as_ref().unwrap()); + ); + 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, - self.input_alive_listener.as_ref().unwrap().device, + listener.device, rv ); - self.input_alive_listener = None; return Err(Error::error()); } + self.device_alive_listeners + .insert(self.input_device.id, listener); } } @@ -3073,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(()); } @@ -3137,19 +3197,19 @@ impl<'ctx> CoreStreamData<'ctx> { 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!( - "({:p}) Failed to cancel monitoring alive status of input device {}. Error: {}", + "({:p}) Failed to cancel monitoring alive status of device {}. Error: {}", self.stm_ptr, - self.input_alive_listener.as_ref().unwrap().device, + listener.device, rv ); r = Err(Error::error()); } - self.input_alive_listener = None; } + self.device_alive_listeners.clear(); r } 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() {