Skip to content

Commit d5efb8a

Browse files
committed
Dynamic failable buffer sizing alsa-backend
Dynamically set the alsa buffer and period based on the device's reported min/max buffer and period sizes. In the event of failure use the device's defaults. This should have no effect on devices that allow for reasonable buffer and period sizes but would allow us to be more forgiving with less reasonable devices or configurations. Closes: #895
1 parent 7160dc1 commit d5efb8a

1 file changed

Lines changed: 174 additions & 13 deletions

File tree

playback/src/audio_backend/alsa.rs

Lines changed: 174 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ use crate::convert::Converter;
44
use crate::decoder::AudioPacket;
55
use crate::{NUM_CHANNELS, SAMPLE_RATE};
66
use alsa::device_name::HintIter;
7-
use alsa::pcm::{Access, Format, HwParams, PCM};
7+
use alsa::pcm::{Access, Format, Frames, HwParams, PCM};
88
use alsa::{Direction, ValueOr};
99
use std::cmp::min;
1010
use std::process::exit;
11-
use std::time::Duration;
1211
use thiserror::Error;
1312

14-
// 0.5 sec buffer.
15-
const PERIOD_TIME: Duration = Duration::from_millis(100);
16-
const BUFFER_TIME: Duration = Duration::from_millis(500);
13+
const MAX_BUFFER: Frames = (SAMPLE_RATE / 2) as Frames;
14+
const MIN_BUFFER: Frames = (SAMPLE_RATE / 10) as Frames;
15+
const ZERO_FRAMES: Frames = 0;
16+
17+
const MAX_PERIOD_DIVISOR: Frames = 4;
18+
const MIN_PERIOD_DIVISOR: Frames = 10;
1719

1820
#[derive(Debug, Error)]
1921
enum AlsaError {
@@ -195,28 +197,187 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
195197
e,
196198
})?;
197199

