Rearchitect audio copying to make it cleaner and more atomic

This commit is contained in:
Jeffrey Pfau 2014-01-15 03:43:56 -08:00
parent b8167f55b1
commit 2b558a5a65
8 changed files with 114 additions and 54 deletions

View File

@ -48,8 +48,6 @@ void GBAAudioInit(struct GBAAudio* audio) {
CircleBufferInit(&audio->right, GBA_AUDIO_SAMPLES * sizeof(int32_t)); CircleBufferInit(&audio->right, GBA_AUDIO_SAMPLES * sizeof(int32_t));
CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE); CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE);
CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE); CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE);
pthread_mutex_init(&audio->bufferMutex, 0);
} }
void GBAAudioDeinit(struct GBAAudio* audio) { void GBAAudioDeinit(struct GBAAudio* audio) {
@ -57,9 +55,6 @@ void GBAAudioDeinit(struct GBAAudio* audio) {
CircleBufferDeinit(&audio->right); CircleBufferDeinit(&audio->right);
CircleBufferDeinit(&audio->chA.fifo); CircleBufferDeinit(&audio->chA.fifo);
CircleBufferDeinit(&audio->chB.fifo); CircleBufferDeinit(&audio->chB.fifo);
pthread_mutex_lock(&audio->bufferMutex);
pthread_mutex_destroy(&audio->bufferMutex);
} }
int32_t GBAAudioProcessEvents(struct GBAAudio* audio, int32_t cycles) { int32_t GBAAudioProcessEvents(struct GBAAudio* audio, int32_t cycles) {
@ -397,6 +392,27 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId) {
CircleBufferRead8(&channel->fifo, &channel->sample); CircleBufferRead8(&channel->fifo, &channel->sample);
} }
unsigned GBAAudioCopy(struct GBAAudio* audio, void* left, void* right, unsigned nSamples) {
GBASyncLockAudio(audio->p->sync);
unsigned read = 0;
if (left) {
unsigned readL = CircleBufferRead(&audio->left, left, nSamples * sizeof(int32_t)) >> 2;
if (readL < nSamples) {
memset((int32_t*) left + readL, 0, nSamples - readL);
}
read = readL;
}
if (right) {
unsigned readR = CircleBufferRead(&audio->right, right, nSamples * sizeof(int32_t)) >> 2;
if (readR < nSamples) {
memset((int32_t*) right + readR, 0, nSamples - readR);
}
read = read >= readR ? read : readR;
}
GBASyncConsumeAudio(audio->p->sync);
return read;
}
static int32_t _updateSquareChannel(struct GBAAudioSquareControl* control, int duty) { static int32_t _updateSquareChannel(struct GBAAudioSquareControl* control, int duty) {
control->hi = !control->hi; control->hi = !control->hi;
int period = 16 * (2048 - control->frequency); int period = 16 * (2048 - control->frequency);
@ -579,14 +595,9 @@ static void _sample(struct GBAAudio* audio) {
sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB; sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB;
} }
pthread_mutex_lock(&audio->bufferMutex); GBASyncLockAudio(audio->p->sync);
while (CircleBufferSize(&audio->left) + (GBA_AUDIO_SAMPLES * 2 / 5) >= audio->left.capacity) { CircleBufferWrite32(&audio->left, sampleLeft << 5);
if (!audio->p->sync->audioWait) { CircleBufferWrite32(&audio->right, sampleRight << 5);
break; unsigned produced = CircleBufferSize(&audio->left);
} GBASyncProduceAudio(audio->p->sync, produced >= GBA_AUDIO_SAMPLES * 3);
GBASyncProduceAudio(audio->p->sync, &audio->bufferMutex);
}
CircleBufferWrite32(&audio->left, sampleLeft);
CircleBufferWrite32(&audio->right, sampleRight);
pthread_mutex_unlock(&audio->bufferMutex);
} }

View File

@ -3,7 +3,6 @@
#include "circle-buffer.h" #include "circle-buffer.h"
#include <pthread.h>
#include <stdint.h> #include <stdint.h>
struct GBADMA; struct GBADMA;
@ -203,8 +202,6 @@ struct GBAAudio {
int32_t nextSample; int32_t nextSample;
int32_t sampleInterval; int32_t sampleInterval;
pthread_mutex_t bufferMutex;
}; };
void GBAAudioInit(struct GBAAudio* audio); void GBAAudioInit(struct GBAAudio* audio);
@ -231,4 +228,6 @@ void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value);
void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value); void GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value);
void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId); void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId);
unsigned GBAAudioCopy(struct GBAAudio* audio, void* left, void* right, unsigned nSamples);
#endif #endif

View File

