Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ogc: rework audio driver to avoid clicks and gaps #38

Merged
merged 1 commit into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 43 additions & 15 deletions src/audio/ogc/SDL_ogcaudio.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,28 @@ static void audio_frame_finished(AESNDPB *pb, u32 state, void *arg)
SDL_AudioDevice *this = (SDL_AudioDevice *)arg;

if (state == VOICE_STATE_STREAM) {
LWP_CondBroadcast(this->hidden->cv);
const size_t buffer_size = DMA_BUFFER_SIZE;
s8 playing_buffer;
void *buffer;

/* Immediately send the next buffer to the DSP. It's important that
* AESND_SetVoiceBuffer() gets called before this callback returns, or
* some audio gaps might be audible. */
contextLock(this);
playing_buffer = (this->hidden->playing_buffer + 1) % NUM_BUFFERS;
buffer = this->hidden->dma_buffers[playing_buffer];
AESND_SetVoiceBuffer(pb, buffer, buffer_size);
this->hidden->playing_buffer = playing_buffer;
contextUnlock(this);

/* If a frame has finished playing, it means that the corresponding
* buffer is no longer in use and can be filled up again. We signal
* this event to the audio thread via a semaphore. */
LWP_SemPost(this->hidden->available_buffers);
} if (state == VOICE_STATE_STOPPED) {
contextLock(this);
this->hidden->playing_buffer = -1;
contextUnlock(this);
}
}

Expand All @@ -126,14 +147,19 @@ static int OGCAUDIO_OpenDevice(_THIS, const char *devname)
devname, this->spec.freq, this->spec.channels);

memset(hidden, 0, sizeof(*hidden));
hidden->playing_buffer = -1;
this->hidden = hidden;

AESND_Init();
AESND_Pause(1);

/* Initialise internal state */
LWP_MutexInit(&hidden->lock, false);
LWP_CondInit(&hidden->cv);
/* We set the initial number of available buffers to NUM_BUFFERS - 1, since
* SDL first calls GetDeviceBuf() and starts filling it without first
* calling WaitDevice(). So we consider the first buffer to be busy already
* at start. */
LWP_SemInit(&hidden->available_buffers, NUM_BUFFERS - 1, NUM_BUFFERS);

if (this->spec.freq <= 0 || this->spec.freq > 144000)
this->spec.freq = DSP_DEFAULT_FREQ;
Expand Down Expand Up @@ -170,26 +196,27 @@ static int OGCAUDIO_OpenDevice(_THIS, const char *devname)
static void OGCAUDIO_PlayDevice(_THIS)
{
void *buffer;
size_t nextbuf;
size_t buffer_size = DMA_BUFFER_SIZE;

/* This only sends the first audio buffer. The following ones will always
* be send from the audio_frame_finished() callback, without having to
* switch between threads. */
contextLock(this);

nextbuf = this->hidden->nextbuf;
this->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS;

if (this->hidden->playing_buffer < 0) {
buffer = this->hidden->dma_buffers[++this->hidden->playing_buffer];
AESND_SetVoiceBuffer(this->hidden->voice, buffer, DMA_BUFFER_SIZE);
}
contextUnlock(this);

buffer = this->hidden->dma_buffers[nextbuf];
DCStoreRange(buffer, buffer_size);
AESND_SetVoiceBuffer(this->hidden->voice, buffer, buffer_size);
}

static void OGCAUDIO_WaitDevice(_THIS)
{
contextLock(this);
LWP_CondWait(this->hidden->cv, this->hidden->lock);
contextUnlock(this);
s8 nextbuf;

/* This will block until at least one buffer is available for writing. */
LWP_SemWait(this->hidden->available_buffers);

nextbuf = this->hidden->nextbuf;
this->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS;
}

static Uint8 *OGCAUDIO_GetDeviceBuf(_THIS)
Expand All @@ -201,6 +228,7 @@ static void OGCAUDIO_CloseDevice(_THIS)
{
struct SDL_PrivateAudioData *hidden = this->hidden;

LWP_SemDestroy(hidden->available_buffers);
if (hidden->voice) {
AESND_SetVoiceStop(hidden->voice, true);
AESND_FreeVoice(hidden->voice);
Expand Down
9 changes: 5 additions & 4 deletions src/audio/ogc/SDL_ogcaudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
#include <aesndlib.h>
#include <ogcsys.h>

#include <ogc/cond.h>
#include <ogc/mutex.h>
#include <ogc/semaphore.h>

/* Hidden "this" pointer for the audio functions */
#define _THIS SDL_AudioDevice *this

#define NUM_BUFFERS 2 /* -- Minimum 2! */
#define NUM_BUFFERS 4 /* -- Minimum 2! */
#define SAMPLES_PER_DMA_BUFFER (DSP_STREAMBUFFER_SIZE)
#define DMA_BUFFER_SIZE (SAMPLES_PER_DMA_BUFFER * 2 * sizeof(short))

Expand All @@ -44,9 +44,10 @@ struct SDL_PrivateAudioData
/* Speaker data */
Uint32 format;
Uint8 bytes_per_sample;
Uint32 nextbuf;
s8 nextbuf;
s8 playing_buffer;
mutex_t lock;
cond_t cv;
sem_t available_buffers;
};

#endif /* _SDL_ogcaudio_h_ */
Expand Down
Loading