output/alsa: add option "stop_dsd_silence" to work around DSD DAC noise

This commit is contained in:
Max Kellermann 2021-10-22 20:33:26 +02:00
parent ee270f9b00
commit c8121176b3
3 changed files with 54 additions and 0 deletions

2
NEWS
View File

@ -1,4 +1,6 @@
ver 0.23.3 (not yet released) ver 0.23.3 (not yet released)
* output
- alsa: add option "stop_dsd_silence" to work around DSD DAC noise
* macOS: fix libfmt related build failure * macOS: fix libfmt related build failure
ver 0.23.2 (2021/10/22) ver 0.23.2 (2021/10/22)

View File

@ -836,6 +836,11 @@ The `Advanced Linux Sound Architecture (ALSA) <http://www.alsa-project.org/>`_ p
- If set to no, then libasound will not attempt to convert between different sample formats (16 bit, 24 bit, floating point, ...). - If set to no, then libasound will not attempt to convert between different sample formats (16 bit, 24 bit, floating point, ...).
* - **dop yes|no** * - **dop yes|no**
- If set to yes, then DSD over PCM according to the `DoP standard <http://dsd-guide.com/dop-open-standard>`_ is enabled. This wraps DSD samples in fake 24 bit PCM, and is understood by some DSD capable products, but may be harmful to other hardware. Therefore, the default is no and you can enable the option at your own risk. - If set to yes, then DSD over PCM according to the `DoP standard <http://dsd-guide.com/dop-open-standard>`_ is enabled. This wraps DSD samples in fake 24 bit PCM, and is understood by some DSD capable products, but may be harmful to other hardware. Therefore, the default is no and you can enable the option at your own risk.
* - **stop_dsd_silence yes|no**
- If enabled, silence is played before manually stopping playback
("stop" or "pause") in DSD mode (native DSD or DoP). This is a
workaround for some DACs which emit noise when stopping DSD
playback.
* - **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.

View File

@ -84,6 +84,23 @@ class AlsaOutput final
* @see http://dsd-guide.com/dop-open-standard * @see http://dsd-guide.com/dop-open-standard
*/ */
bool dop_setting; bool dop_setting;
/**
* Are we currently playing DSD? (Native DSD or DoP)
*/
bool use_dsd;
/**
* Play some silence before closing the output in DSD mode?
* This is a workaround for some DACs which emit noise when
* stopping DSD playback.
*/
const bool stop_dsd_silence;
/**
* Are we currently draining with #stop_dsd_silence?
*/
bool in_stop_dsd_silence;
#endif #endif
/** libasound's buffer_time setting (in microseconds) */ /** libasound's buffer_time setting (in microseconds) */
@ -355,6 +372,9 @@ private:
error = std::current_exception(); error = std::current_exception();
active = false; active = false;
waiting = false; waiting = false;
#ifdef ENABLE_DSD
in_stop_dsd_silence = false;
#endif
cond.notify_one(); cond.notify_one();
} }
@ -411,6 +431,7 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
dop_setting(block.GetBlockValue("dop", false) || dop_setting(block.GetBlockValue("dop", false) ||
/* 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)),
#endif #endif
buffer_time(block.GetPositiveValue("buffer_time", buffer_time(block.GetPositiveValue("buffer_time",
MPD_ALSA_BUFFER_TIME_US)), MPD_ALSA_BUFFER_TIME_US)),
@ -711,6 +732,9 @@ AlsaOutput::Open(AudioFormat &audio_format)
snd_pcm_nonblock(pcm, 1); snd_pcm_nonblock(pcm, 1);
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
use_dsd = audio_format.format == SampleFormat::DSD;
in_stop_dsd_silence = false;
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
@ -844,6 +868,18 @@ AlsaOutput::WriteFromPeriodBuffer() noexcept
inline bool inline bool
AlsaOutput::DrainInternal() AlsaOutput::DrainInternal()
{ {
#ifdef ENABLE_DSD
if (in_stop_dsd_silence) {
/* "stop_dsd_silence" is in progress: clear internal
buffers and instead, fill the period buffer with
silence */
in_stop_dsd_silence = false;
ring_buffer->reset();
period_buffer.Clear();
period_buffer.FillWithSilence(silence, out_frame_size);
}
#endif
/* drain ring_buffer */ /* drain ring_buffer */
CopyRingToPeriodBuffer(); CopyRingToPeriodBuffer();
@ -974,6 +1010,17 @@ AlsaOutput::Cancel() noexcept
return; return;
} }
#ifdef ENABLE_DSD
if (stop_dsd_silence && use_dsd) {
/* play some DSD silence instead of snd_pcm_drop() */
std::unique_lock<Mutex> lock(mutex);
in_stop_dsd_silence = true;
drain = true;
cond.wait(lock, [this]{ return !drain || !active; });
return;
}
#endif
BlockingCall(GetEventLoop(), [this](){ BlockingCall(GetEventLoop(), [this](){
CancelInternal(); CancelInternal();
}); });