Merge tag 'v0.21.11'
release v0.21.11
This commit is contained in:
4
NEWS
4
NEWS
@@ -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)
|
|||||||
* 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 @@
|
|||||||
#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
|
|||||||
|
|
||||||
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
|
|||||||
*/
|
*/
|
||||||
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,13 +142,20 @@ 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
|
||||||
@@ -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:
|
|||||||
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:
|
|||||||
* 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:
|
|||||||
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)
|
|||||||
: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,
|
|||||||
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)
|
|||||||
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
|
|||||||
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
|
|||||||
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)
|
|||||||
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 {
|
|||||||
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);
|
||||||
|
Reference in New Issue
Block a user