|
| 1 | + |
| 2 | +#if defined(USE_SDL_AUDIO) |
| 3 | + |
| 4 | +#include <SDL2/SDL.h> |
| 5 | +#include "oslib/audiostream.h" |
| 6 | +#include "stdclass.h" |
| 7 | + |
| 8 | +static SDL_AudioDeviceID audiodev; |
| 9 | +static bool needs_resampling; |
| 10 | +static cResetEvent read_wait; |
| 11 | +static cMutex stream_mutex; |
| 12 | +static struct { |
| 13 | + uint32_t prevs; |
| 14 | + uint32_t sample_buffer[2048]; |
| 15 | +} audiobuf; |
| 16 | +static unsigned sample_count = 0; |
| 17 | + |
| 18 | +// To easily access samples. |
| 19 | +union Sample { int16_t s[2]; uint32_t l; }; |
| 20 | + |
| 21 | +static float InterpolateCatmull4pt3oX(float x0, float x1, float x2, float x3, float t) { |
| 22 | + return 0.45 * ((2 * x1) + t * ((-x0 + x2) + t * ((2 * x0 - 5 * x1 + 4 * x2 - x3) + t * (-x0 + 3 * x1 - 3 * x2 + x3)))); |
| 23 | +} |
| 24 | + |
| 25 | +static void sdl2_audiocb(void* userdata, Uint8* stream, int len) { |
| 26 | + stream_mutex.Lock(); |
| 27 | + // Wait until there's enough samples to feed the kraken |
| 28 | + unsigned oslen = len / sizeof(uint32_t); |
| 29 | + unsigned islen = needs_resampling ? oslen * 16 / 17 : oslen; |
| 30 | + unsigned minlen = needs_resampling ? islen + 2 : islen; // Resampler looks ahead by 2 samples. |
| 31 | + |
| 32 | + if (sample_count < minlen) { |
| 33 | + // No data, just output a bit of silence for the underrun |
| 34 | + memset(stream, 0, len); |
| 35 | + stream_mutex.Unlock(); |
| 36 | + read_wait.Set(); |
| 37 | + return; |
| 38 | + } |
| 39 | + |
| 40 | + if (!needs_resampling) { |
| 41 | + // Just copy bytes for this case. |
| 42 | + memcpy(stream, &audiobuf.sample_buffer[0], len); |
| 43 | + } |
| 44 | + else { |
| 45 | + // 44.1KHz to 48KHz (actually 46.86KHz) resampling |
| 46 | + uint32_t *outbuf = (uint32_t*)stream; |
| 47 | + const float ra = 1.0f / 17; |
| 48 | + Sample *sbuf = (Sample*)&audiobuf.sample_buffer[0]; // [-1] stores the previous iteration last sample output |
| 49 | + for (int i = 0; i < islen/16; i++) { |
| 50 | + *outbuf++ = sbuf[i*16+ 0].l; // First sample stays at the same location. |
| 51 | + for (int k = 1; k < 17; k++) { |
| 52 | + Sample r; |
| 53 | + // Note we access offset -1 on first iteration, as to access prevs |
| 54 | + 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); |
| 55 | + 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); |
| 56 | + *outbuf++ = r.l; |
| 57 | + } |
| 58 | + } |
| 59 | + audiobuf.prevs = audiobuf.sample_buffer[islen-1]; |
| 60 | + } |
| 61 | + |
| 62 | + // Move samples in the buffer and consume them |
| 63 | + memmove(&audiobuf.sample_buffer[0], &audiobuf.sample_buffer[islen], (sample_count-islen)*sizeof(uint32_t)); |
| 64 | + sample_count -= islen; |
| 65 | + |
| 66 | + stream_mutex.Unlock(); |
| 67 | + read_wait.Set(); |
| 68 | +} |
| 69 | + |
| 70 | +static void sdl2_audio_init() { |
| 71 | + if (!SDL_WasInit(SDL_INIT_AUDIO)) |
| 72 | + SDL_InitSubSystem(SDL_INIT_AUDIO); |
| 73 | + |
| 74 | + // Support 44.1KHz (native) but also upsampling to 48KHz |
| 75 | + SDL_AudioSpec wav_spec, out_spec; |
| 76 | + memset(&wav_spec, 0, sizeof(wav_spec)); |
| 77 | + wav_spec.freq = 44100; |
| 78 | + wav_spec.format = AUDIO_S16; |
| 79 | + wav_spec.channels = 2; |
| 80 | + wav_spec.samples = 1024; // Must be power of two |
| 81 | + wav_spec.callback = sdl2_audiocb; |
| 82 | + |
| 83 | + // Try 44.1KHz which should be faster since it's native. |
| 84 | + audiodev = SDL_OpenAudioDevice(NULL, 0, &wav_spec, &out_spec, 0); |
| 85 | + if (!audiodev) { |
| 86 | + needs_resampling = true; |
| 87 | + wav_spec.freq = 48000; |
| 88 | + audiodev = SDL_OpenAudioDevice(NULL, 0, &wav_spec, &out_spec, 0); |
| 89 | + verify(audiodev); |
| 90 | + } |
| 91 | +} |
| 92 | + |
| 93 | +static u32 sdl2_audio_push(void* frame, u32 samples, bool wait) { |
| 94 | + // Unpause the device shall it be paused. |
| 95 | + if (SDL_GetAudioDeviceStatus(audiodev) != SDL_AUDIO_PLAYING) |
| 96 | + SDL_PauseAudioDevice(audiodev, 0); |
| 97 | + |
| 98 | + // If wait, then wait for the buffer to be smaller than a certain size. |
| 99 | + stream_mutex.Lock(); |
| 100 | + if (wait) { |
| 101 | + while (sample_count + samples > sizeof(audiobuf.sample_buffer)/sizeof(audiobuf.sample_buffer[0])) { |
| 102 | + stream_mutex.Unlock(); |
| 103 | + read_wait.Wait(); |
| 104 | + read_wait.Reset(); |
| 105 | + stream_mutex.Lock(); |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + // Copy as many samples as possible, drop any remaining (this should not happen usually) |
| 110 | + unsigned free_samples = sizeof(audiobuf.sample_buffer) / sizeof(audiobuf.sample_buffer[0]) - sample_count; |
| 111 | + unsigned tocopy = samples < free_samples ? samples : free_samples; |
| 112 | + memcpy(&audiobuf.sample_buffer[sample_count], frame, tocopy * sizeof(uint32_t)); |
| 113 | + sample_count += tocopy; |
| 114 | + stream_mutex.Unlock(); |
| 115 | + |
| 116 | + return 1; |
| 117 | +} |
| 118 | + |
| 119 | +static void sdl2_audio_term() { |
| 120 | + // Stop audio playback. |
| 121 | + SDL_PauseAudioDevice(audiodev, 1); |
| 122 | + read_wait.Set(); |
| 123 | +} |
| 124 | + |
| 125 | +audiobackend_t audiobackend_sdl2audio = { |
| 126 | + "sdl2", // Slug |
| 127 | + "Simple DirectMedia Layer 2 Audio", // Name |
| 128 | + &sdl2_audio_init, |
| 129 | + &sdl2_audio_push, |
| 130 | + &sdl2_audio_term |
| 131 | +}; |
| 132 | + |
| 133 | +static bool sdl2audiobe = RegisterAudioBackend(&audiobackend_sdl2audio); |
| 134 | + |
| 135 | +#endif |
| 136 | + |
0 commit comments