From febd081202ce0d64c6698d11f17fa14a93d84d17 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 28 Jan 2022 12:11:56 +1100 Subject: [PATCH] [client] audio: tune the target latency based on the latency jitter --- client/src/audio.c | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/client/src/audio.c b/client/src/audio.c index 827ba38d..69a58400 100644 --- a/client/src/audio.c +++ b/client/src/audio.c @@ -102,6 +102,7 @@ typedef struct RingBuffer timings; GraphHandle graph; + float jitter; // These two structs contain data specifically for use in the device and // Spice data threads respectively. Keep them on separate cache lines to @@ -172,8 +173,8 @@ static const char * audioGraphFormatFn(const char * name, { static char title[64]; snprintf(title, sizeof(title), - "%s: min:%4.2f max:%4.2f avg:%4.2f now:%4.2f", - name, min, max, avg, last); + "%s: min:%4.2f max:%4.2f avg:%4.2f now:%4.2f jitter:%4.2f", + name, min, max, avg, last, max - min); return title; } @@ -305,6 +306,7 @@ void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format, audio.playback.sampleRate = sampleRate; audio.playback.stride = channels * sizeof(float); audio.playback.state = STREAM_STATE_SETUP; + audio.playback.jitter = 60.0f; //assume 60ms of jitter initially audio.playback.deviceData.periodFrames = 0; audio.playback.deviceData.nextPosition = 0; @@ -329,8 +331,10 @@ void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format, if (audio.audioDev->playback.mute) audio.audioDev->playback.mute(audio.playback.mute); - // if the audio dev can report it's latency setup a timing graph - audio.playback.timings = ringbuffer_new(1200, sizeof(float)); + // timings for jitter calculations and display graph + // spice operates on a period size of (sampleRate / 100), so allocate enough + // room for 4 seconds of timing samples. + audio.playback.timings = ringbuffer_new(sampleRate / 100, sizeof(float)); audio.playback.graph = app_registerGraph("PLAYBACK", audio.playback.timings, 0.0f, 100.0f, audioGraphFormatFn); @@ -375,6 +379,22 @@ void audio_playbackMute(bool mute) audio.audioDev->playback.mute(mute); } +static bool getMinMax(int index, void * value, void * udata) +{ + float ms = *(float *)value; + float * minMax = (float *)udata; + + if (index == 0) + { + minMax[0] = minMax[1] = ms; + return true; + } + + minMax[0] = min(minMax[0], ms); + minMax[1] = max(minMax[1], ms); + return true; +} + void audio_playbackData(uint8_t * data, size_t size) { if (!audio.audioDev || size == 0) @@ -498,7 +518,7 @@ void audio_playbackData(uint8_t * data, size_t size) // device period size, but that would result in underruns if the period size // suddenly increases. It may be better instead to just reduce the maximum // latency on the audio devices, which currently is set quite high - int targetLatencyMs = 70; + int targetLatencyMs = ceil(audio.playback.jitter); int targetLatencyFrames = targetLatencyMs * audio.playback.sampleRate / 1000; @@ -565,6 +585,16 @@ void audio_playbackData(uint8_t * data, size_t size) const float latency = latencyFrames / (float)(audio.playback.sampleRate / 1000); ringbuffer_push(audio.playback.timings, &latency); + + // if the ringbuffer is full calculate the jitter + if (ringbuffer_getCount(audio.playback.timings) == + ringbuffer_getLength(audio.playback.timings)) + { + float minMax[2]; + ringbuffer_forEach(audio.playback.timings, getMinMax, minMax, false); + audio.playback.jitter = minMax[1] - minMax[0]; + } + app_invalidateGraph(audio.playback.graph); }