Skip to content

Commit 750f204

Browse files
committed
Implement device change logic
1 parent 4cdbcea commit 750f204

File tree

2 files changed

+234
-63
lines changed

2 files changed

+234
-63
lines changed

src/backend/mod.rs

Lines changed: 228 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,30 @@ extern "C" fn audiounit_property_listener_callback(
753753
}
754754
stm.switching_device.store(true, Ordering::SeqCst);
755755

756+
let should_input_device_change = stm.core_stream_data.has_input()
757+
&& !stm
758+
.core_stream_data
759+
.input_stream_params
760+
.prefs()
761+
.contains(StreamPrefs::DISABLE_DEVICE_SWITCHING)
762+
&& stm
763+
.core_stream_data
764+
.input_device
765+
.flags
766+
.contains(device_flags::DEV_SELECTED_DEFAULT);
767+
768+
let should_output_device_change = stm.core_stream_data.has_output()
769+
&& !stm
770+
.core_stream_data
771+
.output_stream_params
772+
.prefs()
773+
.contains(StreamPrefs::DISABLE_DEVICE_SWITCHING)
774+
&& stm
775+
.core_stream_data
776+
.output_device
777+
.flags
778+
.contains(device_flags::DEV_SELECTED_DEFAULT);
779+
756780
cubeb_log!(
757781
"({:p}) Audio device changed, {} events.",
758782
stm as *const AudioUnitStream,
@@ -766,32 +790,129 @@ extern "C" fn audiounit_property_listener_callback(
766790
i,
767791
id
768792
);
793+
if !should_output_device_change {
794+
cubeb_log!("Output device should not change, ignore the event");
795+
stm.switching_device.store(false, Ordering::SeqCst);
796+
return NO_ERR;
797+
}
798+
// When a device needs to be updated it always goes to the default one. Thus the
799+
// output device is set to null. The re-init process will replace it with the current
800+
// default device later.
801+
stm.core_stream_data.output_device.id = kAudioObjectUnknown;
802+
// In case of a duplex call the output device info is filled. That will signal to the
803+
// re-init process that an explicit chosen device is selected. However, if the
804+
// user had selected the implicit default device (null deviceId) for the input device
805+
// has to be cleaned in order to signal to reinit process that the implicit default
806+
// device is being used (DEV_SELECTED_DEFAULT is the implicit defaut device).
807+
if stm
808+
.core_stream_data
809+
.input_device
810+
.flags
811+
.contains(device_flags::DEV_SELECTED_DEFAULT)
812+
{
813+
stm.core_stream_data.input_device.id = kAudioObjectUnknown;
814+
}
769815
}
770816
sys::kAudioHardwarePropertyDefaultInputDevice => {
771817
cubeb_log!(
772818
"Event[{}] - mSelector == kAudioHardwarePropertyDefaultInputDevice for id={}",
773819
i,
774820
id
775821
);
822+
// See the comments above for default output case. The code is symmetrical.
823+
if !should_input_device_change {
824+
cubeb_log!("Input device should not change, ignore the event");
825+
stm.switching_device.store(false, Ordering::SeqCst);
826+
return NO_ERR;
827+
}
828+
stm.core_stream_data.input_device.id = kAudioObjectUnknown;
829+
if stm
830+
.core_stream_data
831+
.output_device
832+
.flags
833+
.contains(device_flags::DEV_SELECTED_DEFAULT)
834+
{
835+
stm.core_stream_data.output_device.id = kAudioObjectUnknown;
836+
}
776837
}
777838
sys::kAudioDevicePropertyDeviceIsAlive => {
778839
cubeb_log!(
779840
"Event[{}] - mSelector == kAudioDevicePropertyDeviceIsAlive for id={}",
780841
i,
781842
id
782843
);
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)
844+
845+
// The same (removed) device is used for input and output, for example a headset.
846+
if stm.core_stream_data.input_device.id == id
847+
&& stm.core_stream_data.output_device.id == id
848+
&& (!should_input_device_change || !should_output_device_change)
790849
{
791-
cubeb_log!("It's the default input device, ignore the event");
792-
stm.switching_device.store(false, Ordering::SeqCst);
850+
cubeb_log!("Duplex device should not change, ignore the event");
851+
stm.report_error_async();
793852
return NO_ERR;
794853
}
854+
855+
if stm.core_stream_data.input_device.id == id {
856+
// Keep that first because it is required to return an error callback if the
857+
// device is removed but we don't have the option to change it.
858+
if !should_input_device_change {
859+
cubeb_log!("Input device should not change, ignore the event");
860+
stm.report_error_async();
861+
return NO_ERR;
862+
}
863+
864+
// Since the device will change let default event do so, if that's the case.
865+
if stm
866+
.core_stream_data
867+
.input_device
868+
.flags
869+
.contains(device_flags::DEV_SYSTEM_DEFAULT)
870+
{
871+
cubeb_log!("It's the default input device, ignore the event");
872+
stm.switching_device.store(false, Ordering::SeqCst);
873+
return NO_ERR;
874+
}
875+
876+
// The device is not the default, update it.
877+
stm.core_stream_data.input_device.id = kAudioObjectUnknown;
878+
if stm
879+
.core_stream_data
880+
.output_device
881+
.flags
882+
.contains(device_flags::DEV_SELECTED_DEFAULT)
883+
{
884+
stm.core_stream_data.output_device.id = kAudioObjectUnknown;
885+
}
886+
}
887+
888+
if stm.core_stream_data.output_device.id == id {
889+
if !should_output_device_change {
890+
cubeb_log!("Output device should not change, ignore the event");
891+
stm.report_error_async();
892+
return NO_ERR;
893+
}
894+
895+
if stm
896+
.core_stream_data
897+
.input_device
898+
.flags
899+
.contains(device_flags::DEV_SYSTEM_DEFAULT)
900+
{
901+
cubeb_log!("It's the default input device, ignore the event");
902+
stm.switching_device.store(false, Ordering::SeqCst);
903+
return NO_ERR;
904+
}
905+
906+
stm.core_stream_data.output_device.id = kAudioObjectUnknown;
907+
if stm
908+
.core_stream_data
909+
.input_device
910+
.flags
911+
.contains(device_flags::DEV_SELECTED_DEFAULT)
912+
{
913+
stm.core_stream_data.input_device.id = kAudioObjectUnknown;
914+
}
915+
}
795916
}
796917
sys::kAudioDevicePropertyDataSource => {
797918
cubeb_log!(
@@ -984,7 +1105,7 @@ fn create_audiounit(device: &device_info) -> Result<AudioUnit> {
9841105
.flags
9851106
.contains(device_flags::DEV_INPUT | device_flags::DEV_OUTPUT));
9861107

987-
let unit = create_default_audiounit(device.flags)?;
1108+
let unit = create_default_audiounit()?;
9881109
if device
9891110
.flags
9901111
.contains(device_flags::DEV_SYSTEM_DEFAULT | device_flags::DEV_OUTPUT)
@@ -1080,26 +1201,16 @@ fn set_device_to_audiounit(
10801201
}
10811202
}
10821203

1083-
fn create_default_audiounit(flags: device_flags) -> Result<AudioUnit> {
1084-
let desc = get_audiounit_description(flags);
1204+
fn create_default_audiounit() -> Result<AudioUnit> {
1205+
let desc = get_audiounit_description();
10851206
create_audiounit_by_description(desc)
10861207
}
10871208

1088-
fn get_audiounit_description(flags: device_flags) -> AudioComponentDescription {
1209+
fn get_audiounit_description() -> AudioComponentDescription {
10891210
AudioComponentDescription {
10901211
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.
10951212
#[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-
},
1213+
componentSubType: kAudioUnitSubType_HALOutput,
11031214
#[cfg(target_os = "ios")]
11041215
componentSubType: kAudioUnitSubType_RemoteIO,
11051216
componentManufacturer: kAudioUnitManufacturer_Apple,
@@ -2321,6 +2432,7 @@ struct CoreStreamData<'ctx> {
23212432
default_input_listener: Option<device_property_listener>,
23222433
default_output_listener: Option<device_property_listener>,
23232434
input_alive_listener: Option<device_property_listener>,
2435+
output_alive_listener: Option<device_property_listener>,
23242436
input_source_listener: Option<device_property_listener>,
23252437
output_source_listener: Option<device_property_listener>,
23262438
}
@@ -2359,6 +2471,7 @@ impl<'ctx> Default for CoreStreamData<'ctx> {
23592471
default_input_listener: None,
23602472
default_output_listener: None,
23612473
input_alive_listener: None,
2474+
output_alive_listener: None,
23622475
input_source_listener: None,
23632476
output_source_listener: None,
23642477
}
@@ -2404,6 +2517,7 @@ impl<'ctx> CoreStreamData<'ctx> {
24042517
default_input_listener: None,
24052518
default_output_listener: None,
24062519
input_alive_listener: None,
2520+
output_alive_listener: None,
24072521
input_source_listener: None,
24082522
output_source_listener: None,
24092523
}
@@ -2952,6 +3066,22 @@ impl<'ctx> CoreStreamData<'ctx> {
29523066
cubeb_log!("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv={}, device id={}", rv, self.output_device.id);
29533067
return Err(Error::error());
29543068
}
3069+
3070+
// Event to notify when the input is going away.
3071+
self.output_alive_listener = Some(device_property_listener::new(
3072+
self.output_device.id,
3073+
get_property_address(
3074+
Property::DeviceIsAlive,
3075+
DeviceType::INPUT | DeviceType::OUTPUT,
3076+
),
3077+
audiounit_property_listener_callback,
3078+
));
3079+
let rv = stm.add_device_listener(self.output_alive_listener.as_ref().unwrap());
3080+
if rv != NO_ERR {
3081+
self.output_alive_listener = None;
3082+
cubeb_log!("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDeviceIsAlive rv={}, device id ={}", rv, self.input_device.id);
3083+
return Err(Error::error());
3084+
}
29553085
}
29563086

29573087
if !self.input_unit.is_null() {
@@ -2971,20 +3101,24 @@ impl<'ctx> CoreStreamData<'ctx> {
29713101
return Err(Error::error());
29723102
}
29733103

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());
3104+
// If the event is registered for the output device, it cannot be re-registered to the
3105+
// same device (it will return an 'nope' error).
3106+
if self.input_device.id != self.output_device.id {
3107+
// Event to notify when the input is going away.
3108+
self.input_alive_listener = Some(device_property_listener::new(
3109+
self.input_device.id,
3110+
get_property_address(
3111+
Property::DeviceIsAlive,
3112+
DeviceType::INPUT | DeviceType::OUTPUT,
3113+
),
3114+
audiounit_property_listener_callback,
3115+
));
3116+
let rv = stm.add_device_listener(self.input_alive_listener.as_ref().unwrap());
3117+
if rv != NO_ERR {
3118+
self.input_alive_listener = None;
3119+
cubeb_log!("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv={}, device id ={}", rv, self.input_device.id);
3120+
return Err(Error::error());
3121+
}
29883122
}
29893123
}
29903124

@@ -3043,6 +3177,7 @@ impl<'ctx> CoreStreamData<'ctx> {
30433177
self.output_source_listener.is_none()
30443178
&& self.input_source_listener.is_none()
30453179
&& self.input_alive_listener.is_none()
3180+
&& self.output_alive_listener.is_none()
30463181
);
30473182
return Ok(());
30483183
}
@@ -3061,6 +3196,15 @@ impl<'ctx> CoreStreamData<'ctx> {
30613196
self.output_source_listener = None;
30623197
}
30633198

