[client] audio: add latency tuning parameter

This adds a new `audio:bufferLatency` option which allows the user to
adjust the amount of buffering LG does over the absolute bare minimum. By
default, this is set large enough to absorb typical timing jitter from
Spice. Users may reduce this if they care more about latency than audio
quality.
This commit is contained in:
Chris Spencer 2022-02-22 09:21:36 +00:00 committed by Geoffrey McRae
parent 9908b737b0
commit 7c2d493bb5
3 changed files with 28 additions and 9 deletions

View File

@ -517,16 +517,26 @@ void audio_playbackData(uint8_t * data, size_t size)
spiceData->devNextPosition = deviceTick.nextPosition; spiceData->devNextPosition = deviceTick.nextPosition;
} }
// Determine the target latency. Ideally, this would be precisely equal to // Determine the target latency. This is made up of three components:
// the maximum device period size. However, we need to allow for some timing // 1. Half the Spice period. This is necessary due to the way qemu handles
// jitter to avoid underruns. Packets from Spice in particular can sometimes // audio. Data is not sent as soon as it is produced by the virtual sound
// be delayed by an entire period or more, so include a fixed amount of // card; instead, qemu polls for new data every ~10ms. This results in a
// latency to absorb these gaps. For device jitter use a multiplier, so timing // sawtooth pattern in the packet timing as it drifts in and out of phase
// requirements get progressively stricter as the period size is reduced // with the virtual device. LG measures the average progression of the
int spiceJitterMs = 13; // Spice clock, so sees the packet timing error drift by half a period
// above and below the measured clock. We need to account for this in the
// target latency to avoid underrunning.
// 2. The maximum audio device period, plus a little extra to absorb timing
// jitter.
// 3. A configurable additional buffer period. The default value is set high
// enough to absorb typical timing jitter from Spice, which can be quite
// significant. Users may reduce this if they care more about latency than
// audio quality.
int configLatencyMs = max(g_params.audioBufferLatency, 0);
double targetLatencyFrames = double targetLatencyFrames =
spiceJitterMs * audio.playback.sampleRate / 1000.0 + spiceData->periodFrames / 2.0 +
audio.playback.deviceMaxPeriodFrames * 1.1; audio.playback.deviceMaxPeriodFrames * 1.1 +
configLatencyMs * audio.playback.sampleRate / 1000.0;
// If the device is currently at a lower period size than its maximum (which // If the device is currently at a lower period size than its maximum (which
// can happen, for example, if another application has requested a lower // can happen, for example, if another application has requested a lower

View File

@ -473,6 +473,13 @@ static struct Option options[] =
.type = OPTION_TYPE_INT, .type = OPTION_TYPE_INT,
.value.x_int = 2048 .value.x_int = 2048
}, },
{
.module = "audio",
.name = "bufferLatency",
.description = "Additional buffer latency in milliseconds",
.type = OPTION_TYPE_INT,
.value.x_int = 8
},
{0} {0}
}; };
@ -646,6 +653,7 @@ bool config_load(int argc, char * argv[])
} }
g_params.audioPeriodSize = option_get_int("audio", "periodSize"); g_params.audioPeriodSize = option_get_int("audio", "periodSize");
g_params.audioBufferLatency = option_get_int("audio", "bufferLatency");
return true; return true;
} }

View File

@ -201,6 +201,7 @@ struct AppParams
bool showCursorDot; bool showCursorDot;
int audioPeriodSize; int audioPeriodSize;
int audioBufferLatency;
}; };
struct CBRequest struct CBRequest