2021-03-12 22:05:38 +00:00
|
|
|
use crate::config::AudioFormat;
|
Implement dithering (#694)
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
2021-05-26 19:19:17 +00:00
|
|
|
use crate::convert::Converter;
|
2021-04-13 08:29:34 +00:00
|
|
|
use crate::decoder::AudioPacket;
|
2021-09-27 18:46:26 +00:00
|
|
|
use thiserror::Error;
|
|
|
|
|
|
|
|
#[derive(Debug, Error)]
|
|
|
|
pub enum SinkError {
|
|
|
|
#[error("Audio Sink Error Not Connected: {0}")]
|
|
|
|
NotConnected(String),
|
|
|
|
#[error("Audio Sink Error Connection Refused: {0}")]
|
|
|
|
ConnectionRefused(String),
|
|
|
|
#[error("Audio Sink Error On Write: {0}")]
|
|
|
|
OnWrite(String),
|
|
|
|
#[error("Audio Sink Error Invalid Parameters: {0}")]
|
|
|
|
InvalidParams(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type SinkResult<T> = Result<T, SinkError>;
|
2016-03-20 16:16:11 +00:00
|
|
|
|
|
|
|
pub trait Open {
|
2021-03-12 22:05:38 +00:00
|
|
|
fn open(_: Option<String>, format: AudioFormat) -> Self;
|
2016-03-20 16:16:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub trait Sink {
|
2021-09-27 18:46:26 +00:00
|
|
|
fn start(&mut self) -> SinkResult<()> {
|
Implement dithering (#694)
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
2021-05-26 19:19:17 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-09-27 18:46:26 +00:00
|
|
|
fn stop(&mut self) -> SinkResult<()> {
|
Implement dithering (#694)
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
2021-05-26 19:19:17 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-09-27 18:46:26 +00:00
|
|
|
fn write(&mut self, packet: &AudioPacket, converter: &mut Converter) -> SinkResult<()>;
|
2016-03-20 16:16:11 +00:00
|
|
|
}
|
|
|
|
|
2021-04-10 08:27:24 +00:00
|
|
|
pub type SinkBuilder = fn(Option<String>, AudioFormat) -> Box<dyn Sink>;
|
2021-01-22 21:51:41 +00:00
|
|
|
|
2021-03-12 22:05:38 +00:00
|
|
|
pub trait SinkAsBytes {
|
2021-09-27 18:46:26 +00:00
|
|
|
fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()>;
|
2021-03-12 22:05:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn mk_sink<S: Sink + Open + 'static>(device: Option<String>, format: AudioFormat) -> Box<dyn Sink> {
|
|
|
|
Box::new(S::open(device, format))
|
|
|
|
}
|
|
|
|
|
|
|
|
// reuse code for various backends
|
|
|
|
macro_rules! sink_as_bytes {
|
|
|
|
() => {
|
2021-09-27 18:46:26 +00:00
|
|
|
fn write(&mut self, packet: &AudioPacket, converter: &mut Converter) -> SinkResult<()> {
|
Implement dithering (#694)
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
2021-05-26 19:19:17 +00:00
|
|
|
use crate::convert::i24;
|
2021-03-12 22:05:38 +00:00
|
|
|
use zerocopy::AsBytes;
|
|
|
|
match packet {
|
|
|
|
AudioPacket::Samples(samples) => match self.format {
|
2021-05-30 18:09:39 +00:00
|
|
|
AudioFormat::F64 => self.write_bytes(samples.as_bytes()),
|
|
|
|
AudioFormat::F32 => {
|
|
|
|
let samples_f32: &[f32] = &converter.f64_to_f32(samples);
|
|
|
|
self.write_bytes(samples_f32.as_bytes())
|
|
|
|
}
|
2021-03-13 22:43:24 +00:00
|
|
|
AudioFormat::S32 => {
|
2021-05-30 18:09:39 +00:00
|
|
|
let samples_s32: &[i32] = &converter.f64_to_s32(samples);
|
2021-03-13 22:43:24 +00:00
|
|
|
self.write_bytes(samples_s32.as_bytes())
|
|
|
|
}
|
2021-03-16 23:00:27 +00:00
|
|
|
AudioFormat::S24 => {
|
2021-05-30 18:09:39 +00:00
|
|
|
let samples_s24: &[i32] = &converter.f64_to_s24(samples);
|
2021-03-16 23:00:27 +00:00
|
|
|
self.write_bytes(samples_s24.as_bytes())
|
|
|
|
}
|
|
|
|
AudioFormat::S24_3 => {
|
2021-05-30 18:09:39 +00:00
|
|
|
let samples_s24_3: &[i24] = &converter.f64_to_s24_3(samples);
|
2021-03-16 23:00:27 +00:00
|
|
|
self.write_bytes(samples_s24_3.as_bytes())
|
|
|
|
}
|
2021-03-12 22:05:38 +00:00
|
|
|
AudioFormat::S16 => {
|
2021-05-30 18:09:39 +00:00
|
|
|
let samples_s16: &[i16] = &converter.f64_to_s16(samples);
|
2021-03-12 22:05:38 +00:00
|
|
|
self.write_bytes(samples_s16.as_bytes())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
AudioPacket::OggData(samples) => self.write_bytes(samples),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-03-14 02:16:59 +00:00
|
|
|
#[cfg(feature = "alsa-backend")]
|
|
|
|
mod alsa;
|
|
|
|
#[cfg(feature = "alsa-backend")]
|
|
|
|
use self::alsa::AlsaSink;
|
|
|
|
|
2016-03-20 16:16:11 +00:00
|
|
|
#[cfg(feature = "portaudio-backend")]
|
|
|
|
mod portaudio;
|
2016-05-04 08:56:23 +00:00
|
|
|
#[cfg(feature = "portaudio-backend")]
|
|
|
|
use self::portaudio::PortAudioSink;
|
2016-03-20 16:16:11 +00:00
|
|
|
|
2016-03-20 19:16:32 +00:00
|
|
|
#[cfg(feature = "pulseaudio-backend")]
|
|
|
|
mod pulseaudio;
|
2016-05-04 08:56:23 +00:00
|
|
|
#[cfg(feature = "pulseaudio-backend")]
|
|
|
|
use self::pulseaudio::PulseAudioSink;
|
2016-03-20 19:16:32 +00:00
|
|
|
|
2017-10-05 18:41:02 +00:00
|
|
|
#[cfg(feature = "jackaudio-backend")]
|
|
|
|
mod jackaudio;
|
|
|
|
#[cfg(feature = "jackaudio-backend")]
|
|
|
|
use self::jackaudio::JackSink;
|
|
|
|
|
2019-12-25 11:19:12 +00:00
|
|
|
#[cfg(feature = "gstreamer-backend")]
|
|
|
|
mod gstreamer;
|
|
|
|
#[cfg(feature = "gstreamer-backend")]
|
|
|
|
use self::gstreamer::GstreamerSink;
|
|
|
|
|
2021-02-23 14:05:02 +00:00
|
|
|
#[cfg(any(feature = "rodio-backend", feature = "rodiojack-backend"))]
|
2019-03-20 13:24:03 +00:00
|
|
|
mod rodio;
|
2021-05-31 20:32:39 +00:00
|
|
|
#[cfg(any(feature = "rodio-backend", feature = "rodiojack-backend"))]
|
|
|
|
use self::rodio::RodioSink;
|
2021-02-23 14:05:02 +00:00
|
|
|
|
2018-12-28 02:46:27 +00:00
|
|
|
#[cfg(feature = "sdl-backend")]
|
|
|
|
mod sdl;
|
|
|
|
#[cfg(feature = "sdl-backend")]
|
|
|
|
use self::sdl::SdlSink;
|
2018-11-15 19:34:13 +00:00
|
|
|
|
2016-12-31 12:17:06 +00:00
|
|
|
mod pipe;
|
|
|
|
use self::pipe::StdoutSink;
|
2016-03-20 19:16:32 +00:00
|
|
|
|
2020-01-24 00:35:24 +00:00
|
|
|
mod subprocess;
|
|
|
|
use self::subprocess::SubprocessSink;
|
|
|
|
|
2021-03-01 02:37:22 +00:00
|
|
|
pub const BACKENDS: &[(&str, SinkBuilder)] = &[
|
Implement dithering (#694)
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
2021-05-26 19:19:17 +00:00
|
|
|
#[cfg(feature = "rodio-backend")]
|
2021-05-31 20:32:39 +00:00
|
|
|
(RodioSink::NAME, rodio::mk_rodio), // default goes first
|
2017-05-10 15:26:48 +00:00
|
|
|
#[cfg(feature = "alsa-backend")]
|
2021-05-31 20:32:39 +00:00
|
|
|
(AlsaSink::NAME, mk_sink::<AlsaSink>),
|
2017-05-10 15:26:48 +00:00
|
|
|
#[cfg(feature = "portaudio-backend")]
|
2021-05-31 20:32:39 +00:00
|
|
|
(PortAudioSink::NAME, mk_sink::<PortAudioSink>),
|
2017-05-10 15:26:48 +00:00
|
|
|
#[cfg(feature = "pulseaudio-backend")]
|
2021-05-31 20:32:39 +00:00
|
|
|
(PulseAudioSink::NAME, mk_sink::<PulseAudioSink>),
|
2017-10-05 18:41:02 +00:00
|
|
|
#[cfg(feature = "jackaudio-backend")]
|
2021-05-31 20:32:39 +00:00
|
|
|
(JackSink::NAME, mk_sink::<JackSink>),
|
2019-12-25 11:19:12 +00:00
|
|
|
#[cfg(feature = "gstreamer-backend")]
|
2021-05-31 20:32:39 +00:00
|
|
|
(GstreamerSink::NAME, mk_sink::<GstreamerSink>),
|
2021-02-23 14:05:02 +00:00
|
|
|
#[cfg(feature = "rodiojack-backend")]
|
|
|
|
("rodiojack", rodio::mk_rodiojack),
|
2018-12-28 02:46:27 +00:00
|
|
|
#[cfg(feature = "sdl-backend")]
|
2021-05-31 20:32:39 +00:00
|
|
|
(SdlSink::NAME, mk_sink::<SdlSink>),
|
|
|
|
(StdoutSink::NAME, mk_sink::<StdoutSink>),
|
|
|
|
(SubprocessSink::NAME, mk_sink::<SubprocessSink>),
|
2017-05-10 15:26:48 +00:00
|
|
|
];
|
2017-01-10 16:31:12 +00:00
|
|
|
|
2021-01-22 21:51:41 +00:00
|
|
|
pub fn find(name: Option<String>) -> Option<SinkBuilder> {
|
2017-04-28 22:24:55 +00:00
|
|
|
if let Some(name) = name {
|
2018-02-26 01:50:41 +00:00
|
|
|
BACKENDS
|
|
|
|
.iter()
|
|
|
|
.find(|backend| name == backend.0)
|
|
|
|
.map(|backend| backend.1)
|
2017-01-10 16:31:12 +00:00
|
|
|
} else {
|
2021-10-30 19:22:24 +00:00
|
|
|
BACKENDS.first().map(|backend| backend.1)
|
2017-01-10 16:31:12 +00:00
|
|
|
}
|
|
|
|
}
|