198-
hwp.set_buffer_time_near(BUFFER_TIME.as_micros() as u32, ValueOr::Nearest)
199-
.map_err(AlsaError::HwParams)?;
200+
// Clone the hwp while it's in
201+
// a good working state so that
202+
// in the event of an error setting
203+
// the buffer and period sizes
204+
// we can use the good working clone
205+
// instead of the hwp that's in an
206+
// error state.
207+
let hwp_clone = hwp.clone();
208+
209+
// At a sampling rate of 44100:
210+
// The largest buffer is 22050 Frames (500ms) with 5512 Frame periods (125ms).
211+
// The smallest buffer is 4410 Frames (100ms) with 441 Frame periods (10ms).
212+
// Actual values may vary.
213+
//
214+
// Larger buffer and period sizes are preferred as extremely small values
215+
// will cause high CPU useage.
216+
//
217+
// If no buffer or period size is in those ranges or an error happens
218+
// trying to set the buffer or period size use the device's defaults
219+
// which may not be ideal but are *hopefully* serviceable.
220+
221+
let buffer_size = {
222+
let max = match hwp.get_buffer_size_max() {
223+
Err(e) => {
224+
trace!("Error getting the device's max Buffer size: {}", e);
225+
ZERO_FRAMES
226+
}
227+
Ok(s) => s,
228+
};
200229

201-
hwp.set_period_time_near(PERIOD_TIME.as_micros() as u32, ValueOr::Nearest)
202-
.map_err(AlsaError::HwParams)?;
230+
let min = match hwp.get_buffer_size_min() {
231+
Err(e) => {
232+
trace!("Error getting the device's min Buffer size: {}", e);
233+
ZERO_FRAMES
234+
}
235+
Ok(s) => s,
236+
};
237+
238+
let buffer_size = if min < max {
239+
match (MIN_BUFFER..=MAX_BUFFER)
240+
.rev()
241+
.find(|f| (min..=max).contains(f))
242+
{
243+
Some(size) => {
244+
trace!("Desired Frames per Buffer: {:?}", size);
245+
246+
match hwp.set_buffer_size_near(size) {
247+
Err(e) => {
248+
trace!("Error setting the device's Buffer size: {}", e);
249+
ZERO_FRAMES
250+
}
251+
Ok(s) => s,
252+
}
253+
}
254+
None => {
255+
trace!("No Desired Buffer size in range reported by the device.");
256+
ZERO_FRAMES
257+
}
258+
}
259+
} else {
260+
trace!("The device's min reported Buffer size was greater than or equal to it's max reported Buffer size.");
261+
ZERO_FRAMES
262+
};
263+
264+
if buffer_size == ZERO_FRAMES {
265+
trace!(
266+
"Desired Buffer Frame range: {:?} - {:?}",
267+
MIN_BUFFER,
268+
MAX_BUFFER
269+
);
270+
271+
trace!(
272+
"Actual Buffer Frame range as reported by the device: {:?} - {:?}",
273+
min,
274+
max
275+
);
276+
}
203277

204-
pcm.hw_params(&hwp).map_err(AlsaError::Pcm)?;
278+
buffer_size
279+
};
280+
281+
let period_size = {
282+
if buffer_size == ZERO_FRAMES {
283+
ZERO_FRAMES
284+
} else {
285+
let max = match hwp.get_period_size_max() {
286+
Err(e) => {
287+
trace!("Error getting the device's max Period size: {}", e);
288+
ZERO_FRAMES
289+
}
290+
Ok(s) => s,
291+
};
205292

206-
let swp = pcm.sw_params_current().map_err(AlsaError::Pcm)?;
293+
let min = match hwp.get_period_size_min() {
294+
Err(e) => {
295+
trace!("Error getting the device's min Period size: {}", e);
296+
ZERO_FRAMES
297+
}
298+
Ok(s) => s,
299+
};
300+
301+
let max_period = buffer_size / MAX_PERIOD_DIVISOR;
302+
let min_period = buffer_size / MIN_PERIOD_DIVISOR;
303+
304+
let period_size = if min < max && min_period < max_period {
305+
match (min_period..=max_period)
306+
.rev()
307+
.find(|f| (min..=max).contains(f))
308+
{
309+
Some(size) => {
310+
trace!("Desired Frames per Period: {:?}", size);
311+
312+
match hwp.set_period_size_near(size, ValueOr::Nearest) {
313+
Err(e) => {
314+
trace!("Error setting the device's Period size: {}", e);
315+
ZERO_FRAMES
316+
}
317+
Ok(s) => s,
318+
}
319+
}
320+
None => {
321+
trace!("No Desired Period size in range reported by the device.");
322+
ZERO_FRAMES
323+
}
324+
}
325+
} else {
326+
trace!("The device's min reported Period size was greater than or equal to it's max reported Period size,");
327+
trace!("or the desired min Period size was greater than or equal to the desired max Period size.");
328+
ZERO_FRAMES
329+
};
330+
331+
if period_size == ZERO_FRAMES {
332+
trace!("Buffer size: {:?}", buffer_size);
333+
334+
trace!(
335+
"Desired Period Frame range: {:?} (Buffer size / {:?}) - {:?} (Buffer size / {:?})",
336+
min_period,
337+
MIN_PERIOD_DIVISOR,
338+
max_period,
339+
MAX_PERIOD_DIVISOR,
340+
);
341+
342+
trace!(
343+
"Actual Period Frame range as reported by the device: {:?} - {:?}",
344+
min,
345+
max
346+
);
347+
}
348+
349+
period_size
350+
}
351+
};
352+
353+
if buffer_size == ZERO_FRAMES || period_size == ZERO_FRAMES {
354+
trace!(
355+
"Failed to set Buffer and/or Period size, falling back to the device's defaults."
356+
);
357+
358+
trace!("You may experience higher than normal CPU usage and/or audio issues.");
359+
360+
pcm.hw_params(&hwp_clone).map_err(AlsaError::Pcm)?;
361+
} else {
362+
pcm.hw_params(&hwp).map_err(AlsaError::Pcm)?;
363+
}
364+
365+
let hwp = pcm.hw_params_current().map_err(AlsaError::Pcm)?;
207366

208367
// Don't assume we got what we wanted. Ask to make sure.
209368
let frames_per_period = hwp.get_period_size().map_err(AlsaError::HwParams)?;
210369

211370
let frames_per_buffer = hwp.get_buffer_size().map_err(AlsaError::HwParams)?;
212371

372+
let swp = pcm.sw_params_current().map_err(AlsaError::Pcm)?;
373+
213374
swp.set_start_threshold(frames_per_buffer - frames_per_period)
214375
.map_err(AlsaError::SwParams)?;
215376

216377
pcm.sw_params(&swp).map_err(AlsaError::Pcm)?;
217378

218-
trace!("Frames per Buffer: {:?}", frames_per_buffer);
219-
trace!("Frames per Period: {:?}", frames_per_period);
379+
trace!("Actual Frames per Buffer: {:?}", frames_per_buffer);
380+
trace!("Actual Frames per Period: {:?}", frames_per_period);
220381

221382
// Let ALSA do the math for us.
222383
pcm.frames_to_bytes(frames_per_period) as usize

0 commit comments

Comments
 (0)