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 ccafe3f3cf
in 0.21.3),
the silence generator didn't work reliably.
This commit is contained in:
parent
0c0a354753
commit
61a72a5d13
1
NEWS
1
NEWS
|
@ -6,6 +6,7 @@ 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, 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
|
||||||
|
|
|
@ -56,6 +56,15 @@ 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;
|
||||||
|
|
||||||
Manual<PcmExport> pcm_export;
|
Manual<PcmExport> pcm_export;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,6 +118,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
|
||||||
|
@ -348,6 +359,19 @@ private:
|
||||||
cond.signal();
|
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 */
|
/* 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;
|
||||||
|
@ -359,6 +383,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) ||
|
||||||
|
@ -515,6 +540,7 @@ 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 one period of data
|
/* generate silence if there's less than one period of data
|
||||||
in the ALSA-PCM buffer */
|
in the ALSA-PCM buffer */
|
||||||
|
@ -865,6 +891,7 @@ AlsaOutput::CancelInternal() noexcept
|
||||||
|
|
||||||
MultiSocketMonitor::Reset();
|
MultiSocketMonitor::Reset();
|
||||||
defer_invalidate_sockets.Cancel();
|
defer_invalidate_sockets.Cancel();
|
||||||
|
silence_timer.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -893,6 +920,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();
|
||||||
|
@ -1029,6 +1057,17 @@ try {
|
||||||
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;
|
||||||
|
|
Loading…
Reference in New Issue