@@ -18,13 +18,12 @@ use objc2_audio_toolbox::{
1818} ;
1919use objc2_core_audio:: {
2020 kAudioDevicePropertyAvailableNominalSampleRates, kAudioDevicePropertyBufferFrameSize,
21- kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyDeviceIsAlive,
22- kAudioDevicePropertyNominalSampleRate, kAudioDevicePropertyStreamConfiguration,
23- kAudioDevicePropertyStreamFormat, kAudioObjectPropertyElementMaster,
24- kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput,
25- kAudioObjectPropertyScopeOutput, AudioDeviceID , AudioObjectGetPropertyData ,
26- AudioObjectGetPropertyDataSize , AudioObjectID , AudioObjectPropertyAddress ,
27- AudioObjectPropertyScope , AudioObjectSetPropertyData ,
21+ kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyNominalSampleRate,
22+ kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyStreamFormat,
23+ kAudioObjectPropertyElementMaster, kAudioObjectPropertyScopeGlobal,
24+ kAudioObjectPropertyScopeInput, kAudioObjectPropertyScopeOutput, AudioDeviceID ,
25+ AudioObjectGetPropertyData , AudioObjectGetPropertyDataSize , AudioObjectID ,
26+ AudioObjectPropertyAddress , AudioObjectPropertyScope , AudioObjectSetPropertyData ,
2827} ;
2928use objc2_core_audio_types:: {
3029 AudioBuffer , AudioBufferList , AudioStreamBasicDescription , AudioValueRange ,
@@ -36,12 +35,12 @@ pub use super::enumerate::{
3635use std:: fmt;
3736use std:: mem:: { self } ;
3837use std:: ptr:: { null, NonNull } ;
39- use std:: rc:: Rc ;
4038use std:: slice;
4139use std:: sync:: mpsc:: { channel, RecvTimeoutError } ;
4240use std:: sync:: { Arc , Mutex } ;
4341use std:: time:: { Duration , Instant } ;
4442
43+ use super :: invoke_error_callback;
4544use super :: property_listener:: AudioObjectPropertyListener ;
4645use coreaudio:: audio_unit:: macos_helpers:: get_device_name;
4746/// Attempt to set the device sample rate to the provided rate.
@@ -253,45 +252,6 @@ fn get_io_buffer_frame_size_range(
253252 } )
254253}
255254
256- /// Register the on-disconnect callback.
257- /// This will both stop the stream and call the error callback with DeviceNotAvailable.
258- /// This function should only be called once per stream.
259- fn add_disconnect_listener < E > (
260- stream : & Stream ,
261- error_callback : Arc < Mutex < E > > ,
262- ) -> Result < ( ) , BuildStreamError >
263- where
264- E : FnMut ( StreamError ) + Send + ' static ,
265- {
266- let stream_inner_weak = Rc :: downgrade ( & stream. inner ) ;
267- let mut stream_inner = stream. inner . borrow_mut ( ) ;
268- stream_inner. _disconnect_listener = Some ( AudioObjectPropertyListener :: new (
269- stream_inner. device_id ,
270- AudioObjectPropertyAddress {
271- mSelector : kAudioDevicePropertyDeviceIsAlive,
272- mScope : kAudioObjectPropertyScopeGlobal,
273- mElement : kAudioObjectPropertyElementMaster,
274- } ,
275- move || {
276- if let Some ( stream_inner_strong) = stream_inner_weak. upgrade ( ) {
277- match stream_inner_strong. try_borrow_mut ( ) {
278- Ok ( mut stream_inner) => {
279- let _ = stream_inner. pause ( ) ;
280- }
281- Err ( _) => {
282- // Could not acquire mutable borrow. This can occur if there are
283- // overlapping borrows, if the stream is already in use, or if a panic
284- // occurred during a previous borrow. Still notify about device
285- // disconnection even if we can't pause.
286- }
287- }
288- ( error_callback. lock ( ) . unwrap ( ) ) ( StreamError :: DeviceNotAvailable ) ;
289- }
290- } ,
291- ) ?) ;
292- Ok ( ( ) )
293- }
294-
295255impl DeviceTrait for Device {
296256 type SupportedInputConfigs = SupportedInputConfigs ;
297257 type SupportedOutputConfigs = SupportedOutputConfigs ;
@@ -710,24 +670,22 @@ impl Device {
710670
711671 type Args = render_callback:: Args < data:: Raw > ;
712672 audio_unit. set_input_callback ( move |args : Args | unsafe {
713- let ptr = ( * args. data . data ) . mBuffers . as_ptr ( ) ;
714- let len = ( * args. data . data ) . mNumberBuffers as usize ;
715- let buffers: & [ AudioBuffer ] = slice:: from_raw_parts ( ptr, len) ;
716-
717- // TODO: Perhaps loop over all buffers instead?
673+ // SAFETY: We configure the stream format as interleaved (via asbd_from_config which
674+ // does not set kAudioFormatFlagIsNonInterleaved). Interleaved format always has
675+ // exactly one buffer containing all channels, so mBuffers[0] is always valid.
718676 let AudioBuffer {
719677 mNumberChannels : channels,
720678 mDataByteSize : data_byte_size,
721679 mData : data,
722- } = buffers [ 0 ] ;
680+ } = ( * args . data . data ) . mBuffers [ 0 ] ;
723681
724682 let data = data as * mut ( ) ;
725683 let len = data_byte_size as usize / bytes_per_channel;
726684 let data = Data :: from_parts ( data, len, sample_format) ;
727685
728686 let callback = match host_time_to_stream_instant ( args. time_stamp . mHostTime ) {
729687 Err ( err) => {
730- ( error_callback. lock ( ) . unwrap ( ) ) ( err. into ( ) ) ;
688+ invoke_error_callback ( & error_callback, err. into ( ) ) ;
731689 return Err ( ( ) ) ;
732690 }
733691 Ok ( cb) => cb,
@@ -750,21 +708,36 @@ impl Device {
750708 Ok ( ( ) )
751709 } ) ?;
752710
753- let stream = Stream :: new ( StreamInner {
754- playing : true ,
755- _disconnect_listener : None ,
756- audio_unit,
757- device_id : self . audio_device_id ,
758- _loopback_device : loopback_aggregate,
759- } ) ;
760-
761- // If we didn't request the default device, stop the stream if the
762- // device disconnects.
763- if !is_default_device ( self ) {
764- add_disconnect_listener ( & stream, error_callback_disconnect) ?;
765- }
711+ // Create error callback for stream - either dummy or real based on device type
712+ let error_callback_for_stream: super :: ErrorCallback = if is_default_device ( self ) {
713+ Box :: new ( |_: StreamError | { } )
714+ } else {
715+ let error_callback_clone = error_callback_disconnect. clone ( ) ;
716+ Box :: new ( move |err : StreamError | {
717+ invoke_error_callback ( & error_callback_clone, err) ;
718+ } )
719+ } ;
720+
721+ let stream = Stream :: new (
722+ StreamInner {
723+ playing : true ,
724+ audio_unit,
725+ device_id : self . audio_device_id ,
726+ _loopback_device : loopback_aggregate,
727+ } ,
728+ error_callback_for_stream,
729+ ) ?;
766730
767- stream. inner . borrow_mut ( ) . audio_unit . start ( ) ?;
731+ stream
732+ . inner
733+ . lock ( )
734+ . map_err ( |_| BuildStreamError :: BackendSpecific {
735+ err : BackendSpecificError {
736+ description : "A cpal stream operation panicked while holding the lock - this is a bug, please report it" . to_string ( ) ,
737+ } ,
738+ } ) ?
739+ . audio_unit
740+ . start ( ) ?;
768741
769742 Ok ( stream)
770743 }
@@ -800,9 +773,9 @@ impl Device {
800773
801774 type Args = render_callback:: Args < data:: Raw > ;
802775 audio_unit. set_render_callback ( move |args : Args | unsafe {
803- // If `run()` is currently running, then a callback will be available from this list.
804- // Otherwise, we just fill the buffer with zeroes and return.
805-
776+ // SAFETY: We configure the stream format as interleaved (via asbd_from_config which
777+ // does not set kAudioFormatFlagIsNonInterleaved). Interleaved format always has
778+ // exactly one buffer containing all channels, so mBuffers[0] is always valid.
806779 let AudioBuffer {
807780 mNumberChannels : channels,
808781 mDataByteSize : data_byte_size,
@@ -815,7 +788,7 @@ impl Device {
815788
816789 let callback = match host_time_to_stream_instant ( args. time_stamp . mHostTime ) {
817790 Err ( err) => {
818- ( error_callback. lock ( ) . unwrap ( ) ) ( err. into ( ) ) ;
791+ invoke_error_callback ( & error_callback, err. into ( ) ) ;
819792 return Err ( ( ) ) ;
820793 }
821794 Ok ( cb) => cb,
@@ -838,21 +811,36 @@ impl Device {
838811 Ok ( ( ) )
839812 } ) ?;
840813
841- let stream = Stream :: new ( StreamInner {
842- playing : true ,
843- _disconnect_listener : None ,
844- audio_unit,
845- device_id : self . audio_device_id ,
846- _loopback_device : None ,
847- } ) ;
848-
849- // If we didn't request the default device, stop the stream if the
850- // device disconnects.
851- if !is_default_device ( self ) {
852- add_disconnect_listener ( & stream, error_callback_disconnect) ?;
853- }
814+ // Create error callback for stream - either dummy or real based on device type
815+ let error_callback_for_stream: super :: ErrorCallback = if is_default_device ( self ) {
816+ Box :: new ( |_: StreamError | { } )
817+ } else {
818+ let error_callback_clone = error_callback_disconnect. clone ( ) ;
819+ Box :: new ( move |err : StreamError | {
820+ invoke_error_callback ( & error_callback_clone, err) ;
821+ } )
822+ } ;
823+
824+ let stream = Stream :: new (
825+ StreamInner {
826+ playing : true ,
827+ audio_unit,
828+ device_id : self . audio_device_id ,
829+ _loopback_device : None ,
830+ } ,
831+ error_callback_for_stream,
832+ ) ?;
854833
855- stream. inner . borrow_mut ( ) . audio_unit . start ( ) ?;
834+ stream
835+ . inner
836+ . lock ( )
837+ . map_err ( |_| BuildStreamError :: BackendSpecific {
838+ err : BackendSpecificError {
839+ description : "A cpal stream operation panicked while holding the lock - this is a bug, please report it" . to_string ( ) ,
840+ } ,
841+ } ) ?
842+ . audio_unit
843+ . start ( ) ?;
856844
857845 Ok ( stream)
858846 }
0 commit comments