@ -135,6 +135,7 @@ int GBAThreadStart(struct GBAThread* threadContext) {
pthread_mutex_init(&threadContext->sync.videoFrameMutex, 0); pthread_mutex_init(&threadContext->sync.videoFrameMutex, 0);
pthread_cond_init(&threadContext->sync.videoFrameAvailableCond, 0); pthread_cond_init(&threadContext->sync.videoFrameAvailableCond, 0);
pthread_cond_init(&threadContext->sync.videoFrameRequiredCond, 0); pthread_cond_init(&threadContext->sync.videoFrameRequiredCond, 0);
pthread_mutex_init(&threadContext->sync.audioBufferMutex, 0);
pthread_cond_init(&threadContext->sync.audioRequiredCond, 0); pthread_cond_init(&threadContext->sync.audioRequiredCond, 0);
pthread_mutex_lock(&threadContext->stateMutex); pthread_mutex_lock(&threadContext->stateMutex);
@ -168,6 +169,7 @@ void GBAThreadJoin(struct GBAThread* threadContext) {
pthread_cond_broadcast(&threadContext->sync.audioRequiredCond); pthread_cond_broadcast(&threadContext->sync.audioRequiredCond);
pthread_cond_destroy(&threadContext->sync.audioRequiredCond); pthread_cond_destroy(&threadContext->sync.audioRequiredCond);
pthread_mutex_destroy(&threadContext->sync.audioBufferMutex);
} }
void GBAThreadPause(struct GBAThread* threadContext) { void GBAThreadPause(struct GBAThread* threadContext) {
@ -277,12 +279,18 @@ int GBASyncDrawingFrame(struct GBASync* sync) {
return sync->videoFrameSkip <= 0; return sync->videoFrameSkip <= 0;
} }
void GBASyncProduceAudio(struct GBASync* sync, pthread_mutex_t* mutex) { void GBASyncProduceAudio(struct GBASync* sync, int wait) {
if (&sync->audioWait) { if (sync->audioWait && wait) {
pthread_cond_wait(&sync->audioRequiredCond, mutex); pthread_cond_wait(&sync->audioRequiredCond, &sync->audioBufferMutex);
} }
pthread_mutex_unlock(&sync->audioBufferMutex);
}
void GBASyncLockAudio(struct GBASync* sync) {
pthread_mutex_lock(&sync->audioBufferMutex);
} }
void GBASyncConsumeAudio(struct GBASync* sync) { void GBASyncConsumeAudio(struct GBASync* sync) {
pthread_cond_broadcast(&sync->audioRequiredCond); pthread_cond_broadcast(&sync->audioRequiredCond);
pthread_mutex_unlock(&sync->audioBufferMutex);
} }

View File

@ -49,6 +49,7 @@ struct GBAThread {
int audioWait; int audioWait;
pthread_cond_t audioRequiredCond; pthread_cond_t audioRequiredCond;
pthread_mutex_t audioBufferMutex;
} sync; } sync;
}; };
@ -65,7 +66,8 @@ int GBASyncWaitFrameStart(struct GBASync* sync, int frameskip);
void GBASyncWaitFrameEnd(struct GBASync* sync); void GBASyncWaitFrameEnd(struct GBASync* sync);
int GBASyncDrawingFrame(struct GBASync* sync); int GBASyncDrawingFrame(struct GBASync* sync);
void GBASyncProduceAudio(struct GBASync* sync, pthread_mutex_t* mutex); void GBASyncProduceAudio(struct GBASync* sync, int wait);
void GBASyncLockAudio(struct GBASync* sync);
void GBASyncConsumeAudio(struct GBASync* sync); void GBASyncConsumeAudio(struct GBASync* sync);
#endif #endif

View File