3199+
if self.output_alive_listener.is_some() {
3200+
let rv = stm.remove_device_listener(self.output_alive_listener.as_ref().unwrap());
3201+
if rv != NO_ERR {
3202+
cubeb_log!("AudioObjectRemovePropertyListener/output/kAudioDevicePropertyDeviceIsAlive rv={}, device id={}", rv, self.input_device.id);
3203+
r = Err(Error::error());
3204+
}
3205+
self.output_alive_listener = None;
3206+
}
3207+
30643208
if self.input_source_listener.is_some() {
30653209
let rv = stm.remove_device_listener(self.input_source_listener.as_ref().unwrap());
30663210
if rv != NO_ERR {
@@ -3249,6 +3393,13 @@ impl<'ctx> AudioUnitStream<'ctx> {
32493393
kAudioObjectUnknown
32503394
};
32513395

3396+
let has_output = !self.core_stream_data.output_unit.is_null();
3397+
let output_device = if has_output {
3398+
self.core_stream_data.output_device.id
3399+
} else {
3400+
kAudioObjectUnknown
3401+
};
3402+
32523403
self.core_stream_data.close();
32533404

32543405
// Reinit occurs in one of the following case:
@@ -3270,13 +3421,15 @@ impl<'ctx> AudioUnitStream<'ctx> {
32703421
// Always use the default output on reinit. This is not correct in every
32713422
// case but it is sufficient for Firefox and prevent reinit from reporting
32723423
// 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-
})?;
3424+
if has_output {
3425+
self.core_stream_data.output_device = create_device_info(output_device, DeviceType::OUTPUT).map_err(|e| {
3426+
cubeb_log!(
3427+
"({:p}) Create output device info failed. This can happen when last media device is unplugged",
3428+
self.core_stream_data.stm_ptr
3429+
);
3430+
e
3431+
})?;
3432+
}
32803433

32813434
if self.core_stream_data.setup().is_err() {
32823435
cubeb_log!(
@@ -3321,6 +3474,36 @@ impl<'ctx> AudioUnitStream<'ctx> {
33213474
Ok(())
33223475
}
33233476

3477+
// Stop (and destroy) the stream and fire an error state changed callback in a new thread.
3478+
fn report_error_async(&mut self) {
3479+
let queue = self.queue.clone();
3480+
let mutexed_stm = Arc::new(Mutex::new(self));
3481+
let also_mutexed_stm = Arc::clone(&mutexed_stm);
3482+
queue.run_async(move || {
3483+
let mut stm_guard = also_mutexed_stm.lock().unwrap();
3484+
let stm_ptr = *stm_guard as *const AudioUnitStream;
3485+
if stm_guard.destroy_pending.load(Ordering::SeqCst) {
3486+
cubeb_log!(
3487+
"({:p}) stream pending destroy, cancelling error report",
3488+
stm_ptr
3489+
);
3490+
return;
3491+
}
3492+
3493+
if !stm_guard.shutdown.load(Ordering::SeqCst) {
3494+
stm_guard.core_stream_data.stop_audiounits();
3495+
}
3496+
debug_assert!(
3497+
!stm_guard.core_stream_data.input_unit.is_null()
3498+
|| !stm_guard.core_stream_data.output_unit.is_null()
3499+
);
3500+
stm_guard.core_stream_data.close();
3501+
stm_guard.notify_state_changed(State::Error);
3502+
3503+
stm_guard.switching_device.store(false, Ordering::SeqCst);
3504+
});
3505+
}
3506+
33243507
fn reinit_async(&mut self) {
33253508
if self.reinit_pending.swap(true, Ordering::SeqCst) {
33263509
// A reinit task is already pending, nothing more to do.

0 commit comments

Comments
 (0)