From cbe44f7ff1ee6f299269d14e454ab4a1a6b31a92 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Fri, 6 Jan 2017 02:16:26 -0800 Subject: [PATCH] Added support for using libsamplerate to do audio resampling --- src/audio/SDL_audiocvt.c | 291 +++++++++++++++++++++++++++++++-------- 1 file changed, 230 insertions(+), 61 deletions(-) diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index e54cba5a88e05..58f763b1c5e03 100644 --- a/src/audio/SDL_audiocvt.c +++ b/src/audio/SDL_audiocvt.c @@ -25,9 +25,14 @@ #include "SDL_audio.h" #include "SDL_audio_c.h" +#include "SDL_loadso.h" #include "SDL_assert.h" #include "../SDL_dataqueue.h" +#ifdef HAVE_LIBSAMPLERATE +#include "samplerate.h" +#endif + /* Effectively mix right and left channels into a single channel */ static void SDLCALL @@ -598,6 +603,9 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt, return (cvt->needed); } +typedef int (*SDL_ResampleAudioStreamFunc)(SDL_AudioStream *stream, const float *inbuf, const int inbuflen, float *outbuf, const int outbuflen); +typedef void (*SDL_ResetAudioStreamResamplerFunc)(SDL_AudioStream *stream); +typedef void (*SDL_CleanupAudioStreamResamplerFunc)(SDL_AudioStream *stream); struct SDL_AudioStream { @@ -618,11 +626,203 @@ struct SDL_AudioStream int dst_rate; double rate_incr; Uint8 pre_resample_channels; - SDL_bool resampler_seeded; - float resampler_state[8]; int packetlen; + void *resampler_state; + SDL_ResampleAudioStreamFunc resampler_func; + SDL_ResetAudioStreamResamplerFunc reset_resampler_func; + SDL_CleanupAudioStreamResamplerFunc cleanup_resampler_func; }; +#ifdef HAVE_LIBSAMPLERATE + +typedef struct +{ + void *SRC_lib; + + SRC_STATE* (*src_new)(int converter_type, int channels, int *error); + int (*src_process)(SRC_STATE *state, SRC_DATA *data); + int (*src_reset)(SRC_STATE *state); + SRC_STATE* (*src_delete)(SRC_STATE *state); + const char* (*src_strerror)(int error); + + SRC_STATE *SRC_state; +} SDL_AudioStreamResamplerState_SRC; + +static SDL_bool +LoadLibSampleRate(SDL_AudioStreamResamplerState_SRC *state) +{ +#ifdef LIBSAMPLERATE_DYNAMIC + state->SRC_lib = SDL_LoadObject(LIBSAMPLERATE_DYNAMIC); + if (!state->SRC_lib) { + return SDL_FALSE; + } +#endif + + state->src_new = (SRC_STATE* (*)(int converter_type, int channels, int *error))SDL_LoadFunction(state->SRC_lib, "src_new"); + state->src_process = (int (*)(SRC_STATE *state, SRC_DATA *data))SDL_LoadFunction(state->SRC_lib, "src_process"); + state->src_reset = (int(*)(SRC_STATE *state))SDL_LoadFunction(state->SRC_lib, "src_reset"); + state->src_delete = (SRC_STATE* (*)(SRC_STATE *state))SDL_LoadFunction(state->SRC_lib, "src_delete"); + state->src_strerror = (const char* (*)(int error))SDL_LoadFunction(state->SRC_lib, "src_strerror"); + if (!state->src_new || !state->src_process || !state->src_reset || !state->src_delete || !state->src_strerror) { + return SDL_FALSE; + } + return SDL_TRUE; +} + +static int +SDL_ResampleAudioStream_SRC(SDL_AudioStream *stream, const float *inbuf, const int inbuflen, float *outbuf, const int outbuflen) +{ + SDL_AudioStreamResamplerState_SRC *state = (SDL_AudioStreamResamplerState_SRC*)stream->resampler_state; + SRC_DATA data; + int result; + + data.data_in = inbuf; + data.input_frames = inbuflen / ( sizeof(float) * stream->pre_resample_channels ); + data.input_frames_used = 0; + + data.data_out = outbuf; + data.output_frames = outbuflen / (sizeof(float) * stream->pre_resample_channels); + + data.end_of_input = 0; + data.src_ratio = stream->rate_incr; + + result = state->src_process(state->SRC_state, &data); + if (result != 0) { + SDL_SetError("src_process() failed: %s", state->src_strerror(result)); + return 0; + } + + /* If this fails, we need to store them off somewhere */ + SDL_assert(data.input_frames_used == data.input_frames); + + return data.output_frames_gen * (sizeof(float) * stream->pre_resample_channels); +} + +static void +SDL_ResetAudioStreamResampler_SRC(SDL_AudioStream *stream) +{ + SDL_AudioStreamResamplerState_SRC *state = (SDL_AudioStreamResamplerState_SRC*)stream->resampler_state; + state->src_reset(state->SRC_state); +} + +static void +SDL_CleanupAudioStreamResampler_SRC(SDL_AudioStream *stream) +{ + SDL_AudioStreamResamplerState_SRC *state = (SDL_AudioStreamResamplerState_SRC*)stream->resampler_state; + if (state) { + if (state->SRC_lib) { + SDL_UnloadObject(state->SRC_lib); + } + state->src_delete(state->SRC_state); + SDL_free(state); + } + + stream->resampler_state = NULL; + stream->resampler_func = NULL; + stream->reset_resampler_func = NULL; + stream->cleanup_resampler_func = NULL; +} + +static SDL_bool +SetupLibSampleRateResampling(SDL_AudioStream *stream) +{ + int result; + + SDL_AudioStreamResamplerState_SRC *state = (SDL_AudioStreamResamplerState_SRC *)SDL_calloc(1, sizeof(*state)); + if (!state) { + return SDL_FALSE; + } + + if (!LoadLibSampleRate(state)) { + SDL_free(state); + return SDL_FALSE; + } + + stream->resampler_state = state; + stream->resampler_func = SDL_ResampleAudioStream_SRC; + stream->reset_resampler_func = SDL_ResetAudioStreamResampler_SRC; + stream->cleanup_resampler_func = SDL_CleanupAudioStreamResampler_SRC; + + state->SRC_state = state->src_new(SRC_SINC_FASTEST, stream->pre_resample_channels, &result); + if (!state->SRC_state) { + SDL_SetError("src_new() failed: %s", state->src_strerror(result)); + SDL_CleanupAudioStreamResampler_SRC(stream); + return SDL_FALSE; + } + return SDL_TRUE; +} + +#endif /* HAVE_LIBSAMPLERATE */ + +typedef struct +{ + SDL_bool resampler_seeded; + float resampler_state[8]; +} SDL_AudioStreamResamplerState; + +static int +SDL_ResampleAudioStream(SDL_AudioStream *stream, const float *inbuf, const int inbuflen, float *outbuf, const int outbuflen) +{ + /* !!! FIXME: this resampler sucks, but not much worse than our usual resampler. :) */ /* ... :( */ + SDL_AudioStreamResamplerState *state = (SDL_AudioStreamResamplerState*)stream->resampler_state; + const int chans = (int)stream->pre_resample_channels; + const int framelen = chans * sizeof(float); + const int total = (inbuflen / framelen); + const int finalpos = total - chans; + const double src_incr = 1.0 / stream->rate_incr; + double idx = 0.0; + float *dst = outbuf; + float last_sample[SDL_arraysize(state->resampler_state)]; + int consumed = 0; + int i; + + SDL_assert(chans <= SDL_arraysize(last_sample)); + SDL_assert((inbuflen % framelen) == 0); + + if (!state->resampler_seeded) { + for (i = 0; i < chans; i++) { + state->resampler_state[i] = inbuf[i]; + } + state->resampler_seeded = SDL_TRUE; + } + + for (i = 0; i < chans; i++) { + last_sample[i] = state->resampler_state[i]; + } + + while (consumed < total) { + const int pos = ((int)idx) * chans; + const float *src = &inbuf[(pos >= finalpos) ? finalpos : pos]; + SDL_assert(dst < (outbuf + (outbuflen / framelen))); + for (i = 0; i < chans; i++) { + const float val = *(src++); + *(dst++) = (val + last_sample[i]) * 0.5f; + last_sample[i] = val; + } + consumed = pos + chans; + idx += src_incr; + } + + for (i = 0; i < chans; i++) { + state->resampler_state[i] = last_sample[i]; + } + + return (int)((dst - outbuf) * sizeof(float)); +} + +static void +SDL_ResetAudioStreamResampler(SDL_AudioStream *stream) +{ + SDL_AudioStreamResamplerState *state = (SDL_AudioStreamResamplerState*)stream->resampler_state; + state->resampler_seeded = SDL_FALSE; +} + +static void +SDL_CleanupAudioStreamResampler(SDL_AudioStream *stream) +{ + SDL_free(stream->resampler_state); +} + SDL_AudioStream *SDL_NewAudioStream(const SDL_AudioFormat src_format, const Uint8 src_channels, const int src_rate, @@ -661,84 +861,50 @@ SDL_AudioStream *SDL_NewAudioStream(const SDL_AudioFormat src_format, if (src_rate == dst_rate) { retval->cvt_before_resampling.needed = SDL_FALSE; retval->cvt_before_resampling.len_mult = 1; - if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, src_format, src_channels, dst_rate, dst_format, dst_channels, dst_rate) == -1) { - SDL_free(retval); + if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, src_format, src_channels, dst_rate, dst_format, dst_channels, dst_rate) < 0) { + SDL_FreeAudioStream(retval); return NULL; /* SDL_BuildAudioCVT should have called SDL_SetError. */ } } else { /* Don't resample at first. Just get us to Float32 format. */ /* !!! FIXME: convert to int32 on devices without hardware float. */ - if (SDL_BuildAudioCVT(&retval->cvt_before_resampling, src_format, src_channels, src_rate, AUDIO_F32SYS, pre_resample_channels, src_rate) == -1) { - SDL_free(retval); + if (SDL_BuildAudioCVT(&retval->cvt_before_resampling, src_format, src_channels, src_rate, AUDIO_F32SYS, pre_resample_channels, src_rate) < 0) { + SDL_FreeAudioStream(retval); return NULL; /* SDL_BuildAudioCVT should have called SDL_SetError. */ } +#ifdef HAVE_LIBSAMPLERATE + SetupLibSampleRateResampling(retval); +#endif + + if (!retval->resampler_func) { + retval->resampler_state = SDL_calloc(1, sizeof(SDL_AudioStreamResamplerState)); + if (!retval->resampler_state) { + SDL_FreeAudioStream(retval); + SDL_OutOfMemory(); + return NULL; + } + retval->resampler_func = SDL_ResampleAudioStream; + retval->reset_resampler_func = SDL_ResetAudioStreamResampler; + retval->cleanup_resampler_func = SDL_CleanupAudioStreamResampler; + } + /* Convert us to the final format after resampling. */ - if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, AUDIO_F32SYS, pre_resample_channels, dst_rate, dst_format, dst_channels, dst_rate) == -1) { - SDL_free(retval); + if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, AUDIO_F32SYS, pre_resample_channels, dst_rate, dst_format, dst_channels, dst_rate) < 0) { + SDL_FreeAudioStream(retval); return NULL; /* SDL_BuildAudioCVT should have called SDL_SetError. */ } } retval->queue = SDL_NewDataQueue(packetlen, packetlen * 2); if (!retval->queue) { - SDL_free(retval); + SDL_FreeAudioStream(retval); return NULL; /* SDL_NewDataQueue should have called SDL_SetError. */ } return retval; } - -static int -ResampleAudioStream(SDL_AudioStream *stream, const float *inbuf, const int inbuflen, float *outbuf, const int outbuflen) -{ - /* !!! FIXME: this resampler sucks, but not much worse than our usual resampler. :) */ /* ... :( */ - const int chans = (int) stream->pre_resample_channels; - const int framelen = chans * sizeof (float); - const int total = (inbuflen / framelen); - const int finalpos = total - chans; - const double src_incr = 1.0 / stream->rate_incr; - double idx = 0.0; - float *dst = outbuf; - float last_sample[SDL_arraysize(stream->resampler_state)]; - int consumed = 0; - int i; - - SDL_assert(chans <= SDL_arraysize(last_sample)); - SDL_assert((inbuflen % framelen) == 0); - - if (!stream->resampler_seeded) { - for (i = 0; i < chans; i++) { - stream->resampler_state[i] = inbuf[i]; - } - stream->resampler_seeded = SDL_TRUE; - } - - for (i = 0; i < chans; i++) { - last_sample[i] = stream->resampler_state[i]; - } - - while (consumed < total) { - const int pos = ((int) idx) * chans; - const float *src = &inbuf[(pos >= finalpos) ? finalpos : pos]; - SDL_assert(dst < (outbuf + (outbuflen / framelen))); - for (i = 0; i < chans; i++) { - const float val = *(src++); - *(dst++) = (val + last_sample[i]) * 0.5f; - last_sample[i] = val; - } - consumed = pos + chans; - idx += src_incr; - } - - for (i = 0; i < chans; i++) { - stream->resampler_state[i] = last_sample[i]; - } - - return (int) ((dst - outbuf) * sizeof (float)); -} - static Uint8 * EnsureBufferSize(Uint8 **buf, int *len, const int newlen) { @@ -791,7 +957,7 @@ SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _bufle if (workbuf == NULL) { return -1; /* probably out of memory. */ } - buflen = ResampleAudioStream(stream, (float *) buf, buflen, workbuf, workbuflen); + buflen = stream->resampler_func(stream, (float *) buf, buflen, workbuf, workbuflen); buf = workbuf; } @@ -832,7 +998,7 @@ SDL_AudioStreamClear(SDL_AudioStream *stream) SDL_InvalidParamError("stream"); } else { SDL_ClearDataQueue(stream->queue, stream->packetlen * 2); - stream->resampler_seeded = SDL_FALSE; + stream->reset_resampler_func(stream); } } @@ -866,6 +1032,9 @@ void SDL_FreeAudioStream(SDL_AudioStream *stream) { if (stream) { + if (stream->cleanup_resampler_func) { + stream->cleanup_resampler_func(stream); + } SDL_FreeDataQueue(stream->queue); SDL_free(stream->work_buffer); SDL_free(stream->resample_buffer);