mirror of https://github.com/mgba-emu/mgba.git
Rearchitect audio copying to make it cleaner and more atomic
This commit is contained in:
parent
b8167f55b1
commit
2b558a5a65
|
@ -48,8 +48,6 @@ void GBAAudioInit(struct GBAAudio* audio) {
|
|||
CircleBufferInit(&audio->right, GBA_AUDIO_SAMPLES * sizeof(int32_t));
|
||||
CircleBufferInit(&audio->chA.fifo, GBA_AUDIO_FIFO_SIZE);
|
||||
CircleBufferInit(&audio->chB.fifo, GBA_AUDIO_FIFO_SIZE);
|
||||
|
||||
pthread_mutex_init(&audio->bufferMutex, 0);
|
||||
}
|
||||
|
||||
void GBAAudioDeinit(struct GBAAudio* audio) {
|
||||
|
@ -57,9 +55,6 @@ void GBAAudioDeinit(struct GBAAudio* audio) {
|
|||
CircleBufferDeinit(&audio->right);
|
||||
CircleBufferDeinit(&audio->chA.fifo);
|
||||
CircleBufferDeinit(&audio->chB.fifo);
|
||||
|
||||
pthread_mutex_lock(&audio->bufferMutex);
|
||||
pthread_mutex_destroy(&audio->bufferMutex);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
control->hi = !control->hi;
|
||||
int period = 16 * (2048 - control->frequency);
|
||||
|
@ -579,14 +595,9 @@ static void _sample(struct GBAAudio* audio) {
|
|||
sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&audio->bufferMutex);
|
||||
while (CircleBufferSize(&audio->left) + (GBA_AUDIO_SAMPLES * 2 / 5) >= audio->left.capacity) {
|
||||
if (!audio->p->sync->audioWait) {
|
||||
break;
|
||||
}
|
||||
GBASyncProduceAudio(audio->p->sync, &audio->bufferMutex);
|
||||
}
|
||||
CircleBufferWrite32(&audio->left, sampleLeft);
|
||||
CircleBufferWrite32(&audio->right, sampleRight);
|
||||
pthread_mutex_unlock(&audio->bufferMutex);
|
||||
GBASyncLockAudio(audio->p->sync);
|
||||
CircleBufferWrite32(&audio->left, sampleLeft << 5);
|
||||
CircleBufferWrite32(&audio->right, sampleRight << 5);
|
||||
unsigned produced = CircleBufferSize(&audio->left);
|
||||
GBASyncProduceAudio(audio->p->sync, produced >= GBA_AUDIO_SAMPLES * 3);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "circle-buffer.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct GBADMA;
|
||||
|
@ -203,8 +202,6 @@ struct GBAAudio {
|
|||
int32_t nextSample;
|
||||
|
||||
int32_t sampleInterval;
|
||||
|
||||
pthread_mutex_t bufferMutex;
|
||||
};
|
||||
|
||||
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 GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId);
|
||||
|
||||
unsigned GBAAudioCopy(struct GBAAudio* audio, void* left, void* right, unsigned nSamples);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -135,6 +135,7 @@ int GBAThreadStart(struct GBAThread* threadContext) {
|
|||
pthread_mutex_init(&threadContext->sync.videoFrameMutex, 0);
|
||||
pthread_cond_init(&threadContext->sync.videoFrameAvailableCond, 0);
|
||||
pthread_cond_init(&threadContext->sync.videoFrameRequiredCond, 0);
|
||||
pthread_mutex_init(&threadContext->sync.audioBufferMutex, 0);
|
||||
pthread_cond_init(&threadContext->sync.audioRequiredCond, 0);
|
||||
|
||||
pthread_mutex_lock(&threadContext->stateMutex);
|
||||
|
@ -168,6 +169,7 @@ void GBAThreadJoin(struct GBAThread* threadContext) {
|
|||
|
||||
pthread_cond_broadcast(&threadContext->sync.audioRequiredCond);
|
||||
pthread_cond_destroy(&threadContext->sync.audioRequiredCond);
|
||||
pthread_mutex_destroy(&threadContext->sync.audioBufferMutex);
|
||||
}
|
||||
|
||||
void GBAThreadPause(struct GBAThread* threadContext) {
|
||||
|
@ -277,12 +279,18 @@ int GBASyncDrawingFrame(struct GBASync* sync) {
|
|||
return sync->videoFrameSkip <= 0;
|
||||
}
|
||||
|
||||
void GBASyncProduceAudio(struct GBASync* sync, pthread_mutex_t* mutex) {
|
||||
if (&sync->audioWait) {
|
||||
pthread_cond_wait(&sync->audioRequiredCond, mutex);
|
||||
void GBASyncProduceAudio(struct GBASync* sync, int wait) {
|
||||
if (sync->audioWait && wait) {
|
||||
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) {
|
||||
pthread_cond_broadcast(&sync->audioRequiredCond);
|
||||
pthread_mutex_unlock(&sync->audioBufferMutex);
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ struct GBAThread {
|
|||
|
||||
int audioWait;
|
||||
pthread_cond_t audioRequiredCond;
|
||||
pthread_mutex_t audioBufferMutex;
|
||||
} sync;
|
||||
};
|
||||
|
||||
|
@ -65,7 +66,8 @@ int GBASyncWaitFrameStart(struct GBASync* sync, int frameskip);
|
|||
void GBASyncWaitFrameEnd(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);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
#include "gba.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);
|
||||
|
||||
int GBASDLInitAudio(struct GBASDLAudio* context) {
|
||||
|
@ -34,42 +41,52 @@ void GBASDLDeinitAudio(struct GBASDLAudio* context) {
|
|||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
}
|
||||
|
||||
static void _pulldownResample(struct GBASDLAudio* context) {
|
||||
int32_t value;
|
||||
if (CircleBufferRead32(&context->audio->left, &value)) {
|
||||
context->currentSample.left = value << 5;
|
||||
} else {
|
||||
context->currentSample.left = 0;
|
||||
}
|
||||
if (CircleBufferRead32(&context->audio->right, &value)) {
|
||||
context->currentSample.right = value << 5;
|
||||
} else {
|
||||
context->currentSample.right = 0;
|
||||
static void _pulldownResample(struct GBASDLAudio* context, struct StereoSample* output, ssize_t samples) {
|
||||
int32_t left[BUFFER_SIZE];
|
||||
int32_t right[BUFFER_SIZE];
|
||||
|
||||
// toRead is in GBA samples
|
||||
int toRead = samples / context->ratio;
|
||||
while (samples > 0) {
|
||||
int currentRead = BUFFER_SIZE >> 2;
|
||||
if (currentRead > toRead) {
|
||||
currentRead = toRead;
|
||||
}
|
||||
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) {
|
||||
struct GBASDLAudio* audioContext = context;
|
||||
int i;
|
||||
if (!context || !audioContext->audio) {
|
||||
for (i = 0; i < len; ++i) {
|
||||
data[i] = 0;
|
||||
}
|
||||
memset(data, 0, len);
|
||||
return;
|
||||
}
|
||||
audioContext->ratio = audioContext->obtainedSpec.freq / (float) audioContext->audio->sampleRate;
|
||||
struct StereoSample* ssamples = (struct StereoSample*) data;
|
||||
len /= 2 * audioContext->obtainedSpec.channels;
|
||||
if (audioContext->obtainedSpec.channels == 2) {
|
||||
pthread_mutex_lock(&audioContext->audio->bufferMutex);
|
||||
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);
|
||||
_pulldownResample(audioContext, ssamples, len);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,12 @@
|
|||
|
||||
#include <SDL.h>
|
||||
|
||||
struct StereoSample {
|
||||
Sint16 left;
|
||||
Sint16 right;
|
||||
};
|
||||
|
||||
struct GBASDLAudio {
|
||||
SDL_AudioSpec desiredSpec;
|
||||
SDL_AudioSpec obtainedSpec;
|
||||
float drift;
|
||||
float ratio;
|
||||
struct GBAAudio* audio;
|
||||
struct StereoSample currentSample;
|
||||
};
|
||||
|
||||
int GBASDLInitAudio(struct GBASDLAudio* context);
|
||||
|
|
|
@ -87,3 +87,29 @@ int CircleBufferRead32(struct CircleBuffer* buffer, int32_t* value) {
|
|||
buffer->size -= sizeof(int32_t);
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define CIRCLE_BUFFER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
struct CircleBuffer {
|
||||
void* data;
|
||||
|
@ -18,5 +19,6 @@ int CircleBufferWrite8(struct CircleBuffer* buffer, int8_t value);
|
|||
int CircleBufferWrite32(struct CircleBuffer* buffer, int32_t value);
|
||||
int CircleBufferRead8(struct CircleBuffer* buffer, int8_t* value);
|
||||
int CircleBufferRead32(struct CircleBuffer* buffer, int32_t* value);
|
||||
int CircleBufferRead(struct CircleBuffer* buffer, void* output, size_t length);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue