|  | /* | 
|  | Simple DirectMedia Layer | 
|  | Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org> | 
|  |  | 
|  | This software is provided 'as-is', without any express or implied | 
|  | warranty.  In no event will the authors be held liable for any damages | 
|  | arising from the use of this software. | 
|  |  | 
|  | Permission is granted to anyone to use this software for any purpose, | 
|  | including commercial applications, and to alter it and redistribute it | 
|  | freely, subject to the following restrictions: | 
|  |  | 
|  | 1. The origin of this software must not be misrepresented; you must not | 
|  | claim that you wrote the original software. If you use this software | 
|  | in a product, an acknowledgment in the product documentation would be | 
|  | appreciated but is not required. | 
|  | 2. Altered source versions must be plainly marked as such, and must not be | 
|  | misrepresented as being the original software. | 
|  | 3. This notice may not be removed or altered from any source distribution. | 
|  | */ | 
|  | #include "../SDL_internal.h" | 
|  |  | 
|  | /* Functions for audio drivers to perform runtime conversion of audio format */ | 
|  |  | 
|  | #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 | 
|  |  | 
|  | /* | 
|  | * CHANNEL LAYOUTS AS SDL EXPECTS THEM: | 
|  | * | 
|  | * (Even if the platform expects something else later, that | 
|  | * SDL will swizzle between the app and the platform). | 
|  | * | 
|  | * Abbreviations: | 
|  | * - FRONT=single mono speaker | 
|  | * - FL=front left speaker | 
|  | * - FR=front right speaker | 
|  | * - FC=front center speaker | 
|  | * - BL=back left speaker | 
|  | * - BR=back right speaker | 
|  | * - SR=surround right speaker | 
|  | * - SL=surround left speaker | 
|  | * - BC=back center speaker | 
|  | * - LFE=low-frequency speaker | 
|  | * | 
|  | * These are listed in the order they are laid out in | 
|  | * memory, so "FL+FR" means "the front left speaker is | 
|  | * layed out in memory first, then the front right, then | 
|  | * it repeats for the next audio frame". | 
|  | * | 
|  | * 1 channel (mono) layout: FRONT | 
|  | * 2 channels (stereo) layout: FL+FR | 
|  | * 3 channels (2.1) layout: FL+FR+LFE | 
|  | * 4 channels (quad) layout: FL+FR+BL+BR | 
|  | * 5 channels (4.1) layout: FL+FR+LFE+BL+BR | 
|  | * 6 channels (5.1) layout: FL+FR+FC+LFE+BL+BR | 
|  | * 7 channels (6.1) layout: FL+FR+FC+LFE+BC+SL+SR | 
|  | * 8 channels (7.1) layout: FL+FR+FC+LFE+BL+BR+SL+SR | 
|  | */ | 
|  |  | 
|  | #if HAVE_SSE3_INTRINSICS | 
|  | /* Convert from stereo to mono. Average left and right. */ | 
|  | 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); | 
|  |  | 
|  | /* Do SSE blocks as long as we have 16 bytes available. | 
|  | Just use unaligned load/stores, if the memory at runtime is | 
|  | aligned it'll be just as fast on modern processors */ | 
|  | while (i >= 4) {   /* 4 * float32 */ | 
|  | _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; | 
|  | } | 
|  |  | 
|  | /* Finish off any leftovers with scalar operations. */ | 
|  | 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 | 
|  | /* Convert from mono to stereo. Duplicate to stereo left and right. */ | 
|  | 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); | 
|  |  | 
|  | /* Do SSE blocks as long as we have 16 bytes available. | 
|  | Just use unaligned load/stores, if the memory at runtime is | 
|  | aligned it'll be just as fast on modern processors */ | 
|  | /* convert backwards, since output is growing in-place. */ | 
|  | while (i >= 4) {   /* 4 * float32 */ | 
|  | const __m128 input = _mm_loadu_ps(src);   /* A B C D */ | 
|  | _mm_storeu_ps(dst, _mm_unpacklo_ps(input, input));  /* A A B B */ | 
|  | _mm_storeu_ps(dst+4, _mm_unpackhi_ps(input, input));  /* C C D D */ | 
|  | i -= 4; src -= 4; dst -= 8; | 
|  | } | 
|  |  | 
|  | /* Finish off any leftovers with scalar operations. */ | 
|  | src += 3; dst += 6;  /* adjust for smaller buffers. */ | 
|  | while (i) {     /* convert backwards, since output is growing in-place. */ | 
|  | const float srcFC = src[0]; | 
|  | dst[1] /* FR */ = srcFC; | 
|  | dst[0] /* FL */ = srcFC; | 
|  | i--; src--; dst -= 2; | 
|  | } | 
|  |  | 
|  | cvt->len_cvt *= 2; | 
|  | if (cvt->filters[++cvt->filter_index]) { | 
|  | cvt->filters[cvt->filter_index] (cvt, format); | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  |  | 
|  | /* Include the autogenerated channel converters... */ | 
|  | #include "SDL_audio_channel_converters.h" | 
|  |  | 
|  |  | 
|  |  | 
|  | /* SDL's resampler uses a "bandlimited interpolation" algorithm: | 
|  | https://ccrma.stanford.edu/~jos/resample/ */ | 
|  |  | 
|  | #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; | 
|  | } | 
|  |  | 
|  | /* lpadding and rpadding are expected to be buffers of (ResamplePadding(inrate, outrate) * chans * sizeof (float)) bytes. */ | 
|  | 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) | 
|  | { | 
|  | /* Note that this used to be double, but it looks like we can get by with float in most cases at | 
|  | almost twice the speed on Intel processors, and orders of magnitude more | 
|  | on CPUs that need a software fallback for double calculations. */ | 
|  | typedef float ResampleFloatType; | 
|  |  | 
|  | const ResampleFloatType finrate = (ResampleFloatType) inrate; | 
|  | const ResampleFloatType outtimeincr = ((ResampleFloatType) 1.0f) / ((ResampleFloatType) outrate); | 
|  | 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);  /* outbuflen isn't total to write, it's total available. */ | 
|  | 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; | 
|  |  | 
|  | /* do this twice to calculate the sample, once for the "left wing" and then same for the right. */ | 
|  | for (j = 0; (filterindex1 + (j * RESAMPLER_SAMPLES_PER_ZERO_CROSSING)) < RESAMPLER_FILTER_SIZE; j++) { | 
|  | const int srcframe = srcindex - j; | 
|  | /* !!! FIXME: we can bubble this conditional out of here by doing a pre loop. */ | 
|  | 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)]))); | 
|  | } | 
|  |  | 
|  | /* Do the right wing! */ | 
|  | 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; | 
|  | /* !!! FIXME: we can bubble this conditional out of here by doing a post loop. */ | 
|  | 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 = outtimeincr * i; | 
|  | } | 
|  |  | 
|  | return outframes * chans * sizeof (float); | 
|  | } | 
|  |  | 
|  | int | 
|  | SDL_ConvertAudio(SDL_AudioCVT * cvt) | 
|  | { | 
|  | /* !!! FIXME: (cvt) should be const; stack-copy it here. */ | 
|  | /* !!! FIXME: (actually, we can't...len_cvt needs to be updated. Grr.) */ | 
|  |  | 
|  | /* Make sure there's data to convert */ | 
|  | if (cvt->buf == NULL) { | 
|  | return SDL_SetError("No buffer allocated for conversion"); | 
|  | } | 
|  |  | 
|  | /* Return okay if no conversion is necessary */ | 
|  | cvt->len_cvt = cvt->len; | 
|  | if (cvt->filters[0] == NULL) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Set up the conversion and go! */ | 
|  | 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]) { | 
|  | /* flip endian flag for data. */ | 
|  | 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; /* Moving terminator */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | SDL_BuildAudioTypeCVTToFloat(SDL_AudioCVT *cvt, const SDL_AudioFormat src_fmt) | 
|  | { | 
|  | int retval = 0;  /* 0 == no conversion necessary. */ | 
|  |  | 
|  | 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;  /* added a converter. */ | 
|  | } | 
|  |  | 
|  | 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;  /* added a converter. */ | 
|  | } | 
|  |  | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | static int | 
|  | SDL_BuildAudioTypeCVTFromFloat(SDL_AudioCVT *cvt, const SDL_AudioFormat dst_fmt) | 
|  | { | 
|  | int retval = 0;  /* 0 == no conversion necessary. */ | 
|  |  | 
|  | 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;  /* added a converter. */ | 
|  | } | 
|  |  | 
|  | 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;  /* added a converter. */ | 
|  | } | 
|  |  | 
|  | 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; /* Older versions of libsamplerate had a non-const pointer, but didn't write to it */ | 
|  | 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); /* Simple API converts the whole buffer at once.  No need for initialization. */ | 
|  | /* !!! FIXME: Handle library failures? */ | 
|  | #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 /* HAVE_LIBSAMPLERATE_H */ | 
|  |  | 
|  | static void | 
|  | SDL_ResampleCVT(SDL_AudioCVT *cvt, const int chans, const SDL_AudioFormat format) | 
|  | { | 
|  | /* !!! FIXME in 2.1: there are ten slots in the filter list, and the theoretical maximum we use is six (seven with NULL terminator). | 
|  | !!! FIXME in 2.1:   We need to store data for this resampler, because the cvt structure doesn't store the original sample rates, | 
|  | !!! FIXME in 2.1:   so we steal the ninth and tenth slot.  :( */ | 
|  | 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; | 
|  | const int dstlen = (cvt->len * cvt->len_mult);*/ | 
|  | /* !!! FIXME: remove this if we can get the resampler to work in-place again. */ | 
|  | 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); | 
|  |  | 
|  | /* we keep no streaming state here, so pad with silence on both ends. */ | 
|  | 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);  /* !!! FIXME: remove this if we can get the resampler to work in-place again. */ | 
|  |  | 
|  | if (cvt->filters[++cvt->filter_index]) { | 
|  | cvt->filters[cvt->filter_index](cvt, format); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* !!! FIXME: We only have this macro salsa because SDL_AudioCVT doesn't | 
|  | !!! FIXME:  store channel info, so we have to have function entry | 
|  | !!! FIXME:  points for each supported channel count and multiple | 
|  | !!! FIXME:  vs arbitrary. When we rev the ABI, clean this up. */ | 
|  | #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 /* HAVE_LIBSAMPLERATE_H */ | 
|  |  | 
|  | 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 /* HAVE_LIBSAMPLERATE_H */ | 
|  |  | 
|  | 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;  /* no conversion necessary. */ | 
|  | } | 
|  |  | 
|  | filter = ChooseCVTResampler(dst_channels); | 
|  | if (filter == NULL) { | 
|  | return SDL_SetError("No conversion available for these rates"); | 
|  | } | 
|  |  | 
|  | /* Update (cvt) with filter details... */ | 
|  | if (SDL_AddAudioCVTFilter(cvt, filter) < 0) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* !!! FIXME in 2.1: there are ten slots in the filter list, and the theoretical maximum we use is six (seven with NULL terminator). | 
|  | !!! FIXME in 2.1:   We need to store data for this resampler, because the cvt structure doesn't store the original sample rates, | 
|  | !!! FIXME in 2.1:   so we steal the ninth and tenth slot.  :( */ | 
|  | 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); | 
|  | } | 
|  |  | 
|  | /* !!! FIXME: remove this if we can get the resampler to work in-place again. */ | 
|  | /* the buffer is big enough to hold the destination now, but | 
|  | we need it large enough to hold a separate scratch buffer. */ | 
|  | cvt->len_mult *= 2; | 
|  |  | 
|  | return 1;               /* added a converter. */ | 
|  | } | 
|  |  | 
|  | 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;  /* supported. */ | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return SDL_FALSE;  /* unsupported. */ | 
|  | } | 
|  |  | 
|  | static SDL_bool | 
|  | SDL_SupportedChannelCount(const int channels) | 
|  | { | 
|  | return ((channels >= 1) && (channels <= 8)) ? SDL_TRUE : SDL_FALSE; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Creates a set of audio filters to convert from one format to another. | 
|  | Returns 0 if no conversion is needed, 1 if the audio filter is set up, | 
|  | or -1 if an error like invalid parameter, unsupported format, etc. occurred. | 
|  | */ | 
|  |  | 
|  | 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; | 
|  |  | 
|  | /* Sanity check target pointer */ | 
|  | if (cvt == NULL) { | 
|  | return SDL_InvalidParamError("cvt"); | 
|  | } | 
|  |  | 
|  | /* Make sure we zero out the audio conversion before error checking */ | 
|  | 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 | 
|  |  | 
|  | /* Start off with no conversion necessary */ | 
|  | 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); | 
|  |  | 
|  | /* Make sure we've chosen audio conversion functions (SIMD, scalar, etc.) */ | 
|  | SDL_ChooseAudioConverters(); | 
|  |  | 
|  | /* Type conversion goes like this now: | 
|  | - byteswap to CPU native format first if necessary. | 
|  | - convert to native Float32 if necessary. | 
|  | - resample and change channel count if necessary. | 
|  | - convert to final data format. | 
|  | - byteswap back to foreign format if necessary. | 
|  |  | 
|  | The expectation is we can process data faster in float32 | 
|  | (possibly with SIMD), and making several passes over the same | 
|  | buffer is likely to be CPU cache-friendly, avoiding the | 
|  | biggest performance hit in modern times. Previously we had | 
|  | (script-generated) custom converters for every data type and | 
|  | it was a bloat on SDL compile times and final library size. */ | 
|  |  | 
|  | /* see if we can skip float conversion entirely. */ | 
|  | if (src_rate == dst_rate && src_channels == dst_channels) { | 
|  | if (src_fmt == dst_fmt) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* just a byteswap needed? */ | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Convert data types, if necessary. Updates (cvt). */ | 
|  | if (SDL_BuildAudioTypeCVTToFloat(cvt, src_fmt) < 0) { | 
|  | return -1;              /* shouldn't happen, but just in case... */ | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Channel conversion */ | 
|  |  | 
|  | /* SDL_SupportedChannelCount should have caught these asserts, or we added a new format and forgot to update the table. */ | 
|  | 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)) { | 
|  | /* All combinations of supported channel counts should have been handled by now, but let's be defensive */ | 
|  | return SDL_SetError("Invalid channel combination"); | 
|  | } else if (channel_converter != NULL) { | 
|  | /* swap in some SIMD versions for a few of these. */ | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* Do rate conversion, if necessary. Updates (cvt). */ | 
|  | if (SDL_BuildAudioResampleCVT(cvt, dst_channels, src_rate, dst_rate) < 0) { | 
|  | return -1;              /* shouldn't happen, but just in case... */ | 
|  | } | 
|  |  | 
|  | /* Move to final data type. */ | 
|  | if (SDL_BuildAudioTypeCVTFromFloat(cvt, dst_fmt) < 0) { | 
|  | return -1;              /* shouldn't happen, but just in case... */ | 
|  | } | 
|  |  | 
|  | 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;  /* maybe unaligned pointer from SDL_realloc(). */ | 
|  | 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; | 
|  | } | 
|  | /* Make sure we're aligned to 16 bytes for SIMD code. */ | 
|  | 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));  /* SDL_AudioStreamPut() shouldn't allow in-place resamples. */ | 
|  |  | 
|  | data.data_in = (float *)inbuf; /* Older versions of libsamplerate had a non-const pointer, but didn't write to it */ | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* 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) | 
|  | { | 
|  | 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 /* HAVE_LIBSAMPLERATE_H */ | 
|  |  | 
|  |  | 
|  | 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; /* we set this up so there are valid padding samples at the end of the input buffer. */ | 
|  | const int cpy = SDL_min(inbuflen, paddingbytes); | 
|  | int retval; | 
|  |  | 
|  | SDL_assert(inbuf != ((const float *) outbuf));  /* SDL_AudioStreamPut() shouldn't allow in-place resamples. */ | 
|  |  | 
|  | retval = SDL_ResampleAudio(chans, inrate, outrate, lpadding, rpadding, inbuf, inbuflen, outbuf, outbuflen); | 
|  |  | 
|  | /* update our left padding with end of current input, for next run. */ | 
|  | SDL_memcpy((lpadding + paddingsamples) - (cpy / sizeof (float)), inbufend - cpy, cpy); | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | static void | 
|  | SDL_ResetAudioStreamResampler(SDL_AudioStream *stream) | 
|  | { | 
|  | /* set all the padding to silence. */ | 
|  | 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;  /* !!! FIXME: good enough for now. */ | 
|  | Uint8 pre_resample_channels; | 
|  | SDL_AudioStream *retval; | 
|  |  | 
|  | retval = (SDL_AudioStream *) SDL_calloc(1, sizeof (SDL_AudioStream)); | 
|  | if (!retval) { | 
|  | SDL_OutOfMemory(); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | /* If increasing channels, do it after resampling, since we'd just | 
|  | do more work to resample duplicate channels. If we're decreasing, do | 
|  | it first so we resample the interpolated data instead of interpolating | 
|  | the resampled data (!!! FIXME: decide if that works in practice, though!). */ | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Not resampling? It's an easy conversion (and maybe not even that!) */ | 
|  | 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;  /* 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) < 0) { | 
|  | SDL_FreeAudioStream(retval); | 
|  | return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */ | 
|  | } | 
|  |  | 
|  | #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; | 
|  | } | 
|  |  | 
|  | /* 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) < 0) { | 
|  | SDL_FreeAudioStream(retval); | 
|  | return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */ | 
|  | } | 
|  | } | 
|  |  | 
|  | retval->queue = SDL_NewDataQueue(packetlen, packetlen * 2); | 
|  | if (!retval->queue) { | 
|  | SDL_FreeAudioStream(retval); | 
|  | return NULL;  /* SDL_NewDataQueue should have called SDL_SetError. */ | 
|  | } | 
|  |  | 
|  | 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; | 
|  |  | 
|  | /* !!! FIXME: several converters can take advantage of SIMD, but only | 
|  | !!! FIXME:  if the data is aligned to 16 bytes. EnsureStreamBufferSize() | 
|  | !!! FIXME:  guarantees the buffer will align, but the | 
|  | !!! FIXME:  converters will iterate over the data backwards if | 
|  | !!! FIXME:  the output grows, and this means we won't align if buflen | 
|  | !!! FIXME:  isn't a multiple of 16. In these cases, we should chop off | 
|  | !!! FIXME:  a few samples at the end and convert them separately. */ | 
|  |  | 
|  | /* no padding prepended on first run. */ | 
|  | neededpaddingbytes = stream->resampler_padding_samples * sizeof (float); | 
|  | paddingbytes = stream->first_run ? 0 : neededpaddingbytes; | 
|  | stream->first_run = SDL_FALSE; | 
|  |  | 
|  | /* Make sure the work buffer can hold all the data we need at once... */ | 
|  | workbuflen = buflen; | 
|  | if (stream->cvt_before_resampling.needed) { | 
|  | workbuflen *= stream->cvt_before_resampling.len_mult; | 
|  | } | 
|  |  | 
|  | if (stream->dst_rate != stream->src_rate) { | 
|  | /* resamples can't happen in place, so make space for second buf. */ | 
|  | 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) { | 
|  | /* !!! FIXME: buffer might be big enough already? */ | 
|  | 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;  /* probably out of memory. */ | 
|  | } | 
|  |  | 
|  | resamplebuf = workbuf;  /* default if not resampling. */ | 
|  |  | 
|  | 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;   /* uhoh! */ | 
|  | } | 
|  | 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) { | 
|  | /* save off some samples at the end; they are used for padding now so | 
|  | the resampler is coherent and then used at the start of the next | 
|  | put operation. Prepend last put operation's padding, too. */ | 
|  |  | 
|  | /* prepend prior put's padding. :P */ | 
|  | if (paddingbytes) { | 
|  | SDL_memcpy(workbuf, stream->resampler_padding, paddingbytes); | 
|  | buflen += paddingbytes; | 
|  | } | 
|  |  | 
|  | /* save off the data at the end for the next run. */ | 
|  | SDL_memcpy(stream->resampler_padding, workbuf + (buflen - neededpaddingbytes), neededpaddingbytes); | 
|  |  | 
|  | resamplebuf = workbuf + buflen;  /* skip to second piece of workbuf. */ | 
|  | 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;   /* uhoh! */ | 
|  | } | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* resamplebuf holds the final output, even if we didn't resample. */ | 
|  | return buflen ? SDL_WriteToDataQueue(stream->queue, resamplebuf, buflen) : 0; | 
|  | } | 
|  |  | 
|  | int | 
|  | SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, int len) | 
|  | { | 
|  | /* !!! FIXME: several converters can take advantage of SIMD, but only | 
|  | !!! FIXME:  if the data is aligned to 16 bytes. EnsureStreamBufferSize() | 
|  | !!! FIXME:  guarantees the buffer will align, but the | 
|  | !!! FIXME:  converters will iterate over the data backwards if | 
|  | !!! FIXME:  the output grows, and this means we won't align if buflen | 
|  | !!! FIXME:  isn't a multiple of 16. In these cases, we should chop off | 
|  | !!! FIXME:  a few samples at the end and convert them separately. */ | 
|  |  | 
|  | #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;  /* nothing to do. */ | 
|  | } | 
|  | 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 we don't have a staging buffer or we're given enough data that | 
|  | we don't need to store it for later, skip the staging process. | 
|  | */ | 
|  | if (!stream->staging_buffer_filled && len >= stream->staging_buffer_size) { | 
|  | return SDL_AudioStreamPutInternal(stream, buf, len, NULL); | 
|  | } | 
|  |  | 
|  | /* If there's not enough data to fill the staging buffer, just save it */ | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* Fill the staging buffer, process it, and continue */ | 
|  | 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 | 
|  |  | 
|  | /* shouldn't use a staging buffer if we're not resampling. */ | 
|  | SDL_assert((stream->dst_rate != stream->src_rate) || (stream->staging_buffer_filled == 0)); | 
|  |  | 
|  | if (stream->staging_buffer_filled > 0) { | 
|  | /* push the staging buffer + silence. We need to flush out not just | 
|  | the staging buffer, but the piece that the stream was saving off | 
|  | for right-side resampler padding. */ | 
|  | 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) {  /* don't bother if nothing to flush. */ | 
|  | /* This is how many bytes we're expecting without silence appended. */ | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* we have flushed out (or initially filled) the pending right-side | 
|  | resampler padding, but we need to push more silence to guarantee | 
|  | the staging buffer is fully flushed out, too. */ | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* get converted/resampled data from the stream */ | 
|  | 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;  /* nothing to do. */ | 
|  | } | 
|  | 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); | 
|  | } | 
|  |  | 
|  | /* number of converted/resampled bytes available */ | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* dispose of a stream */ | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* vi: set ts=4 sw=4 expandtab: */ |