Skip to content

Commit a1af6bb

Browse files
committed
chore: Add ability to change player sample rate
Adds the ability for the player to change its sample rate, to be able to accurately play back local files that aren't sampled at 44.1 kHz. The functionality to change sample rate must be implemented individually by each sink. In this commit, support for this has only been added to the alsa sink. Testing this by swapping between a 48.0 kHz local file and a 44.1 kHz Spotify song appears to play back both without any distortion of sound.
1 parent 414432a commit a1af6bb

File tree

12 files changed

+125
-34
lines changed

12 files changed

+125
-34
lines changed

playback/src/audio_backend/alsa.rs

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ use alsa::{Direction, ValueOr};
99
use std::process::exit;
1010
use thiserror::Error;
1111

12-
const MAX_BUFFER: Frames = (SAMPLE_RATE / 2) as Frames;
13-
const MIN_BUFFER: Frames = (SAMPLE_RATE / 10) as Frames;
1412
const ZERO_FRAMES: Frames = 0;
1513

1614
const MAX_PERIOD_DIVISOR: Frames = 4;
@@ -162,7 +160,7 @@ fn list_compatible_devices() -> SinkResult<()> {
162160
Ok(())
163161
}
164162

165-
fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)> {
163+
fn open_device(dev_name: &str, format: AudioFormat, sample_rate: u32) -> SinkResult<(PCM, usize)> {
166164
let pcm = PCM::new(dev_name, Direction::Playback, false).map_err(|e| AlsaError::PcmSetUp {
167165
device: dev_name.to_string(),
168166
e,
@@ -187,10 +185,10 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
187185
e,
188186
})?;
189187

190-
hwp.set_rate(SAMPLE_RATE, ValueOr::Nearest).map_err(|e| {
188+
hwp.set_rate(sample_rate, ValueOr::Nearest).map_err(|e| {
191189
AlsaError::UnsupportedSampleRate {
192190
device: dev_name.to_string(),
193-
samplerate: SAMPLE_RATE,
191+
samplerate: sample_rate,
194192
e,
195193
}
196194
})?;
@@ -240,8 +238,11 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
240238
Ok(s) => s,
241239
};
242240

241+
let max_buffer: Frames = (sample_rate / 2) as Frames;
242+
let min_buffer: Frames = (sample_rate / 10) as Frames;
243+
243244
let buffer_size = if min < max {
244-
match (MIN_BUFFER..=MAX_BUFFER)
245+
match (min_buffer..=max_buffer)
245246
.rev()
246247
.find(|f| (min..=max).contains(f))
247248
{
@@ -269,7 +270,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
269270
};
270271

271272
if buffer_size == ZERO_FRAMES {
272-
trace!("Desired Buffer Frame range: {MIN_BUFFER:?} - {MAX_BUFFER:?}",);
273+
trace!("Desired Buffer Frame range: {min_buffer:?} - {max_buffer:?}",);
273274

274275
trace!("Actual Buffer Frame range as reported by the device: {min:?} - {max:?}",);
275276
}
@@ -416,18 +417,7 @@ impl Open for AlsaSink {
416417
impl Sink for AlsaSink {
417418
fn start(&mut self) -> SinkResult<()> {
418419
if self.pcm.is_none() {
419-
let (pcm, bytes_per_period) = open_device(&self.device, self.format)?;
420-
self.pcm = Some(pcm);
421-
422-
if self.period_buffer.capacity() != bytes_per_period {
423-
self.period_buffer = Vec::with_capacity(bytes_per_period);
424-
}
425-
426-
// Should always match the "Period Buffer size in bytes: " trace! message.
427-
trace!(
428-
"Period Buffer capacity: {:?}",
429-
self.period_buffer.capacity()
430-
);
420+
self.start_internal(SAMPLE_RATE)?;
431421
}
432422

433423
Ok(())
@@ -448,6 +438,13 @@ impl Sink for AlsaSink {
448438
Ok(())
449439
}
450440

441+
fn update_sample_rate(&mut self, new_sample_rate: u32) -> SinkResult<()> {
442+
self.stop()?;
443+
self.start_internal(new_sample_rate)?;
444+
445+
Ok(())
446+
}
447+
451448
sink_as_bytes!();
452449
}
453450

@@ -483,6 +480,23 @@ impl SinkAsBytes for AlsaSink {
483480
impl AlsaSink {
484481
pub const NAME: &'static str = "alsa";
485482

483+
fn start_internal(&mut self, sample_rate: u32) -> SinkResult<()> {
484+
let (pcm, bytes_per_period) = open_device(&self.device, self.format, sample_rate)?;
485+
self.pcm = Some(pcm);
486+
487+
if self.period_buffer.capacity() != bytes_per_period {
488+
self.period_buffer = Vec::with_capacity(bytes_per_period);
489+
}
490+
491+
// Should always match the "Period Buffer size in bytes: " trace! message.
492+
trace!(
493+
"Period Buffer capacity: {:?}",
494+
self.period_buffer.capacity()
495+
);
496+
497+
Ok(())
498+
}
499+
486500
fn write_buf(&mut self) -> SinkResult<()> {
487501
if self.pcm.is_some() {
488502
let write_result = {

playback/src/audio_backend/gstreamer.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ impl Sink for GstreamerSink {
172172
Ok(())
173173
}
174174

175+
fn update_sample_rate(&mut self, _new_sample_rate: u32) -> SinkResult<()> {
176+
Err(SinkError::SampleRateChange("not implemented".into()))
177+
}
178+
175179
sink_as_bytes!();
176180
}
177181

playback/src/audio_backend/jackaudio.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ impl Sink for JackSink {
8080
}
8181
Ok(())
8282
}
83+
84+
fn update_sample_rate(&mut self, _new_sample_rate: u32) -> SinkResult<()> {
85+
Err(SinkError::SampleRateChange("not implemented".into()))
86+
}
8387
}
8488

8589
impl JackSink {

playback/src/audio_backend/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub enum SinkError {
1515
InvalidParams(String),
1616
#[error("Audio Sink Error Changing State: {0}")]
1717
StateChange(String),
18+
#[error("Audio Sink Error Updating Sample Rate: {0}")]
19+
SampleRateChange(String),
1820
}
1921

2022
pub type SinkResult<T> = Result<T, SinkError>;
@@ -30,6 +32,9 @@ pub trait Sink {
3032
fn stop(&mut self) -> SinkResult<()> {
3133
Ok(())
3234
}
35+
36+
fn update_sample_rate(&mut self, new_sample_rate: u32) -> SinkResult<()>;
37+
3338
fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()>;
3439
}
3540

playback/src/audio_backend/pipe.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ impl Sink for StdoutSink {
9292
Ok(())
9393
}
9494

95+
fn update_sample_rate(&mut self, _new_sample_rate: u32) -> SinkResult<()> {
96+
Err(SinkError::SampleRateChange("not implemented".into()))
97+
}
98+
9599
sink_as_bytes!();
96100
}
97101

playback/src/audio_backend/portaudio.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ impl Sink for PortAudioSink<'_> {
173173

174174
Ok(())
175175
}
176+
177+
fn update_sample_rate(&mut self, _new_sample_rate: u32) -> SinkResult<()> {
178+
Err(SinkError::SampleRateChange("not implemented".into()))
179+
}
176180
}
177181

178182
impl Drop for PortAudioSink<'_> {

playback/src/audio_backend/pulseaudio.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ impl Sink for PulseAudioSink {
133133
Ok(())
134134
}
135135

136+
fn update_sample_rate(&mut self, _new_sample_rate: u32) -> SinkResult<()> {
137+
Err(SinkError::SampleRateChange("not implemented".into()))
138+
}
139+
136140
sink_as_bytes!();
137141
}
138142

playback/src/audio_backend/rodio.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ impl Sink for RodioSink {
240240
Ok(())
241241
}
242242

243+
fn update_sample_rate(&mut self, _new_sample_rate: u32) -> SinkResult<()> {
244+
Err(SinkError::SampleRateChange("not implemented".into()))
245+
}
246+
243247
fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()> {
244248
let samples = packet
245249
.samples()

playback/src/audio_backend/sdl.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ impl Sink for SdlSink {
114114
};
115115
result.map_err(SinkError::OnWrite)
116116
}
117+
118+
fn update_sample_rate(&mut self, _new_sample_rate: u32) -> SinkResult<()> {
119+
Err(SinkError::SampleRateChange("not implemented".into()))
120+
}
117121
}
118122

119123
impl SdlSink {

playback/src/audio_backend/subprocess.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ impl Sink for SubprocessSink {
135135
}
136136
}
137137

138+
fn update_sample_rate(&mut self, _new_sample_rate: u32) -> SinkResult<()> {
139+
Err(SinkError::SampleRateChange("not implemented".into()))
140+
}
141+
138142
sink_as_bytes!();
139143
}
140144

0 commit comments

Comments
 (0)