mirror of
https://github.com/moonlight-stream/moonlight-chrome.git
synced 2025-08-17 16:46:31 +00:00
Use a wait-free algorithm for audio queueing to minimize audio hiccups. This closes #8
This commit is contained in:
parent
cf5b2ccbb4
commit
3ce2d68250
136
auddec.cpp
136
auddec.cpp
@ -1,81 +1,42 @@
|
|||||||
#include "moonlight.hpp"
|
#include "moonlight.hpp"
|
||||||
|
|
||||||
#define MAX_CHANNEL_COUNT 6
|
#define MAX_CHANNEL_COUNT 2
|
||||||
#define FRAME_SIZE 240
|
#define FRAME_SIZE 240
|
||||||
|
|
||||||
typedef struct decoded_sample_entry {
|
#define CIRCULAR_BUFFER_SIZE 32
|
||||||
struct decoded_sample_entry *next;
|
|
||||||
int sampleLength;
|
|
||||||
short sampleBuffer[1];
|
|
||||||
} decoded_sample_entry_t;
|
|
||||||
|
|
||||||
#define MAX_QUEUE_LENGTH 14
|
// This code uses volatiles for synchronization between the producer and consumer side. This is
|
||||||
#define QUEUE_PRUNING_LENGTH 7
|
// only safe because this code executes under very specific conditions, namely that the framework
|
||||||
|
// ensures AudioPlayerSampleCallback and AudDecDecodeAndPlaySample are each only active on one thread
|
||||||
|
// at a time.
|
||||||
|
|
||||||
static int s_OpusChannelCount;
|
static short s_CircularBuffer[CIRCULAR_BUFFER_SIZE][FRAME_SIZE * MAX_CHANNEL_COUNT];
|
||||||
static decoded_sample_entry_t* s_SampleQueueHead;
|
static int s_ReadIndex = 0;
|
||||||
static decoded_sample_entry_t* s_SampleQueueTail;
|
static int s_WriteIndex = 0;
|
||||||
static int s_SampleQueueLength;
|
|
||||||
static pthread_mutex_t s_SampleQueueLock;
|
|
||||||
|
|
||||||
static void ReapSampleQueue() {
|
|
||||||
decoded_sample_entry_t *entry;
|
|
||||||
|
|
||||||
while (s_SampleQueueHead) {
|
|
||||||
entry = s_SampleQueueHead->next;
|
|
||||||
free(s_SampleQueueHead);
|
|
||||||
s_SampleQueueHead = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
s_SampleQueueTail = NULL;
|
|
||||||
|
|
||||||
s_SampleQueueLength = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void AudioPlayerSampleCallback(void* samples, uint32_t buffer_size, void* data) {
|
static void AudioPlayerSampleCallback(void* samples, uint32_t buffer_size, void* data) {
|
||||||
unsigned char* buffer = (unsigned char *)samples;
|
// It should only ask us for complete buffers
|
||||||
int offset = 0;
|
assert(buffer_size == FRAME_SIZE * MAX_CHANNEL_COUNT * sizeof(short));
|
||||||
|
|
||||||
pthread_mutex_lock(&s_SampleQueueLock);
|
// If the indexes aren't equal, we have a sample
|
||||||
|
if (s_WriteIndex != s_ReadIndex) {
|
||||||
|
memcpy(samples, s_CircularBuffer[s_ReadIndex], buffer_size);
|
||||||
|
|
||||||
while (s_SampleQueueHead && s_SampleQueueHead->sampleLength <= buffer_size - offset) {
|
// Use a full memory barrier to ensure the circular buffer is read before incrementing the index
|
||||||
decoded_sample_entry_t* lastEnt;
|
__sync_synchronize();
|
||||||
|
|
||||||
memcpy(&buffer[offset], s_SampleQueueHead->sampleBuffer, s_SampleQueueHead->sampleLength);
|
// This can race with the reader in the AudDecDecodeAndPlaySample function. This is
|
||||||
offset += s_SampleQueueHead->sampleLength;
|
// not a problem because at worst, it just won't see that we've consumed this sample yet.
|
||||||
|
s_ReadIndex = (s_ReadIndex + 1) % CIRCULAR_BUFFER_SIZE;
|
||||||
lastEnt = s_SampleQueueHead;
|
|
||||||
s_SampleQueueHead = s_SampleQueueHead->next;
|
|
||||||
free(lastEnt);
|
|
||||||
s_SampleQueueLength--;
|
|
||||||
|
|
||||||
// Remove another sample if we're in pruning mode
|
|
||||||
if (s_SampleQueueLength > QUEUE_PRUNING_LENGTH) {
|
|
||||||
lastEnt = s_SampleQueueHead;
|
|
||||||
s_SampleQueueHead = s_SampleQueueHead->next;
|
|
||||||
free(lastEnt);
|
|
||||||
s_SampleQueueLength--;
|
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
|
memset(samples, 0, buffer_size);
|
||||||
if (!s_SampleQueueHead) {
|
|
||||||
s_SampleQueueTail = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&s_SampleQueueLock);
|
|
||||||
|
|
||||||
// Zero the remaining portion of the sample buffer to reduce noise when underflowing
|
|
||||||
if (buffer_size != offset) {
|
|
||||||
memset(&buffer[offset], 0, buffer_size - offset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoonlightInstance::AudDecInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig) {
|
void MoonlightInstance::AudDecInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig) {
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
pthread_mutex_init(&s_SampleQueueLock, NULL);
|
|
||||||
|
|
||||||
s_OpusChannelCount = opusConfig->channelCount;
|
|
||||||
g_Instance->m_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
|
g_Instance->m_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate,
|
||||||
opusConfig->channelCount,
|
opusConfig->channelCount,
|
||||||
opusConfig->streams,
|
opusConfig->streams,
|
||||||
@ -83,56 +44,41 @@ void MoonlightInstance::AudDecInit(int audioConfiguration, POPUS_MULTISTREAM_CON
|
|||||||
opusConfig->mapping,
|
opusConfig->mapping,
|
||||||
&rc);
|
&rc);
|
||||||
|
|
||||||
pp::AudioConfig audioConfig = pp::AudioConfig(g_Instance, PP_AUDIOSAMPLERATE_48000, FRAME_SIZE * 3);
|
g_Instance->m_AudioPlayer = pp::Audio(g_Instance, pp::AudioConfig(g_Instance, PP_AUDIOSAMPLERATE_48000, FRAME_SIZE),
|
||||||
|
AudioPlayerSampleCallback, NULL);
|
||||||
g_Instance->m_AudioPlayer = pp::Audio(g_Instance, audioConfig, AudioPlayerSampleCallback, NULL);
|
|
||||||
|
|
||||||
// Start playback now
|
// Start playback now
|
||||||
g_Instance->m_AudioPlayer.StartPlayback();
|
g_Instance->m_AudioPlayer.StartPlayback();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoonlightInstance::AudDecCleanup(void) {
|
void MoonlightInstance::AudDecCleanup(void) {
|
||||||
pthread_mutex_destroy(&s_SampleQueueLock);
|
// Stop playback
|
||||||
|
g_Instance->m_AudioPlayer.StopPlayback();
|
||||||
|
|
||||||
if (g_Instance->m_OpusDecoder) {
|
if (g_Instance->m_OpusDecoder) {
|
||||||
opus_multistream_decoder_destroy(g_Instance->m_OpusDecoder);
|
opus_multistream_decoder_destroy(g_Instance->m_OpusDecoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReapSampleQueue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoonlightInstance::AudDecDecodeAndPlaySample(char* sampleData, int sampleLength) {
|
void MoonlightInstance::AudDecDecodeAndPlaySample(char* sampleData, int sampleLength) {
|
||||||
decoded_sample_entry_t* entry = (decoded_sample_entry_t*)malloc(sizeof(decoded_sample_entry_t) +
|
int decodeLen;
|
||||||
(s_OpusChannelCount * FRAME_SIZE * sizeof(short)));
|
|
||||||
if (entry) {
|
// Check if there is space for this sample in the buffer. Again, this can race
|
||||||
int decodeLen = opus_multistream_decode(g_Instance->m_OpusDecoder, (unsigned char *)sampleData, sampleLength,
|
// but in the worst case, we'll not see the sample callback having consumed a sample.
|
||||||
entry->sampleBuffer, FRAME_SIZE, 0);
|
if (((s_WriteIndex + 1) % CIRCULAR_BUFFER_SIZE) == s_ReadIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeLen = opus_multistream_decode(g_Instance->m_OpusDecoder, (unsigned char *)sampleData, sampleLength,
|
||||||
|
s_CircularBuffer[s_WriteIndex], FRAME_SIZE, 0);
|
||||||
if (decodeLen > 0) {
|
if (decodeLen > 0) {
|
||||||
entry->sampleLength = decodeLen * s_OpusChannelCount * sizeof(short);
|
// Use a full memory barrier to ensure the circular buffer is written before incrementing the index
|
||||||
entry->next = NULL;
|
__sync_synchronize();
|
||||||
|
|
||||||
pthread_mutex_lock(&s_SampleQueueLock);
|
// This can race with the reader in the sample callback, however this is a benign
|
||||||
|
// race since we'll either read the original value of s_WriteIndex (which is safe,
|
||||||
if (s_SampleQueueLength == MAX_QUEUE_LENGTH) {
|
// we just won't consider this sample) or the new value of s_WriteIndex
|
||||||
printf("Reaped sample queue\n");
|
s_WriteIndex = (s_WriteIndex + 1) % CIRCULAR_BUFFER_SIZE;
|
||||||
ReapSampleQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!s_SampleQueueTail) {
|
|
||||||
s_SampleQueueHead = s_SampleQueueTail = entry;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
s_SampleQueueTail->next = entry;
|
|
||||||
s_SampleQueueTail = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
s_SampleQueueLength++;
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&s_SampleQueueLock);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
free(entry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user