diff options
author | Max Kellermann <max@musicpd.org> | 2019-06-28 14:51:27 +0200 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2019-06-28 18:04:49 +0200 |
commit | 61a72a5d131e89f069d2d2de9411de8fe4c21f23 (patch) | |
tree | c8892c7a82517ecec07535ed0dfe2c5f48023fff | |
parent | 0c0a35475313c6bb96c7cdf6059d245746f3843d (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-- | NEWS | 1 | ||||
-rw-r--r-- | src/output/plugins/AlsaOutputPlugin.cxx | 39 |
2 files changed, 40 insertions, 0 deletions
@@ -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; |