@@ -8,7 +8,7 @@ extern crate alsa_sys;
88extern crate libc;
99
1010use std:: {
11- cmp , fmt,
11+ fmt,
1212 sync:: {
1313 atomic:: { AtomicBool , Ordering } ,
1414 Arc , Mutex ,
@@ -368,7 +368,7 @@ impl Device {
368368
369369 let hw_params = set_hw_params_from_format ( & handle, conf, sample_format) ?;
370370 let ( buffer_size, period_size) = set_sw_params_from_format ( & handle, stream_type) ?;
371- if buffer_size == 0 {
371+ if buffer_size == 0 || period_size == 0 {
372372 return Err ( ErrorKind :: DeviceNotAvailable . into ( ) ) ;
373373 }
374374
@@ -494,68 +494,52 @@ impl Device {
494494 //SND_PCM_FORMAT_U18_3BE,
495495 ] ;
496496
497- // Collect supported formats, deduplicating since we test both LE and BE variants.
498- // If hardware supports both endiannesses (rare), we only report the format once.
499- let mut supported_formats = Vec :: new ( ) ;
500- for & ( sample_format, alsa_format) in FORMATS . iter ( ) {
501- if hw_params. test_format ( alsa_format) . is_ok ( )
502- && !supported_formats. contains ( & sample_format)
503- {
504- supported_formats. push ( sample_format) ;
505- }
506- }
507-
508497 let min_rate = hw_params. get_rate_min ( ) ?;
509498 let max_rate = hw_params. get_rate_max ( ) ?;
510499
511500 let sample_rates = if min_rate == max_rate || hw_params. test_rate ( min_rate + 1 ) . is_ok ( ) {
501+ // Fixed rate or continuous range.
512502 vec ! [ ( min_rate, max_rate) ]
513503 } else {
514- let mut rates = Vec :: new ( ) ;
515- for & sample_rate in COMMON_SAMPLE_RATES . iter ( ) {
516- if hw_params . test_rate ( sample_rate ) . is_ok ( ) {
517- rates . push ( ( sample_rate , sample_rate ) ) ;
518- }
519- }
520-
521- if rates . is_empty ( ) {
522- vec ! [ ( min_rate , max_rate ) ]
523- } else {
524- rates
525- }
504+ // Discrete rates: probe the standard list plus the hardware's own min and max so
505+ // that rates outside ` COMMON_SAMPLE_RATES` are not missed.
506+ let mut probe : Vec < SampleRate > = COMMON_SAMPLE_RATES . to_vec ( ) ;
507+ probe . push ( min_rate ) ;
508+ probe . push ( max_rate ) ;
509+ probe . sort_unstable ( ) ;
510+ probe . dedup ( ) ;
511+ probe
512+ . into_iter ( )
513+ . filter ( | & r| ( min_rate..=max_rate ) . contains ( & r ) && hw_params . test_rate ( r ) . is_ok ( ) )
514+ . map ( |r| ( r , r ) )
515+ . collect ( )
526516 } ;
527517
528518 let min_channels = hw_params. get_channels_min ( ) ?;
529- let max_channels = hw_params. get_channels_max ( ) ?;
519+ let max_channels = hw_params. get_channels_max ( ) ?. min ( 32 ) ; // TODO: cap at 32 or too many configs
530520
531- let max_channels = cmp:: min ( max_channels, 32 ) ; // TODO: limiting to 32 channels or too much stuff is returned
532- let supported_channels = ( min_channels..=max_channels)
533- . filter_map ( |num| {
534- if hw_params. test_channels ( num) . is_ok ( ) {
535- Some ( num as ChannelCount )
536- } else {
537- None
538- }
539- } )
540- . collect :: < Vec < _ > > ( ) ;
521+ let mut output = Vec :: new ( ) ;
522+ let mut seen_formats: Vec < SampleFormat > = Vec :: new ( ) ;
523+ for & ( sample_format, alsa_format) in FORMATS . iter ( ) {
524+ if seen_formats. contains ( & sample_format) || hw_params. test_format ( alsa_format) . is_err ( )
525+ {
526+ continue ;
527+ }
528+ seen_formats. push ( sample_format) ;
541529
542- let ( min_buffer_size, max_buffer_size) = hw_params_buffer_size_min_max ( & hw_params) ;
543- let buffer_size_range = SupportedBufferSize :: Range {
544- min : min_buffer_size,
545- max : max_buffer_size,
546- } ;
530+ for channels in min_channels..=max_channels {
531+ if hw_params. test_channels ( channels) . is_err ( ) {
532+ continue ;
533+ }
534+ let channels = channels as ChannelCount ;
535+ let buffer_size = supported_period_size_range ( & pcm, alsa_format, channels) ;
547536
548- let mut output = Vec :: with_capacity (
549- supported_formats. len ( ) * supported_channels. len ( ) * sample_rates. len ( ) ,
550- ) ;
551- for & sample_format in supported_formats. iter ( ) {
552- for & channels in supported_channels. iter ( ) {
553537 for & ( min_rate, max_rate) in sample_rates. iter ( ) {
554538 output. push ( SupportedStreamConfigRange {
555539 channels,
556540 min_sample_rate : min_rate,
557541 max_sample_rate : max_rate,
558- buffer_size : buffer_size_range ,
542+ buffer_size,
559543 sample_format,
560544 } ) ;
561545 }
@@ -579,7 +563,7 @@ impl Device {
579563 let mut formats: Vec < _ > = {
580564 match self . supported_configs ( stream_t) {
581565 // EINVAL when querying direction the device does not support (input-only or output-only)
582- Err ( err) if err. kind ( ) == ErrorKind :: InvalidInput => {
566+ Err ( err) if err. kind ( ) == ErrorKind :: UnsupportedConfig => {
583567 let dir = match stream_t {
584568 alsa:: Direction :: Capture => "input" ,
585569 alsa:: Direction :: Playback => "output" ,
@@ -1036,9 +1020,7 @@ fn try_resume(handle: &alsa::PCM) -> Result<Poll, Error> {
10361020 // device is still resuming; poll again until it is ready.
10371021 Err ( e) if e. errno ( ) == libc:: EAGAIN => Ok ( Poll :: Pending ) ,
10381022 // hardware does not support soft resume; treat as xrun so the worker calls prepare()
1039- Err ( e) if e. errno ( ) == libc:: ENOSYS => {
1040- Err ( Error :: with_message ( ErrorKind :: Xrun , e. to_string ( ) ) )
1041- }
1023+ Err ( e) if e. errno ( ) == libc:: ENOSYS => Err ( ErrorKind :: Xrun . into ( ) ) ,
10421024 Err ( e) => Err ( e. into ( ) ) ,
10431025 }
10441026}
@@ -1110,9 +1092,7 @@ fn poll_for_period(
11101092 // POLLIN/POLLOUT: data is ready, fall through to process it.
11111093 let ( avail_frames, delay_frames) = match stream. handle . avail_delay ( ) {
11121094 // Xrun: recover via prepare() (+ start() for capture, handled by the worker).
1113- Err ( err) if err. errno ( ) == libc:: EPIPE => {
1114- return Err ( Error :: with_message ( ErrorKind :: Xrun , err. to_string ( ) ) )
1115- }
1095+ Err ( err) if err. errno ( ) == libc:: EPIPE => return Err ( ErrorKind :: Xrun . into ( ) ) ,
11161096 // Suspend: try hardware resume first; fall back to prepare() if unsupported.
11171097 Err ( err) if err. errno ( ) == libc:: ESTRPIPE => return try_resume ( & stream. handle ) ,
11181098 res => res,
@@ -1168,13 +1148,11 @@ fn process_input(
11681148 if frames_read == 0 {
11691149 return Ok ( ( ) ) ;
11701150 } else {
1171- return Err ( Error :: with_message ( ErrorKind :: Xrun , err . to_string ( ) ) ) ;
1151+ return Err ( ErrorKind :: Xrun . into ( ) ) ;
11721152 }
11731153 }
11741154 // EPIPE = xrun: full underrun recovery (prepare + start) required.
1175- Err ( err) if err. errno ( ) == libc:: EPIPE => {
1176- return Err ( Error :: with_message ( ErrorKind :: Xrun , err. to_string ( ) ) )
1177- }
1155+ Err ( err) if err. errno ( ) == libc:: EPIPE => return Err ( ErrorKind :: Xrun . into ( ) ) ,
11781156 // ESTRPIPE = hardware suspend: try soft resume first, falling back to underrun
11791157 // recovery if the hardware doesn't support it.
11801158 Err ( err) if err. errno ( ) == libc:: ESTRPIPE => {
@@ -1238,13 +1216,11 @@ fn process_output(
12381216 if frames_written == 0 {
12391217 return Ok ( ( ) ) ;
12401218 } else {
1241- return Err ( Error :: with_message ( ErrorKind :: Xrun , err . to_string ( ) ) ) ;
1219+ return Err ( ErrorKind :: Xrun . into ( ) ) ;
12421220 }
12431221 }
12441222 // EPIPE = xrun: full underrun recovery (prepare) required.
1245- Err ( err) if err. errno ( ) == libc:: EPIPE => {
1246- return Err ( Error :: with_message ( ErrorKind :: Xrun , err. to_string ( ) ) )
1247- }
1223+ Err ( err) if err. errno ( ) == libc:: EPIPE => return Err ( ErrorKind :: Xrun . into ( ) ) ,
12481224 // ESTRPIPE = hardware suspend: try soft resume first, falling back to underrun
12491225 // recovery if the hardware doesn't support it.
12501226 Err ( err) if err. errno ( ) == libc:: ESTRPIPE => {
@@ -1439,22 +1415,52 @@ impl StreamTrait for Stream {
14391415 }
14401416}
14411417
1442- // Convert ALSA frames to FrameCount, clamping to valid range.
1443- // ALSA Frames are i64 (64-bit) or i32 (32-bit).
1444- fn clamp_frame_count ( buffer_size : alsa:: pcm:: Frames ) -> FrameCount {
1445- buffer_size. max ( 1 ) . try_into ( ) . unwrap_or ( FrameCount :: MAX )
1418+ fn supported_period_size_range (
1419+ pcm : & alsa:: pcm:: PCM ,
1420+ alsa_format : alsa:: pcm:: Format ,
1421+ channels : ChannelCount ,
1422+ ) -> SupportedBufferSize {
1423+ let Ok ( p) = alsa:: pcm:: HwParams :: any ( pcm) else {
1424+ return SupportedBufferSize :: Unknown ;
1425+ } ;
1426+ if p. set_access ( alsa:: pcm:: Access :: RWInterleaved ) . is_err ( )
1427+ || p. set_channels ( channels as u32 ) . is_err ( )
1428+ || p. set_format ( alsa_format) . is_err ( )
1429+ {
1430+ return SupportedBufferSize :: Unknown ;
1431+ }
1432+ let Some ( ( min, max) ) = hw_params_period_size_min_max ( & p) else {
1433+ return SupportedBufferSize :: Unknown ;
1434+ } ;
1435+ let min_frames = min. max ( 1 ) ;
1436+ // cpal double-buffers (ring = DEFAULT_PERIODS × period), so the achievable
1437+ // period maximum is also bounded by max_buffer / DEFAULT_PERIODS.
1438+ let effective_max = match p. get_buffer_size_max ( ) {
1439+ Ok ( max_buf) if max_buf > 0 => max. min ( max_buf / DEFAULT_PERIODS ) ,
1440+ _ => max,
1441+ } ;
1442+ if effective_max >= min_frames {
1443+ let Ok ( min) = min_frames. try_into ( ) else {
1444+ return SupportedBufferSize :: Unknown ;
1445+ } ;
1446+ SupportedBufferSize :: Range {
1447+ min,
1448+ max : effective_max. try_into ( ) . unwrap_or ( FrameCount :: MAX ) ,
1449+ }
1450+ } else {
1451+ SupportedBufferSize :: Unknown
1452+ }
14461453}
14471454
1448- fn hw_params_buffer_size_min_max ( hw_params : & alsa:: pcm:: HwParams ) -> ( FrameCount , FrameCount ) {
1449- let min_buf = hw_params
1450- . get_buffer_size_min ( )
1451- . map ( clamp_frame_count)
1452- . unwrap_or ( 1 ) ;
1453- let max_buf = hw_params
1454- . get_buffer_size_max ( )
1455- . map ( clamp_frame_count)
1456- . unwrap_or ( FrameCount :: MAX ) ;
1457- ( min_buf, max_buf)
1455+ fn hw_params_period_size_min_max (
1456+ hw_params : & alsa:: pcm:: HwParams ,
1457+ ) -> Option < ( alsa:: pcm:: Frames , alsa:: pcm:: Frames ) > {
1458+ let min = hw_params. get_period_size_min ( ) . ok ( ) ?;
1459+ let max = hw_params. get_period_size_max ( ) . ok ( ) ?;
1460+ // min=0 means no hardware lower bound (PipeWire reports this on unconstrained params);
1461+ // it is handled in the caller by clamping to 1. max <= 0 is degenerate (or ULONG_MAX
1462+ // wrapping negative), so we return None in that case rather than a misleading range.
1463+ ( max > 0 && max >= min) . then_some ( ( min, max) )
14581464}
14591465
14601466fn init_hw_params < ' a > (
@@ -1563,21 +1569,42 @@ fn set_hw_params_from_format(
15631569 // When BufferSize::Fixed(x) is specified, we configure double-buffering with
15641570 // buffer_size = 2x and period_size = x. This provides consistent low-latency
15651571 // behavior across different ALSA implementations and hardware.
1566- if let BufferSize :: Fixed ( buffer_frames) = config. buffer_size {
1567- // Validate the requested size against the device's supported range using the same PCM
1572+ if let BufferSize :: Fixed ( period_size) = config. buffer_size {
1573+ if period_size == 0 {
1574+ return Err ( Error :: with_message (
1575+ ErrorKind :: InvalidInput ,
1576+ "Buffer size must be greater than 0" ,
1577+ ) ) ;
1578+ }
1579+
1580+ let period_size = period_size as alsa:: pcm:: Frames ;
1581+
1582+ // Validate the requested size against the device's supported ranges using the same PCM
15681583 // handle we'll use for streaming. This avoids a second PCM open (which can disturb
15691584 // hardware clock state on some drivers) while still catching wildly out-of-range
15701585 // requests before set_period_size_near silently rounds them.
1571- let ( min_buffer, max_buffer) = hw_params_buffer_size_min_max ( & hw_params) ;
1572- if !( min_buffer..=max_buffer) . contains ( & buffer_frames) {
1573- return Err ( Error :: with_message (
1574- ErrorKind :: UnsupportedConfig ,
1575- format ! ( "Buffer size {buffer_frames} is not in the supported range {min_buffer}..={max_buffer}" ) ,
1576- ) ) ;
1586+ if let Some ( ( min_period, max_period) ) = hw_params_period_size_min_max ( & hw_params) {
1587+ if !( min_period..=max_period) . contains ( & period_size) {
1588+ return Err ( Error :: with_message (
1589+ ErrorKind :: UnsupportedConfig ,
1590+ format ! ( "Buffer size {period_size} is not in the supported range {min_period}..={max_period}" ) ,
1591+ ) ) ;
1592+ }
1593+ }
1594+
1595+ let buffer_size = DEFAULT_PERIODS * period_size;
1596+ if let Ok ( max_buffer) = hw_params. get_buffer_size_max ( ) {
1597+ if max_buffer > 0 && buffer_size > max_buffer {
1598+ let effective_max = max_buffer / DEFAULT_PERIODS ;
1599+ return Err ( Error :: with_message (
1600+ ErrorKind :: UnsupportedConfig ,
1601+ format ! ( "Buffer size {period_size} exceeds the maximum supported value of {effective_max}" ) ,
1602+ ) ) ;
1603+ }
15771604 }
1578- hw_params . set_buffer_size_near ( DEFAULT_PERIODS * buffer_frames as alsa :: pcm :: Frames ) ? ;
1579- hw_params
1580- . set_period_size_near ( buffer_frames as alsa :: pcm :: Frames , alsa:: ValueOr :: Nearest ) ?;
1605+
1606+ hw_params. set_buffer_size_near ( buffer_size ) ? ;
1607+ hw_params . set_period_size_near ( period_size , alsa:: ValueOr :: Nearest ) ?;
15811608 }
15821609
15831610 // Apply hardware parameters
@@ -1587,7 +1614,7 @@ fn set_hw_params_from_format(
15871614 // PipeWire-ALSA picks a good period size but pairs it with many periods (huge buffer).
15881615 // We need to re-initialize hw_params and set BOTH period and buffer to constrain properly.
15891616 if config. buffer_size == BufferSize :: Default {
1590- if let Ok ( period_size) = hw_params. get_period_size ( ) . map ( |s| s as alsa :: pcm :: Frames ) {
1617+ if let Ok ( period_size) = hw_params. get_period_size ( ) {
15911618 // Re-initialize hw_params to clear previous constraints
15921619 let hw_params = init_hw_params ( pcm_handle, config, sample_format) ?;
15931620
@@ -1655,18 +1682,11 @@ fn canonical_pcm_id(pcm_id: &str) -> String {
16551682impl From < alsa:: Error > for Error {
16561683 fn from ( err : alsa:: Error ) -> Self {
16571684 match err. errno ( ) {
1658- libc:: ENODEV | libc:: ENOENT | LIBC_ENOTSUPP => {
1659- Error :: with_message ( ErrorKind :: DeviceNotAvailable , err. to_string ( ) )
1660- }
1661- libc:: EPERM | libc:: EACCES => {
1662- Error :: with_message ( ErrorKind :: PermissionDenied , err. to_string ( ) )
1663- }
1664- libc:: EBUSY | libc:: EAGAIN => {
1665- Error :: with_message ( ErrorKind :: DeviceBusy , err. to_string ( ) )
1666- }
1667- libc:: EINVAL => Error :: with_message ( ErrorKind :: InvalidInput , err. to_string ( ) ) ,
1668- libc:: EPIPE => Error :: with_message ( ErrorKind :: Xrun , err. to_string ( ) ) ,
1669- libc:: ENOSYS => Error :: with_message ( ErrorKind :: UnsupportedOperation , err. to_string ( ) ) ,
1685+ libc:: ENODEV | libc:: ENOENT | LIBC_ENOTSUPP => ErrorKind :: DeviceNotAvailable . into ( ) ,
1686+ libc:: EPERM | libc:: EACCES => ErrorKind :: PermissionDenied . into ( ) ,
1687+ libc:: EBUSY | libc:: EAGAIN => ErrorKind :: DeviceBusy . into ( ) ,
1688+ libc:: EINVAL | libc:: ENOSYS => ErrorKind :: UnsupportedConfig . into ( ) ,
1689+ libc:: EPIPE => ErrorKind :: Xrun . into ( ) ,
16701690 _ => Error :: with_message ( ErrorKind :: BackendError , err. to_string ( ) ) ,
16711691 }
16721692 }
0 commit comments