mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-23 15:41:31 +00:00
[client] audio: fix underrun clock recovery
Recover stable local playback in the device clock domain and restart rate control from the measured feed-forward clock ratio. Ignore catch-up packet sizing until the normal source cadence returns.
This commit is contained in:
@@ -173,7 +173,9 @@ typedef struct
|
||||
double sourcePhaseBaselineSec;
|
||||
double sourcePhaseReserveSec;
|
||||
double sourcePacketDurationSec;
|
||||
unsigned int sourcePacketPacedCount;
|
||||
bool sourcePhaseBaselineValid;
|
||||
bool sourcePacketRecovering;
|
||||
bool mediaClockValid;
|
||||
bool mediaPositionValid;
|
||||
bool mediaClockFromSource;
|
||||
@@ -1438,7 +1440,9 @@ static bool playbackStart(const LG_AudioFormat * format,
|
||||
audio.playback.sourceData.sourcePhaseBaselineSec = 0.0;
|
||||
audio.playback.sourceData.sourcePhaseReserveSec = 0.0;
|
||||
audio.playback.sourceData.sourcePacketDurationSec = 0.0;
|
||||
audio.playback.sourceData.sourcePacketPacedCount = 0;
|
||||
audio.playback.sourceData.sourcePhaseBaselineValid = false;
|
||||
audio.playback.sourceData.sourcePacketRecovering = false;
|
||||
audio.playback.sourceData.deviceClock.valid = false;
|
||||
audio.playback.sourceData.outputClock.valid = false;
|
||||
playbackPrepareMediaClock(&audio.playback.sourceData, sourceClock);
|
||||
@@ -2130,6 +2134,26 @@ static int playbackSlewBuffer(
|
||||
return advanced;
|
||||
}
|
||||
|
||||
static bool playbackUseLowWaterRecovery(bool providerRateControl,
|
||||
bool bufferUnderrun, const PlaybackSourceData * sourceData)
|
||||
{
|
||||
return providerRateControl ||
|
||||
(bufferUnderrun && (!sourceData->deviceClock.valid ||
|
||||
!sourceData->deviceClockStable));
|
||||
}
|
||||
|
||||
static void playbackResetRateControl(PlaybackSourceData * sourceData,
|
||||
bool providerRateControl)
|
||||
{
|
||||
sourceData->offsetError = 0.0;
|
||||
sourceData->offsetErrorIntegral = 0.0;
|
||||
sourceData->ratioIntegral = 0.0;
|
||||
sourceData->lastRatio = providerRateControl ?
|
||||
1.0 : sourceData->lastClockRatio;
|
||||
if (providerRateControl)
|
||||
sourceData->nextFeedbackTime = 0;
|
||||
}
|
||||
|
||||
static PlaybackDataResult playbackData(const void * data, size_t frameCount,
|
||||
const LG_AudioClock * sourceClock, int64_t arrivalTime)
|
||||
{
|
||||
@@ -2201,6 +2225,7 @@ static PlaybackDataResult playbackData(const void * data, size_t frameCount,
|
||||
ringbuffer_getCount(audio.playback.buffer) < 0;
|
||||
bool discontinuity =
|
||||
bufferUnderrun || (sourceClock && sourceClock->discontinuity);
|
||||
const int64_t previousArrivalTime = sourceData->lastArrivalTime;
|
||||
const int64_t packetTime =
|
||||
playbackMapMediaTime(sourceData, sourceClock, frames,
|
||||
audio.playback.sampleRate, arrivalTime, &discontinuity);
|
||||
@@ -2251,9 +2276,46 @@ static PlaybackDataResult playbackData(const void * data, size_t frameCount,
|
||||
sourceData->sourceClock.phaseResidualSec;
|
||||
const double packetSec =
|
||||
frames * nominalFrameSec;
|
||||
const double decayedPacketDurationSec =
|
||||
sourceData->sourcePacketDurationSec *
|
||||
exp(-packetSec / PLAYBACK_PHASE_RESERVE_DECAY_SEC);
|
||||
const bool cadenceReset = discontinuity ||
|
||||
state == STREAM_STATE_KEEP_ALIVE ||
|
||||
state == STREAM_STATE_RESUMING;
|
||||
if (cadenceReset && sourceData->sourcePacketDurationSec > 0.0)
|
||||
{
|
||||
sourceData->sourcePacketRecovering = true;
|
||||
sourceData->sourcePacketPacedCount = 0;
|
||||
}
|
||||
else if (sourceData->sourcePacketRecovering)
|
||||
{
|
||||
if (packetSec <= decayedPacketDurationSec + nominalFrameSec)
|
||||
{
|
||||
sourceData->sourcePacketRecovering = false;
|
||||
sourceData->sourcePacketPacedCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool paced = previousArrivalTime != INT64_MIN &&
|
||||
arrivalTime > previousArrivalTime &&
|
||||
(arrivalTime - previousArrivalTime) * 2.0e-9 >= packetSec;
|
||||
sourceData->sourcePacketPacedCount = paced ?
|
||||
sourceData->sourcePacketPacedCount + 1 : 0;
|
||||
if (sourceData->sourcePacketPacedCount >= 2)
|
||||
{
|
||||
sourceData->sourcePacketRecovering = false;
|
||||
sourceData->sourcePacketPacedCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Catch-up packets describe audio accumulated during a gap, not the future
|
||||
* delivery cadence. Resume when the prior cadence returns or a larger
|
||||
* cadence is confirmed by consecutive plausibly paced packets. */
|
||||
sourceData->sourcePacketDurationSec =
|
||||
max(packetSec, sourceData->sourcePacketDurationSec *
|
||||
exp(-packetSec / PLAYBACK_PHASE_RESERVE_DECAY_SEC));
|
||||
!sourceData->sourcePacketRecovering ?
|
||||
max(packetSec, decayedPacketDurationSec) :
|
||||
decayedPacketDurationSec;
|
||||
|
||||
if (!sourceData->sourcePhaseBaselineValid ||
|
||||
sourceData->sourceClock.updates == 1)
|
||||
@@ -2396,7 +2458,8 @@ static PlaybackDataResult playbackData(const void * data, size_t frameCount,
|
||||
|
||||
double devPosition = DBL_MIN;
|
||||
state = playbackGetState();
|
||||
if ((providerRateControl || bufferUnderrun) &&
|
||||
if (playbackUseLowWaterRecovery(providerRateControl,
|
||||
bufferUnderrun, sourceData) &&
|
||||
(discontinuity ||
|
||||
state == STREAM_STATE_KEEP_ALIVE ||
|
||||
state == STREAM_STATE_RESUMING))
|
||||
@@ -2409,14 +2472,7 @@ static PlaybackDataResult playbackData(const void * data, size_t frameCount,
|
||||
sourceData->outputPosition += actualSlew;
|
||||
curPosition += actualSlew;
|
||||
|
||||
sourceData->offsetError = 0.0;
|
||||
sourceData->offsetErrorIntegral = 0.0;
|
||||
sourceData->ratioIntegral = 0.0;
|
||||
if (providerRateControl && bufferUnderrun)
|
||||
{
|
||||
sourceData->lastRatio = 1.0;
|
||||
sourceData->nextFeedbackTime = 0;
|
||||
}
|
||||
playbackResetRateControl(sourceData, providerRateControl);
|
||||
if (state == STREAM_STATE_KEEP_ALIVE ||
|
||||
state == STREAM_STATE_RESUMING)
|
||||
atomic_compare_exchange_strong_explicit(
|
||||
@@ -2437,9 +2493,7 @@ static PlaybackDataResult playbackData(const void * data, size_t frameCount,
|
||||
sourceData->outputPosition += actualSlew;
|
||||
curPosition += actualSlew;
|
||||
|
||||
sourceData->offsetError = 0.0;
|
||||
sourceData->offsetErrorIntegral = 0.0;
|
||||
sourceData->ratioIntegral = 0.0;
|
||||
playbackResetRateControl(sourceData, providerRateControl);
|
||||
if (state == STREAM_STATE_KEEP_ALIVE ||
|
||||
state == STREAM_STATE_RESUMING)
|
||||
atomic_compare_exchange_strong_explicit(
|
||||
|
||||
@@ -820,6 +820,7 @@ if(ENABLE_AUDIO)
|
||||
provider
|
||||
playback-retry
|
||||
jitter
|
||||
recovery
|
||||
consent
|
||||
quiesce
|
||||
)
|
||||
|
||||
@@ -654,6 +654,54 @@ static void testPlaybackJitter(void)
|
||||
CHECK(source.arrivalJitterSec < 0.011);
|
||||
}
|
||||
|
||||
static void testPlaybackRecovery(void)
|
||||
{
|
||||
reset();
|
||||
startAudio();
|
||||
|
||||
const LG_AudioFormat format = makeFormat(LG_AUDIO_FMT_S16_LE);
|
||||
CHECK(playbackStart(&format, NULL, false, false));
|
||||
CHECK(audio.playback.rateControl == PLAYBACK_RATE_BACKEND);
|
||||
playbackSetState(STREAM_STATE_RUN);
|
||||
|
||||
PlaybackSourceData * source = &audio.playback.sourceData;
|
||||
const int64_t arrival = nanotime();
|
||||
playbackClockReset(&source->deviceClock,
|
||||
arrival, 0.0, 1.0 / format.sampleRate);
|
||||
source->deviceClockStable = true;
|
||||
source->outputPosition = 0;
|
||||
source->lastRatio = 0.999;
|
||||
source->lastClockRatio = 1.0002;
|
||||
source->sourcePacketDurationSec = 0.010;
|
||||
playbackPublishDeviceTiming(16, arrival, 0, 0.0, 0);
|
||||
|
||||
CHECK(ringbuffer_consume(audio.playback.buffer, NULL, 16) == 16);
|
||||
CHECK(ringbuffer_getCount(audio.playback.buffer) == -16);
|
||||
uint8_t frames[1200 * 2 * 2] = { 0 };
|
||||
CHECK(playbackData(frames, 1200, NULL, arrival) ==
|
||||
PLAYBACK_DATA_PROCESSED);
|
||||
|
||||
CHECK(source->sourcePacketDurationSec < 0.010);
|
||||
CHECK(source->sourcePacketDurationSec > 0.009);
|
||||
CHECK(source->sourcePacketRecovering);
|
||||
CHECK(source->outputPosition == 1770);
|
||||
CHECK(ringbuffer_getCount(audio.playback.buffer) == 1754);
|
||||
CHECK(fabs(atomic_load(&audio.playback.backendResampleRatio) -
|
||||
source->lastClockRatio) < 0.000001);
|
||||
|
||||
CHECK(playbackData(frames, 1200, NULL, arrival + 1000) ==
|
||||
PLAYBACK_DATA_PROCESSED);
|
||||
CHECK(source->sourcePacketDurationSec < 0.010);
|
||||
CHECK(source->sourcePacketRecovering);
|
||||
|
||||
CHECK(playbackData(frames, 480, NULL,
|
||||
arrival + INT64_C(10000000)) == PLAYBACK_DATA_PROCESSED);
|
||||
CHECK(!source->sourcePacketRecovering);
|
||||
CHECK(fabs(source->sourcePacketDurationSec - 0.010) < 0.000001);
|
||||
|
||||
stopAudio();
|
||||
}
|
||||
|
||||
static void testConsent(void)
|
||||
{
|
||||
reset();
|
||||
@@ -781,6 +829,7 @@ static const struct Test tests[] =
|
||||
{ "provider" , testProvider },
|
||||
{ "playback-retry", testPlaybackRetry },
|
||||
{ "jitter" , testPlaybackJitter },
|
||||
{ "recovery" , testPlaybackRecovery },
|
||||
{ "consent" , testConsent },
|
||||
{ "quiesce" , testQuiesce },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user