mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-23 15:41:31 +00:00
[client] audio: recover physical underrun debt
Some checks are pending
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / module (push) Waiting to run
build / host-linux (push) Waiting to run
build / host-windows-cross (push) Waiting to run
build / host-windows-native (push) Waiting to run
build / idd (push) Waiting to run
build / obs (clang) (push) Waiting to run
build / obs (gcc) (push) Waiting to run
build / docs (push) Waiting to run
Some checks are pending
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Waiting to run
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Waiting to run
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Waiting to run
build / module (push) Waiting to run
build / host-linux (push) Waiting to run
build / host-windows-cross (push) Waiting to run
build / host-windows-native (push) Waiting to run
build / idd (push) Waiting to run
build / obs (clang) (push) Waiting to run
build / obs (gcc) (push) Waiting to run
build / docs (push) Waiting to run
A stable clock model can request a negative recovery slew while the unbounded ring is physically underrun. This leaves the reader ahead of the writer indefinitely and outputs silence until playback is restarted. Floor active recovery at the physical low-water requirement. Rebase the device-clock phase by any extra refill so modeled latency remains at the target without restarting the underrun correction cycle. Add a deterministic regression for negative physical debt opposing the stable clock model.
This commit is contained in:
@@ -2609,10 +2609,35 @@ static PlaybackDataResult playbackData(const void * data, size_t frameCount,
|
||||
sourceData->deviceClockStable)
|
||||
{
|
||||
devPosition = computeDevicePosition(curTime);
|
||||
const double slew = devPosition + targetBufferFrames - curPosition;
|
||||
const bool activeUnderrun =
|
||||
bufferUnderrun && state == STREAM_STATE_RUN;
|
||||
const double clockSlew =
|
||||
devPosition + targetBufferFrames - curPosition;
|
||||
double slew = clockSlew;
|
||||
if (activeUnderrun)
|
||||
{
|
||||
const int occupancy = ringbuffer_getCount(audio.playback.buffer);
|
||||
const double physicalSlew =
|
||||
ceil(targetLowWaterFrames - occupancy);
|
||||
if (physicalSlew > slew)
|
||||
slew = physicalSlew;
|
||||
}
|
||||
|
||||
const int slewFrames = clamp(llrint(slew), (int64_t)INT_MIN,
|
||||
(int64_t)INT_MAX);
|
||||
const int actualSlew = playbackSlewBuffer(sourceData, slewFrames);
|
||||
|
||||
/* A negative unbounded ring must be restored in the physical domain even
|
||||
* when the fitted clock asks for a smaller correction. Move the clock's
|
||||
* phase translation by the excess so the forced refill neither appears as
|
||||
* additional latency nor restarts the underrun/slew cycle. */
|
||||
const double correction = actualSlew - clockSlew;
|
||||
if (activeUnderrun && correction > 0.0)
|
||||
{
|
||||
sourceData->devicePositionOffsetFrames += correction;
|
||||
devPosition += correction;
|
||||
}
|
||||
|
||||
sourceData->outputPosition += actualSlew;
|
||||
curPosition += actualSlew;
|
||||
|
||||
|
||||
@@ -821,6 +821,7 @@ if(ENABLE_AUDIO)
|
||||
playback-retry
|
||||
jitter
|
||||
recovery
|
||||
stable-underrun
|
||||
resume
|
||||
consent
|
||||
quiesce
|
||||
|
||||
@@ -702,6 +702,78 @@ static void testPlaybackRecovery(void)
|
||||
stopAudio();
|
||||
}
|
||||
|
||||
static void testPlaybackStableUnderrun(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->devicePositionOffsetFrames = 0.0;
|
||||
source->outputPosition = 1000;
|
||||
source->lastRatio = 0.999;
|
||||
source->lastClockRatio = 1.0002;
|
||||
source->sourcePacketDurationSec = 0.010;
|
||||
playbackPublishDeviceTiming(16, arrival, 0, 0.0, 0);
|
||||
|
||||
CHECK(source->outputPosition - computeDevicePosition(arrival) > 900.0);
|
||||
CHECK(ringbuffer_append(audio.playback.buffer, NULL, 1000) == 1000);
|
||||
CHECK(ringbuffer_consume(audio.playback.buffer, NULL, 1128) == 1128);
|
||||
CHECK(ringbuffer_getCount(audio.playback.buffer) == -128);
|
||||
uint8_t frames[64 * 2 * 2];
|
||||
uint8_t output[64 * 2 * 2];
|
||||
memset(frames, 0x5a, sizeof(frames));
|
||||
CHECK(playbackData(frames, 64, NULL, arrival) ==
|
||||
PLAYBACK_DATA_PROCESSED);
|
||||
|
||||
const int recoveredOccupancy =
|
||||
ringbuffer_getCount(audio.playback.buffer) - 64;
|
||||
const double recoveredPosition = source->outputPosition - 64;
|
||||
const double recoveredLatency = recoveredPosition -
|
||||
computeDevicePosition(source->sourceClock.time);
|
||||
const double targetLowWater =
|
||||
audio.playback.deviceMaxPeriodFrames * 1.1 +
|
||||
0.001 * format.sampleRate;
|
||||
const double sourceReserve =
|
||||
max(source->sourcePacketDurationSec * 0.5,
|
||||
source->sourcePhaseReserveSec) * format.sampleRate;
|
||||
const double targetLatency = targetLowWater + sourceReserve;
|
||||
CHECK(recoveredOccupancy >= (int)ceil(targetLowWater));
|
||||
CHECK(fabs(recoveredLatency - targetLatency) < 0.000001);
|
||||
CHECK(!source->bufferOverrunPending);
|
||||
CHECK(source->deviceClockStable);
|
||||
CHECK(source->devicePositionOffsetFrames > 0.0);
|
||||
CHECK(fabs(source->deviceClock.frameSec -
|
||||
1.0 / format.sampleRate) < 0.000000001);
|
||||
CHECK(fabs(atomic_load(&audio.playback.backendResampleRatio) -
|
||||
source->lastClockRatio) < 0.000001);
|
||||
|
||||
CHECK(ringbuffer_consume(audio.playback.buffer, output, 64) == 64);
|
||||
CHECK(ringbuffer_getCount(audio.playback.buffer) == recoveredOccupancy);
|
||||
|
||||
const double recoveredOffset =
|
||||
source->devicePositionOffsetFrames;
|
||||
const int64_t secondPacketArrival = arrival +
|
||||
llrint(64.0e9 / format.sampleRate);
|
||||
CHECK(playbackData(frames, 64, NULL,
|
||||
secondPacketArrival) == PLAYBACK_DATA_PROCESSED);
|
||||
CHECK(source->devicePositionOffsetFrames == recoveredOffset);
|
||||
CHECK(ringbuffer_getCount(audio.playback.buffer) ==
|
||||
recoveredOccupancy + 64);
|
||||
CHECK(ringbuffer_consume(audio.playback.buffer, output, 64) == 64);
|
||||
CHECK(ringbuffer_getCount(audio.playback.buffer) == recoveredOccupancy);
|
||||
|
||||
stopAudio();
|
||||
}
|
||||
|
||||
static void testPlaybackResume(void)
|
||||
{
|
||||
reset();
|
||||
@@ -948,6 +1020,7 @@ static const struct Test tests[] =
|
||||
{ "playback-retry" , testPlaybackRetry },
|
||||
{ "jitter" , testPlaybackJitter },
|
||||
{ "recovery" , testPlaybackRecovery },
|
||||
{ "stable-underrun", testPlaybackStableUnderrun },
|
||||
{ "resume" , testPlaybackResume },
|
||||
{ "consent" , testConsent },
|
||||
{ "quiesce" , testQuiesce },
|
||||
|
||||
Reference in New Issue
Block a user