summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2019-06-28 14:51:27 +0200
committerMax Kellermann <max@musicpd.org>2019-06-28 18:04:49 +0200
commit61a72a5d131e89f069d2d2de9411de8fe4c21f23 (patch)
treec8892c7a82517ecec07535ed0dfe2c5f48023fff
parent0c0a35475313c6bb96c7cdf6059d245746f3843d (diff)
output/alsa: schedule a timer to generate silence
Without this timer, DispatchSockets() may disable the MultiSocketMonitor and if Play() doesn't get called soon, it never gets a chance to generate silence. However if Play() gets called, generating silence isn't necessary anymore... Resulting from this misdesign (added by commit ccafe3f3cf3 in 0.21.3), the silence generator didn't work reliably.
-rw-r--r--NEWS1
-rw-r--r--src/output/plugins/AlsaOutputPlugin.cxx39
2 files changed, 40 insertions, 0 deletions
diff --git a/NEWS b/NEWS
index 542cd5d04..354491ea5 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,7 @@ ver 0.21.11 (not yet released)
* output
- alsa: fix busy loop while draining
- alsa: fix missing drain call
+ - alsa: improve xrun-avoiding silence generator
- alsa, osx: fix distortions with DSD_U32 and DoP on 32 bit CPUs
* protocol
- fix "list" with multiple "group" levels
diff --git a/src/output/plugins/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx
index 96daac192..7e1919e43 100644
--- a/src/output/plugins/AlsaOutputPlugin.cxx
+++ b/src/output/plugins/AlsaOutputPlugin.cxx
@@ -56,6 +56,15 @@ class AlsaOutput final
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;
+
Manual<PcmExport> pcm_export;
/**
@@ -109,6 +118,8 @@ class AlsaOutput final
*/
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
* is available in the #ring_buffer, we need to play some
@@ -348,6 +359,19 @@ private:
cond.signal();
}
+ /**
+ * 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 */
std::chrono::steady_clock::duration PrepareSockets() noexcept override;
void DispatchSockets() noexcept override;
@@ -359,6 +383,7 @@ AlsaOutput::AlsaOutput(EventLoop &_loop, const ConfigBlock &block)
:AudioOutput(FLAG_ENABLE_DISABLE),
MultiSocketMonitor(_loop),
defer_invalidate_sockets(_loop, BIND_THIS_METHOD(InvalidateSockets)),
+ silence_timer(_loop, BIND_THIS_METHOD(OnSilenceTimer)),
device(block.GetBlockValue("device", "")),
#ifdef ENABLE_DSD
dop_setting(block.GetBlockValue("dop", false) ||
@@ -515,6 +540,7 @@ AlsaOutput::Setup(AudioFormat &audio_format,
alsa_period_size = 1;
period_frames = alsa_period_size;
+ effective_period_duration = audio_format.FramesToTime<decltype(effective_period_duration)>(period_frames);
/* generate silence if there's less than one period of data
in the ALSA-PCM buffer */
@@ -865,6 +891,7 @@ AlsaOutput::CancelInternal() noexcept
MultiSocketMonitor::Reset();
defer_invalidate_sockets.Cancel();
+ silence_timer.Cancel();
}
void
@@ -893,6 +920,7 @@ AlsaOutput::Close() noexcept
BlockingCall(GetEventLoop(), [this](){
MultiSocketMonitor::Reset();
defer_invalidate_sockets.Cancel();
+ silence_timer.Cancel();
});
period_buffer.Free();
@@ -1029,6 +1057,17 @@ try {
if (!CopyRingToPeriodBuffer()) {
MultiSocketMonitor::Reset();
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;