@@ -86,6 +86,9 @@ mod enumerate;
8686
8787const DEFAULT_DEVICE : & str = "default" ;
8888
89+ // Some ALSA plugins (e.g. alsaequal, certain USB drivers) are not reentrant.
90+ static ALSA_OPEN_MUTEX : std:: sync:: Mutex < ( ) > = std:: sync:: Mutex :: new ( ( ) ) ;
91+
8992// TODO: Not yet defined in rust-lang/libc crate
9093const LIBC_ENOTSUPP : libc:: c_int = 524 ;
9194
@@ -371,9 +374,11 @@ impl Device {
371374 }
372375 }
373376
374- let handle = match alsa:: pcm:: PCM :: new ( & self . pcm_id , stream_type, true )
375- . map_err ( |e| ( e, e. errno ( ) ) )
376- {
377+ let open_result = {
378+ let _guard = ALSA_OPEN_MUTEX . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
379+ alsa:: pcm:: PCM :: new ( & self . pcm_id , stream_type, true ) . map_err ( |e| ( e, e. errno ( ) ) )
380+ } ;
381+ let handle = match open_result {
377382 Err ( ( _, libc:: ENOENT ) )
378383 | Err ( ( _, libc:: EPERM ) )
379384 | Err ( ( _, libc:: ENODEV ) )
@@ -410,7 +415,8 @@ impl Device {
410415
411416 // Pre-compute a period-sized buffer filled with silence values.
412417 let period_frames = period_samples / conf. channels as usize ;
413- let period_bytes = period_samples * sample_format. sample_size ( ) ;
418+ let frame_size = sample_format. sample_size ( ) * conf. channels as usize ;
419+ let period_bytes = period_frames * frame_size;
414420 let mut silence_template = vec ! [ 0u8 ; period_bytes] . into_boxed_slice ( ) ;
415421
416422 // Only fill buffer for unsigned formats that don't have a zero value for silence.
@@ -426,6 +432,7 @@ impl Device {
426432 conf,
427433 period_samples,
428434 period_frames,
435+ frame_size,
429436 silence_template,
430437 can_pause,
431438 creation_instant,
@@ -472,21 +479,24 @@ impl Device {
472479 & self ,
473480 stream_t : alsa:: Direction ,
474481 ) -> Result < VecIntoIter < SupportedStreamConfigRange > , SupportedStreamConfigsError > {
475- let pcm =
476- match alsa:: pcm:: PCM :: new ( & self . pcm_id , stream_t, true ) . map_err ( |e| ( e, e. errno ( ) ) ) {
477- Err ( ( _, libc:: ENOENT ) )
478- | Err ( ( _, libc:: EPERM ) )
479- | Err ( ( _, libc:: ENODEV ) )
480- | Err ( ( _, LIBC_ENOTSUPP ) ) => {
481- return Err ( SupportedStreamConfigsError :: DeviceNotAvailable )
482- }
483- Err ( ( _, libc:: EBUSY ) ) | Err ( ( _, libc:: EAGAIN ) ) => {
484- return Err ( SupportedStreamConfigsError :: DeviceBusy )
485- }
486- Err ( ( _, libc:: EINVAL ) ) => return Err ( SupportedStreamConfigsError :: InvalidArgument ) ,
487- Err ( ( e, _) ) => return Err ( e. into ( ) ) ,
488- Ok ( pcm) => pcm,
489- } ;
482+ let open_result = {
483+ let _guard = ALSA_OPEN_MUTEX . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
484+ alsa:: pcm:: PCM :: new ( & self . pcm_id , stream_t, true ) . map_err ( |e| ( e, e. errno ( ) ) )
485+ } ;
486+ let pcm = match open_result {
487+ Err ( ( _, libc:: ENOENT ) )
488+ | Err ( ( _, libc:: EPERM ) )
489+ | Err ( ( _, libc:: ENODEV ) )
490+ | Err ( ( _, LIBC_ENOTSUPP ) ) => {
491+ return Err ( SupportedStreamConfigsError :: DeviceNotAvailable )
492+ }
493+ Err ( ( _, libc:: EBUSY ) ) | Err ( ( _, libc:: EAGAIN ) ) => {
494+ return Err ( SupportedStreamConfigsError :: DeviceBusy )
495+ }
496+ Err ( ( _, libc:: EINVAL ) ) => return Err ( SupportedStreamConfigsError :: InvalidArgument ) ,
497+ Err ( ( e, _) ) => return Err ( e. into ( ) ) ,
498+ Ok ( pcm) => pcm,
499+ } ;
490500
491501 let hw_params = alsa:: pcm:: HwParams :: any ( & pcm) ?;
492502
@@ -711,6 +721,7 @@ struct StreamInner {
711721 // Cached values for performance in audio callback hot path
712722 period_samples : usize ,
713723 period_frames : usize ,
724+ frame_size : usize ,
714725 silence_template : Box < [ u8 ] > ,
715726
716727 #[ allow( dead_code) ]
@@ -939,33 +950,6 @@ fn try_resume(channel: &alsa::PCM) -> Result<Poll, StreamError> {
939950 }
940951}
941952
942- /// Validate the result of a `writei` or `readi` call and map ALSA errors to [`StreamError`].
943- #[ inline]
944- fn check_io_result (
945- result : Result < usize , alsa:: Error > ,
946- period_frames : usize ,
947- channel : & alsa:: PCM ,
948- direction : alsa:: Direction ,
949- ) -> Result < ( ) , StreamError > {
950- match result {
951- Ok ( n) if n != period_frames => Err ( BackendSpecificError {
952- description : format ! (
953- "partial {}: expected {period_frames} frames, transferred {n}" ,
954- match direction {
955- alsa:: Direction :: Capture => "read" ,
956- alsa:: Direction :: Playback => "write" ,
957- }
958- ) ,
959- }
960- . into ( ) ) ,
961- Ok ( _) => Ok ( ( ) ) ,
962- Err ( err) if err. errno ( ) == libc:: EPIPE => Err ( StreamError :: BufferUnderrun ) ,
963- Err ( err) if err. errno ( ) == libc:: ESTRPIPE => try_resume ( channel) . map ( |_| ( ) ) ,
964- Err ( err) if err. errno ( ) == libc:: ENODEV => Err ( StreamError :: DeviceNotAvailable ) ,
965- Err ( err) => Err ( err. into ( ) ) ,
966- }
967- }
968-
969953enum Poll {
970954 Pending ,
971955 Ready {
@@ -1047,13 +1031,23 @@ fn process_input(
10471031 delay_frames : usize ,
10481032 data_callback : & mut ( dyn FnMut ( & Data , & InputCallbackInfo ) + Send + ' static ) ,
10491033) -> Result < ( ) , StreamError > {
1050- let result = stream. channel . io_bytes ( ) . readi ( buffer) ;
1051- check_io_result (
1052- result,
1053- stream. period_frames ,
1054- & stream. channel ,
1055- alsa:: Direction :: Capture ,
1056- ) ?;
1034+ let mut frames_read = 0 ;
1035+ while frames_read < stream. period_frames {
1036+ match stream
1037+ . channel
1038+ . io_bytes ( )
1039+ . readi ( & mut buffer[ frames_read * stream. frame_size ..] )
1040+ {
1041+ Ok ( n) => frames_read += n,
1042+ Err ( err) if err. errno ( ) == libc:: EPIPE || err. errno ( ) == libc:: ESTRPIPE => {
1043+ // EPIPE = xrun, ESTRPIPE = hardware suspend. Both require prepare()+restart;
1044+ // attempting resume mid-loop with a partial transfer in the ring buffer is unsafe.
1045+ return Err ( StreamError :: BufferUnderrun ) ;
1046+ }
1047+ Err ( err) if err. errno ( ) == libc:: ENODEV => return Err ( StreamError :: DeviceNotAvailable ) ,
1048+ Err ( err) => return Err ( err. into ( ) ) ,
1049+ }
1050+ }
10571051 let data = buffer. as_mut_ptr ( ) as * mut ( ) ;
10581052 let data = unsafe { Data :: from_parts ( data, stream. period_samples , stream. sample_format ) } ;
10591053 let callback = if stream. use_hw_timestamps {
@@ -1108,13 +1102,24 @@ fn process_output(
11081102 data_callback ( & mut data, & info) ;
11091103 }
11101104
1111- let result = stream. channel . io_bytes ( ) . writei ( buffer) ;
1112- check_io_result (
1113- result,
1114- stream. period_frames ,
1115- & stream. channel ,
1116- alsa:: Direction :: Playback ,
1117- )
1105+ let mut frames_written = 0 ;
1106+ while frames_written < stream. period_frames {
1107+ match stream
1108+ . channel
1109+ . io_bytes ( )
1110+ . writei ( & buffer[ frames_written * stream. frame_size ..] )
1111+ {
1112+ Ok ( n) => frames_written += n,
1113+ Err ( err) if err. errno ( ) == libc:: EPIPE || err. errno ( ) == libc:: ESTRPIPE => {
1114+ // EPIPE = xrun, ESTRPIPE = hardware suspend. Both require prepare()+restart;
1115+ // attempting resume mid-loop with a partial transfer in the ring buffer is unsafe.
1116+ return Err ( StreamError :: BufferUnderrun ) ;
1117+ }
1118+ Err ( err) if err. errno ( ) == libc:: ENODEV => return Err ( StreamError :: DeviceNotAvailable ) ,
1119+ Err ( err) => return Err ( err. into ( ) ) ,
1120+ }
1121+ }
1122+ Ok ( ( ) )
11181123}
11191124
11201125// Use hardware timestamps from ALSA.
0 commit comments