output/alsa: add option thesycon_dsd_workaround
This commit is contained in:
parent
356d13e9dd
commit
3413b1aeb4
@ -841,6 +841,11 @@ The `Advanced Linux Sound Architecture (ALSA) <http://www.alsa-project.org/>`_ p
|
|||||||
("stop" or "pause") in DSD mode (native DSD or DoP). This is a
|
("stop" or "pause") in DSD mode (native DSD or DoP). This is a
|
||||||
workaround for some DACs which emit noise when stopping DSD
|
workaround for some DACs which emit noise when stopping DSD
|
||||||
playback.
|
playback.
|
||||||
|
* - **thesycon_dsd_workaround yes|no**
|
||||||
|
- If enabled, enables a workaround for a bug in Thesycon USB
|
||||||
|
audio receivers. On these devices, playing DSD512 or PCM
|
||||||
|
causes all subsequent attempts to play other DSD rates to fail,
|
||||||
|
which can be fixed by briefly playing PCM at 44.1 kHz.
|
||||||
* - **allowed_formats F1 F2 ...**
|
* - **allowed_formats F1 F2 ...**
|
||||||
- Specifies a list of allowed audio formats, separated by a space. All items may contain asterisks as a wild card, and may be followed by "=dop" to enable DoP (DSD over PCM) for this particular format. The first matching format is used, and if none matches, MPD chooses the best fallback of this list.
|
- Specifies a list of allowed audio formats, separated by a space. All items may contain asterisks as a wild card, and may be followed by "=dop" to enable DoP (DSD over PCM) for this particular format. The first matching format is used, and if none matches, MPD chooses the best fallback of this list.
|
||||||
|
|
||||||
|
@ -43,6 +43,10 @@
|
|||||||
#include "event/Call.hxx"
|
#include "event/Call.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
|
#ifdef ENABLE_DSD
|
||||||
|
#include "util/AllocatedArray.hxx"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
|
||||||
#include <boost/lockfree/spsc_queue.hpp>
|
#include <boost/lockfree/spsc_queue.hpp>
|
||||||
@ -102,6 +106,16 @@ class AlsaOutput final
|
|||||||
* Are we currently draining with #stop_dsd_silence?
|
* Are we currently draining with #stop_dsd_silence?
|
||||||
*/
|
*/
|
||||||
bool in_stop_dsd_silence;
|
bool in_stop_dsd_silence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the DSD sync workaround for Thesycon USB audio
|
||||||
|
* receivers? On this device, playing DSD512 or PCM causes
|
||||||
|
* all subsequent attempts to play other DSD rates to fail,
|
||||||
|
* which can be fixed by briefly playing PCM at 44.1 kHz.
|
||||||
|
*/
|
||||||
|
const bool thesycon_dsd_workaround;
|
||||||
|
|
||||||
|
bool need_thesycon_dsd_workaround = thesycon_dsd_workaround;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** libasound's buffer_time setting (in microseconds) */
|
/** libasound's buffer_time setting (in microseconds) */
|
||||||
@ -433,6 +447,8 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
|
|||||||
/* legacy name from MPD 0.18 and older: */
|
/* legacy name from MPD 0.18 and older: */
|
||||||
block.GetBlockValue("dsd_usb", false)),
|
block.GetBlockValue("dsd_usb", false)),
|
||||||
stop_dsd_silence(block.GetBlockValue("stop_dsd_silence", false)),
|
stop_dsd_silence(block.GetBlockValue("stop_dsd_silence", false)),
|
||||||
|
thesycon_dsd_workaround(block.GetBlockValue("thesycon_dsd_workaround",
|
||||||
|
false)),
|
||||||
#endif
|
#endif
|
||||||
buffer_time(block.GetPositiveValue("buffer_time",
|
buffer_time(block.GetPositiveValue("buffer_time",
|
||||||
MPD_ALSA_BUFFER_TIME_US)),
|
MPD_ALSA_BUFFER_TIME_US)),
|
||||||
@ -675,6 +691,97 @@ BestMatch(const std::forward_list<Alsa::AllowedFormat> &haystack,
|
|||||||
return haystack.front();
|
return haystack.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_DSD
|
||||||
|
|
||||||
|
static void
|
||||||
|
Play_44_1_Silence(snd_pcm_t *pcm)
|
||||||
|
{
|
||||||
|
snd_pcm_hw_params_t *hw;
|
||||||
|
snd_pcm_hw_params_alloca(&hw);
|
||||||
|
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = snd_pcm_hw_params_any(pcm, hw);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_hw_params_any() failed");
|
||||||
|
|
||||||
|
err = snd_pcm_hw_params_set_access(pcm, hw,
|
||||||
|
SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_access() failed");
|
||||||
|
|
||||||
|
err = snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_S16);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_format() failed");
|
||||||
|
|
||||||
|
unsigned channels = 1;
|
||||||
|
err = snd_pcm_hw_params_set_channels_near(pcm, hw, &channels);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_channels_near() failed");
|
||||||
|
|
||||||
|
constexpr snd_pcm_uframes_t rate = 44100;
|
||||||
|
err = snd_pcm_hw_params_set_rate(pcm, hw, rate, 0);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_rate() failed");
|
||||||
|
|
||||||
|
snd_pcm_uframes_t buffer_size = 1;
|
||||||
|
err = snd_pcm_hw_params_set_buffer_size_near(pcm, hw, &buffer_size);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_buffer_size_near() failed");
|
||||||
|
|
||||||
|
snd_pcm_uframes_t period_size = 1;
|
||||||
|
int dir = 0;
|
||||||
|
err = snd_pcm_hw_params_set_period_size_near(pcm, hw, &period_size,
|
||||||
|
&dir);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_hw_params_set_period_size_near() failed");
|
||||||
|
|
||||||
|
err = snd_pcm_hw_params(pcm, hw);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_hw_params() failed");
|
||||||
|
|
||||||
|
snd_pcm_sw_params_t *sw;
|
||||||
|
snd_pcm_sw_params_alloca(&sw);
|
||||||
|
|
||||||
|
err = snd_pcm_sw_params_current(pcm, sw);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_sw_params_current() failed");
|
||||||
|
|
||||||
|
err = snd_pcm_sw_params_set_start_threshold(pcm, sw, period_size);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_sw_params_set_start_threshold() failed");
|
||||||
|
|
||||||
|
err = snd_pcm_sw_params(pcm, sw);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_sw_params() failed");
|
||||||
|
|
||||||
|
err = snd_pcm_prepare(pcm);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_prepare() failed");
|
||||||
|
|
||||||
|
AllocatedArray<int16_t> buffer{channels * period_size};
|
||||||
|
std::fill(buffer.begin(), buffer.end(), 0);
|
||||||
|
|
||||||
|
/* play at least 250ms of silence */
|
||||||
|
for (snd_pcm_uframes_t remaining_frames = rate / 4;;) {
|
||||||
|
auto n = snd_pcm_writei(pcm, buffer.data(),
|
||||||
|
period_size);
|
||||||
|
if (n < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_writei() failed");
|
||||||
|
|
||||||
|
if (snd_pcm_uframes_t(n) >= remaining_frames)
|
||||||
|
break;
|
||||||
|
|
||||||
|
remaining_frames -= snd_pcm_uframes_t(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = snd_pcm_drain(pcm);
|
||||||
|
if (err < 0)
|
||||||
|
throw Alsa::MakeError(err, "snd_pcm_drain() failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
void
|
void
|
||||||
AlsaOutput::Open(AudioFormat &audio_format)
|
AlsaOutput::Open(AudioFormat &audio_format)
|
||||||
{
|
{
|
||||||
@ -709,6 +816,22 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
|||||||
snd_pcm_name(pcm),
|
snd_pcm_name(pcm),
|
||||||
snd_pcm_type_name(snd_pcm_type(pcm)));
|
snd_pcm_type_name(snd_pcm_type(pcm)));
|
||||||
|
|
||||||
|
#ifdef ENABLE_DSD
|
||||||
|
if (need_thesycon_dsd_workaround &&
|
||||||
|
audio_format.format == SampleFormat::DSD &&
|
||||||
|
audio_format.sample_rate <= 256 * 44100 / 8) {
|
||||||
|
LogDebug(alsa_output_domain, "Playing some 44.1 kHz silence");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Play_44_1_Silence(pcm);
|
||||||
|
} catch (...) {
|
||||||
|
LogError(std::current_exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
need_thesycon_dsd_workaround = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
PcmExport::Params params;
|
PcmExport::Params params;
|
||||||
params.alsa_channel_order = true;
|
params.alsa_channel_order = true;
|
||||||
|
|
||||||
@ -733,6 +856,11 @@ AlsaOutput::Open(AudioFormat &audio_format)
|
|||||||
use_dsd = audio_format.format == SampleFormat::DSD;
|
use_dsd = audio_format.format == SampleFormat::DSD;
|
||||||
in_stop_dsd_silence = false;
|
in_stop_dsd_silence = false;
|
||||||
|
|
||||||
|
if (thesycon_dsd_workaround &&
|
||||||
|
(!use_dsd ||
|
||||||
|
audio_format.sample_rate > 256 * 44100 / 8))
|
||||||
|
need_thesycon_dsd_workaround = true;
|
||||||
|
|
||||||
if (params.dsd_mode == PcmExport::DsdMode::DOP)
|
if (params.dsd_mode == PcmExport::DsdMode::DOP)
|
||||||
LogDebug(alsa_output_domain, "DoP (DSD over PCM) enabled");
|
LogDebug(alsa_output_domain, "DoP (DSD over PCM) enabled");
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user