@ -3,6 +3,13 @@
#include "gba.h" #include "gba.h"
#include "gba-thread.h" #include "gba-thread.h"
#define BUFFER_SIZE (GBA_AUDIO_SAMPLES >> 2)
struct StereoSample {
Sint16 left;
Sint16 right;
};
static void _GBASDLAudioCallback(void* context, Uint8* data, int len); static void _GBASDLAudioCallback(void* context, Uint8* data, int len);
int GBASDLInitAudio(struct GBASDLAudio* context) { int GBASDLInitAudio(struct GBASDLAudio* context) {
@ -34,42 +41,52 @@ void GBASDLDeinitAudio(struct GBASDLAudio* context) {
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
} }
static void _pulldownResample(struct GBASDLAudio* context) { static void _pulldownResample(struct GBASDLAudio* context, struct StereoSample* output, ssize_t samples) {
int32_t value; int32_t left[BUFFER_SIZE];
if (CircleBufferRead32(&context->audio->left, &value)) { int32_t right[BUFFER_SIZE];
context->currentSample.left = value << 5;
} else { // toRead is in GBA samples
context->currentSample.left = 0; int toRead = samples / context->ratio;
} while (samples > 0) {
if (CircleBufferRead32(&context->audio->right, &value)) { int currentRead = BUFFER_SIZE >> 2;
context->currentSample.right = value << 5; if (currentRead > toRead) {
} else { currentRead = toRead;
context->currentSample.right = 0; }
unsigned read = GBAAudioCopy(context->audio, left, right, currentRead);
toRead -= read;
unsigned i;
for (i = 0; i < read; ++i) {
context->drift += context->ratio;
while (context->drift >= 0) {
output->left = left[i];
output->right = right[i];
++output;
--samples;
#ifndef NDEBUG
if (samples < 0) {
abort();
}
#endif
context->drift -= 1.f;
}
}
if (read < BUFFER_SIZE >> 2) {
memset(output, 0, toRead);
return;
}
} }
} }
static void _GBASDLAudioCallback(void* context, Uint8* data, int len) { static void _GBASDLAudioCallback(void* context, Uint8* data, int len) {
struct GBASDLAudio* audioContext = context; struct GBASDLAudio* audioContext = context;
int i;
if (!context || !audioContext->audio) { if (!context || !audioContext->audio) {
for (i = 0; i < len; ++i) { memset(data, 0, len);
data[i] = 0;
}
return; return;
} }
audioContext->ratio = audioContext->obtainedSpec.freq / (float) audioContext->audio->sampleRate;
struct StereoSample* ssamples = (struct StereoSample*) data; struct StereoSample* ssamples = (struct StereoSample*) data;
len /= 2 * audioContext->obtainedSpec.channels; len /= 2 * audioContext->obtainedSpec.channels;
if (audioContext->obtainedSpec.channels == 2) { if (audioContext->obtainedSpec.channels == 2) {
pthread_mutex_lock(&audioContext->audio->bufferMutex); _pulldownResample(audioContext, ssamples, len);
for (i = 0; i < len; ++i) {
audioContext->drift += audioContext->audio->sampleRate / (float) audioContext->obtainedSpec.freq;
while (audioContext->drift >= 0) {
_pulldownResample(audioContext);
audioContext->drift -= 1.f;
}
ssamples[i] = audioContext->currentSample;
}
GBASyncConsumeAudio(audioContext->audio->p->sync);
pthread_mutex_unlock(&audioContext->audio->bufferMutex);
} }
} }

View File

@ -3,17 +3,12 @@
#include <SDL.h> #include <SDL.h>
struct StereoSample {
Sint16 left;
Sint16 right;
};
struct GBASDLAudio { struct GBASDLAudio {
SDL_AudioSpec desiredSpec; SDL_AudioSpec desiredSpec;
SDL_AudioSpec obtainedSpec; SDL_AudioSpec obtainedSpec;
float drift; float drift;
float ratio;
struct GBAAudio* audio; struct GBAAudio* audio;
struct StereoSample currentSample;
}; };
int GBASDLInitAudio(struct GBASDLAudio* context); int GBASDLInitAudio(struct GBASDLAudio* context);

View File

@ -87,3 +87,29 @@ int CircleBufferRead32(struct CircleBuffer* buffer, int32_t* value) {
buffer->size -= sizeof(int32_t); buffer->size -= sizeof(int32_t);
return 1; return 1;
} }
int CircleBufferRead(struct CircleBuffer* buffer, void* output, size_t length) {
int8_t* data = buffer->readPtr;
if (buffer->size == 0) {
return 0;
}
if (length > buffer->size) {
length = buffer->size;
}
size_t remaining = buffer->capacity - ((int8_t*) data - (int8_t*) buffer->data);
if (length <= remaining) {
memcpy(output, data, length);
if (length == remaining) {
buffer->readPtr = buffer->data;
} else {
buffer->readPtr = (int8_t*) data + length;
}
} else {
memcpy(output, data, remaining);
memcpy((int8_t*) output + remaining, buffer->data, length - remaining);
buffer->readPtr = (int8_t*) buffer->data + length - remaining;
}
buffer->size -= length;
return length;
}

View File

@ -2,6 +2,7 @@
#define CIRCLE_BUFFER_H #define CIRCLE_BUFFER_H
#include <stdint.h> #include <stdint.h>
#include <string.h>
struct CircleBuffer { struct CircleBuffer {
void* data; void* data;
@ -18,5 +19,6 @@ int CircleBufferWrite8(struct CircleBuffer* buffer, int8_t value);
int CircleBufferWrite32(struct CircleBuffer* buffer, int32_t value); int CircleBufferWrite32(struct CircleBuffer* buffer, int32_t value);
int CircleBufferRead8(struct CircleBuffer* buffer, int8_t* value); int CircleBufferRead8(struct CircleBuffer* buffer, int8_t* value);
int CircleBufferRead32(struct CircleBuffer* buffer, int32_t* value); int CircleBufferRead32(struct CircleBuffer* buffer, int32_t* value);
int CircleBufferRead(struct CircleBuffer* buffer, void* output, size_t length);
#endif #endif