Skip to content

Commit 9e8e471

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 9e8e471

File tree

12 files changed

+121
-31
lines changed

12 files changed

+121
-31
lines changed

playback/src/audio_backend/alsa.rs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::process::exit;
1010
use thiserror::Error;
1111

1212
const MAX_BUFFER: Frames = (SAMPLE_RATE / 2) as Frames;
13-
const MIN_BUFFER: Frames = (SAMPLE_RATE / 10) as Frames;
13+
const MIN_BUFFER: Frames = (SAMPLE_RATE / 10) as Frames; // TODO: this is likely no longer valid with variable sampling rates
1414
const ZERO_FRAMES: Frames = 0;
1515

1616
const MAX_PERIOD_DIVISOR: Frames = 4;
@@ -162,7 +162,7 @@ fn list_compatible_devices() -> SinkResult<()> {
162162
Ok(())
163163
}
164164

165-
fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)> {
165+
fn open_device(dev_name: &str, format: AudioFormat, sample_rate: u32) -> SinkResult<(PCM, usize)> {
166166
let pcm = PCM::new(dev_name, Direction::Playback, false).map_err(|e| AlsaError::PcmSetUp {
167167
device: dev_name.to_string(),
168168
e,
@@ -187,10 +187,10 @@ fn open_device(dev_name: &str, format: AudioFormat) -> SinkResult<(PCM, usize)>
187187
e,
188188
})?;
189189

190-
hwp.set_rate(SAMPLE_RATE, ValueOr::Nearest).map_err(|e| {
190+
hwp.set_rate(sample_rate, ValueOr::Nearest).map_err(|e| {
191191
AlsaError::UnsupportedSampleRate {
192192
device: dev_name.to_string(),
193-
samplerate: SAMPLE_RATE,
193+
samplerate: sample_rate,
194194
e,
195195
}
196196
})?;
@@ -416,18 +416,7 @@ impl Open for AlsaSink {
416416
impl Sink for AlsaSink {
417417
fn start(&mut self) -> SinkResult<()> {
418418
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-
);
419+
self.start_internal(SAMPLE_RATE)?;
431420
}
432421

433422
Ok(())
@@ -448,6 +437,13 @@ impl Sink for AlsaSink {
448437
Ok(())
449438
}
450439

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

@@ -483,6 +479,23 @@ impl SinkAsBytes for AlsaSink {
483479
impl AlsaSink {
484480
pub const NAME: &'static str = "alsa";
485481

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