Skip to content

Commit 48a9af5

Browse files
committed
Implement device change logic
1 parent 4cdbcea commit 48a9af5

File tree

1 file changed

+202
-46
lines changed

1 file changed

+202
-46
lines changed

src/backend/mod.rs

+202-46
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,24 @@ extern "C" fn audiounit_property_listener_callback(
753753
}
754754
stm.switching_device.store(true, Ordering::SeqCst);
755755

756+
let should_input_device_change =
757+
stm.core_stream_data.has_input() &&
758+
!stm.core_stream_data
759+
.input_stream_params.prefs()
760+
.contains(StreamPrefs::DISABLE_DEVICE_SWITCHING) &&
761+
stm.core_stream_data
762+
.input_device.flags
763+
.contains(device_flags::DEV_SELECTED_DEFAULT);
764+
765+
let should_output_device_change =
766+
stm.core_stream_data.has_output() &&
767+
!stm.core_stream_data
768+
.output_stream_params.prefs()
769+
.contains(StreamPrefs::DISABLE_DEVICE_SWITCHING) &&
770+
stm.core_stream_data
771+
.output_device.flags
772+
.contains(device_flags::DEV_SELECTED_DEFAULT);
773+
756774
cubeb_log!(
757775
"({:p}) Audio device changed, {} events.",
758776
stm as *const AudioUnitStream,
@@ -766,32 +784,108 @@ extern "C" fn audiounit_property_listener_callback(
766784
i,
767785
id
768786
);
787+
if !should_output_device_change {
788+
cubeb_log!("Output device should not change, ignore the event");
789+
stm.switching_device.store(false, Ordering::SeqCst);
790+
return NO_ERR;
791+
}
792+
// When a device needs to be updated it always goes to the default one. Thus the
793+
// output device is set to null. The re-init process will replace it with the current
794+
// default device later.
795+
stm.core_stream_data.output_device.id = kAudioObjectUnknown;
796+
// In case of a duplex call the output device info is filled. That will signal to the
797+
// re-init process that an explicit chosen device is selected. However, if the
798+
// user had selected the implicit default device (null deviceId) for the input device
799+
// has to be cleaned in order to signal to reinit process that the implicit default
800+
// device is being used (DEV_SELECTED_DEFAULT is the implicit defaut device).
801+
if stm.core_stream_data.input_device.flags.contains(device_flags::DEV_SELECTED_DEFAULT) {
802+
stm.core_stream_data.input_device.id = kAudioObjectUnknown;
803+
}
769804
}
770805
sys::kAudioHardwarePropertyDefaultInputDevice => {
771806
cubeb_log!(
772807
"Event[{}] - mSelector == kAudioHardwarePropertyDefaultInputDevice for id={}",
773808
i,
774809
id
775810
);
811+
// See the comments above for default output case. The code is symmetrical.
812+
if !should_input_device_change {
813+
cubeb_log!("Input device should not change, ignore the event");
814+
stm.switching_device.store(false, Ordering::SeqCst);
815+
return NO_ERR;
816+
}
817+
stm.core_stream_data.input_device.id = kAudioObjectUnknown;
818+
if stm.core_stream_data.output_device.flags.contains(device_flags::DEV_SELECTED_DEFAULT) {
819+
stm.core_stream_data.output_device.id = kAudioObjectUnknown;
820+
}
776821
}
777822
sys::kAudioDevicePropertyDeviceIsAlive => {
778823
cubeb_log!(
779824
"Event[{}] - mSelector == kAudioDevicePropertyDeviceIsAlive for id={}",
780825
i,
781826
id
782827
);
783-
// If this is the default input device ignore the event,
784-
// kAudioHardwarePropertyDefaultInputDevice will take care of the switch
785-
if stm
786-
.core_stream_data
787-
.input_device
788-
.flags
789-
.contains(device_flags::DEV_SYSTEM_DEFAULT)
790-
{
791-
cubeb_log!("It's the default input device, ignore the event");
792-
stm.switching_device.store(false, Ordering::SeqCst);
828+
829+
// The same (removed) device is used for input and output, for example a headset.
830+
if stm.core_stream_data.input_device.id == id &&
831+
stm.core_stream_data.output_device.id == id &&
832+
(!should_input_device_change || !should_output_device_change){
833+
cubeb_log!("Duplex device should not change, ignore the event");
834+
stm.report_error_async();
793835
return NO_ERR;
794836
}
837+
838+
if stm.core_stream_data.input_device.id == id {
839+
// Keep that first because it is required to return an error callback if the
840+
// device is removed but we don't have the option to change it.
841+
if !should_input_device_change {
842+
cubeb_log!("Input device should not change, ignore the event");
843+
stm.report_error_async();
844+
return NO_ERR;
845+
}
846+
847+
// Since the device will change let default event do so, if that's the case.
848+
if stm
849+
.core_stream_data
850+
.input_device
851+
.flags
852+
.contains(device_flags::DEV_SYSTEM_DEFAULT)
853+
{
854+
cubeb_log!("It's the default input device, ignore the event");
855+
stm.switching_device.store(false, Ordering::SeqCst);
856+
return NO_ERR;
857+
}
858+
859+
// The device is not the default, update it.
860+
stm.core_stream_data.input_device.id = kAudioObjectUnknown;
861+
if stm.core_stream_data.output_device.flags.contains(device_flags::DEV_SELECTED_DEFAULT) {
862+
stm.core_stream_data.output_device.id = kAudioObjectUnknown;
863+
}
864+
}
865+
866+
if stm.core_stream_data.output_device.id == id {
867+
if !should_output_device_change {
868+
cubeb_log!("Output device should not change, ignore the event");
869+
stm.report_error_async();
870+
return NO_ERR;
871+
}
872+
873+
if stm
874+
.core_stream_data
875+
.input_device
876+
.flags
877+
.contains(device_flags::DEV_SYSTEM_DEFAULT)
878+
{
879+
cubeb_log!("It's the default input device, ignore the event");
880+
stm.switching_device.store(false, Ordering::SeqCst);
881+
return NO_ERR;
882+
}
883+
884+
stm.core_stream_data.output_device.id = kAudioObjectUnknown;
885+
if stm.core_stream_data.input_device.flags.contains(device_flags::DEV_SELECTED_DEFAULT) {
886+
stm.core_stream_data.input_device.id = kAudioObjectUnknown;
887+
}
888+
}
795889
}
796890
sys::kAudioDevicePropertyDataSource => {
797891
cubeb_log!(
@@ -984,7 +1078,7 @@ fn create_audiounit(device: &device_info) -> Result<AudioUnit> {
9841078
.flags
9851079
.contains(device_flags::DEV_INPUT | device_flags::DEV_OUTPUT));
9861080

987-
let unit = create_default_audiounit(device.flags)?;
1081+
let unit = create_default_audiounit()?;
9881082
if device
9891083
.flags
9901084
.contains(device_flags::DEV_SYSTEM_DEFAULT | device_flags::DEV_OUTPUT)
@@ -1080,26 +1174,16 @@ fn set_device_to_audiounit(
10801174
}
10811175
}
10821176

1083-
fn create_default_audiounit(flags: device_flags) -> Result<AudioUnit> {
1084-
let desc = get_audiounit_description(flags);
1177+
fn create_default_audiounit() -> Result<AudioUnit> {
1178+
let desc = get_audiounit_description();
10851179
create_audiounit_by_description(desc)
10861180
}
10871181

1088-
fn get_audiounit_description(flags: device_flags) -> AudioComponentDescription {
1182+
fn get_audiounit_description() -> AudioComponentDescription {
10891183
AudioComponentDescription {
10901184
componentType: kAudioUnitType_Output,
1091-
// Use the DefaultOutputUnit for output when no device is specified
1092-
// so we retain automatic output device switching when the default
1093-
// changes. Once we have complete support for device notifications
1094-
// and switching, we can use the AUHAL for everything.
10951185
#[cfg(not(target_os = "ios"))]
1096-
componentSubType: if flags
1097-
.contains(device_flags::DEV_SYSTEM_DEFAULT | device_flags::DEV_OUTPUT)
1098-
{
1099-
kAudioUnitSubType_DefaultOutput
1100-
} else {
1101-
kAudioUnitSubType_HALOutput
1102-
},
1186+
componentSubType: kAudioUnitSubType_HALOutput,
11031187
#[cfg(target_os = "ios")]
11041188
componentSubType: kAudioUnitSubType_RemoteIO,
11051189
componentManufacturer: kAudioUnitManufacturer_Apple,
@@ -2321,6 +2405,7 @@ struct CoreStreamData<'ctx> {
23212405
default_input_listener: Option<device_property_listener>,
23222406
default_output_listener: Option<device_property_listener>,
23232407
input_alive_listener: Option<device_property_listener>,
2408+
output_alive_listener: Option<device_property_listener>,
23242409
input_source_listener: Option<device_property_listener>,
23252410
output_source_listener: Option<device_property_listener>,
23262411
}
@@ -2359,6 +2444,7 @@ impl<'ctx> Default for CoreStreamData<'ctx> {
23592444
default_input_listener: None,
23602445
default_output_listener: None,
23612446
input_alive_listener: None,
2447+
output_alive_listener: None,
23622448
input_source_listener: None,
23632449
output_source_listener: None,
23642450
}
@@ -2404,6 +2490,7 @@ impl<'ctx> CoreStreamData<'ctx> {
24042490
default_input_listener: None,
24052491
default_output_listener: None,
24062492
input_alive_listener: None,
2493+
output_alive_listener: None,
24072494
input_source_listener: None,
24082495
output_source_listener: None,
24092496
}
@@ -2952,6 +3039,22 @@ impl<'ctx> CoreStreamData<'ctx> {
29523039
cubeb_log!("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv={}, device id={}", rv, self.output_device.id);
29533040
return Err(Error::error());
29543041
}
3042+
3043+
// Event to notify when the input is going away.
3044+
self.output_alive_listener = Some(device_property_listener::new(
3045+
self.output_device.id,
3046+
get_property_address(
3047+
Property::DeviceIsAlive,
3048+
DeviceType::INPUT | DeviceType::OUTPUT,
3049+
),
3050+
audiounit_property_listener_callback,
3051+
));
3052+
let rv = stm.add_device_listener(self.output_alive_listener.as_ref().unwrap());
3053+
if rv != NO_ERR {
3054+
self.output_alive_listener = None;
3055+
cubeb_log!("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDeviceIsAlive rv={}, device id ={}", rv, self.input_device.id);
3056+
return Err(Error::error());
3057+
}
29553058
}
29563059

29573060
if !self.input_unit.is_null() {
@@ -2971,20 +3074,24 @@ impl<'ctx> CoreStreamData<'ctx> {
29713074
return Err(Error::error());
29723075
}
29733076

2974-
// Event to notify when the input is going away.
2975-
self.input_alive_listener = Some(device_property_listener::new(
2976-
self.input_device.id,
2977-
get_property_address(
2978-
Property::DeviceIsAlive,
2979-
DeviceType::INPUT | DeviceType::OUTPUT,
2980-
),
2981-
audiounit_property_listener_callback,
2982-
));
2983-
let rv = stm.add_device_listener(self.input_alive_listener.as_ref().unwrap());
2984-
if rv != NO_ERR {
2985-
self.input_alive_listener = None;
2986-
cubeb_log!("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv={}, device id ={}", rv, self.input_device.id);
2987-
return Err(Error::error());
3077+
// If the event is registered for the output device, it cannot be re-registered to the
3078+
// same device (it will return an 'nope' error).
3079+
if self.input_device.id != self.output_device.id {
3080+
// Event to notify when the input is going away.
3081+
self.input_alive_listener = Some(device_property_listener::new(
3082+
self.input_device.id,
3083+
get_property_address(
3084+
Property::DeviceIsAlive,
3085+
DeviceType::INPUT | DeviceType::OUTPUT,
3086+
),
3087+
audiounit_property_listener_callback,
3088+
));
3089+
let rv = stm.add_device_listener(self.input_alive_listener.as_ref().unwrap());
3090+
if rv != NO_ERR {
3091+
self.input_alive_listener = None;
3092+
cubeb_log!("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv={}, device id ={}", rv, self.input_device.id);
3093+
return Err(Error::error());
3094+
}
29883095
}
29893096
}
29903097

@@ -3043,6 +3150,7 @@ impl<'ctx> CoreStreamData<'ctx> {
30433150
self.output_source_listener.is_none()
30443151
&& self.input_source_listener.is_none()
30453152
&& self.input_alive_listener.is_none()
3153+
&& self.output_alive_listener.is_none()
30463154
);
30473155
return Ok(());
30483156
}
@@ -3061,6 +3169,15 @@ impl<'ctx> CoreStreamData<'ctx> {
30613169
self.output_source_listener = None;
30623170
}
30633171

3172+
if self.output_alive_listener.is_some() {
3173+
let rv = stm.remove_device_listener(self.output_alive_listener.as_ref().unwrap());
3174+
if rv != NO_ERR {
3175+
cubeb_log!("AudioObjectRemovePropertyListener/output/kAudioDevicePropertyDeviceIsAlive rv={}, device id={}", rv, self.input_device.id);
3176+
r = Err(Error::error());
3177+
}
3178+
self.output_alive_listener = None;
3179+
}
3180+
30643181
if self.input_source_listener.is_some() {
30653182
let rv = stm.remove_device_listener(self.input_source_listener.as_ref().unwrap());
30663183
if rv != NO_ERR {
@@ -3249,6 +3366,13 @@ impl<'ctx> AudioUnitStream<'ctx> {
32493366
kAudioObjectUnknown
32503367
};
32513368

3369+
let has_output = !self.core_stream_data.output_unit.is_null();
3370+
let output_device = if has_output {
3371+
self.core_stream_data.output_device.id
3372+
} else {
3373+
kAudioObjectUnknown
3374+
};
3375+
32523376
self.core_stream_data.close();
32533377

32543378
// Reinit occurs in one of the following case:
@@ -3270,13 +3394,15 @@ impl<'ctx> AudioUnitStream<'ctx> {
32703394
// Always use the default output on reinit. This is not correct in every
32713395
// case but it is sufficient for Firefox and prevent reinit from reporting
32723396
// failures. It will change soon when reinit mechanism will be updated.
3273-
self.core_stream_data.output_device = create_device_info(kAudioObjectUnknown, DeviceType::OUTPUT).map_err(|e| {
3274-
cubeb_log!(
3275-
"({:p}) Create output device info failed. This can happen when last media device is unplugged",
3276-
self.core_stream_data.stm_ptr
3277-
);
3278-
e
3279-
})?;
3397+
if has_output {
3398+
self.core_stream_data.output_device = create_device_info(output_device, DeviceType::OUTPUT).map_err(|e| {
3399+
cubeb_log!(
3400+
"({:p}) Create output device info failed. This can happen when last media device is unplugged",
3401+
self.core_stream_data.stm_ptr
3402+
);
3403+
e
3404+
})?;
3405+
}
32803406

32813407
if self.core_stream_data.setup().is_err() {
32823408
cubeb_log!(
@@ -3321,6 +3447,36 @@ impl<'ctx> AudioUnitStream<'ctx> {
33213447
Ok(())
33223448
}
33233449

3450+
// Stop (and destroy) the stream and fire an error state changed callback in a new thread.
3451+
fn report_error_async(&mut self) {
3452+
let queue = self.queue.clone();
3453+
let mutexed_stm = Arc::new(Mutex::new(self));
3454+
let also_mutexed_stm = Arc::clone(&mutexed_stm);
3455+
queue.run_async(move || {
3456+
let mut stm_guard = also_mutexed_stm.lock().unwrap();
3457+
let stm_ptr = *stm_guard as *const AudioUnitStream;
3458+
if stm_guard.destroy_pending.load(Ordering::SeqCst) {
3459+
cubeb_log!(
3460+
"({:p}) stream pending destroy, cancelling error report",
3461+
stm_ptr
3462+
);
3463+
return;
3464+
}
3465+
3466+
if !stm_guard.shutdown.load(Ordering::SeqCst) {
3467+
stm_guard.core_stream_data.stop_audiounits();
3468+
}
3469+
debug_assert!(
3470+
!stm_guard.core_stream_data.input_unit.is_null()
3471+
|| !stm_guard.core_stream_data.output_unit.is_null()
3472+
);
3473+
stm_guard.core_stream_data.close();
3474+
stm_guard.notify_state_changed(State::Error);
3475+
3476+
stm_guard.switching_device.store(false, Ordering::SeqCst);
3477+
});
3478+
}
3479+
33243480
fn reinit_async(&mut self) {
33253481
if self.reinit_pending.swap(true, Ordering::SeqCst) {
33263482
// A reinit task is already pending, nothing more to do.

0 commit comments

Comments
 (0)