Commit 55709864 authored by Max Kellermann's avatar Max Kellermann

Merge tag 'v0.21.11'

release v0.21.11
parents 44aaf513 f6125f0c
...@@ -12,7 +12,7 @@ ver 0.22 (not yet released) ...@@ -12,7 +12,7 @@ ver 0.22 (not yet released)
- ffmpeg: new plugin based on FFmpeg's libavfilter library - ffmpeg: new plugin based on FFmpeg's libavfilter library
- hdcd: new plugin based on FFmpeg's "af_hdcd" for HDCD playback - hdcd: new plugin based on FFmpeg's "af_hdcd" for HDCD playback
ver 0.21.11 (not yet released) ver 0.21.11 (2019/07/03)
* input * input
- tidal: deprecated because Tidal has changed the protocol - tidal: deprecated because Tidal has changed the protocol
* decoder * decoder
...@@ -20,6 +20,8 @@ ver 0.21.11 (not yet released) ...@@ -20,6 +20,8 @@ ver 0.21.11 (not yet released)
* output * output
- alsa: fix busy loop while draining - alsa: fix busy loop while draining
- alsa: fix missing drain call - alsa: fix missing drain call
- alsa: improve xrun-avoiding silence generator
- alsa: log when generating silence due to slow decoder
- alsa, osx: fix distortions with DSD_U32 and DoP on 32 bit CPUs - alsa, osx: fix distortions with DSD_U32 and DoP on 32 bit CPUs
* protocol * protocol
- fix "list" with multiple "group" levels - fix "list" with multiple "group" levels
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "../OutputAPI.hxx" #include "../OutputAPI.hxx"
#include "mixer/MixerList.hxx" #include "mixer/MixerList.hxx"
#include "pcm/Export.hxx" #include "pcm/Export.hxx"
#include "system/PeriodClock.hxx"
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
#include "thread/Cond.hxx" #include "thread/Cond.hxx"
#include "util/Manual.hxx" #include "util/Manual.hxx"
...@@ -56,6 +57,17 @@ class AlsaOutput final ...@@ -56,6 +57,17 @@ class AlsaOutput final
DeferEvent defer_invalidate_sockets; DeferEvent defer_invalidate_sockets;
/**
* This timer is used to re-schedule the #MultiSocketMonitor
* after it had been disabled to wait for the next Play() call
* to deliver more data. This timer is necessary to start
* generating silence if Play() doesn't get called soon enough
* to avoid the xrun.
*/
TimerEvent silence_timer;
PeriodClock throttle_silence_log;
Manual<PcmExport> pcm_export; Manual<PcmExport> pcm_export;
/** /**
...@@ -109,6 +121,8 @@ class AlsaOutput final ...@@ -109,6 +121,8 @@ class AlsaOutput final
*/ */
snd_pcm_uframes_t period_frames; snd_pcm_uframes_t period_frames;
std::chrono::steady_clock::duration effective_period_duration;
/** /**
* If snd_pcm_avail() goes above this value and no more data * If snd_pcm_avail() goes above this value and no more data
* is available in the #ring_buffer, we need to play some * is available in the #ring_buffer, we need to play some
...@@ -128,14 +142,21 @@ class AlsaOutput final ...@@ -128,14 +142,21 @@ class AlsaOutput final
bool work_around_drain_bug; bool work_around_drain_bug;
/** /**
* After Open(), has this output been activated by a Play() * After Open() or Cancel(), has this output been activated by
* command? * a Play() command?
* *
* Protected by #mutex. * Protected by #mutex.
*/ */
bool active; bool active;
/** /**
* Is this output waiting for more data?
*
* Protected by #mutex.
*/
bool waiting;
/**
* Do we need to call snd_pcm_prepare() before the next write? * Do we need to call snd_pcm_prepare() before the next write?
* It means that we put the device to SND_PCM_STATE_SETUP by * It means that we put the device to SND_PCM_STATE_SETUP by
* calling snd_pcm_drop(). * calling snd_pcm_drop().
...@@ -176,7 +197,7 @@ class AlsaOutput final ...@@ -176,7 +197,7 @@ class AlsaOutput final
Alsa::PeriodBuffer period_buffer; Alsa::PeriodBuffer period_buffer;
/** /**
* Protects #cond, #error, #active, #drain. * Protects #cond, #error, #active, #waiting, #drain.
*/ */
mutable Mutex mutex; mutable Mutex mutex;
...@@ -248,6 +269,12 @@ private: ...@@ -248,6 +269,12 @@ private:
return active; return active;
} }
gcc_pure
bool LockIsActiveAndNotWaiting() const noexcept {
const std::lock_guard<Mutex> lock(mutex);
return active && !waiting;
}
/** /**
* Activate the output by registering the sockets in the * Activate the output by registering the sockets in the
* #EventLoop. Before calling this, filling the ring buffer * #EventLoop. Before calling this, filling the ring buffer
...@@ -260,10 +287,11 @@ private: ...@@ -260,10 +287,11 @@ private:
* was never unlocked * was never unlocked
*/ */
bool Activate() noexcept { bool Activate() noexcept {
if (active) if (active && !waiting)
return false; return false;
active = true; active = true;
waiting = false;
const ScopeUnlock unlock(mutex); const ScopeUnlock unlock(mutex);
defer_invalidate_sockets.Schedule(); defer_invalidate_sockets.Schedule();
...@@ -331,9 +359,23 @@ private: ...@@ -331,9 +359,23 @@ private:
const std::lock_guard<Mutex> lock(mutex); const std::lock_guard<Mutex> lock(mutex);
error = std::current_exception(); error = std::current_exception();
active = false; active = false;
waiting = false;
cond.notify_one(); cond.notify_one();
} }
/**
* Callback for @silence_timer
*/
void OnSilenceTimer() noexcept {
{
const std::lock_guard<Mutex> lock(mutex);
assert(active);
waiting = false;
}
MultiSocketMonitor::InvalidateSockets();
}
/* virtual methods from class MultiSocketMonitor */ /* virtual methods from class MultiSocketMonitor */
std::chrono::steady_clock::duration PrepareSockets() noexcept override; std::chrono::steady_clock::duration PrepareSockets() noexcept override;
void DispatchSockets() noexcept override; void DispatchSockets() noexcept override;
...@@ -345,6 +387,7 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block) ...@@ -345,6 +387,7 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
:AudioOutput(FLAG_ENABLE_DISABLE), :AudioOutput(FLAG_ENABLE_DISABLE),
MultiSocketMonitor(_loop), MultiSocketMonitor(_loop),
defer_invalidate_sockets(_loop, BIND_THIS_METHOD(InvalidateSockets)), defer_invalidate_sockets(_loop, BIND_THIS_METHOD(InvalidateSockets)),
silence_timer(_loop, BIND_THIS_METHOD(OnSilenceTimer)),
device(block.GetBlockValue("device", "")), device(block.GetBlockValue("device", "")),
#ifdef ENABLE_DSD #ifdef ENABLE_DSD
dop_setting(block.GetBlockValue("dop", false) || dop_setting(block.GetBlockValue("dop", false) ||
...@@ -501,8 +544,9 @@ AlsaOutput::Setup(AudioFormat &audio_format, ...@@ -501,8 +544,9 @@ AlsaOutput::Setup(AudioFormat &audio_format,
alsa_period_size = 1; alsa_period_size = 1;
period_frames = alsa_period_size; period_frames = alsa_period_size;
effective_period_duration = audio_format.FramesToTime<decltype(effective_period_duration)>(period_frames);
/* generate silence if there's less than once period of data /* generate silence if there's less than one period of data
in the ALSA-PCM buffer */ in the ALSA-PCM buffer */
max_avail_frames = hw_result.buffer_size - hw_result.period_size; max_avail_frames = hw_result.buffer_size - hw_result.period_size;
...@@ -685,6 +729,7 @@ AlsaOutput::Open(AudioFormat &audio_format) ...@@ -685,6 +729,7 @@ AlsaOutput::Open(AudioFormat &audio_format)
period_buffer.Allocate(period_frames, out_frame_size); period_buffer.Allocate(period_frames, out_frame_size);
active = false; active = false;
waiting = false;
must_prepare = false; must_prepare = false;
written = false; written = false;
error = {}; error = {};
...@@ -848,9 +893,11 @@ AlsaOutput::CancelInternal() noexcept ...@@ -848,9 +893,11 @@ AlsaOutput::CancelInternal() noexcept
ring_buffer->reset(); ring_buffer->reset();
active = false; active = false;
waiting = false;
MultiSocketMonitor::Reset(); MultiSocketMonitor::Reset();
defer_invalidate_sockets.Cancel(); defer_invalidate_sockets.Cancel();
silence_timer.Cancel();
} }
void void
...@@ -879,6 +926,7 @@ AlsaOutput::Close() noexcept ...@@ -879,6 +926,7 @@ AlsaOutput::Close() noexcept
BlockingCall(GetEventLoop(), [this](){ BlockingCall(GetEventLoop(), [this](){
MultiSocketMonitor::Reset(); MultiSocketMonitor::Reset();
defer_invalidate_sockets.Cancel(); defer_invalidate_sockets.Cancel();
silence_timer.Cancel();
}); });
period_buffer.Free(); period_buffer.Free();
...@@ -927,7 +975,7 @@ AlsaOutput::Play(const void *chunk, size_t size) ...@@ -927,7 +975,7 @@ AlsaOutput::Play(const void *chunk, size_t size)
std::chrono::steady_clock::duration std::chrono::steady_clock::duration
AlsaOutput::PrepareSockets() noexcept AlsaOutput::PrepareSockets() noexcept
{ {
if (!LockIsActive()) { if (!LockIsActiveAndNotWaiting()) {
ClearSocketList(); ClearSocketList();
return std::chrono::steady_clock::duration(-1); return std::chrono::steady_clock::duration(-1);
} }
...@@ -992,28 +1040,42 @@ try { ...@@ -992,28 +1040,42 @@ try {
whenever more data arrives */ whenever more data arrives */
/* the same applies when there is still enough /* the same applies when there is still enough
data in the ALSA-PCM buffer (determined by data in the ALSA-PCM buffer (determined by
snd_pcm_avail()); this can happend at the snd_pcm_avail()); this can happen at the
start of playback, when our ring_buffer is start of playback, when our ring_buffer is
smaller than the ALSA-PCM buffer */ smaller than the ALSA-PCM buffer */
{ {
const std::lock_guard<Mutex> lock(mutex); const std::lock_guard<Mutex> lock(mutex);
active = false; waiting = true;
cond.notify_one(); cond.notify_one();
} }
/* avoid race condition: see if data has /* avoid race condition: see if data has
arrived meanwhile before disabling the arrived meanwhile before disabling the
event (but after clearing the "active" event (but after setting the "waiting"
flag) */ flag) */
if (!CopyRingToPeriodBuffer()) { if (!CopyRingToPeriodBuffer()) {
MultiSocketMonitor::Reset(); MultiSocketMonitor::Reset();
defer_invalidate_sockets.Cancel(); defer_invalidate_sockets.Cancel();
/* just in case Play() doesn't get
called soon enough, schedule a
timer which generates silence
before the xrun occurs */
/* the timer fires in half of a
period; this short duration may
produce a few more wakeups than
necessary, but should be small
enough to avoid the xrun */
silence_timer.Schedule(effective_period_duration / 2);
} }
return; return;
} }
if (throttle_silence_log.CheckUpdate(std::chrono::seconds(5)))
FormatWarning(alsa_output_domain, "Decoder is too slow; playing silence to avoid xrun");
/* insert some silence if the buffer has not enough /* insert some silence if the buffer has not enough
data yet, to avoid ALSA xrun */ data yet, to avoid ALSA xrun */
period_buffer.FillWithSilence(silence, out_frame_size); period_buffer.FillWithSilence(silence, out_frame_size);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment