137 lines
4.2 KiB
C++
137 lines
4.2 KiB
C++
|
|
#if defined(USE_SDL_AUDIO)
|
|
|
|
#include <SDL2/SDL.h>
|
|
#include "audiostream.h"
|
|
#include "stdclass.h"
|
|
|
|
static SDL_AudioDeviceID audiodev;
|
|
static bool needs_resampling;
|
|
static cResetEvent read_wait;
|
|
static cMutex stream_mutex;
|
|
static struct {
|
|
uint32_t prevs;
|
|
uint32_t sample_buffer[2048];
|
|
} audiobuf;
|
|
static unsigned sample_count = 0;
|
|
|
|
// To easily access samples.
|
|
union Sample { int16_t s[2]; uint32_t l; };
|
|
|
|
static float InterpolateCatmull4pt3oX(float x0, float x1, float x2, float x3, float t) {
|
|
return 0.45 * ((2 * x1) + t * ((-x0 + x2) + t * ((2 * x0 - 5 * x1 + 4 * x2 - x3) + t * (-x0 + 3 * x1 - 3 * x2 + x3))));
|
|
}
|
|
|
|
static void sdl2_audiocb(void* userdata, Uint8* stream, int len) {
|
|
stream_mutex.Lock();
|
|
// Wait until there's enough samples to feed the kraken
|
|
unsigned oslen = len / sizeof(uint32_t);
|
|
unsigned islen = needs_resampling ? oslen * 16 / 17 : oslen;
|
|
unsigned minlen = needs_resampling ? islen + 2 : islen; // Resampler looks ahead by 2 samples.
|
|
|
|
if (sample_count < minlen) {
|
|
// No data, just output a bit of silence for the underrun
|
|
memset(stream, 0, len);
|
|
stream_mutex.Unlock();
|
|
read_wait.Set();
|
|
return;
|
|
}
|
|
|
|
if (!needs_resampling) {
|
|
// Just copy bytes for this case.
|
|
memcpy(stream, &audiobuf.sample_buffer[0], len);
|
|
}
|
|
else {
|
|
// 44.1KHz to 48KHz (actually 46.86KHz) resampling
|
|
uint32_t *outbuf = (uint32_t*)stream;
|
|
const float ra = 1.0f / 17;
|
|
Sample *sbuf = (Sample*)&audiobuf.sample_buffer[0]; // [-1] stores the previous iteration last sample output
|
|
for (int i = 0; i < islen/16; i++) {
|
|
*outbuf++ = sbuf[i*16+ 0].l; // First sample stays at the same location.
|
|
for (int k = 1; k < 17; k++) {
|
|
Sample r;
|
|
// Note we access offset -1 on first iteration, as to access prevs
|
|
r.s[0] = InterpolateCatmull4pt3oX(sbuf[i*16+k-2].s[0], sbuf[i*16+k-1].s[0], sbuf[i*16+k].s[0], sbuf[i*16+k+1].s[0], 1 - ra*k);
|
|
r.s[1] = InterpolateCatmull4pt3oX(sbuf[i*16+k-2].s[1], sbuf[i*16+k-1].s[1], sbuf[i*16+k].s[1], sbuf[i*16+k+1].s[1], 1 - ra*k);
|
|
*outbuf++ = r.l;
|
|
}
|
|
}
|
|
audiobuf.prevs = audiobuf.sample_buffer[islen-1];
|
|
}
|
|
|
|
// Move samples in the buffer and consume them
|
|
memmove(&audiobuf.sample_buffer[0], &audiobuf.sample_buffer[islen], (sample_count-islen)*sizeof(uint32_t));
|
|
sample_count -= islen;
|
|
|
|
stream_mutex.Unlock();
|
|
read_wait.Set();
|
|
}
|
|
|
|
static void sdl2_audio_init() {
|
|
if (!SDL_WasInit(SDL_INIT_AUDIO))
|
|
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
|
|
|
// Support 44.1KHz (native) but also upsampling to 48KHz
|
|
SDL_AudioSpec wav_spec, out_spec;
|
|
memset(&wav_spec, 0, sizeof(wav_spec));
|
|
wav_spec.freq = 44100;
|
|
wav_spec.format = AUDIO_S16;
|
|
wav_spec.channels = 2;
|
|
wav_spec.samples = 1024; // Must be power of two
|
|
wav_spec.callback = sdl2_audiocb;
|
|
|
|
// Try 44.1KHz which should be faster since it's native.
|
|
audiodev = SDL_OpenAudioDevice(NULL, 0, &wav_spec, &out_spec, 0);
|
|
if (!audiodev) {
|
|
needs_resampling = true;
|
|
wav_spec.freq = 48000;
|
|
audiodev = SDL_OpenAudioDevice(NULL, 0, &wav_spec, &out_spec, 0);
|
|
verify(audiodev);
|
|
}
|
|
}
|
|
|
|
static u32 sdl2_audio_push(void* frame, u32 samples, bool wait) {
|
|
// Unpause the device shall it be paused.
|
|
if (SDL_GetAudioDeviceStatus(audiodev) != SDL_AUDIO_PLAYING)
|
|
SDL_PauseAudioDevice(audiodev, 0);
|
|
|
|
// If wait, then wait for the buffer to be smaller than a certain size.
|
|
stream_mutex.Lock();
|
|
if (wait) {
|
|
while (sample_count + samples > sizeof(audiobuf.sample_buffer)/sizeof(audiobuf.sample_buffer[0])) {
|
|
stream_mutex.Unlock();
|
|
read_wait.Wait();
|
|
read_wait.Reset();
|
|
stream_mutex.Lock();
|
|
}
|
|
}
|
|
|
|
// Copy as many samples as possible, drop any remaining (this should not happen usually)
|
|
unsigned free_samples = sizeof(audiobuf.sample_buffer) / sizeof(audiobuf.sample_buffer[0]) - sample_count;
|
|
unsigned tocopy = samples < free_samples ? samples : free_samples;
|
|
memcpy(&audiobuf.sample_buffer[sample_count], frame, tocopy * sizeof(uint32_t));
|
|
sample_count += tocopy;
|
|
stream_mutex.Unlock();
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void sdl2_audio_term() {
|
|
// Stop audio playback.
|
|
SDL_PauseAudioDevice(audiodev, 1);
|
|
read_wait.Set();
|
|
}
|
|
|
|
audiobackend_t audiobackend_sdl2audio = {
|
|
"sdl2", // Slug
|
|
"Simple DirectMedia Layer 2 Audio", // Name
|
|
&sdl2_audio_init,
|
|
&sdl2_audio_push,
|
|
&sdl2_audio_term
|
|
};
|
|
|
|
static bool sdl2audiobe = RegisterAudioBackend(&audiobackend_sdl2audio);
|
|
|
|
#endif
|
|
|