#include "../SDL_internal.h"
#include "SDL.h"
#include "SDL_audio.h"
#include "SDL_audio_c.h"
#include "SDL_loadso.h"
#include "../SDL_dataqueue.h"
#include "SDL_cpuinfo.h"
#define DEBUG_AUDIOSTREAM 0
#ifdef __ARM_NEON
#define HAVE_NEON_INTRINSICS 1
#endif
#ifdef __SSE__
#define HAVE_SSE_INTRINSICS 1
#endif
#ifdef __SSE3__
#define HAVE_SSE3_INTRINSICS 1
#endif
#if defined(HAVE_IMMINTRIN_H) && !defined(SDL_DISABLE_IMMINTRIN_H)
#define HAVE_AVX_INTRINSICS 1
#endif
#if defined __clang__
# if (!__has_attribute(target))
# undef HAVE_AVX_INTRINSICS
# endif
# if (defined(_MSC_VER) || defined(__SCE__)) && !defined(__AVX__)
# undef HAVE_AVX_INTRINSICS
# endif
#elif defined __GNUC__
# if (__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 9)
# undef HAVE_AVX_INTRINSICS
# endif
#endif
#if HAVE_SSE3_INTRINSICS
static void SDLCALL
SDL_ConvertStereoToMono_SSE3(SDL_AudioCVT * cvt, SDL_AudioFormat format)
{
const __m128 divby2 = _mm_set1_ps(0.5f);
float *dst = (float *) cvt->buf;
const float *src = dst;
int i = cvt->len_cvt / 8;
LOG_DEBUG_CONVERT("stereo", "mono (using SSE3)");
SDL_assert(format == AUDIO_F32SYS);
while (i >= 4) {
_mm_storeu_ps(dst, _mm_mul_ps(_mm_hadd_ps(_mm_loadu_ps(src), _mm_loadu_ps(src+4)), divby2));
i -= 4; src += 8; dst += 4;
}
while (i) {
*dst = (src[0] + src[1]) * 0.5f;
dst++; i--; src += 2;
}
cvt->len_cvt /= 2;
if (cvt->filters[++cvt->filter_index]) {
cvt->filters[cvt->filter_index] (cvt, format);
}
}
#endif
#if HAVE_SSE_INTRINSICS
static void SDLCALL
SDL_ConvertMonoToStereo_SSE(SDL_AudioCVT * cvt, SDL_AudioFormat format)
{
float *dst = ((float *) (cvt->buf + (cvt->len_cvt * 2))) - 8;
const float *src = ((const float *) (cvt->buf + cvt->len_cvt)) - 4;
int i = cvt->len_cvt / sizeof (float);
LOG_DEBUG_CONVERT("mono", "stereo (using SSE)");
SDL_assert(format == AUDIO_F32SYS);
while (i >= 4) {
const __m128 input = _mm_loadu_ps(src);
_mm_storeu_ps(dst, _mm_unpacklo_ps(input, input));
_mm_storeu_ps(dst+4, _mm_unpackhi_ps(input, input));
i -= 4; src -= 4; dst -= 8;
}
src += 3; dst += 6;
while (i) {
const float srcFC = src[0];
dst[1] = srcFC;
dst[0] = srcFC;
i--; src--; dst -= 2;
}
cvt->len_cvt *= 2;
if (cvt->filters[++cvt->filter_index]) {
cvt->filters[cvt->filter_index] (cvt, format);
}
}
#endif
#include "SDL_audio_channel_converters.h"
#include "SDL_audio_resampler_filter.h"
static int
ResamplerPadding(const int inrate, const int outrate)
{
if (inrate == outrate) {
return 0;
}
if (inrate > outrate) {
return (int) SDL_ceilf(((float) (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * inrate) / ((float) outrate)));
}
return RESAMPLER_SAMPLES_PER_ZERO_CROSSING;
}
static int
SDL_ResampleAudio(const int chans, const int inrate, const int outrate,
const float *lpadding, const float *rpadding,
const float *inbuf, const int inbuflen,
float *outbuf, const int outbuflen)
{
typedef float ResampleFloatType;
const ResampleFloatType finrate = (ResampleFloatType) inrate;
const ResampleFloatType ratio = ((float) outrate) / ((float) inrate);
const int paddinglen = ResamplerPadding(inrate, outrate);
const int framelen = chans * (int)sizeof (float);
const int inframes = inbuflen / framelen;
const int wantedoutframes = (int) ((inbuflen / framelen) * ratio);
const int maxoutframes = outbuflen / framelen;
const int outframes = SDL_min(wantedoutframes, maxoutframes);
ResampleFloatType outtime = 0.0f;
float *dst = outbuf;
int i, j, chan;
for (i = 0; i < outframes; i++) {
const int srcindex = (int) (outtime * inrate);
const ResampleFloatType intime = ((ResampleFloatType) srcindex) / finrate;
const ResampleFloatType innexttime = ((ResampleFloatType) (srcindex + 1)) / finrate;
const ResampleFloatType indeltatime = innexttime - intime;
const ResampleFloatType interpolation1 = (indeltatime == 0.0f) ? 1.0f : (1.0f - ((innexttime - outtime) / indeltatime));
const int filterindex1 = (int) (interpolation1 * RESAMPLER_SAMPLES_PER_ZERO_CROSSING);
const ResampleFloatType interpolation2 = 1.0f - interpolation1;
const int filterindex2 = (int) (interpolation2 * RESAMPLER_SAMPLES_PER_ZERO_CROSSING);
for (chan = 0; chan < chans; chan++) {
float outsample = 0.0f;
for (j = 0; (filterindex1 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) {
const int srcframe = srcindex - j;
const float insample = (srcframe < 0) ? lpadding[((paddinglen + srcframe) * chans) + chan] : inbuf[(srcframe * chans) + chan];
outsample += (float)(insample * (ResamplerFilter[filterindex1 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)] + (interpolation1 * ResamplerFilterDifference[filterindex1 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)])));
}
for (j = 0; (filterindex2 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) {
const int jsamples = j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING;
const int srcframe = srcindex + 1 + j;
const float insample = (srcframe >= inframes) ? rpadding[((srcframe - inframes) * chans) + chan] : inbuf[(srcframe * chans) + chan];
outsample += (float)(insample * (ResamplerFilter[filterindex2 + jsamples] + (interpolation2 * ResamplerFilterDifference[filterindex2 + jsamples])));
}
*(dst++) = outsample;
}
outtime = ((ResampleFloatType) i) / ((ResampleFloatType) outrate);
}
return outframes * chans * sizeof (float);
}
int
SDL_ConvertAudio(SDL_AudioCVT * cvt)
{
if (cvt->buf == NULL) {
return SDL_SetError("No buffer allocated for conversion");
}
cvt->len_cvt = cvt->len;
if (cvt->filters[0] == NULL) {
return 0;
}
cvt->filter_index = 0;
cvt->filters[0] (cvt, cvt->src_format);
return 0;
}
static void SDLCALL
SDL_Convert_Byteswap(SDL_AudioCVT *cvt, SDL_AudioFormat format)
{
#if DEBUG_CONVERT
SDL_Log("SDL_AUDIO_CONVERT: Converting byte order\n");
#endif
switch (SDL_AUDIO_BITSIZE(format)) {
#define CASESWAP(b) \
case b: { \
Uint##b *ptr = (Uint##b *) cvt->buf; \
int i; \
for (i = cvt->len_cvt / sizeof (*ptr); i; --i, ++ptr) { \
*ptr = SDL_Swap##b(*ptr); \
} \
break; \
}
CASESWAP(16);
CASESWAP(32);
CASESWAP(64);
#undef CASESWAP
default: SDL_assert(!"unhandled byteswap datatype!"); break;
}
if (cvt->filters[++cvt->filter_index]) {
if (format & SDL_AUDIO_MASK_ENDIAN) {
format &= ~SDL_AUDIO_MASK_ENDIAN;
} else {
format |= SDL_AUDIO_MASK_ENDIAN;
}
cvt->filters[cvt->filter_index](cvt, format);
}
}
static int
SDL_AddAudioCVTFilter(SDL_AudioCVT *cvt, const SDL_AudioFilter filter)
{
if (cvt->filter_index >= SDL_AUDIOCVT_MAX_FILTERS) {
return SDL_SetError("Too many filters needed for conversion, exceeded maximum of %d", SDL_AUDIOCVT_MAX_FILTERS);
}
SDL_assert(filter != NULL);
cvt->filters[cvt->filter_index++] = filter;
cvt->filters[cvt->filter_index] = NULL;
return 0;
}
static int
SDL_BuildAudioTypeCVTToFloat(SDL_AudioCVT *cvt, const SDL_AudioFormat src_fmt)
{
int retval = 0;
if ((SDL_AUDIO_ISBIGENDIAN(src_fmt) != 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN) && SDL_AUDIO_BITSIZE(src_fmt) > 8) {
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert_Byteswap) < 0) {
return -1;
}
retval = 1;
}
if (!SDL_AUDIO_ISFLOAT(src_fmt)) {
const Uint16 src_bitsize = SDL_AUDIO_BITSIZE(src_fmt);
const Uint16 dst_bitsize = 32;
SDL_AudioFilter filter = NULL;
switch (src_fmt & ~SDL_AUDIO_MASK_ENDIAN) {
case AUDIO_S8: filter = SDL_Convert_S8_to_F32; break;
case AUDIO_U8: filter = SDL_Convert_U8_to_F32; break;
case AUDIO_S16: filter = SDL_Convert_S16_to_F32; break;
case AUDIO_U16: filter = SDL_Convert_U16_to_F32; break;
case AUDIO_S32: filter = SDL_Convert_S32_to_F32; break;
default: SDL_assert(!"Unexpected audio format!"); break;
}
if (!filter) {
return SDL_SetError("No conversion from source format to float available");
}
if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
return -1;
}
if (src_bitsize < dst_bitsize) {
const int mult = (dst_bitsize / src_bitsize);
cvt->len_mult *= mult;
cvt->len_ratio *= mult;
} else if (src_bitsize > dst_bitsize) {
cvt->len_ratio /= (src_bitsize / dst_bitsize);
}
retval = 1;
}
return retval;
}
static int
SDL_BuildAudioTypeCVTFromFloat(SDL_AudioCVT *cvt, const SDL_AudioFormat dst_fmt)
{
int retval = 0;
if (!SDL_AUDIO_ISFLOAT(dst_fmt)) {
const Uint16 dst_bitsize = SDL_AUDIO_BITSIZE(dst_fmt);
const Uint16 src_bitsize = 32;
SDL_AudioFilter filter = NULL;
switch (dst_fmt & ~SDL_AUDIO_MASK_ENDIAN) {
case AUDIO_S8: filter = SDL_Convert_F32_to_S8; break;
case AUDIO_U8: filter = SDL_Convert_F32_to_U8; break;
case AUDIO_S16: filter = SDL_Convert_F32_to_S16; break;
case AUDIO_U16: filter = SDL_Convert_F32_to_U16; break;
case AUDIO_S32: filter = SDL_Convert_F32_to_S32; break;
default: SDL_assert(!"Unexpected audio format!"); break;
}
if (!filter) {
return SDL_SetError("No conversion from float to format 0x%.4x available", dst_fmt);
}
if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
return -1;
}
if (src_bitsize < dst_bitsize) {
const int mult = (dst_bitsize / src_bitsize);
cvt->len_mult *= mult;
cvt->len_ratio *= mult;
} else if (src_bitsize > dst_bitsize) {
const int div = (src_bitsize / dst_bitsize);
cvt->len_ratio /= div;
}
retval = 1;
}
if ((SDL_AUDIO_ISBIGENDIAN(dst_fmt) != 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN) && SDL_AUDIO_BITSIZE(dst_fmt) > 8) {
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert_Byteswap) < 0) {
return -1;
}
retval = 1;
}
return retval;
}
#ifdef HAVE_LIBSAMPLERATE_H
static void
SDL_ResampleCVT_SRC(SDL_AudioCVT *cvt, const int chans, const SDL_AudioFormat format)
{
const float *src = (const float *) cvt->buf;
const int srclen = cvt->len_cvt;
float *dst = (float *) (cvt->buf + srclen);
const int dstlen = (cvt->len * cvt->len_mult) - srclen;
const int framelen = sizeof(float) * chans;
int result = 0;
SRC_DATA data;
SDL_zero(data);
data.data_in = (float *)src;
data.input_frames = srclen / framelen;
data.data_out = dst;
data.output_frames = dstlen / framelen;
data.src_ratio = cvt->rate_incr;
result = SRC_src_simple(&data, SRC_converter, chans);
#ifdef DEBUG_CONVERT
if (result != 0) {
SDL_Log("src_simple() failed: %s", SRC_src_strerror(result));
}
#endif
cvt->len_cvt = data.output_frames_gen * framelen;
SDL_memmove(cvt->buf, dst, cvt->len_cvt);
if (cvt->filters[++cvt->filter_index]) {
cvt->filters[cvt->filter_index](cvt, format);
}
}
#endif
static void
SDL_ResampleCVT(SDL_AudioCVT *cvt, const int chans, const SDL_AudioFormat format)
{
const int inrate = (int) (size_t) cvt->filters[SDL_AUDIOCVT_MAX_FILTERS-1];
const int outrate = (int) (size_t) cvt->filters[SDL_AUDIOCVT_MAX_FILTERS];
const float *src = (const float *) cvt->buf;
const int srclen = cvt->len_cvt;
float *dst = (float *) (cvt->buf + srclen);
const int dstlen = (cvt->len * cvt->len_mult) - srclen;
const int requestedpadding = ResamplerPadding(inrate, outrate);
int paddingsamples;
float *padding;
if (requestedpadding < SDL_MAX_SINT32 / chans) {
paddingsamples = requestedpadding * chans;
} else {
paddingsamples = 0;
}
SDL_assert(format == AUDIO_F32SYS);
padding = (float *) SDL_calloc(paddingsamples ? paddingsamples : 1, sizeof (float));
if (!padding) {
SDL_OutOfMemory();
return;
}
cvt->len_cvt = SDL_ResampleAudio(chans, inrate, outrate, padding, padding, src, srclen, dst, dstlen);
SDL_free(padding);
SDL_memmove(cvt->buf, dst, cvt->len_cvt);
if (cvt->filters[++cvt->filter_index]) {
cvt->filters[cvt->filter_index](cvt, format);
}
}
#define RESAMPLER_FUNCS(chans) \
static void SDLCALL \
SDL_ResampleCVT_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
SDL_ResampleCVT(cvt, chans, format); \
}
RESAMPLER_FUNCS(1)
RESAMPLER_FUNCS(2)
RESAMPLER_FUNCS(4)
RESAMPLER_FUNCS(6)
RESAMPLER_FUNCS(8)
#undef RESAMPLER_FUNCS
#ifdef HAVE_LIBSAMPLERATE_H
#define RESAMPLER_FUNCS(chans) \
static void SDLCALL \
SDL_ResampleCVT_SRC_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
SDL_ResampleCVT_SRC(cvt, chans, format); \
}
RESAMPLER_FUNCS(1)
RESAMPLER_FUNCS(2)
RESAMPLER_FUNCS(4)
RESAMPLER_FUNCS(6)
RESAMPLER_FUNCS(8)
#undef RESAMPLER_FUNCS
#endif
static SDL_AudioFilter
ChooseCVTResampler(const int dst_channels)
{
#ifdef HAVE_LIBSAMPLERATE_H
if (SRC_available) {
switch (dst_channels) {
case 1: return SDL_ResampleCVT_SRC_c1;
case 2: return SDL_ResampleCVT_SRC_c2;
case 4: return SDL_ResampleCVT_SRC_c4;
case 6: return SDL_ResampleCVT_SRC_c6;
case 8: return SDL_ResampleCVT_SRC_c8;
default: break;
}
}
#endif
switch (dst_channels) {
case 1: return SDL_ResampleCVT_c1;
case 2: return SDL_ResampleCVT_c2;
case 4: return SDL_ResampleCVT_c4;
case 6: return SDL_ResampleCVT_c6;
case 8: return SDL_ResampleCVT_c8;
default: break;
}
return NULL;
}
static int
SDL_BuildAudioResampleCVT(SDL_AudioCVT * cvt, const int dst_channels,
const int src_rate, const int dst_rate)
{
SDL_AudioFilter filter;
if (src_rate == dst_rate) {
return 0;
}
filter = ChooseCVTResampler(dst_channels);
if (filter == NULL) {
return SDL_SetError("No conversion available for these rates");
}
if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
return -1;
}
if (cvt->filter_index >= (SDL_AUDIOCVT_MAX_FILTERS-2)) {
return SDL_SetError("Too many filters needed for conversion, exceeded maximum of %d", SDL_AUDIOCVT_MAX_FILTERS-2);
}
cvt->filters[SDL_AUDIOCVT_MAX_FILTERS-1] = (SDL_AudioFilter) (uintptr_t) src_rate;
cvt->filters[SDL_AUDIOCVT_MAX_FILTERS] = (SDL_AudioFilter) (uintptr_t) dst_rate;
if (src_rate < dst_rate) {
const double mult = ((double) dst_rate) / ((double) src_rate);
cvt->len_mult *= (int) SDL_ceil(mult);
cvt->len_ratio *= mult;
} else {
cvt->len_ratio /= ((double) src_rate) / ((double) dst_rate);
}
cvt->len_mult *= 2;
return 1;
}
static SDL_bool
SDL_SupportedAudioFormat(const SDL_AudioFormat fmt)
{
switch (fmt) {
case AUDIO_U8:
case AUDIO_S8:
case AUDIO_U16LSB:
case AUDIO_S16LSB:
case AUDIO_U16MSB:
case AUDIO_S16MSB:
case AUDIO_S32LSB:
case AUDIO_S32MSB:
case AUDIO_F32LSB:
case AUDIO_F32MSB:
return SDL_TRUE;
default:
break;
}
return SDL_FALSE;
}
static SDL_bool
SDL_SupportedChannelCount(const int channels)
{
return ((channels >= 1) && (channels <= 8)) ? SDL_TRUE : SDL_FALSE;
}
int
SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
SDL_AudioFormat src_fmt, Uint8 src_channels, int src_rate,
SDL_AudioFormat dst_fmt, Uint8 dst_channels, int dst_rate)
{
SDL_AudioFilter channel_converter = NULL;
if (cvt == NULL) {
return SDL_InvalidParamError("cvt");
}
SDL_zerop(cvt);
if (!SDL_SupportedAudioFormat(src_fmt)) {
return SDL_SetError("Invalid source format");
}
if (!SDL_SupportedAudioFormat(dst_fmt)) {
return SDL_SetError("Invalid destination format");
}
if (!SDL_SupportedChannelCount(src_channels)) {
return SDL_SetError("Invalid source channels");
}
if (!SDL_SupportedChannelCount(dst_channels)) {
return SDL_SetError("Invalid destination channels");
}
if (src_rate <= 0) {
return SDL_SetError("Source rate is equal to or less than zero");
}
if (dst_rate <= 0) {
return SDL_SetError("Destination rate is equal to or less than zero");
}
if (src_rate >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) {
return SDL_SetError("Source rate is too high");
}
if (dst_rate >= SDL_MAX_SINT32 / RESAMPLER_SAMPLES_PER_ZERO_CROSSING) {
return SDL_SetError("Destination rate is too high");
}
#if DEBUG_CONVERT
SDL_Log("SDL_AUDIO_CONVERT: Build format %04x->%04x, channels %u->%u, rate %d->%d\n",
src_fmt, dst_fmt, src_channels, dst_channels, src_rate, dst_rate);
#endif
cvt->src_format = src_fmt;
cvt->dst_format = dst_fmt;
cvt->needed = 0;
cvt->filter_index = 0;
SDL_zeroa(cvt->filters);
cvt->len_mult = 1;
cvt->len_ratio = 1.0;
cvt->rate_incr = ((double) dst_rate) / ((double) src_rate);
SDL_ChooseAudioConverters();
if (src_rate == dst_rate && src_channels == dst_channels) {
if (src_fmt == dst_fmt) {
return 0;
}
if ((src_fmt & ~SDL_AUDIO_MASK_ENDIAN) == (dst_fmt & ~SDL_AUDIO_MASK_ENDIAN)) {
if (SDL_AUDIO_BITSIZE(dst_fmt) == 8) {
return 0;
}
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert_Byteswap) < 0) {
return -1;
}
cvt->needed = 1;
return 1;
}
}
if (SDL_BuildAudioTypeCVTToFloat(cvt, src_fmt) < 0) {
return -1;
}
SDL_assert(src_channels <= SDL_arraysize(channel_converters));
SDL_assert(dst_channels <= SDL_arraysize(channel_converters[0]));
channel_converter = channel_converters[src_channels-1][dst_channels-1];
if ((channel_converter == NULL) != (src_channels == dst_channels)) {
return SDL_SetError("Invalid channel combination");
} else if (channel_converter != NULL) {
if (channel_converter == SDL_ConvertStereoToMono) {
SDL_AudioFilter filter = NULL;
#if HAVE_SSE3_INTRINSICS
if (!filter && SDL_HasSSE3()) { filter = SDL_ConvertStereoToMono_SSE3; }
#endif
if (filter) { channel_converter = filter; }
} else if (channel_converter == SDL_ConvertMonoToStereo) {
SDL_AudioFilter filter = NULL;
#if HAVE_SSE_INTRINSICS
if (!filter && SDL_HasSSE()) { filter = SDL_ConvertMonoToStereo_SSE; }
#endif
if (filter) { channel_converter = filter; }
}
if (SDL_AddAudioCVTFilter(cvt, channel_converter) < 0) {
return -1;
}
if (src_channels < dst_channels) {
cvt->len_mult = ((cvt->len_mult * dst_channels) + (src_channels-1)) / src_channels;
}
cvt->len_ratio = (cvt->len_ratio * dst_channels) / src_channels;
src_channels = dst_channels;
}
if (SDL_BuildAudioResampleCVT(cvt, dst_channels, src_rate, dst_rate) < 0) {
return -1;
}
if (SDL_BuildAudioTypeCVTFromFloat(cvt, dst_fmt) < 0) {
return -1;
}
cvt->needed = (cvt->filter_index != 0);
return (cvt->needed);
}
typedef int (*SDL_ResampleAudioStreamFunc)(SDL_AudioStream *stream, const void *inbuf, const int inbuflen, void *outbuf, const int outbuflen);
typedef void (*SDL_ResetAudioStreamResamplerFunc)(SDL_AudioStream *stream);
typedef void (*SDL_CleanupAudioStreamResamplerFunc)(SDL_AudioStream *stream);
struct _SDL_AudioStream
{
SDL_AudioCVT cvt_before_resampling;
SDL_AudioCVT cvt_after_resampling;
SDL_DataQueue *queue;
SDL_bool first_run;
Uint8 *staging_buffer;
int staging_buffer_size;
int staging_buffer_filled;
Uint8 *work_buffer_base;
int work_buffer_len;
int src_sample_frame_size;
SDL_AudioFormat src_format;
Uint8 src_channels;
int src_rate;
int dst_sample_frame_size;
SDL_AudioFormat dst_format;
Uint8 dst_channels;
int dst_rate;
double rate_incr;
Uint8 pre_resample_channels;
int packetlen;
int resampler_padding_samples;
float *resampler_padding;
void *resampler_state;
SDL_ResampleAudioStreamFunc resampler_func;
SDL_ResetAudioStreamResamplerFunc reset_resampler_func;
SDL_CleanupAudioStreamResamplerFunc cleanup_resampler_func;
};
static Uint8 *
EnsureStreamBufferSize(SDL_AudioStream *stream, const int newlen)
{
Uint8 *ptr;
size_t offset;
if (stream->work_buffer_len >= newlen) {
ptr = stream->work_buffer_base;
} else {
ptr = (Uint8 *) SDL_realloc(stream->work_buffer_base, newlen + 32);
if (!ptr) {
SDL_OutOfMemory();
return NULL;
}
stream->work_buffer_base = ptr;
stream->work_buffer_len = newlen;
}
offset = ((size_t) ptr) & 15;
return offset ? ptr + (16 - offset) : ptr;
}
#ifdef HAVE_LIBSAMPLERATE_H
static int
SDL_ResampleAudioStream_SRC(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
{
const float *inbuf = (const float *) _inbuf;
float *outbuf = (float *) _outbuf;
const int framelen = sizeof(float) * stream->pre_resample_channels;
SRC_STATE *state = (SRC_STATE *)stream->resampler_state;
SRC_DATA data;
int result;
SDL_assert(inbuf != ((const float *) outbuf));
data.data_in = (float *)inbuf;
data.input_frames = inbuflen / framelen;
data.input_frames_used = 0;
data.data_out = outbuf;
data.output_frames = outbuflen / framelen;
data.end_of_input = 0;
data.src_ratio = stream->rate_incr;
result = SRC_src_process(state, &data);
if (result != 0) {
SDL_SetError("src_process() failed: %s", SRC_src_strerror(result));
return 0;
}
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)
{
SRC_src_reset((SRC_STATE *)stream->resampler_state);
}
static void
SDL_CleanupAudioStreamResampler_SRC(SDL_AudioStream *stream)
{
SRC_STATE *state = (SRC_STATE *)stream->resampler_state;
if (state) {
SRC_src_delete(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 = 0;
SRC_STATE *state = NULL;
if (SRC_available) {
state = SRC_src_new(SRC_converter, stream->pre_resample_channels, &result);
if (!state) {
SDL_SetError("src_new() failed: %s", SRC_src_strerror(result));
}
}
if (!state) {
SDL_CleanupAudioStreamResampler_SRC(stream);
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;
return SDL_TRUE;
}
#endif
static int
SDL_ResampleAudioStream(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
{
const Uint8 *inbufend = ((const Uint8 *) _inbuf) + inbuflen;
const float *inbuf = (const float *) _inbuf;
float *outbuf = (float *) _outbuf;
const int chans = (int) stream->pre_resample_channels;
const int inrate = stream->src_rate;
const int outrate = stream->dst_rate;
const int paddingsamples = stream->resampler_padding_samples;
const int paddingbytes = paddingsamples * sizeof (float);
float *lpadding = (float *) stream->resampler_state;
const float *rpadding = (const float *) inbufend;
const int cpy = SDL_min(inbuflen, paddingbytes);
int retval;
SDL_assert(inbuf != ((const float *) outbuf));
retval = SDL_ResampleAudio(chans, inrate, outrate, lpadding, rpadding, inbuf, inbuflen, outbuf, outbuflen);
SDL_memcpy((lpadding + paddingsamples) - (cpy / sizeof (float)), inbufend - cpy, cpy);
return retval;
}
static void
SDL_ResetAudioStreamResampler(SDL_AudioStream *stream)
{
const int len = stream->resampler_padding_samples;
SDL_memset(stream->resampler_state, '\0', len * sizeof (float));
}
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,
const SDL_AudioFormat dst_format,
const Uint8 dst_channels,
const int dst_rate)
{
const int packetlen = 4096;
Uint8 pre_resample_channels;
SDL_AudioStream *retval;
retval = (SDL_AudioStream *) SDL_calloc(1, sizeof (SDL_AudioStream));
if (!retval) {
SDL_OutOfMemory();
return NULL;
}
pre_resample_channels = SDL_min(src_channels, dst_channels);
retval->first_run = SDL_TRUE;
retval->src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels;
retval->src_format = src_format;
retval->src_channels = src_channels;
retval->src_rate = src_rate;
retval->dst_sample_frame_size = (SDL_AUDIO_BITSIZE(dst_format) / 8) * dst_channels;
retval->dst_format = dst_format;
retval->dst_channels = dst_channels;
retval->dst_rate = dst_rate;
retval->pre_resample_channels = pre_resample_channels;
retval->packetlen = packetlen;
retval->rate_incr = ((double) dst_rate) / ((double) src_rate);
retval->resampler_padding_samples = ResamplerPadding(retval->src_rate, retval->dst_rate) * pre_resample_channels;
retval->resampler_padding = (float *) SDL_calloc(retval->resampler_padding_samples ? retval->resampler_padding_samples : 1, sizeof (float));
if (retval->resampler_padding == NULL) {
SDL_FreeAudioStream(retval);
SDL_OutOfMemory();
return NULL;
}
retval->staging_buffer_size = ((retval->resampler_padding_samples / retval->pre_resample_channels) * retval->src_sample_frame_size);
if (retval->staging_buffer_size > 0) {
retval->staging_buffer = (Uint8 *) SDL_malloc(retval->staging_buffer_size);
if (retval->staging_buffer == NULL) {
SDL_FreeAudioStream(retval);
SDL_OutOfMemory();
return NULL;
}
}
if (src_rate == dst_rate) {
retval->cvt_before_resampling.needed = SDL_FALSE;
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;
}
} else {
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;
}
#ifdef HAVE_LIBSAMPLERATE_H
SetupLibSampleRateResampling(retval);
#endif
if (!retval->resampler_func) {
retval->resampler_state = SDL_calloc(retval->resampler_padding_samples, sizeof (float));
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;
}
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;
}
}
retval->queue = SDL_NewDataQueue(packetlen, packetlen * 2);
if (!retval->queue) {
SDL_FreeAudioStream(retval);
return NULL;
}
return retval;
}
static int
SDL_AudioStreamPutInternal(SDL_AudioStream *stream, const void *buf, int len, int *maxputbytes)
{
int buflen = len;
int workbuflen;
Uint8 *workbuf;
Uint8 *resamplebuf = NULL;
int resamplebuflen = 0;
int neededpaddingbytes;
int paddingbytes;
neededpaddingbytes = stream->resampler_padding_samples * sizeof (float);
paddingbytes = stream->first_run ? 0 : neededpaddingbytes;
stream->first_run = SDL_FALSE;
workbuflen = buflen;
if (stream->cvt_before_resampling.needed) {
workbuflen *= stream->cvt_before_resampling.len_mult;
}
if (stream->dst_rate != stream->src_rate) {
const int framesize = stream->pre_resample_channels * sizeof (float);
const int frames = workbuflen / framesize;
resamplebuflen = ((int) SDL_ceil(frames * stream->rate_incr)) * framesize;
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: will resample %d bytes to %d (ratio=%.6f)\n", workbuflen, resamplebuflen, stream->rate_incr);
#endif
workbuflen += resamplebuflen;
}
if (stream->cvt_after_resampling.needed) {
workbuflen *= stream->cvt_after_resampling.len_mult;
}
workbuflen += neededpaddingbytes;
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: Putting %d bytes of preconverted audio, need %d byte work buffer\n", buflen, workbuflen);
#endif
workbuf = EnsureStreamBufferSize(stream, workbuflen);
if (!workbuf) {
return -1;
}
resamplebuf = workbuf;
SDL_memcpy(workbuf + paddingbytes, buf, buflen);
if (stream->cvt_before_resampling.needed) {
stream->cvt_before_resampling.buf = workbuf + paddingbytes;
stream->cvt_before_resampling.len = buflen;
if (SDL_ConvertAudio(&stream->cvt_before_resampling) == -1) {
return -1;
}
buflen = stream->cvt_before_resampling.len_cvt;
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: After initial conversion we have %d bytes\n", buflen);
#endif
}
if (stream->dst_rate != stream->src_rate) {
if (paddingbytes) {
SDL_memcpy(workbuf, stream->resampler_padding, paddingbytes);
buflen += paddingbytes;
}
SDL_memcpy(stream->resampler_padding, workbuf + (buflen - neededpaddingbytes), neededpaddingbytes);
resamplebuf = workbuf + buflen;
SDL_assert(buflen >= neededpaddingbytes);
if (buflen > neededpaddingbytes) {
buflen = stream->resampler_func(stream, workbuf, buflen - neededpaddingbytes, resamplebuf, resamplebuflen);
} else {
buflen = 0;
}
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: After resampling we have %d bytes\n", buflen);
#endif
}
if (stream->cvt_after_resampling.needed && (buflen > 0)) {
stream->cvt_after_resampling.buf = resamplebuf;
stream->cvt_after_resampling.len = buflen;
if (SDL_ConvertAudio(&stream->cvt_after_resampling) == -1) {
return -1;
}
buflen = stream->cvt_after_resampling.len_cvt;
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: After final conversion we have %d bytes\n", buflen);
#endif
}
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: Final output is %d bytes\n", buflen);
#endif
if (maxputbytes) {
const int maxbytes = *maxputbytes;
if (buflen > maxbytes)
buflen = maxbytes;
*maxputbytes -= buflen;
}
return buflen ? SDL_WriteToDataQueue(stream->queue, resamplebuf, buflen) : 0;
}
int
SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, int len)
{
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: wants to put %d preconverted bytes\n", buflen);
#endif
if (!stream) {
return SDL_InvalidParamError("stream");
}
if (!buf) {
return SDL_InvalidParamError("buf");
}
if (len == 0) {
return 0;
}
if ((len % stream->src_sample_frame_size) != 0) {
return SDL_SetError("Can't add partial sample frames");
}
if (!stream->cvt_before_resampling.needed &&
(stream->dst_rate == stream->src_rate) &&
!stream->cvt_after_resampling.needed) {
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: no conversion needed at all, queueing %d bytes.\n", len);
#endif
return SDL_WriteToDataQueue(stream->queue, buf, len);
}
while (len > 0) {
int amount;
if (!stream->staging_buffer_filled && len >= stream->staging_buffer_size) {
return SDL_AudioStreamPutInternal(stream, buf, len, NULL);
}
if ((stream->staging_buffer_filled + len) < stream->staging_buffer_size) {
SDL_memcpy(stream->staging_buffer + stream->staging_buffer_filled, buf, len);
stream->staging_buffer_filled += len;
return 0;
}
amount = (stream->staging_buffer_size - stream->staging_buffer_filled);
SDL_assert(amount > 0);
SDL_memcpy(stream->staging_buffer + stream->staging_buffer_filled, buf, amount);
stream->staging_buffer_filled = 0;
if (SDL_AudioStreamPutInternal(stream, stream->staging_buffer, stream->staging_buffer_size, NULL) < 0) {
return -1;
}
buf = (void *)((Uint8 *)buf + amount);
len -= amount;
}
return 0;
}
int SDL_AudioStreamFlush(SDL_AudioStream *stream)
{
if (!stream) {
return SDL_InvalidParamError("stream");
}
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: flushing! staging_buffer_filled=%d bytes\n", stream->staging_buffer_filled);
#endif
SDL_assert((stream->dst_rate != stream->src_rate) || (stream->staging_buffer_filled == 0));
if (stream->staging_buffer_filled > 0) {
const SDL_bool first_run = stream->first_run;
const int filled = stream->staging_buffer_filled;
int actual_input_frames = filled / stream->src_sample_frame_size;
if (!first_run)
actual_input_frames += stream->resampler_padding_samples / stream->pre_resample_channels;
if (actual_input_frames > 0) {
int flush_remaining = ((int) SDL_ceil(actual_input_frames * stream->rate_incr)) * stream->dst_sample_frame_size;
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: flushing with padding to get max %d bytes!\n", flush_remaining);
#endif
SDL_memset(stream->staging_buffer + filled, '\0', stream->staging_buffer_size - filled);
if (SDL_AudioStreamPutInternal(stream, stream->staging_buffer, stream->staging_buffer_size, &flush_remaining) < 0) {
return -1;
}
SDL_memset(stream->staging_buffer, '\0', filled);
if (SDL_AudioStreamPutInternal(stream, stream->staging_buffer, stream->staging_buffer_size, &flush_remaining) < 0) {
return -1;
}
}
}
stream->staging_buffer_filled = 0;
stream->first_run = SDL_TRUE;
return 0;
}
int
SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, int len)
{
#if DEBUG_AUDIOSTREAM
SDL_Log("AUDIOSTREAM: want to get %d converted bytes\n", len);
#endif
if (!stream) {
return SDL_InvalidParamError("stream");
}
if (!buf) {
return SDL_InvalidParamError("buf");
}
if (len <= 0) {
return 0;
}
if ((len % stream->dst_sample_frame_size) != 0) {
return SDL_SetError("Can't request partial sample frames");
}
return (int) SDL_ReadFromDataQueue(stream->queue, buf, len);
}
int
SDL_AudioStreamAvailable(SDL_AudioStream *stream)
{
return stream ? (int) SDL_CountDataQueue(stream->queue) : 0;
}
void
SDL_AudioStreamClear(SDL_AudioStream *stream)
{
if (!stream) {
SDL_InvalidParamError("stream");
} else {
SDL_ClearDataQueue(stream->queue, stream->packetlen * 2);
if (stream->reset_resampler_func) {
stream->reset_resampler_func(stream);
}
stream->first_run = SDL_TRUE;
stream->staging_buffer_filled = 0;
}
}
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->staging_buffer);
SDL_free(stream->work_buffer_base);
SDL_free(stream->resampler_padding);
SDL_free(stream);
}
}