Skip to content

Monitor explicitly chosen output devices for liveness #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions run_device_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,17 @@ 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

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
Expand Down
65 changes: 60 additions & 5 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ extern "C" fn audiounit_property_listener_callback(
}
stm.switching_device.store(true, Ordering::SeqCst);

let mut input_device_dead = false;
let mut explicit_device_dead = false;

cubeb_log!(
"({:p}) Handling {} device changed events for device {}",
Expand All @@ -765,13 +765,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;
explicit_device_dead = true;
}
}

// Handle the events
if input_device_dead {
cubeb_log!("The user-selected input device is dead, entering error state");
if explicit_device_dead {
cubeb_log!("The user-selected input or output device is dead, entering error state");
stm.stopped.store(true, Ordering::SeqCst);
stm.core_stream_data.stop_audiounits();
stm.close_on_error();
Expand Down Expand Up @@ -2303,6 +2303,7 @@ struct CoreStreamData<'ctx> {
default_output_listener: Option<device_property_listener>,
input_alive_listener: Option<device_property_listener>,
input_source_listener: Option<device_property_listener>,
output_alive_listener: Option<device_property_listener>,
output_source_listener: Option<device_property_listener>,
input_logging: Option<InputCallbackLogger>,
}
Expand Down Expand Up @@ -2339,6 +2340,7 @@ impl<'ctx> Default for CoreStreamData<'ctx> {
default_output_listener: None,
input_alive_listener: None,
input_source_listener: None,
output_alive_listener: None,
output_source_listener: None,
input_logging: None,
}
Expand Down Expand Up @@ -2382,6 +2384,7 @@ impl<'ctx> CoreStreamData<'ctx> {
default_output_listener: None,
input_alive_listener: None,
input_source_listener: None,
output_alive_listener: None,
output_source_listener: None,
input_logging: None,
}
Expand Down Expand Up @@ -3004,6 +3007,16 @@ impl<'ctx> CoreStreamData<'ctx> {
|| self.input_alive_listener.is_some()))
);

// We have either default_output_listener or output_alive_listener.
// We cannot have both of them at the same time.
assert!(
!self.has_output()
|| ((self.default_output_listener.is_some()
!= self.output_alive_listener.is_some())
&& (self.default_output_listener.is_some()
|| self.output_alive_listener.is_some()))
);

Ok(())
}

Expand Down Expand Up @@ -3050,6 +3063,10 @@ impl<'ctx> CoreStreamData<'ctx> {
self.output_source_listener.is_none(),
"register output_source_listener without unregistering the one in use"
);
assert!(
self.output_alive_listener.is_none(),
"register output_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 headset into the headphone jack.
Expand All @@ -3064,6 +3081,29 @@ impl<'ctx> CoreStreamData<'ctx> {
cubeb_log!("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv={}, device id={}", rv, self.output_device.id);
return Err(Error::error());
}

// Get the notification when the output device is going away
// if the output doesn't follow the system default.
if !self
.output_device
.flags
.contains(device_flags::DEV_SELECTED_DEFAULT)
{
self.output_alive_listener = Some(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(self.output_alive_listener.as_ref().unwrap());
if rv != NO_ERR {
self.output_alive_listener = None;
cubeb_log!("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDeviceIsAlive rv={}, device id ={}", rv, self.output_device.id);
return Err(Error::error());
}
}
}

if !self.input_unit.is_null() {
Expand Down Expand Up @@ -3123,7 +3163,12 @@ impl<'ctx> CoreStreamData<'ctx> {
assert!(!self.stm_ptr.is_null());
let stm = unsafe { &(*self.stm_ptr) };

if !self.output_unit.is_null() {
if !self.output_unit.is_null()
&& 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"
Expand Down Expand Up @@ -3185,6 +3230,7 @@ impl<'ctx> CoreStreamData<'ctx> {
if self.stm_ptr.is_null() {
assert!(
self.output_source_listener.is_none()
&& self.output_alive_listener.is_none()
&& self.input_source_listener.is_none()
&& self.input_alive_listener.is_none()
);
Expand All @@ -3205,6 +3251,15 @@ impl<'ctx> CoreStreamData<'ctx> {
self.output_source_listener = None;
}

if self.output_alive_listener.is_some() {
let rv = stm.remove_device_listener(self.output_alive_listener.as_ref().unwrap());
if rv != NO_ERR {
cubeb_log!("AudioObjectRemovePropertyListener/output/kAudioDevicePropertyDeviceIsAlive rv={}, device id={}", rv, self.output_device.id);
r = Err(Error::error());
}
self.output_alive_listener = None;
}

if self.input_source_listener.is_some() {
let rv = stm.remove_device_listener(self.input_source_listener.as_ref().unwrap());
if rv != NO_ERR {
Expand Down
14 changes: 6 additions & 8 deletions src/backend/resampler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ impl Resampler {
reclock: ffi::cubeb_resampler_reclock,
) -> Self {
let raw_resampler = unsafe {
let in_params = if input_params.is_some() {
input_params.as_mut().unwrap() as *mut ffi::cubeb_stream_params
} else {
ptr::null_mut()
let in_params = match &mut input_params {
Some(p) => p,
None => ptr::null_mut(),
};
let out_params = if output_params.is_some() {
output_params.as_mut().unwrap() as *mut ffi::cubeb_stream_params
} else {
ptr::null_mut()
let out_params = match &mut output_params {
Some(p) => p,
None => ptr::null_mut(),
};
ffi::cubeb_resampler_create(
stream,
Expand Down
4 changes: 0 additions & 4 deletions src/backend/tests/device_change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,14 +471,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() {
Expand Down Expand Up @@ -528,14 +526,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() {
Expand Down
5 changes: 3 additions & 2 deletions src/backend/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -884,8 +884,9 @@ impl TestDevicePlugger {
);
CFRelease(device_uid as *const c_void);

// This device is private to the process creating it.
let private_value: i32 = 1;
// Make this device NOT private to the process creating it.
// On MacOS 14 devicechange events are not triggered when it is private.
let private_value: i32 = 0;
let device_private_key = CFNumberCreate(
kCFAllocatorDefault,
i64::from(kCFNumberIntType),
Expand Down