Skip to content

Commit 8d70fd9

Browse files
authored
Implement common SinkError and SinkResult (#820)
* Make error messages more consistent and concise. * `impl From<AlsaError> for io::Error` so `AlsaErrors` can be thrown to player as `io::Errors`. This little bit of boilerplate goes a long way to simplifying things further down in the code. And will make any needed future changes easier. * Bonus: handle ALSA backend buffer sizing a little better.
1 parent 57937a1 commit 8d70fd9

10 files changed

Lines changed: 280 additions & 225 deletions

File tree

playback/src/audio_backend/alsa.rs

Lines changed: 104 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{Open, Sink, SinkAsBytes};
1+
use super::{Open, Sink, SinkAsBytes, SinkError, SinkResult};
22
use crate::config::AudioFormat;
33
use crate::convert::Converter;
44
use crate::decoder::AudioPacket;
@@ -7,7 +7,6 @@ use alsa::device_name::HintIter;
77
use alsa::pcm::{Access, Format, HwParams, PCM};
88
use alsa::{Direction, ValueOr};
99
use std::cmp::min;
10-
use std::io;
1110
use std::process::exit;
1211
use std::time::Duration;
1312
use thiserror::Error;
@@ -18,34 +17,67 @@ const BUFFER_TIME: Duration = Duration::from_millis(500);
1817

1918
#[derive(Debug, Error)]
2019
enum AlsaError {
21-
#[error("AlsaSink, device {device} may be invalid or busy, {err}")]
22-
PcmSetUp { device: String, err: alsa::Error },
23-
#[error("AlsaSink, device {device} unsupported access type RWInterleaved, {err}")]
24-
UnsupportedAccessType { device: String, err: alsa::Error },
25-
#[error("AlsaSink, device {device} unsupported format {format:?}, {err}")]
20+
#[error("<AlsaSink> Device {device} Unsupported Format {alsa_format:?} ({format:?}), {e}")]
2621
UnsupportedFormat {
2722
device: String,
23+
alsa_format: Format,
2824
format: AudioFormat,
29-
err: alsa::Error,
25+
e: alsa::Error,
3026
},
31-
#[error("AlsaSink, device {device} unsupported sample rate {samplerate}, {err}")]
32-
UnsupportedSampleRate {
33-
device: String,
34-
samplerate: u32,
35-
err: alsa::Error,
36-
},
37-
#[error("AlsaSink, device {device} unsupported channel count {channel_count}, {err}")]
27+
28+
#[error("<AlsaSink> Device {device} Unsupported Channel Count {channel_count}, {e}")]
3829
UnsupportedChannelCount {
3930
device: String,
4031
channel_count: u8,
41-
err: alsa::Error,
32+
e: alsa::Error,
33+
},
34+
35+
#[error("<AlsaSink> Device {device} Unsupported Sample Rate {samplerate}, {e}")]
36+
UnsupportedSampleRate {
37+
device: String,
38+
samplerate: u32,
39+
e: alsa::Error,
4240
},
43-
#[error("AlsaSink Hardware Parameters Error, {0}")]
41+
42+
#[error("<AlsaSink> Device {device} Unsupported Access Type RWInterleaved, {e}")]
43+
UnsupportedAccessType { device: String, e: alsa::Error },
44+
45+
#[error("<AlsaSink> Device {device} May be Invalid, Busy, or Already in Use, {e}")]
46+
PcmSetUp { device: String, e: alsa::Error },
47+
48+
#[error("<AlsaSink> Failed to Drain PCM Buffer, {0}")]
49+
DrainFailure(alsa::Error),
50+
51+
#[error("<AlsaSink> {0}")]
52+
OnWrite(alsa::Error),
53+
54+
#[error("<AlsaSink> Hardware, {0}")]
4455
HwParams(alsa::Error),
45-
#[error("AlsaSink Software Parameters Error, {0}")]
56+
57+
#[error("<AlsaSink> Software, {0}")]
4658
SwParams(alsa::Error),
47-
#[error("AlsaSink PCM Error, {0}")]
59+
60+
#[error("<AlsaSink> PCM, {0}")]
4861
Pcm(alsa::Error),
62+
63+
#[error("<AlsaSink> Could Not Parse Ouput Name(s) and/or Description(s)")]
64+
Parsing,
65+
66+
#[error("<AlsaSink>")]
67+
NotConnected,
68+
}
69+
70+
impl From<AlsaError> for SinkError {
71+
fn from(e: AlsaError) -> SinkError {
72+
use AlsaError::*;
73+
let es = e.to_string();
74+
match e {
75+
DrainFailure(_) | OnWrite(_) => SinkError::OnWrite(es),
76+
PcmSetUp { .. } => SinkError::ConnectionRefused(es),
77+
NotConnected => SinkError::NotConnected(es),
78+
_ => SinkError::InvalidParams(es),
79+
}
80+
}
4981
}
5082

5183
pub struct AlsaSink {
@@ -55,25 +87,19 @@ pub struct AlsaSink {
5587
period_buffer: Vec<u8>,
5688
}
5789

58-
fn list_outputs() -> io::Result<()> {
90+
fn list_outputs() -> SinkResult<()> {
5991
println!("Listing available Alsa outputs:");
6092
for t in &["pcm", "ctl", "hwdep"] {
6193
println!("{} devices:", t);
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-
};
94+
95+
let i = HintIter::new_str(None, &t).map_err(|_| AlsaError::Parsing)?;
96+
6897
for a in i {
6998
if let Some(Direction::Playback) = a.direction {
7099
// mimic aplay -L
71-
let name = a
72-
.name
73-
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Could not parse name"))?;
74-
let desc = a
75-
.desc
76-
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Could not parse desc"))?;
100+
let name = a.name.ok_or(AlsaError::Parsing)?;
101+
let desc = a.desc.ok_or(AlsaError::Parsing)?;
102+
77103
println!("{}\n\t{}\n", name, desc.replace("\n", "\n\t"));
78104
}
79105
}
@@ -82,10 +108,10 @@ fn list_outputs() -> io::Result<()> {
82108
Ok(())
83109
}
84110

85-
fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, usize), AlsaError> {
111+
fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)> {
86112
let pcm = PCM::new(dev_name, Direction::Playback, false).map_err(|e| AlsaError::PcmSetUp {
87113
device: dev_name.to_string(),
88-
err: e,
114+
e,
89115
})?;
90116

91117
let alsa_format = match format {
@@ -103,32 +129,34 @@ fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, usize), Alsa
103129

104130
let bytes_per_period = {
105131
let hwp = HwParams::any(&pcm).map_err(AlsaError::HwParams)?;
132+
106133
hwp.set_access(Access::RWInterleaved)
107134
.map_err(|e| AlsaError::UnsupportedAccessType {
108135
device: dev_name.to_string(),
109-
err: e,
136+
e,
110137
})?;
111138

112139
hwp.set_format(alsa_format)
113140
.map_err(|e| AlsaError::UnsupportedFormat {
114141
device: dev_name.to_string(),
142+
alsa_format,
115143
format,
116-
err: e,
144+
e,
117145
})?;
118146

119147
hwp.set_rate(SAMPLE_RATE, ValueOr::Nearest).map_err(|e| {
120148
AlsaError::UnsupportedSampleRate {
121149
device: dev_name.to_string(),
122150
samplerate: SAMPLE_RATE,
123-
err: e,
151+
e,
124152
}
125153
})?;
126154

127155
hwp.set_channels(NUM_CHANNELS as u32)
128156
.map_err(|e| AlsaError::UnsupportedChannelCount {
129157
device: dev_name.to_string(),
130158
channel_count: NUM_CHANNELS,
131-
err: e,
159+
e,
132160
})?;
133161

134162
hwp.set_buffer_time_near(BUFFER_TIME.as_micros() as u32, ValueOr::Nearest)
@@ -141,8 +169,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, usize), Alsa
141169

142170
let swp = pcm.sw_params_current().map_err(AlsaError::Pcm)?;
143171

144-
// Don't assume we got what we wanted.
145-
// Ask to make sure.
172+
// Don't assume we got what we wanted. Ask to make sure.
146173
let frames_per_period = hwp.get_period_size().map_err(AlsaError::HwParams)?;
147174

148175
let frames_per_buffer = hwp.get_buffer_size().map_err(AlsaError::HwParams)?;
@@ -171,8 +198,8 @@ impl Open for AlsaSink {
171198
Ok(_) => {
172199
exit(0);
173200
}
174-
Err(err) => {
175-
error!("Error listing Alsa outputs, {}", err);
201+
Err(e) => {
202+
error!("{}", e);
176203
exit(1);
177204
}
178205
},
@@ -193,53 +220,40 @@ impl Open for AlsaSink {
193220
}
194221

195222
impl Sink for AlsaSink {
196-
fn start(&mut self) -> io::Result<()> {
223+
fn start(&mut self) -> SinkResult<()> {
197224
if self.pcm.is_none() {
198-
match open_device(&self.device, self.format) {
199-
Ok((pcm, bytes_per_period)) => {
200-
self.pcm = Some(pcm);
201-
// If the capacity is greater than we want shrink it
202-
// to it's current len (which should be zero) before
203-
// setting the capacity with reserve_exact.
204-
if self.period_buffer.capacity() > bytes_per_period {
205-
self.period_buffer.shrink_to_fit();
206-
}
207-
// This does nothing if the capacity is already sufficient.
208-
// Len should always be zero, but for the sake of being thorough...
209-
self.period_buffer
210-
.reserve_exact(bytes_per_period - self.period_buffer.len());
211-
212-
// Should always match the "Period Buffer size in bytes: " trace! message.
213-
trace!(
214-
"Period Buffer capacity: {:?}",
215-
self.period_buffer.capacity()
216-
);
217-
}
218-
Err(e) => {
219-
return Err(io::Error::new(io::ErrorKind::Other, e));
220-
}
225+
let (pcm, bytes_per_period) = open_device(&self.device, self.format)?;
226+
self.pcm = Some(pcm);
227+
228+
let current_capacity = self.period_buffer.capacity();
229+
230+
if current_capacity > bytes_per_period {
231+
self.period_buffer.truncate(bytes_per_period);
232+
self.period_buffer.shrink_to_fit();
233+
} else if current_capacity < bytes_per_period {
234+
let extra = bytes_per_period - self.period_buffer.len();
235+
self.period_buffer.reserve_exact(extra);
221236
}
237+
238+
// Should always match the "Period Buffer size in bytes: " trace! message.
239+
trace!(
240+
"Period Buffer capacity: {:?}",
241+
self.period_buffer.capacity()
242+
);
222243
}
223244

224245
Ok(())
225246
}
226247

227-
fn stop(&mut self) -> io::Result<()> {
248+
fn stop(&mut self) -> SinkResult<()> {
228249
// Zero fill the remainder of the period buffer and
229250
// write any leftover data before draining the actual PCM buffer.
230251
self.period_buffer.resize(self.period_buffer.capacity(), 0);
231252
self.write_buf()?;
232253

233-
let pcm = self.pcm.as_mut().ok_or_else(|| {
234-
io::Error::new(io::ErrorKind::Other, "Error stopping AlsaSink, PCM is None")
235-
})?;
254+
let pcm = self.pcm.as_mut().ok_or(AlsaError::NotConnected)?;
236255

237-
pcm.drain().map_err(|e| {
238-
io::Error::new(
239-
io::ErrorKind::Other,
240-
format!("Error stopping AlsaSink {}", e),
241-
)
242-
})?;
256+
pcm.drain().map_err(AlsaError::DrainFailure)?;
243257

244258
self.pcm = None;
245259
Ok(())
@@ -249,23 +263,28 @@ impl Sink for AlsaSink {
249263
}
250264

251265
impl SinkAsBytes for AlsaSink {
252-
fn write_bytes(&mut self, data: &[u8]) -> io::Result<()> {
266+
fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()> {
253267
let mut start_index = 0;
254268
let data_len = data.len();
255269
let capacity = self.period_buffer.capacity();
270+
256271
loop {
257272
let data_left = data_len - start_index;
258273
let space_left = capacity - self.period_buffer.len();
259274
let data_to_buffer = min(data_left, space_left);
260275
let end_index = start_index + data_to_buffer;
276+
261277
self.period_buffer
262278
.extend_from_slice(&data[start_index..end_index]);
279+
263280
if self.period_buffer.len() == capacity {
264281
self.write_buf()?;
265282
}
283+
266284
if end_index == data_len {
267285
break Ok(());
268286
}
287+
269288
start_index = end_index;
270289
}
271290
}
@@ -274,30 +293,18 @@ impl SinkAsBytes for AlsaSink {
274293
impl AlsaSink {
275294
pub const NAME: &'static str = "alsa";
276295

277-
fn write_buf(&mut self) -> io::Result<()> {
278-
let pcm = self.pcm.as_mut().ok_or_else(|| {
279-
io::Error::new(
280-
io::ErrorKind::Other,
281-
"Error writing from AlsaSink buffer to PCM, PCM is None",
282-
)
283-
})?;
284-
let io = pcm.io_bytes();
285-
if let Err(err) = io.writei(&self.period_buffer) {
296+
fn write_buf(&mut self) -> SinkResult<()> {
297+
let pcm = self.pcm.as_mut().ok_or(AlsaError::NotConnected)?;
298+
299+
if let Err(e) = pcm.io_bytes().writei(&self.period_buffer) {
286300
// Capture and log the original error as a warning, and then try to recover.
287301
// If recovery fails then forward that error back to player.
288302
warn!(
289-
"Error writing from AlsaSink buffer to PCM, trying to recover {}",
290-
err
303+
"Error writing from AlsaSink buffer to PCM, trying to recover, {}",
304+
e
291305
);
292-
pcm.try_recover(err, false).map_err(|e| {
293-
io::Error::new(
294-
io::ErrorKind::Other,
295-
format!(
296-
"Error writing from AlsaSink buffer to PCM, recovery failed {}",
297-
e
298-
),
299-
)
300-
})?
306+
307+
pcm.try_recover(e, false).map_err(AlsaError::OnWrite)?
301308
}
302309

303310
self.period_buffer.clear();

playback/src/audio_backend/gstreamer.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{Open, Sink, SinkAsBytes};
1+
use super::{Open, Sink, SinkAsBytes, SinkResult};
22
use crate::config::AudioFormat;
33
use crate::convert::Converter;
44
use crate::decoder::AudioPacket;
@@ -11,7 +11,7 @@ use gst::prelude::*;
1111
use zerocopy::AsBytes;
1212

1313
use std::sync::mpsc::{sync_channel, SyncSender};
14-
use std::{io, thread};
14+
use std::thread;
1515

1616
#[allow(dead_code)]
1717
pub struct GstreamerSink {
@@ -131,7 +131,7 @@ impl Sink for GstreamerSink {
131131
}
132132

133133
impl SinkAsBytes for GstreamerSink {
134-
fn write_bytes(&mut self, data: &[u8]) -> io::Result<()> {
134+
fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()> {
135135
// Copy expensively (in to_vec()) to avoid thread synchronization
136136
self.tx
137137
.send(data.to_vec())

0 commit comments

Comments
 (0)