mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Add support for S32 output format
While at it, add a small tweak when converting "silent" samples from float to integer. This ensures 0.0 converts to 0 and vice versa.
This commit is contained in:
parent
a4ef174fd0
commit
5f26a745d7
10 changed files with 81 additions and 18 deletions
|
@ -37,6 +37,22 @@ pub enum AudioPacket {
|
|||
OggData(Vec<u8>),
|
||||
}
|
||||
|
||||
// Losslessly represent [-1.0, 1.0] to [$type::MIN, $type::MAX] while maintaining DC linearity.
|
||||
macro_rules! convert_samples_to {
|
||||
($type: ident, $samples: expr) => {
|
||||
$samples
|
||||
.iter()
|
||||
.map(|sample| {
|
||||
if *sample == 0.0 {
|
||||
0 as $type
|
||||
} else {
|
||||
(*sample as f64 * (std::$type::MAX as f64 + 0.5) - 0.5) as $type
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
|
||||
impl AudioPacket {
|
||||
pub fn samples(&self) -> &[f32] {
|
||||
match self {
|
||||
|
@ -59,11 +75,12 @@ impl AudioPacket {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn f32_to_s32(samples: &[f32]) -> Vec<i32> {
|
||||
convert_samples_to!(i32, samples)
|
||||
}
|
||||
|
||||
pub fn f32_to_s16(samples: &[f32]) -> Vec<i16> {
|
||||
samples
|
||||
.iter()
|
||||
.map(|sample| (*sample as f64 * (0x7FFF as f64 + 0.5) - 0.5) as i16)
|
||||
.collect()
|
||||
convert_samples_to!(i16, samples)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,13 @@ where
|
|||
packet
|
||||
.data
|
||||
.iter()
|
||||
.map(|sample| ((*sample as f64 + 0.5) / (0x7FFF as f64 + 0.5)) as f32)
|
||||
.map(|sample| {
|
||||
if *sample == 0 {
|
||||
0.0
|
||||
} else {
|
||||
((*sample as f64 + 0.5) / (0x7FFF as f64 + 0.5)) as f32
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)));
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, Frames), Box
|
|||
let pcm = PCM::new(dev_name, Direction::Playback, false)?;
|
||||
let (alsa_format, sample_size) = match format {
|
||||
AudioFormat::F32 => (Format::float(), mem::size_of::<f32>()),
|
||||
AudioFormat::S32 => (Format::s32(), mem::size_of::<i32>()),
|
||||
AudioFormat::S16 => (Format::s16(), mem::size_of::<i16>()),
|
||||
};
|
||||
|
||||
|
@ -157,6 +158,11 @@ impl AlsaSink {
|
|||
let io = pcm.io_f32().unwrap();
|
||||
io.writei(&self.buffer)
|
||||
}
|
||||
AudioFormat::S32 => {
|
||||
let io = pcm.io_i32().unwrap();
|
||||
let buf_s32: Vec<i32> = AudioPacket::f32_to_s32(&self.buffer);
|
||||
io.writei(&buf_s32[..])
|
||||
}
|
||||
AudioFormat::S16 => {
|
||||
let io = pcm.io_i16().unwrap();
|
||||
let buf_s16: Vec<i16> = AudioPacket::f32_to_s16(&self.buffer);
|
||||
|
|
|
@ -28,6 +28,10 @@ macro_rules! sink_as_bytes {
|
|||
match packet {
|
||||
AudioPacket::Samples(samples) => match self.format {
|
||||
AudioFormat::F32 => self.write_bytes(samples.as_bytes()),
|
||||
AudioFormat::S32 => {
|
||||
let samples_s32 = AudioPacket::f32_to_s32(samples);
|
||||
self.write_bytes(samples_s32.as_bytes())
|
||||
}
|
||||
AudioFormat::S16 => {
|
||||
let samples_s16 = AudioPacket::f32_to_s16(samples);
|
||||
self.write_bytes(samples_s16.as_bytes())
|
||||
|
|
|
@ -14,6 +14,10 @@ pub enum PortAudioSink<'a> {
|
|||
Option<portaudio_rs::stream::Stream<'a, f32, f32>>,
|
||||
StreamParameters<f32>,
|
||||
),
|
||||
S32(
|
||||
Option<portaudio_rs::stream::Stream<'a, i32, i32>>,
|
||||
StreamParameters<i32>,
|
||||
),
|
||||
S16(
|
||||
Option<portaudio_rs::stream::Stream<'a, i16, i16>>,
|
||||
StreamParameters<i16>,
|
||||
|
@ -70,19 +74,20 @@ impl<'a> Open for PortAudioSink<'a> {
|
|||
};
|
||||
|
||||
macro_rules! open_sink {
|
||||
($sink: expr, $data: expr) => {{
|
||||
($sink: expr, $type: ty) => {{
|
||||
let params = StreamParameters {
|
||||
device: device_idx,
|
||||
channel_count: NUM_CHANNELS as u32,
|
||||
suggested_latency: latency,
|
||||
data: $data,
|
||||
data: 0.0 as $type,
|
||||
};
|
||||
$sink(None, params)
|
||||
}};
|
||||
}
|
||||
match format {
|
||||
AudioFormat::F32 => open_sink!(PortAudioSink::F32, 0.0),
|
||||
AudioFormat::S16 => open_sink!(PortAudioSink::S16, 0),
|
||||
AudioFormat::F32 => open_sink!(PortAudioSink::F32, f32),
|
||||
AudioFormat::S32 => open_sink!(PortAudioSink::S32, i32),
|
||||
AudioFormat::S16 => open_sink!(PortAudioSink::S16, i16),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +114,7 @@ impl<'a> Sink for PortAudioSink<'a> {
|
|||
}
|
||||
match self {
|
||||
PortAudioSink::F32(stream, parameters) => start_sink!(stream, parameters),
|
||||
PortAudioSink::S32(stream, parameters) => start_sink!(stream, parameters),
|
||||
PortAudioSink::S16(stream, parameters) => start_sink!(stream, parameters),
|
||||
};
|
||||
|
||||
|
@ -124,6 +130,7 @@ impl<'a> Sink for PortAudioSink<'a> {
|
|||
}
|
||||
match self {
|
||||
PortAudioSink::F32(stream, _parameters) => stop_sink!(stream),
|
||||
PortAudioSink::S32(stream, _parameters) => stop_sink!(stream),
|
||||
PortAudioSink::S16(stream, _parameters) => stop_sink!(stream),
|
||||
};
|
||||
|
||||
|
@ -141,6 +148,10 @@ impl<'a> Sink for PortAudioSink<'a> {
|
|||
let samples = packet.samples();
|
||||
write_sink!(stream, &samples)
|
||||
}
|
||||
PortAudioSink::S32(stream, _parameters) => {
|
||||
let samples_s32: Vec<i32> = AudioPacket::f32_to_s32(packet.samples());
|
||||
write_sink!(stream, &samples_s32)
|
||||
}
|
||||
PortAudioSink::S16(stream, _parameters) => {
|
||||
let samples_s16: Vec<i16> = AudioPacket::f32_to_s16(packet.samples());
|
||||
write_sink!(stream, &samples_s16)
|
||||
|
|
|
@ -22,6 +22,7 @@ impl Open for PulseAudioSink {
|
|||
|
||||
let pulse_format = match format {
|
||||
AudioFormat::F32 => pulse::sample::Format::F32le,
|
||||
AudioFormat::S32 => pulse::sample::Format::S32le,
|
||||
AudioFormat::S16 => pulse::sample::Format::S16le,
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ use cpal::traits::{DeviceTrait, HostTrait};
|
|||
use std::process::exit;
|
||||
use std::{io, thread, time};
|
||||
|
||||
const FORMAT_NOT_SUPPORTED: &'static str = "Rodio currently does not support that output format";
|
||||
|
||||
// most code is shared between RodioSink and JackRodioSink
|
||||
macro_rules! rodio_sink {
|
||||
($name: ident) => {
|
||||
|
@ -27,12 +29,13 @@ macro_rules! rodio_sink {
|
|||
AudioFormat::F32 => {
|
||||
let source = rodio::buffer::SamplesBuffer::new(2, 44100, samples);
|
||||
self.rodio_sink.append(source)
|
||||
}
|
||||
},
|
||||
AudioFormat::S16 => {
|
||||
let samples_s16: Vec<i16> = AudioPacket::f32_to_s16(samples);
|
||||
let source = rodio::buffer::SamplesBuffer::new(2, 44100, samples_s16);
|
||||
self.rodio_sink.append(source)
|
||||
}
|
||||
},
|
||||
_ => panic!(FORMAT_NOT_SUPPORTED),
|
||||
};
|
||||
|
||||
// Chunk sizes seem to be about 256 to 3000 ish items long.
|
||||
|
@ -48,12 +51,16 @@ macro_rules! rodio_sink {
|
|||
|
||||
impl $name {
|
||||
fn open_sink(host: &cpal::Host, device: Option<String>, format: AudioFormat) -> $name {
|
||||
if format != AudioFormat::S16 {
|
||||
match format {
|
||||
AudioFormat::F32 => {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
warn!("Rodio output to Alsa is known to cause garbled sound on output formats other than 16-bit signed integer.");
|
||||
warn!("Consider using `--backend alsa` OR `--format {:?}`", AudioFormat::S16);
|
||||
}
|
||||
},
|
||||
AudioFormat::S16 => {},
|
||||
_ => panic!(FORMAT_NOT_SUPPORTED),
|
||||
}
|
||||
|
||||
let rodio_device = match_device(&host, device);
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::{io, mem, thread, time};
|
|||
|
||||
pub enum SdlSink {
|
||||
F32(AudioQueue<f32>),
|
||||
S32(AudioQueue<i32>),
|
||||
S16(AudioQueue<i16>),
|
||||
}
|
||||
|
||||
|
@ -39,6 +40,7 @@ impl Open for SdlSink {
|
|||
}
|
||||
match format {
|
||||
AudioFormat::F32 => open_sink!(SdlSink::F32, f32),
|
||||
AudioFormat::S32 => open_sink!(SdlSink::S32, i32),
|
||||
AudioFormat::S16 => open_sink!(SdlSink::S16, i16),
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +56,7 @@ impl Sink for SdlSink {
|
|||
}
|
||||
match self {
|
||||
SdlSink::F32(queue) => start_sink!(queue),
|
||||
SdlSink::S32(queue) => start_sink!(queue),
|
||||
SdlSink::S16(queue) => start_sink!(queue),
|
||||
};
|
||||
Ok(())
|
||||
|
@ -68,6 +71,7 @@ impl Sink for SdlSink {
|
|||
}
|
||||
match self {
|
||||
SdlSink::F32(queue) => stop_sink!(queue),
|
||||
SdlSink::S32(queue) => stop_sink!(queue),
|
||||
SdlSink::S16(queue) => stop_sink!(queue),
|
||||
};
|
||||
Ok(())
|
||||
|
@ -87,6 +91,11 @@ impl Sink for SdlSink {
|
|||
drain_sink!(queue, mem::size_of::<f32>());
|
||||
queue.queue(packet.samples())
|
||||
}
|
||||
SdlSink::S32(queue) => {
|
||||
drain_sink!(queue, mem::size_of::<i32>());
|
||||
let samples_s32: Vec<i32> = AudioPacket::f32_to_s32(packet.samples());
|
||||
queue.queue(&samples_s32)
|
||||
}
|
||||
SdlSink::S16(queue) => {
|
||||
drain_sink!(queue, mem::size_of::<i16>());
|
||||
let samples_s16: Vec<i16> = AudioPacket::f32_to_s16(packet.samples());
|
||||
|
|
|
@ -29,6 +29,7 @@ impl Default for Bitrate {
|
|||
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub enum AudioFormat {
|
||||
F32,
|
||||
S32,
|
||||
S16,
|
||||
}
|
||||
|
||||
|
@ -37,6 +38,7 @@ impl TryFrom<&String> for AudioFormat {
|
|||
fn try_from(s: &String) -> Result<Self, Self::Error> {
|
||||
match s.to_uppercase().as_str() {
|
||||
"F32" => Ok(AudioFormat::F32),
|
||||
"S32" => Ok(AudioFormat::S32),
|
||||
"S16" => Ok(AudioFormat::S16),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ fn setup(args: &[String]) -> Setup {
|
|||
.optopt(
|
||||
"",
|
||||
"format",
|
||||
"Output format (F32 or S16). Defaults to F32",
|
||||
"Output format (F32, S32 or S16). Defaults to F32",
|
||||
"FORMAT",
|
||||
)
|
||||
.optopt("", "mixer", "Mixer to use (alsa or softvol)", "MIXER")
|
||||
|
|
Loading…
Reference in a new issue