mirror of
https://github.com/oSumAtrIX/free-librespot.git
synced 2025-12-19 09:54:25 +00:00
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
168 lines
5.2 KiB
Rust
168 lines
5.2 KiB
Rust
use super::{Open, Sink, SinkAsBytes};
|
|
use crate::config::AudioFormat;
|
|
use crate::convert::Converter;
|
|
use crate::decoder::AudioPacket;
|
|
use crate::player::{NUM_CHANNELS, SAMPLES_PER_SECOND, SAMPLE_RATE};
|
|
use alsa::device_name::HintIter;
|
|
use alsa::pcm::{Access, Format, Frames, HwParams, PCM};
|
|
use alsa::{Direction, Error, ValueOr};
|
|
use std::cmp::min;
|
|
use std::ffi::CString;
|
|
use std::io;
|
|
use std::process::exit;
|
|
|
|
const BUFFERED_LATENCY: f32 = 0.125; // seconds
|
|
const BUFFERED_PERIODS: Frames = 4;
|
|
|
|
pub struct AlsaSink {
|
|
pcm: Option<PCM>,
|
|
format: AudioFormat,
|
|
device: String,
|
|
buffer: Vec<u8>,
|
|
}
|
|
|
|
fn list_outputs() {
|
|
for t in &["pcm", "ctl", "hwdep"] {
|
|
println!("{} devices:", t);
|
|
let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
|
|
for a in i {
|
|
if let Some(Direction::Playback) = a.direction {
|
|
// mimic aplay -L
|
|
println!(
|
|
"{}\n\t{}\n",
|
|
a.name.unwrap(),
|
|
a.desc.unwrap().replace("\n", "\n\t")
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn open_device(dev_name: &str, format: AudioFormat) -> Result<(PCM, Frames), Box<Error>> {
|
|
let pcm = PCM::new(dev_name, Direction::Playback, false)?;
|
|
let alsa_format = match format {
|
|
AudioFormat::F32 => Format::float(),
|
|
AudioFormat::S32 => Format::s32(),
|
|
AudioFormat::S24 => Format::s24(),
|
|
AudioFormat::S24_3 => Format::S243LE,
|
|
AudioFormat::S16 => Format::s16(),
|
|
};
|
|
|
|
// http://www.linuxjournal.com/article/6735?page=0,1#N0x19ab2890.0x19ba78d8
|
|
// latency = period_size * periods / (rate * bytes_per_frame)
|
|
// For stereo samples encoded as 32-bit float, one frame has a length of eight bytes.
|
|
let mut period_size = ((SAMPLES_PER_SECOND * format.size() as u32) as f32
|
|
* (BUFFERED_LATENCY / BUFFERED_PERIODS as f32)) as Frames;
|
|
{
|
|
let hwp = HwParams::any(&pcm)?;
|
|
hwp.set_access(Access::RWInterleaved)?;
|
|
hwp.set_format(alsa_format)?;
|
|
hwp.set_rate(SAMPLE_RATE, ValueOr::Nearest)?;
|
|
hwp.set_channels(NUM_CHANNELS as u32)?;
|
|
period_size = hwp.set_period_size_near(period_size, ValueOr::Greater)?;
|
|
hwp.set_buffer_size_near(period_size * BUFFERED_PERIODS)?;
|
|
pcm.hw_params(&hwp)?;
|
|
|
|
let swp = pcm.sw_params_current()?;
|
|
swp.set_start_threshold(hwp.get_buffer_size()? - hwp.get_period_size()?)?;
|
|
pcm.sw_params(&swp)?;
|
|
}
|
|
|
|
Ok((pcm, period_size))
|
|
}
|
|
|
|
impl Open for AlsaSink {
|
|
fn open(device: Option<String>, format: AudioFormat) -> Self {
|
|
info!("Using Alsa sink with format: {:?}", format);
|
|
|
|
let name = match device.as_ref().map(AsRef::as_ref) {
|
|
Some("?") => {
|
|
println!("Listing available Alsa outputs:");
|
|
list_outputs();
|
|
exit(0)
|
|
}
|
|
Some(device) => device,
|
|
None => "default",
|
|
}
|
|
.to_string();
|
|
|
|
Self {
|
|
pcm: None,
|
|
format,
|
|
device: name,
|
|
buffer: vec![],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Sink for AlsaSink {
|
|
fn start(&mut self) -> io::Result<()> {
|
|
if self.pcm.is_none() {
|
|
let pcm = open_device(&self.device, self.format);
|
|
match pcm {
|
|
Ok((p, period_size)) => {
|
|
self.pcm = Some(p);
|
|
// Create a buffer for all samples for a full period
|
|
self.buffer = Vec::with_capacity(
|
|
period_size as usize * BUFFERED_PERIODS as usize * self.format.size(),
|
|
);
|
|
}
|
|
Err(e) => {
|
|
error!("Alsa error PCM open {}", e);
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::Other,
|
|
"Alsa error: PCM open failed",
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn stop(&mut self) -> io::Result<()> {
|
|
{
|
|
// Write any leftover data in the period buffer
|
|
// before draining the actual buffer
|
|
self.write_bytes(&[]).expect("could not flush buffer");
|
|
let pcm = self.pcm.as_mut().unwrap();
|
|
pcm.drain().unwrap();
|
|
}
|
|
self.pcm = None;
|
|
Ok(())
|
|
}
|
|
|
|
sink_as_bytes!();
|
|
}
|
|
|
|
impl SinkAsBytes for AlsaSink {
|
|
fn write_bytes(&mut self, data: &[u8]) -> io::Result<()> {
|
|
let mut processed_data = 0;
|
|
while processed_data < data.len() {
|
|
let data_to_buffer = min(
|
|
self.buffer.capacity() - self.buffer.len(),
|
|
data.len() - processed_data,
|
|
);
|
|
self.buffer
|
|
.extend_from_slice(&data[processed_data..processed_data + data_to_buffer]);
|
|
processed_data += data_to_buffer;
|
|
if self.buffer.len() == self.buffer.capacity() {
|
|
self.write_buf();
|
|
self.buffer.clear();
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl AlsaSink {
|
|
fn write_buf(&mut self) {
|
|
let pcm = self.pcm.as_mut().unwrap();
|
|
let io = pcm.io_bytes();
|
|
match io.writei(&self.buffer) {
|
|
Ok(_) => (),
|
|
Err(err) => pcm.try_recover(err, false).unwrap(),
|
|
};
|
|
}
|
|
}
|