[client] audio: add audio playback latency interface and graph

This commit is contained in:
Geoffrey McRae 2022-01-17 22:13:41 +11:00
parent 5629655f74
commit 689cc53255
5 changed files with 81 additions and 5 deletions

View File

@ -370,6 +370,16 @@ static void pipewire_playbackMute(bool mute)
pw_thread_loop_unlock(pw.thread); 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) static void pipewire_recordStopStream(void)
{ {
if (!pw.record.stream) if (!pw.record.stream)
@ -527,11 +537,12 @@ struct LG_AudioDevOps LGAD_PipeWire =
.free = pipewire_free, .free = pipewire_free,
.playback = .playback =
{ {
.start = pipewire_playbackStart, .start = pipewire_playbackStart,
.play = pipewire_playbackPlay, .play = pipewire_playbackPlay,
.stop = pipewire_playbackStop, .stop = pipewire_playbackStop,
.volume = pipewire_playbackVolume, .volume = pipewire_playbackVolume,
.mute = pipewire_playbackMute .mute = pipewire_playbackMute,
.latency = pipewire_playbackLatency
}, },
.record = .record =
{ {

View File

@ -59,6 +59,9 @@ struct LG_AudioDevOps
/* [optional] called to set muting of the output */ /* [optional] called to set muting of the output */
void (*mute)(bool mute); void (*mute)(bool mute);
/* return the current total playback latency in microseconds */
uint64_t (*latency)(void);
} }
playback; playback;

View File

@ -18,6 +18,7 @@
* Temple Place, Suite 330, Boston, MA 02111-1307 USA * Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include "audio.h"
#include "main.h" #include "main.h"
#include "common/array.h" #include "common/array.h"
#include "common/util.h" #include "common/util.h"
@ -36,6 +37,10 @@ typedef struct
int volumeChannels; int volumeChannels;
uint16_t volume[8]; uint16_t volume[8];
bool mute; bool mute;
LG_Lock lock;
RingBuffer timings;
GraphHandle graph;
} }
playback; playback;
@ -60,6 +65,7 @@ void audio_init(void)
if (LG_AudioDevs[i]->init()) if (LG_AudioDevs[i]->init())
{ {
audio.audioDev = LG_AudioDevs[i]; audio.audioDev = LG_AudioDevs[i];
LG_LOCK_INIT(audio.playback.lock);
DEBUG_INFO("Using AudioDev: %s", audio.audioDev->name); DEBUG_INFO("Using AudioDev: %s", audio.audioDev->name);
return; return;
} }
@ -77,6 +83,7 @@ void audio_free(void)
audio.audioDev->free(); audio.audioDev->free();
audio.audioDev = NULL; audio.audioDev = NULL;
LG_LOCK_FREE(audio.playback.lock);
} }
bool audio_supportsPlayback(void) bool audio_supportsPlayback(void)
@ -84,6 +91,16 @@ bool audio_supportsPlayback(void)
return audio.audioDev && audio.audioDev->playback.start; 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, void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format,
uint32_t time) uint32_t time)
{ {
@ -101,6 +118,8 @@ void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format,
return; return;
} }
LG_LOCK(audio.playback.lock);
lastChannels = channels; lastChannels = channels;
lastSampleRate = sampleRate; lastSampleRate = sampleRate;
audio.playback.started = true; audio.playback.started = true;
@ -116,6 +135,16 @@ void audio_playbackStart(int channels, int sampleRate, PSAudioFormat format,
// set the inital mute state // set the inital mute state
if (audio.audioDev->playback.mute) if (audio.audioDev->playback.mute)
audio.audioDev->playback.mute(audio.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) void audio_playbackStop(void)
@ -123,8 +152,18 @@ void audio_playbackStop(void)
if (!audio.audioDev || !audio.playback.started) if (!audio.audioDev || !audio.playback.started)
return; return;
LG_LOCK(audio.playback.lock);
audio.audioDev->playback.stop(); audio.audioDev->playback.stop();
audio.playback.started = false; 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[]) void audio_playbackVolume(int channels, const uint16_t volume[])
@ -244,3 +283,19 @@ void audio_recordMute(bool mute)
audio.audioDev->record.mute(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);
}

View File

@ -18,6 +18,9 @@
* Temple Place, Suite 330, Boston, MA 02111-1307 USA * Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
#include <stdbool.h>
#include <purespice.h>
void audio_init(void); void audio_init(void);
void audio_free(void); void audio_free(void);
@ -34,3 +37,5 @@ void audio_recordStart(int channels, int sampleRate, PSAudioFormat format);
void audio_recordStop(void); void audio_recordStop(void);
void audio_recordVolume(int channels, const uint16_t volume[]); void audio_recordVolume(int channels, const uint16_t volume[]);
void audio_recordMute(bool mute); void audio_recordMute(bool mute);
void audio_tick(unsigned long long tickCount);

View File

@ -155,6 +155,8 @@ static bool tickTimerFn(void * unused)
if (needsRender) if (needsRender)
app_invalidateWindow(false); app_invalidateWindow(false);
audio_tick(tickCount);
++tickCount; ++tickCount;
return true; return true;
} }