@@ -5,43 +5,90 @@ use crate::decoder::AudioPacket;
55use crate :: { NUM_CHANNELS , SAMPLE_RATE } ;
66use alsa:: device_name:: HintIter ;
77use alsa:: pcm:: { Access , Format , HwParams , PCM } ;
8- use alsa:: { Direction , Error , ValueOr } ;
8+ use alsa:: { Direction , ValueOr } ;
99use std:: cmp:: min;
10- use std:: ffi:: CString ;
1110use std:: io;
1211use std:: process:: exit;
1312use std:: time:: Duration ;
13+ use thiserror:: Error ;
1414
1515// 125 ms Period time * 4 periods = 0.5 sec buffer.
1616const PERIOD_TIME : Duration = Duration :: from_millis ( 125 ) ;
1717const NUM_PERIODS : u32 = 4 ;
1818
19+ #[ derive( Debug , Error ) ]
20+ enum AlsaError {
21+ #[ error( "AlsaSink, device {device} may be invalid or busy, {err}" ) ]
22+ PCMSetUpError { device : String , err : alsa:: Error } ,
23+ #[ error( "AlsaSink, device {device} unsupported access type RWInterleaved, {err}" ) ]
24+ UnsupportedAccessTypeError { device : String , err : alsa:: Error } ,
25+ #[ error( "AlsaSink, device {device} unsupported format {format:?}, {err}" ) ]
26+ UnsupportedFormatError {
27+ device : String ,
28+ format : AudioFormat ,
29+ err : alsa:: Error ,
30+ } ,
31+ #[ error( "AlsaSink, device {device} unsupported sample rate {samplerate}, {err}" ) ]
32+ UnsupportedSampleRateError {
33+ device : String ,
34+ samplerate : u32 ,
35+ err : alsa:: Error ,
36+ } ,
37+ #[ error( "AlsaSink, device {device} unsupported channel count {channel_count}, {err}" ) ]
38+ UnsupportedChannelCountError {
39+ device : String ,
40+ channel_count : u8 ,
41+ err : alsa:: Error ,
42+ } ,
43+ #[ error( "AlsaSink Hardware Parameters Error, {0}" ) ]
44+ HwParamsError ( alsa:: Error ) ,
45+ #[ error( "AlsaSink Software Parameters Error, {0}" ) ]
46+ SwParamsError ( alsa:: Error ) ,
47+ #[ error( "AlsaSink PCM Error, {0}" ) ]
48+ PCMError ( alsa:: Error ) ,
49+ }
50+
1951pub struct AlsaSink {
2052 pcm : Option < PCM > ,
2153 format : AudioFormat ,
2254 device : String ,
2355 period_buffer : Vec < u8 > ,
2456}
2557
26- fn list_outputs ( ) {
58+ fn list_outputs ( ) -> io:: Result < ( ) > {
59+ println ! ( "Listing available Alsa outputs:" ) ;
2760 for t in & [ "pcm" , "ctl" , "hwdep" ] {
2861 println ! ( "{} devices:" , t) ;
29- let i = HintIter :: new ( None , & * CString :: new ( * t) . unwrap ( ) ) . unwrap ( ) ;
62+ let i = match HintIter :: new_str ( None , & t) {
63+ Ok ( i) => i,
64+ Err ( e) => {
65+ return Err ( io:: Error :: new ( io:: ErrorKind :: Other , e) ) ;
66+ }
67+ } ;
3068 for a in i {
3169 if let Some ( Direction :: Playback ) = a. direction {
3270 // mimic aplay -L
33- println ! (
34- "{}\n \t {}\n " ,
35- a. name. unwrap( ) ,
36- a. desc. unwrap( ) . replace( "\n " , "\n \t " )
37- ) ;
71+ let name = a
72+ . name
73+ . ok_or ( io:: Error :: new ( io:: ErrorKind :: Other , "Could not parse name" ) ) ?;
74+ let desc = a
75+ . desc
76+ . ok_or ( io:: Error :: new ( io:: ErrorKind :: Other , "Could not parse desc" ) ) ?;
77+ println ! ( "{}\n \t {}\n " , name, desc. replace( "\n " , "\n \t " ) ) ;
3878 }
3979 }
4080 }
81+
82+ Ok ( ( ) )
4183}
4284
43- fn open_device ( dev_name : & str , format : AudioFormat ) -> Result < ( PCM , usize ) , Box < Error > > {
44- let pcm = PCM :: new ( dev_name, Direction :: Playback , false ) ?;
85+ fn open_device ( dev_name : & str , format : AudioFormat ) -> Result < ( PCM , usize ) , AlsaError > {
86+ let pcm =
87+ PCM :: new ( dev_name, Direction :: Playback , false ) . map_err ( |e| AlsaError :: PCMSetUpError {
88+ device : dev_name. to_string ( ) ,
89+ err : e,
90+ } ) ?;
91+
4592 let alsa_format = match format {
4693 AudioFormat :: F64 => Format :: float64 ( ) ,
4794 AudioFormat :: F32 => Format :: float ( ) ,
@@ -56,23 +103,64 @@ fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, usize), Box<
56103 } ;
57104
58105 let bytes_per_period = {
59- let hwp = HwParams :: any ( & pcm) ?;
60- hwp. set_access ( Access :: RWInterleaved ) ?;
61- hwp. set_format ( alsa_format) ?;
62- hwp. set_rate ( SAMPLE_RATE , ValueOr :: Nearest ) ?;
63- hwp. set_channels ( NUM_CHANNELS as u32 ) ?;
106+ let hwp = HwParams :: any ( & pcm) . map_err ( |e| AlsaError :: HwParamsError ( e) ) ?;
107+ hwp. set_access ( Access :: RWInterleaved ) . map_err ( |e| {
108+ AlsaError :: UnsupportedAccessTypeError {
109+ device : dev_name. to_string ( ) ,
110+ err : e,
111+ }
112+ } ) ?;
113+
114+ hwp. set_format ( alsa_format)
115+ . map_err ( |e| AlsaError :: UnsupportedFormatError {
116+ device : dev_name. to_string ( ) ,
117+ format : format,
118+ err : e,
119+ } ) ?;
120+
121+ hwp. set_rate ( SAMPLE_RATE , ValueOr :: Nearest ) . map_err ( |e| {
122+ AlsaError :: UnsupportedSampleRateError {
123+ device : dev_name. to_string ( ) ,
124+ samplerate : SAMPLE_RATE ,
125+ err : e,
126+ }
127+ } ) ?;
128+
129+ hwp. set_channels ( NUM_CHANNELS as u32 ) . map_err ( |e| {
130+ AlsaError :: UnsupportedChannelCountError {
131+ device : dev_name. to_string ( ) ,
132+ channel_count : NUM_CHANNELS ,
133+ err : e,
134+ }
135+ } ) ?;
136+
64137 // Deal strictly in time and periods.
65- hwp. set_periods ( NUM_PERIODS , ValueOr :: Nearest ) ?;
66- hwp. set_period_time_near ( PERIOD_TIME . as_micros ( ) as u32 , ValueOr :: Nearest ) ?;
67- pcm. hw_params ( & hwp) ?;
138+ hwp. set_periods ( NUM_PERIODS , ValueOr :: Nearest )
139+ . map_err ( |e| AlsaError :: HwParamsError ( e) ) ?;
140+
141+ hwp. set_period_time_near ( PERIOD_TIME . as_micros ( ) as u32 , ValueOr :: Nearest )
142+ . map_err ( |e| AlsaError :: HwParamsError ( e) ) ?;
143+
144+ pcm. hw_params ( & hwp) . map_err ( |e| AlsaError :: PCMError ( e) ) ?;
145+
146+ let swp = pcm
147+ . sw_params_current ( )
148+ . map_err ( |e| AlsaError :: PCMError ( e) ) ?;
68149
69- let swp = pcm. sw_params_current ( ) ?;
70150 // Don't assume we got what we wanted.
71151 // Ask to make sure.
72- let frames_per_period = hwp. get_period_size ( ) ?;
152+ let frames_per_period = hwp
153+ . get_period_size ( )
154+ . map_err ( |e| AlsaError :: HwParamsError ( e) ) ?;
155+
156+ let frames_per_buffer = hwp
157+ . get_buffer_size ( )
158+ . map_err ( |e| AlsaError :: HwParamsError ( e) ) ?;
159+
160+ swp. set_start_threshold ( frames_per_buffer - frames_per_period)
161+ . map_err ( |e| AlsaError :: SwParamsError ( e) ) ?;
73162
74- swp. set_start_threshold ( hwp. get_buffer_size ( ) ? - frames_per_period) ?;
75- pcm. sw_params ( & swp) ?;
163+ pcm. sw_params ( & swp) . map_err ( |e| AlsaError :: PCMError ( e) ) ?;
76164
77165 // Let ALSA do the math for us.
78166 pcm. frames_to_bytes ( frames_per_period) as usize
@@ -83,19 +171,23 @@ fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, usize), Box<
83171
84172impl Open for AlsaSink {
85173 fn open ( device : Option < String > , format : AudioFormat ) -> Self {
86- info ! ( "Using Alsa sink with format: {:?}" , format) ;
87-
88174 let name = match device. as_deref ( ) {
89- Some ( "?" ) => {
90- println ! ( "Listing available Alsa outputs:" ) ;
91- list_outputs ( ) ;
92- exit ( 0 )
93- }
175+ Some ( "?" ) => match list_outputs ( ) {
176+ Ok ( _) => {
177+ exit ( 0 ) ;
178+ }
179+ Err ( err) => {
180+ error ! ( "Error listing Alsa outputs, {}" , err) ;
181+ exit ( 1 ) ;
182+ }
183+ } ,
94184 Some ( device) => device,
95185 None => "default" ,
96186 }
97187 . to_string ( ) ;
98188
189+ info ! ( "Using AlsaSink with format: {:?}" , format) ;
190+
99191 Self {
100192 pcm : None ,
101193 format,
@@ -108,18 +200,13 @@ impl Open for AlsaSink {
108200impl Sink for AlsaSink {
109201 fn start ( & mut self ) -> io:: Result < ( ) > {
110202 if self . pcm . is_none ( ) {
111- let pcm = open_device ( & self . device , self . format ) ;
112- match pcm {
113- Ok ( ( p, bytes_per_period) ) => {
114- self . pcm = Some ( p) ;
203+ match open_device ( & self . device , self . format ) {
204+ Ok ( ( pcm, bytes_per_period) ) => {
205+ self . pcm = Some ( pcm) ;
115206 self . period_buffer = Vec :: with_capacity ( bytes_per_period) ;
116207 }
117208 Err ( e) => {
118- error ! ( "Alsa error PCM open {}" , e) ;
119- return Err ( io:: Error :: new (
120- io:: ErrorKind :: Other ,
121- "Alsa error: PCM open failed" ,
122- ) ) ;
209+ return Err ( io:: Error :: new ( io:: ErrorKind :: Other , e) ) ;
123210 }
124211 }
125212 }
@@ -131,9 +218,17 @@ impl Sink for AlsaSink {
131218 {
132219 // Write any leftover data in the period buffer
133220 // before draining the actual buffer
134- self . write_bytes ( & [ ] ) . expect ( "could not flush buffer" ) ;
135- let pcm = self . pcm . as_mut ( ) . unwrap ( ) ;
136- pcm. drain ( ) . unwrap ( ) ;
221+ self . write_bytes ( & [ ] ) ?;
222+ let pcm = self . pcm . as_mut ( ) . ok_or ( io:: Error :: new (
223+ io:: ErrorKind :: Other ,
224+ "Error stopping AlsaSink, PCM is None" ,
225+ ) ) ?;
226+ pcm. drain ( ) . map_err ( |e| {
227+ io:: Error :: new (
228+ io:: ErrorKind :: Other ,
229+ format ! ( "Error stopping AlsaSink {}" , e) ,
230+ )
231+ } ) ?
137232 }
138233 self . pcm = None ;
139234 Ok ( ( ) )
@@ -154,7 +249,7 @@ impl SinkAsBytes for AlsaSink {
154249 . extend_from_slice ( & data[ processed_data..processed_data + data_to_buffer] ) ;
155250 processed_data += data_to_buffer;
156251 if self . period_buffer . len ( ) == self . period_buffer . capacity ( ) {
157- self . write_buf ( ) ;
252+ self . write_buf ( ) ? ;
158253 self . period_buffer . clear ( ) ;
159254 }
160255 }
@@ -166,12 +261,30 @@ impl SinkAsBytes for AlsaSink {
166261impl AlsaSink {
167262 pub const NAME : & ' static str = "alsa" ;
168263
169- fn write_buf ( & mut self ) {
170- let pcm = self . pcm . as_mut ( ) . unwrap ( ) ;
264+ fn write_buf ( & mut self ) -> io:: Result < ( ) > {
265+ let pcm = self . pcm . as_mut ( ) . ok_or ( io:: Error :: new (
266+ io:: ErrorKind :: Other ,
267+ "Error writing from AlsaSink buffer to PCM, PCM is None" ,
268+ ) ) ?;
171269 let io = pcm. io_bytes ( ) ;
172- match io. writei ( & self . period_buffer ) {
173- Ok ( _) => ( ) ,
174- Err ( err) => pcm. try_recover ( err, false ) . unwrap ( ) ,
175- } ;
270+ if let Err ( err) = io. writei ( & self . period_buffer ) {
271+ // Capture and log the original error as a warning, and then try to recover.
272+ // If recovery fails then forward that error back to player.
273+ warn ! (
274+ "Error writing from AlsaSink buffer to PCM, trying to recover {}" ,
275+ err
276+ ) ;
277+ pcm. try_recover ( err, false ) . map_err ( |e| {
278+ io:: Error :: new (
279+ io:: ErrorKind :: Other ,
280+ format ! (
281+ "Error writing from AlsaSink buffer to PCM, recovery failed {}" ,
282+ e
283+ ) ,
284+ )
285+ } ) ?
286+ }
287+
288+ Ok ( ( ) )
176289 }
177290}
0 commit comments