Skip to content

Commit fe2d5ca

Browse files
authored
Store and process samples in 64 bit (#773)
1 parent 8062bd2 commit fe2d5ca

19 files changed

Lines changed: 177 additions & 149 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- [playback] Add support for dithering with `--dither` for lower requantization error (breaking)
1212
- [playback] Add `--volume-range` option to set dB range and control `log` and `cubic` volume control curves
1313
- [playback] `alsamixer`: support for querying dB range from Alsa softvol
14+
- [playback] Add `--format F64` (supported by Alsa and GStreamer only)
1415

1516
### Changed
1617
- [audio, playback] Moved `VorbisDecoder`, `VorbisError`, `AudioPacket`, `PassthroughDecoder`, `PassthroughError`, `AudioError`, `AudioDecoder` and the `convert` module from `librespot-audio` to `librespot-playback`. The underlying crates `vorbis`, `librespot-tremor`, `lewton` and `ogg` should be used directly. (breaking)
1718
- [connect, playback] Moved volume controls from `librespot-connect` to `librespot-playback` crate
1819
- [connect] Synchronize player volume with mixer volume on playback
20+
- [playback] Store and pass samples in 64-bit floating point
1921
- [playback] Make cubic volume control available to all mixers with `--volume-ctrl cubic`
2022
- [playback] Normalize volumes to `[0.0..1.0]` instead of `[0..65535]` for greater precision and performance (breaking)
2123
- [playback] `alsamixer`: complete rewrite (breaking)

playback/src/audio_backend/alsa.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ fn list_outputs() {
4141
fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, Frames), Box<Error>> {
4242
let pcm = PCM::new(dev_name, Direction::Playback, false)?;
4343
let alsa_format = match format {
44+
AudioFormat::F64 => Format::float64(),
4445
AudioFormat::F32 => Format::float(),
4546
AudioFormat::S32 => Format::s32(),
4647
AudioFormat::S24 => Format::s24(),

playback/src/audio_backend/jackaudio.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,10 @@ impl Open for JackSink {
7070
}
7171

7272
impl Sink for JackSink {
73-
fn write(&mut self, packet: &AudioPacket, _: &mut Converter) -> io::Result<()> {
74-
for s in packet.samples().iter() {
75-
let res = self.send.send(*s);
73+
fn write(&mut self, packet: &AudioPacket, converter: &mut Converter) -> io::Result<()> {
74+
let samples_f32: &[f32] = &converter.f64_to_f32(packet.samples());
75+
for sample in samples_f32.iter() {
76+
let res = self.send.send(*sample);
7677
if res.is_err() {
7778
error!("cannot write to channel");
7879
}

playback/src/audio_backend/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,25 @@ macro_rules! sink_as_bytes {
3535
use zerocopy::AsBytes;
3636
match packet {
3737
AudioPacket::Samples(samples) => match self.format {
38-
AudioFormat::F32 => self.write_bytes(samples.as_bytes()),
38+
AudioFormat::F64 => self.write_bytes(samples.as_bytes()),
39+
AudioFormat::F32 => {
40+
let samples_f32: &[f32] = &converter.f64_to_f32(samples);
41+
self.write_bytes(samples_f32.as_bytes())
42+
}
3943
AudioFormat::S32 => {
40-
let samples_s32: &[i32] = &converter.f32_to_s32(samples);
44+
let samples_s32: &[i32] = &converter.f64_to_s32(samples);
4145
self.write_bytes(samples_s32.as_bytes())
4246
}
4347
AudioFormat::S24 => {
44-
let samples_s24: &[i32] = &converter.f32_to_s24(samples);
48+
let samples_s24: &[i32] = &converter.f64_to_s24(samples);
4549
self.write_bytes(samples_s24.as_bytes())
4650
}
4751
AudioFormat::S24_3 => {
48-
let samples_s24_3: &[i24] = &converter.f32_to_s24_3(samples);
52+
let samples_s24_3: &[i24] = &converter.f64_to_s24_3(samples);
4953
self.write_bytes(samples_s24_3.as_bytes())
5054
}
5155
AudioFormat::S16 => {
52-
let samples_s16: &[i16] = &converter.f32_to_s16(samples);
56+
let samples_s16: &[i16] = &converter.f64_to_s16(samples);
5357
self.write_bytes(samples_s16.as_bytes())
5458
}
5559
},

playback/src/audio_backend/portaudio.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,15 @@ impl<'a> Sink for PortAudioSink<'a> {
151151
let samples = packet.samples();
152152
let result = match self {
153153
Self::F32(stream, _parameters) => {
154-
write_sink!(ref mut stream, samples)
154+
let samples_f32: &[f32] = &converter.f64_to_f32(samples);
155+
write_sink!(ref mut stream, samples_f32)
155156
}
156157
Self::S32(stream, _parameters) => {
157-
let samples_s32: &[i32] = &converter.f32_to_s32(samples);
158+
let samples_s32: &[i32] = &converter.f64_to_s32(samples);
158159
write_sink!(ref mut stream, samples_s32)
159160
}
160161
Self::S16(stream, _parameters) => {
161-
let samples_s16: &[i16] = &converter.f32_to_s16(samples);
162+
let samples_s16: &[i16] = &converter.f64_to_s16(samples);
162163
write_sink!(ref mut stream, samples_s16)
163164
}
164165
};

playback/src/audio_backend/pulseaudio.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ impl Open for PulseAudioSink {
2828
AudioFormat::S24 => pulse::sample::Format::S24_32le,
2929
AudioFormat::S24_3 => pulse::sample::Format::S24le,
3030
AudioFormat::S16 => pulse::sample::Format::S16le,
31+
_ => {
32+
unimplemented!("PulseAudio currently does not support {:?} output", format)
33+
}
3134
};
3235

3336
let ss = pulse::sample::Spec {

playback/src/audio_backend/rodio.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,16 @@ impl Sink for RodioSink {
178178
let samples = packet.samples();
179179
match self.format {
180180
AudioFormat::F32 => {
181-
let source =
182-
rodio::buffer::SamplesBuffer::new(NUM_CHANNELS as u16, SAMPLE_RATE, samples);
181+
let samples_f32: &[f32] = &converter.f64_to_f32(samples);
182+
let source = rodio::buffer::SamplesBuffer::new(
183+
NUM_CHANNELS as u16,
184+
SAMPLE_RATE,
185+
samples_f32,
186+
);
183187
self.rodio_sink.append(source);
184188
}
185189
AudioFormat::S16 => {
186-
let samples_s16: &[i16] = &converter.f32_to_s16(samples);
190+
let samples_s16: &[i16] = &converter.f64_to_s16(samples);
187191
let source = rodio::buffer::SamplesBuffer::new(
188192
NUM_CHANNELS as u16,
189193
SAMPLE_RATE,

playback/src/audio_backend/sdl.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,17 @@ impl Sink for SdlSink {
9494
let samples = packet.samples();
9595
match self {
9696
Self::F32(queue) => {
97+
let samples_f32: &[f32] = &converter.f64_to_f32(samples);
9798
drain_sink!(queue, AudioFormat::F32.size());
98-
queue.queue(samples)
99+
queue.queue(samples_f32)
99100
}
100101
Self::S32(queue) => {
101-
let samples_s32: &[i32] = &converter.f32_to_s32(samples);
102+
let samples_s32: &[i32] = &converter.f64_to_s32(samples);
102103
drain_sink!(queue, AudioFormat::S32.size());
103104
queue.queue(samples_s32)
104105
}
105106
Self::S16(queue) => {
106-
let samples_s16: &[i16] = &converter.f32_to_s16(samples);
107+
let samples_s16: &[i16] = &converter.f64_to_s16(samples);
107108
drain_sink!(queue, AudioFormat::S16.size());
108109
queue.queue(samples_s16)
109110
}

playback/src/config.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ impl Default for Bitrate {
3333

3434
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
3535
pub enum AudioFormat {
36+
F64,
3637
F32,
3738
S32,
3839
S24,
@@ -44,6 +45,7 @@ impl TryFrom<&String> for AudioFormat {
4445
type Error = ();
4546
fn try_from(s: &String) -> Result<Self, Self::Error> {
4647
match s.to_uppercase().as_str() {
48+
"F64" => Ok(Self::F64),
4749
"F32" => Ok(Self::F32),
4850
"S32" => Ok(Self::S32),
4951
"S24" => Ok(Self::S24),
@@ -65,6 +67,8 @@ impl AudioFormat {
6567
#[allow(dead_code)]
6668
pub fn size(&self) -> usize {
6769
match self {
70+
Self::F64 => mem::size_of::<f64>(),
71+
Self::F32 => mem::size_of::<f32>(),
6872
Self::S24_3 => mem::size_of::<i24>(),
6973
Self::S16 => mem::size_of::<i16>(),
7074
_ => mem::size_of::<i32>(), // S32 and S24 are both stored in i32
@@ -127,11 +131,11 @@ pub struct PlayerConfig {
127131
pub normalisation: bool,
128132
pub normalisation_type: NormalisationType,
129133
pub normalisation_method: NormalisationMethod,
130-
pub normalisation_pregain: f32,
131-
pub normalisation_threshold: f32,
132-
pub normalisation_attack: f32,
133-
pub normalisation_release: f32,
134-
pub normalisation_knee: f32,
134+
pub normalisation_pregain: f64,
135+
pub normalisation_threshold: f64,
136+
pub normalisation_attack: f64,
137+
pub normalisation_release: f64,
138+
pub normalisation_knee: f64,
135139

136140
// pass function pointers so they can be lazily instantiated *after* spawning a thread
137141
// (thereby circumventing Send bounds that they might not satisfy)
@@ -160,10 +164,10 @@ impl Default for PlayerConfig {
160164
// fields are intended for volume control range in dB
161165
#[derive(Clone, Copy, Debug)]
162166
pub enum VolumeCtrl {
163-
Cubic(f32),
167+
Cubic(f64),
164168
Fixed,
165169
Linear,
166-
Log(f32),
170+
Log(f64),
167171
}
168172

169173
impl FromStr for VolumeCtrl {
@@ -183,9 +187,9 @@ impl VolumeCtrl {
183187
pub const MAX_VOLUME: u16 = std::u16::MAX;
184188

185189
// Taken from: https://www.dr-lex.be/info-stuff/volumecontrols.html
186-
pub const DEFAULT_DB_RANGE: f32 = 60.0;
190+
pub const DEFAULT_DB_RANGE: f64 = 60.0;
187191

188-
pub fn from_str_with_range(s: &str, db_range: f32) -> Result<Self, <Self as FromStr>::Err> {
192+
pub fn from_str_with_range(s: &str, db_range: f64) -> Result<Self, <Self as FromStr>::Err> {
189193
use self::VolumeCtrl::*;
190194
match s.to_lowercase().as_ref() {
191195
"cubic" => Ok(Cubic(db_range)),

playback/src/convert.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,34 +30,37 @@ impl Converter {
3030
}
3131
}
3232

33-
// Denormalize and dither
34-
pub fn scale(&mut self, sample: f32, factor: i64) -> f32 {
33+
const SCALE_S32: f64 = 2147483648.;
34+
const SCALE_S24: f64 = 8388608.;
35+
const SCALE_S16: f64 = 32768.;
36+
37+
pub fn scale(&mut self, sample: f64, factor: f64) -> f64 {
3538
let dither = match self.ditherer {
3639
Some(ref mut d) => d.noise(),
3740
None => 0.0,
3841
};
3942

4043
// From the many float to int conversion methods available, match what
4144
// the reference Vorbis implementation uses: sample * 32768 (for 16 bit)
42-
let int_value = sample * factor as f32 + dither;
45+
let int_value = sample * factor + dither;
4346

4447
// Casting float to integer rounds towards zero by default, i.e. it
4548
// truncates, and that generates larger error than rounding to nearest.
4649
// Absolute lowest error is gained from rounding ties to even.
47-
math::round::half_to_even(int_value.into(), 0) as f32
50+
math::round::half_to_even(int_value, 0)
4851
}
4952

5053
// Special case for samples packed in a word of greater bit depth (e.g.
5154
// S24): clamp between min and max to ensure that the most significant
5255
// byte is zero. Otherwise, dithering may cause an overflow. This is not
5356
// necessary for other formats, because casting to integer will saturate
5457
// to the bounds of the primitive.
55-
pub fn clamping_scale(&mut self, sample: f32, factor: i64) -> f32 {
58+
pub fn clamping_scale(&mut self, sample: f64, factor: f64) -> f64 {
5659
let int_value = self.scale(sample, factor);
5760

5861
// In two's complement, there are more negative than positive values.
59-
let min = -factor as f32;
60-
let max = (factor - 1) as f32;
62+
let min = -factor;
63+
let max = factor - 1.0;
6164

6265
if int_value < min {
6366
return min;
@@ -67,38 +70,42 @@ impl Converter {
6770
int_value
6871
}
6972

70-
pub fn f32_to_s32(&mut self, samples: &[f32]) -> Vec<i32> {
73+
pub fn f64_to_f32(&mut self, samples: &[f64]) -> Vec<f32> {
74+
samples.iter().map(|sample| *sample as f32).collect()
75+
}
76+
77+
pub fn f64_to_s32(&mut self, samples: &[f64]) -> Vec<i32> {
7178
samples
7279
.iter()
73-
.map(|sample| self.scale(*sample, 0x80000000) as i32)
80+
.map(|sample| self.scale(*sample, Self::SCALE_S32) as i32)
7481
.collect()
7582
}
7683

7784
// S24 is 24-bit PCM packed in an upper 32-bit word
78-
pub fn f32_to_s24(&mut self, samples: &[f32]) -> Vec<i32> {
85+
pub fn f64_to_s24(&mut self, samples: &[f64]) -> Vec<i32> {
7986
samples
8087
.iter()
81-
.map(|sample| self.clamping_scale(*sample, 0x800000) as i32)
88+
.map(|sample| self.clamping_scale(*sample, Self::SCALE_S24) as i32)
8289
.collect()
8390
}
8491

8592
// S24_3 is 24-bit PCM in a 3-byte array
86-
pub fn f32_to_s24_3(&mut self, samples: &[f32]) -> Vec<i24> {
93+
pub fn f64_to_s24_3(&mut self, samples: &[f64]) -> Vec<i24> {
8794
samples
8895
.iter()
8996
.map(|sample| {
9097
// Not as DRY as calling f32_to_s24 first, but this saves iterating
9198
// over all samples twice.
92-
let int_value = self.clamping_scale(*sample, 0x800000) as i32;
99+
let int_value = self.clamping_scale(*sample, Self::SCALE_S24) as i32;
93100
i24::from_s24(int_value)
94101
})
95102
.collect()
96103
}
97104

98-
pub fn f32_to_s16(&mut self, samples: &[f32]) -> Vec<i16> {
105+
pub fn f64_to_s16(&mut self, samples: &[f64]) -> Vec<i16> {
99106
samples
100107
.iter()
101-
.map(|sample| self.scale(*sample, 0x8000) as i16)
108+
.map(|sample| self.scale(*sample, Self::SCALE_S16) as i16)
102109
.collect()
103110
}
104111
}

0 commit comments

Comments
 (0)