From ed8bcd7329c46747585c0570d6a5609226a61476 Mon Sep 17 00:00:00 2001 From: David Guillen Fandos Date: Wed, 8 May 2019 20:47:58 +0200 Subject: [PATCH] Add SDL2 audio backend. The backend supports 44.1KHz and 48KHz (with resamping). The resampler is not great, has some noise but no idea where it comes from. This enables the switch port, since using SDL2 is the quickest way to get audio working. TODO: Add support in the cmake, once cmake is fixed at master/HEAD. --- core/oslib/audiobackend_sdl2.cpp | 136 +++++++++++++++++++++++++++++++ shell/linux/Makefile | 6 ++ 2 files changed, 142 insertions(+) create mode 100644 core/oslib/audiobackend_sdl2.cpp diff --git a/core/oslib/audiobackend_sdl2.cpp b/core/oslib/audiobackend_sdl2.cpp new file mode 100644 index 000000000..39d2e8464 --- /dev/null +++ b/core/oslib/audiobackend_sdl2.cpp @@ -0,0 +1,136 @@ + +#if defined(USE_SDL_AUDIO) + +#include +#include "oslib/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 + diff --git a/shell/linux/Makefile b/shell/linux/Makefile index 6af3aed05..b96a05891 100644 --- a/shell/linux/Makefile +++ b/shell/linux/Makefile @@ -6,6 +6,7 @@ FOR_LINUX :=1 WEBUI :=1 USE_OSS := 1 #USE_PULSEAUDIO := 1 +#USE_SDLAUDIO := 1 #USE_LIBAO := 1 USE_EVDEV := 1 USE_UDEV := 1 @@ -360,6 +361,11 @@ ifdef USE_PULSEAUDIO LIBS += `pkg-config --libs libpulse-simple` endif +ifdef USE_SDLAUDIO + CXXFLAGS += `sdl2-config --cflags` -D USE_SDL_AUDIO + LIBS += `sdl2-config --libs` +endif + ifdef USE_LIBAO CXXFLAGS += `pkg-config --cflags ao` -D USE_LIBAO LIBS += `pkg-config --libs ao`