From 689cc53255333bf55a42711bd217ad9f64ee4269 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Mon, 17 Jan 2022 22:13:41 +1100 Subject: [PATCH] [client] audio: add audio playback latency interface and graph --- client/audiodevs/PipeWire/pipewire.c | 21 ++++++++--- client/include/interface/audiodev.h | 3 ++ client/src/audio.c | 55 ++++++++++++++++++++++++++++ client/src/audio.h | 5 +++ client/src/main.c | 2 + 5 files changed, 81 insertions(+), 5 deletions(-) diff --git a/client/audiodevs/PipeWire/pipewire.c b/client/audiodevs/PipeWire/pipewire.c index 06dae0d1..be121e3c 100644 --- a/client/audiodevs/PipeWire/pipewire.c +++ b/client/audiodevs/PipeWire/pipewire.c @@ -370,6 +370,16 @@ static void pipewire_playbackMute(bool mute) pw_thread_loop_unlock(pw.thread); } +static uint64_t pipewire_playbackLatency(void) +{ + const int frames = ringbuffer_getCount(pw.playback.buffer); + if (frames == 0) + return 0; + + // TODO: we should really include the hw latency here too + return (uint64_t)pw.playback.sampleRate * 1000ULL / frames; +} + static void pipewire_recordStopStream(void) { if (!pw.record.stream) @@ -527,11 +537,12 @@ struct LG_AudioDevOps LGAD_PipeWire = .free = pipewire_free, .playback = { - .start = pipewire_playbackStart, - .play = pipewire_playbackPlay, - .stop = pipewire_playbackStop, - .volume = pipewire_playbackVolume, - .mute = pipewire_playbackMute + .start = pipewire_playbackStart, + .play = pipewire_playbackPlay, + .stop = pipewire_playbackStop, + .volume = pipewire_playbackVolume, + .mute = pipewire_playbackMute, + .latency = pipewire_playbackLatency }, .record = { diff --git a/client/include/interface/audiodev.h b/client/include/interface/audiodev.h index 5e5fb3f4..fb2d423b 100644 --- a/client/include/interface/audiodev.h +++ b/client/include/interface/audiodev.h @@ -59,6 +59,9 @@ struct LG_AudioDevOps /* [optional] called to set muting of the output */ void (*mute)(bool mute); + + /* return the current total playback latency in microseconds */ + uint64_t (*latency)(void); } playback; diff --git a/client/src/audio.c b/client/src/audio.c index 2a66c85a..82dd8f05 100644 --- a/client/src/audio.c +++ b/client/src/audio.c @@ -18,6 +18,7 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "audio.h" #include "main.h" #include "common/array.h" #include "common/util.h" @@ -36,6 +37,10 @@ typedef struct int volumeChannels; uint16_t volume[8]; bool mute; + + LG_Lock lock; + RingBuffer timings; + GraphHandle graph; } playback; @@ -60,6 +65,7 @@ void audio_init(void) if (LG_AudioDevs[i]->init()) { audio.audioDev = LG_AudioDevs[i]; + LG_LOCK_INIT(audio.playback.lock); DEBUG_INFO("Using AudioDev: %s", audio.audioDev->name); return; } @@ -77,6 +83,7 @@ void audio_free(void) audio.audioDev->free(); audio.audioDev = NULL; + LG_LOCK_FREE(audio.playback.lock); } bool audio_supportsPlayback(void) @@ -84,6 +91,16 @@ bool audio_supportsPlayback(void) return audio.audioDev && audio.audioDev->playback.start; } +static const char * audioGraphFormatFn(const char * name, + float min, float max, float avg, float freq, float last) +{ + 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); + return title; +} + void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format, uint32_t time) { @@ -101,6 +118,8 @@ void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format, return; } + LG_LOCK(audio.playback.lock); + lastChannels = channels; lastSampleRate = sampleRate; audio.playback.started = true; @@ -116,6 +135,16 @@ void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format, // set the inital mute state 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 + if (audio.audioDev->playback.latency) + { + audio.playback.timings = ringbuffer_new(2400, sizeof(float)); + audio.playback.graph = app_registerGraph("PLAYBACK", + audio.playback.timings, 0.0f, 100.0f, audioGraphFormatFn); + } + + LG_UNLOCK(audio.playback.lock); } void audio_playbackStop(void) @@ -123,8 +152,18 @@ void audio_playbackStop(void) if (!audio.audioDev || !audio.playback.started) return; + LG_LOCK(audio.playback.lock); + audio.audioDev->playback.stop(); audio.playback.started = false; + + if (audio.playback.timings) + { + app_unregisterGraph(audio.playback.graph); + ringbuffer_free(&audio.playback.timings); + } + + LG_UNLOCK(audio.playback.lock); } void audio_playbackVolume(int channels, const uint16_t volume[]) @@ -244,3 +283,19 @@ void audio_recordMute(bool mute) audio.audioDev->record.mute(mute); } + +void audio_tick(unsigned long long tickCount) +{ + LG_LOCK(audio.playback.lock); + if (!audio.playback.timings) + { + LG_UNLOCK(audio.playback.lock); + return; + } + + const uint64_t latency = audio.audioDev->playback.latency(); + const float flatency = latency > 0 ? (float)latency / 1000.0f : 0.0f; + ringbuffer_push(audio.playback.timings, &flatency); + + LG_UNLOCK(audio.playback.lock); +} diff --git a/client/src/audio.h b/client/src/audio.h index ec160f1d..e4ebccb6 100644 --- a/client/src/audio.h +++ b/client/src/audio.h @@ -18,6 +18,9 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include +#include + void audio_init(void); void audio_free(void); @@ -34,3 +37,5 @@ void audio_recordStart(int channels, int sampleRate, PSAudioFormat format); void audio_recordStop(void); void audio_recordVolume(int channels, const uint16_t volume[]); void audio_recordMute(bool mute); + +void audio_tick(unsigned long long tickCount); diff --git a/client/src/main.c b/client/src/main.c index 48aa0c6e..526658bf 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -155,6 +155,8 @@ static bool tickTimerFn(void * unused) if (needsRender) app_invalidateWindow(false); + audio_tick(tickCount); + ++tickCount; return true; }