Skip to content

Commit b0a94f9

Browse files
authored
Merge pull request #789 from JasonLG1979/alsa_backend_fix
Improve ALSA buffer size
2 parents 5764f1f + c309592 commit b0a94f9

2 files changed

Lines changed: 30 additions & 29 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4141
- [playback] `alsamixer`: make `cubic` consistent between cards that report minimum volume as mute, and cards that report some dB value
4242
- [playback] `alsamixer`: make `--volume-ctrl {linear|log}` work as expected
4343
- [playback] `alsa`, `gstreamer`, `pulseaudio`: always output in native endianness
44+
- [playback] `alsa`: revert buffer size to ~500 ms
4445

4546
## [0.2.0] - 2021-05-04
4647

playback/src/audio_backend/alsa.rs

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,25 @@ use super::{Open, Sink, SinkAsBytes};
22
use crate::config::AudioFormat;
33
use crate::convert::Converter;
44
use crate::decoder::AudioPacket;
5-
use crate::{NUM_CHANNELS, SAMPLES_PER_SECOND, SAMPLE_RATE};
5+
use crate::{NUM_CHANNELS, SAMPLE_RATE};
66
use alsa::device_name::HintIter;
7-
use alsa::pcm::{Access, Format, Frames, HwParams, PCM};
7+
use alsa::pcm::{Access, Format, HwParams, PCM};
88
use alsa::{Direction, Error, ValueOr};
99
use std::cmp::min;
1010
use std::ffi::CString;
1111
use std::io;
1212
use std::process::exit;
1313
use std::time::Duration;
1414

15-
const BUFFERED_LATENCY: Duration = Duration::from_millis(125);
16-
const BUFFERED_PERIODS: Frames = 4;
15+
// 125 ms Period time * 4 periods = 0.5 sec buffer.
16+
const PERIOD_TIME: Duration = Duration::from_millis(125);
17+
const NUM_PERIODS: u32 = 4;
1718

1819
pub struct AlsaSink {
1920
pcm: Option<PCM>,
2021
format: AudioFormat,
2122
device: String,
22-
buffer: Vec<u8>,
23+
period_buffer: Vec<u8>,
2324
}
2425

2526
fn list_outputs() {
@@ -39,7 +40,7 @@ fn list_outputs() {
3940
}
4041
}
4142

42-
fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, Frames), Box<Error>> {
43+
fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, usize), Box<Error>> {
4344
let pcm = PCM::new(dev_name, Direction::Playback, false)?;
4445
let alsa_format = match format {
4546
AudioFormat::F64 => Format::float64(),
@@ -54,28 +55,30 @@ fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, Frames), Box
5455
AudioFormat::S24_3 => Format::S243BE,
5556
};
5657

57-
// http://www.linuxjournal.com/article/6735?page=0,1#N0x19ab2890.0x19ba78d8
58-
// latency = period_size * periods / (rate * bytes_per_frame)
59-
// For stereo samples encoded as 32-bit float, one frame has a length of eight bytes.
60-
let mut period_size = ((SAMPLES_PER_SECOND * format.size() as u32) as f32
61-
* (BUFFERED_LATENCY.as_secs_f32() / BUFFERED_PERIODS as f32))
62-
as Frames;
63-
{
58+
let bytes_per_period = {
6459
let hwp = HwParams::any(&pcm)?;
6560
hwp.set_access(Access::RWInterleaved)?;
6661
hwp.set_format(alsa_format)?;
6762
hwp.set_rate(SAMPLE_RATE, ValueOr::Nearest)?;
6863
hwp.set_channels(NUM_CHANNELS as u32)?;
69-
period_size = hwp.set_period_size_near(period_size, ValueOr::Greater)?;
70-
hwp.set_buffer_size_near(period_size * BUFFERED_PERIODS)?;
64+
// 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)?;
7167
pcm.hw_params(&hwp)?;
7268

7369
let swp = pcm.sw_params_current()?;
74-
swp.set_start_threshold(hwp.get_buffer_size()? - hwp.get_period_size()?)?;
70+
// Don't assume we got what we wanted.
71+
// Ask to make sure.
72+
let frames_per_period = hwp.get_period_size()?;
73+
74+
swp.set_start_threshold(hwp.get_buffer_size()? - frames_per_period)?;
7575
pcm.sw_params(&swp)?;
76-
}
7776

78-
Ok((pcm, period_size))
77+
// Let ALSA do the math for us.
78+
pcm.frames_to_bytes(frames_per_period) as usize
79+
};
80+
81+
Ok((pcm, bytes_per_period))
7982
}
8083

8184
impl Open for AlsaSink {
@@ -97,7 +100,7 @@ impl Open for AlsaSink {
97100
pcm: None,
98101
format,
99102
device: name,
100-
buffer: vec![],
103+
period_buffer: vec![],
101104
}
102105
}
103106
}
@@ -107,12 +110,9 @@ impl Sink for AlsaSink {
107110
if self.pcm.is_none() {
108111
let pcm = open_device(&self.device, self.format);
109112
match pcm {
110-
Ok((p, period_size)) => {
113+
Ok((p, bytes_per_period)) => {
111114
self.pcm = Some(p);
112-
// Create a buffer for all samples for a full period
113-
self.buffer = Vec::with_capacity(
114-
period_size as usize * BUFFERED_PERIODS as usize * self.format.size(),
115-
);
115+
self.period_buffer = Vec::with_capacity(bytes_per_period);
116116
}
117117
Err(e) => {
118118
error!("Alsa error PCM open {}", e);
@@ -147,15 +147,15 @@ impl SinkAsBytes for AlsaSink {
147147
let mut processed_data = 0;
148148
while processed_data < data.len() {
149149
let data_to_buffer = min(
150-
self.buffer.capacity() - self.buffer.len(),
150+
self.period_buffer.capacity() - self.period_buffer.len(),
151151
data.len() - processed_data,
152152
);
153-
self.buffer
153+
self.period_buffer
154154
.extend_from_slice(&data[processed_data..processed_data + data_to_buffer]);
155155
processed_data += data_to_buffer;
156-
if self.buffer.len() == self.buffer.capacity() {
156+
if self.period_buffer.len() == self.period_buffer.capacity() {
157157
self.write_buf();
158-
self.buffer.clear();
158+
self.period_buffer.clear();
159159
}
160160
}
161161

@@ -169,7 +169,7 @@ impl AlsaSink {
169169
fn write_buf(&mut self) {
170170
let pcm = self.pcm.as_mut().unwrap();
171171
let io = pcm.io_bytes();
172-
match io.writei(&self.buffer) {
172+
match io.writei(&self.period_buffer) {
173173
Ok(_) => (),
174174
Err(err) => pcm.try_recover(err, false).unwrap(),
175175
};

0 commit comments

Comments
 (0)