2015-06-21 15:33:46 +00:00
|
|
|
/*
|
|
|
|
Simple DirectMedia Layer
|
2017-01-02 02:33:28 +00:00
|
|
|
Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
|
2015-06-21 15:33:46 +00:00
|
|
|
|
|
|
|
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_audio.h"
|
|
|
|
#include "SDL_audio_c.h"
|
|
|
|
|
2017-01-06 10:16:26 +00:00
|
|
|
#include "SDL_loadso.h"
|
2015-06-21 15:33:46 +00:00
|
|
|
#include "SDL_assert.h"
|
2017-01-06 00:29:38 +00:00
|
|
|
#include "../SDL_dataqueue.h"
|
2017-01-23 05:57:19 +00:00
|
|
|
#include "SDL_cpuinfo.h"
|
|
|
|
|
2017-01-23 06:05:44 +00:00
|
|
|
#ifdef __SSE3__
|
|
|
|
#define HAVE_SSE3_INTRINSICS 1
|
2017-01-23 05:57:19 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#if HAVE_SSE3_INTRINSICS
|
|
|
|
/* Effectively mix right and left channels into a single channel */
|
|
|
|
static void SDLCALL
|
|
|
|
SDL_ConvertStereoToMono_SSE3(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
|
|
|
/* We can only do this if dst is aligned to 16 bytes; since src is the
|
|
|
|
same pointer and it moves by 2, it can't be forcibly aligned. */
|
|
|
|
if ((((size_t) dst) & 15) == 0) {
|
|
|
|
/* Aligned! Do SSE blocks as long as we have 16 bytes available. */
|
|
|
|
const __m128 divby2 = _mm_set1_ps(0.5f);
|
|
|
|
while (i >= 4) { /* 4 * float32 */
|
|
|
|
_mm_store_ps(dst, _mm_mul_ps(_mm_hadd_ps(_mm_load_ps(src), _mm_load_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
|
|
|
|
|
2015-06-21 15:33:46 +00:00
|
|
|
/* Effectively mix right and left channels into a single channel */
|
|
|
|
static void SDLCALL
|
2017-01-08 21:18:49 +00:00
|
|
|
SDL_ConvertStereoToMono(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
2015-06-21 15:33:46 +00:00
|
|
|
{
|
2016-11-05 06:34:38 +00:00
|
|
|
float *dst = (float *) cvt->buf;
|
|
|
|
const float *src = dst;
|
2015-06-21 15:33:46 +00:00
|
|
|
int i;
|
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
LOG_DEBUG_CONVERT("stereo", "mono");
|
|
|
|
SDL_assert(format == AUDIO_F32SYS);
|
|
|
|
|
|
|
|
for (i = cvt->len_cvt / 8; i; --i, src += 2) {
|
2017-01-23 01:18:59 +00:00
|
|
|
*(dst++) = (src[0] + src[1]) * 0.5f;
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cvt->len_cvt /= 2;
|
|
|
|
if (cvt->filters[++cvt->filter_index]) {
|
|
|
|
cvt->filters[cvt->filter_index] (cvt, format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-01-08 21:18:49 +00:00
|
|
|
/* Convert from 5.1 to stereo. Average left and right, discard subwoofer. */
|
2015-06-21 15:33:46 +00:00
|
|
|
static void SDLCALL
|
2017-01-08 21:18:49 +00:00
|
|
|
SDL_Convert51ToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
2015-06-21 15:33:46 +00:00
|
|
|
{
|
2016-11-05 06:34:38 +00:00
|
|
|
float *dst = (float *) cvt->buf;
|
|
|
|
const float *src = dst;
|
2015-06-21 15:33:46 +00:00
|
|
|
int i;
|
|
|
|
|
2017-01-08 21:18:49 +00:00
|
|
|
LOG_DEBUG_CONVERT("5.1", "stereo");
|
2016-11-05 06:34:38 +00:00
|
|
|
SDL_assert(format == AUDIO_F32SYS);
|
2015-06-21 15:33:46 +00:00
|
|
|
|
2017-01-08 21:18:49 +00:00
|
|
|
/* this assumes FL+FR+FC+subwoof+BL+BR layout. */
|
2016-11-05 06:34:38 +00:00
|
|
|
for (i = cvt->len_cvt / (sizeof (float) * 6); i; --i, src += 6, dst += 2) {
|
2017-01-08 21:18:49 +00:00
|
|
|
const double front_center = (double) src[2];
|
|
|
|
dst[0] = (float) ((src[0] + front_center + src[4]) / 3.0); /* left */
|
|
|
|
dst[1] = (float) ((src[1] + front_center + src[5]) / 3.0); /* right */
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cvt->len_cvt /= 3;
|
|
|
|
if (cvt->filters[++cvt->filter_index]) {
|
|
|
|
cvt->filters[cvt->filter_index] (cvt, format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-01-08 21:18:49 +00:00
|
|
|
/* Convert from 5.1 to quad */
|
2015-06-21 15:33:46 +00:00
|
|
|
static void SDLCALL
|
2017-01-08 21:18:49 +00:00
|
|
|
SDL_Convert51ToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
2015-06-21 15:33:46 +00:00
|
|
|
{
|
2016-11-05 06:34:38 +00:00
|
|
|
float *dst = (float *) cvt->buf;
|
|
|
|
const float *src = dst;
|
2015-06-21 15:33:46 +00:00
|
|
|
int i;
|
|
|
|
|
2017-01-08 21:18:49 +00:00
|
|
|
LOG_DEBUG_CONVERT("5.1", "quad");
|
2016-11-05 06:34:38 +00:00
|
|
|
SDL_assert(format == AUDIO_F32SYS);
|
2015-06-21 15:33:46 +00:00
|
|
|
|
2017-01-08 21:18:49 +00:00
|
|
|
/* assumes quad is FL+FR+BL+BR layout and 5.1 is FL+FR+FC+subwoof+BL+BR */
|
2016-11-05 06:34:38 +00:00
|
|
|
for (i = cvt->len_cvt / (sizeof (float) * 6); i; --i, src += 6, dst += 4) {
|
2017-01-08 21:18:49 +00:00
|
|
|
/* FIXME: this is a good candidate for SIMD. */
|
|
|
|
const double front_center = (double) src[2];
|
|
|
|
dst[0] = (float) ((src[0] + front_center) * 0.5); /* FL */
|
|
|
|
dst[1] = (float) ((src[1] + front_center) * 0.5); /* FR */
|
|
|
|
dst[2] = (float) ((src[4] + front_center) * 0.5); /* BL */
|
|
|
|
dst[3] = (float) ((src[5] + front_center) * 0.5); /* BR */
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cvt->len_cvt /= 6;
|
|
|
|
cvt->len_cvt *= 4;
|
|
|
|
if (cvt->filters[++cvt->filter_index]) {
|
|
|
|
cvt->filters[cvt->filter_index] (cvt, format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-08 21:18:49 +00:00
|
|
|
|
2015-06-21 15:33:46 +00:00
|
|
|
/* Duplicate a mono channel to both stereo channels */
|
|
|
|
static void SDLCALL
|
2017-01-08 21:18:49 +00:00
|
|
|
SDL_ConvertMonoToStereo(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
2015-06-21 15:33:46 +00:00
|
|
|
{
|
2016-11-05 06:34:38 +00:00
|
|
|
const float *src = (const float *) (cvt->buf + cvt->len_cvt);
|
|
|
|
float *dst = (float *) (cvt->buf + cvt->len_cvt * 2);
|
2015-06-21 15:33:46 +00:00
|
|
|
int i;
|
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
LOG_DEBUG_CONVERT("mono", "stereo");
|
|
|
|
SDL_assert(format == AUDIO_F32SYS);
|
2015-06-21 15:33:46 +00:00
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
for (i = cvt->len_cvt / sizeof (float); i; --i) {
|
|
|
|
src--;
|
|
|
|
dst -= 2;
|
|
|
|
dst[0] = dst[1] = *src;
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cvt->len_cvt *= 2;
|
|
|
|
if (cvt->filters[++cvt->filter_index]) {
|
|
|
|
cvt->filters[cvt->filter_index] (cvt, format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Duplicate a stereo channel to a pseudo-5.1 stream */
|
|
|
|
static void SDLCALL
|
2017-01-08 21:18:49 +00:00
|
|
|
SDL_ConvertStereoTo51(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
2015-06-21 15:33:46 +00:00
|
|
|
{
|
|
|
|
int i;
|
2016-11-05 06:34:38 +00:00
|
|
|
float lf, rf, ce;
|
|
|
|
const float *src = (const float *) (cvt->buf + cvt->len_cvt);
|
|
|
|
float *dst = (float *) (cvt->buf + cvt->len_cvt * 3);
|
|
|
|
|
|
|
|
LOG_DEBUG_CONVERT("stereo", "5.1");
|
|
|
|
SDL_assert(format == AUDIO_F32SYS);
|
|
|
|
|
|
|
|
for (i = cvt->len_cvt / 8; i; --i) {
|
|
|
|
dst -= 6;
|
|
|
|
src -= 2;
|
|
|
|
lf = src[0];
|
|
|
|
rf = src[1];
|
2017-01-08 21:18:49 +00:00
|
|
|
ce = (lf + rf) * 0.5f;
|
|
|
|
dst[0] = lf + (lf - ce); /* FL */
|
|
|
|
dst[1] = rf + (rf - ce); /* FR */
|
|
|
|
dst[2] = ce; /* FC */
|
|
|
|
dst[3] = ce; /* !!! FIXME: wrong! This is the subwoofer. */
|
|
|
|
dst[4] = lf; /* BL */
|
|
|
|
dst[5] = rf; /* BR */
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
2016-11-05 06:34:38 +00:00
|
|
|
|
2015-06-21 15:33:46 +00:00
|
|
|
cvt->len_cvt *= 3;
|
|
|
|
if (cvt->filters[++cvt->filter_index]) {
|
|
|
|
cvt->filters[cvt->filter_index] (cvt, format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Duplicate a stereo channel to a pseudo-4.0 stream */
|
|
|
|
static void SDLCALL
|
2017-01-08 21:18:49 +00:00
|
|
|
SDL_ConvertStereoToQuad(SDL_AudioCVT * cvt, SDL_AudioFormat format)
|
2015-06-21 15:33:46 +00:00
|
|
|
{
|
2016-11-05 06:34:38 +00:00
|
|
|
const float *src = (const float *) (cvt->buf + cvt->len_cvt);
|
|
|
|
float *dst = (float *) (cvt->buf + cvt->len_cvt * 2);
|
2017-01-08 21:18:49 +00:00
|
|
|
float lf, rf;
|
2015-06-21 15:33:46 +00:00
|
|
|
int i;
|
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
LOG_DEBUG_CONVERT("stereo", "quad");
|
|
|
|
SDL_assert(format == AUDIO_F32SYS);
|
|
|
|
|
|
|
|
for (i = cvt->len_cvt / 8; i; --i) {
|
|
|
|
dst -= 4;
|
|
|
|
src -= 2;
|
|
|
|
lf = src[0];
|
|
|
|
rf = src[1];
|
2017-01-08 21:18:49 +00:00
|
|
|
dst[0] = lf; /* FL */
|
|
|
|
dst[1] = rf; /* FR */
|
|
|
|
dst[2] = lf; /* BL */
|
|
|
|
dst[3] = rf; /* BR */
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
2016-11-05 06:34:38 +00:00
|
|
|
|
2015-06-21 15:33:46 +00:00
|
|
|
cvt->len_cvt *= 2;
|
|
|
|
if (cvt->filters[++cvt->filter_index]) {
|
|
|
|
cvt->filters[cvt->filter_index] (cvt, format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-09 11:00:58 +00:00
|
|
|
static int
|
|
|
|
SDL_ResampleAudioSimple(const int chans, const double rate_incr,
|
|
|
|
float *last_sample, const float *inbuf,
|
|
|
|
const int inbuflen, float *outbuf, const int outbuflen)
|
|
|
|
{
|
2017-01-18 07:11:56 +00:00
|
|
|
const int framelen = chans * (int)sizeof (float);
|
2017-01-09 11:00:58 +00:00
|
|
|
const int total = (inbuflen / framelen);
|
2017-01-18 07:11:56 +00:00
|
|
|
const int finalpos = (total * chans) - chans;
|
|
|
|
const int dest_samples = (int)(((double)total) * rate_incr);
|
2017-01-09 11:00:58 +00:00
|
|
|
const double src_incr = 1.0 / rate_incr;
|
2017-01-23 04:48:15 +00:00
|
|
|
float *dst;
|
|
|
|
double idx;
|
2017-01-09 11:00:58 +00:00
|
|
|
int i;
|
|
|
|
|
2017-01-18 07:11:56 +00:00
|
|
|
SDL_assert((dest_samples * framelen) <= outbuflen);
|
2017-01-09 11:00:58 +00:00
|
|
|
SDL_assert((inbuflen % framelen) == 0);
|
|
|
|
|
2017-01-23 21:45:50 +00:00
|
|
|
if (rate_incr > 1.0) { /* upsample */
|
2017-01-23 04:48:15 +00:00
|
|
|
float *target = (outbuf + chans);
|
|
|
|
dst = outbuf + (dest_samples * chans);
|
|
|
|
idx = (double) total;
|
|
|
|
|
2017-01-23 21:45:50 +00:00
|
|
|
if (chans == 1) {
|
|
|
|
const float final_sample = inbuf[finalpos];
|
|
|
|
float earlier_sample = inbuf[finalpos];
|
|
|
|
while (dst > target) {
|
|
|
|
const int pos = ((int) idx) * chans;
|
|
|
|
const float *src = &inbuf[pos];
|
2017-01-23 04:48:15 +00:00
|
|
|
const float val = *(--src);
|
2017-01-23 21:45:50 +00:00
|
|
|
SDL_assert(pos >= 0.0);
|
|
|
|
*(--dst) = (val + earlier_sample) * 0.5f;
|
|
|
|
earlier_sample = val;
|
|
|
|
idx -= src_incr;
|
|
|
|
}
|
|
|
|
/* do last sample, interpolated against previous run's state. */
|
|
|
|
*(--dst) = (inbuf[0] + last_sample[0]) * 0.5f;
|
|
|
|
*last_sample = final_sample;
|
|
|
|
} else if (chans == 2) {
|
|
|
|
const float final_sample2 = inbuf[finalpos+1];
|
|
|
|
const float final_sample1 = inbuf[finalpos];
|
|
|
|
float earlier_sample2 = inbuf[finalpos];
|
|
|
|
float earlier_sample1 = inbuf[finalpos-1];
|
|
|
|
while (dst > target) {
|
|
|
|
const int pos = ((int) idx) * chans;
|
|
|
|
const float *src = &inbuf[pos];
|
|
|
|
const float val2 = *(--src);
|
|
|
|
const float val1 = *(--src);
|
|
|
|
SDL_assert(pos >= 0.0);
|
|
|
|
*(--dst) = (val2 + earlier_sample2) * 0.5f;
|
|
|
|
*(--dst) = (val1 + earlier_sample1) * 0.5f;
|
|
|
|
earlier_sample2 = val2;
|
|
|
|
earlier_sample1 = val1;
|
|
|
|
idx -= src_incr;
|
2017-01-23 04:48:15 +00:00
|
|
|
}
|
2017-01-23 21:45:50 +00:00
|
|
|
/* do last sample, interpolated against previous run's state. */
|
|
|
|
*(--dst) = (inbuf[1] + last_sample[1]) * 0.5f;
|
|
|
|
*(--dst) = (inbuf[0] + last_sample[0]) * 0.5f;
|
|
|
|
last_sample[1] = final_sample2;
|
|
|
|
last_sample[0] = final_sample1;
|
|
|
|
} else {
|
|
|
|
const float *earlier_sample = &inbuf[finalpos];
|
|
|
|
float final_sample[8];
|
|
|
|
SDL_memcpy(final_sample, &inbuf[finalpos], framelen);
|
|
|
|
while (dst > target) {
|
|
|
|
const int pos = ((int) idx) * chans;
|
|
|
|
const float *src = &inbuf[pos];
|
|
|
|
SDL_assert(pos >= 0.0);
|
|
|
|
for (i = chans - 1; i >= 0; i--) {
|
|
|
|
const float val = *(--src);
|
|
|
|
*(--dst) = (val + earlier_sample[i]) * 0.5f;
|
|
|
|
}
|
|
|
|
earlier_sample = src;
|
|
|
|
idx -= src_incr;
|
|
|
|
}
|
|
|
|
/* do last sample, interpolated against previous run's state. */
|
|
|
|
for (i = chans - 1; i >= 0; i--) {
|
|
|
|
const float val = inbuf[i];
|
|
|
|
*(--dst) = (val + last_sample[i]) * 0.5f;
|
|
|
|
}
|
|
|
|
SDL_memcpy(last_sample, final_sample, framelen);
|
2017-01-23 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
2017-01-24 05:03:36 +00:00
|
|
|
dst = (outbuf + (dest_samples * chans));
|
2017-01-23 21:45:50 +00:00
|
|
|
} else { /* downsample */
|
2017-01-23 04:48:15 +00:00
|
|
|
float *target = (outbuf + (dest_samples * chans));
|
|
|
|
dst = outbuf;
|
|
|
|
idx = 0.0;
|
2017-01-23 21:45:50 +00:00
|
|
|
if (chans == 1) {
|
|
|
|
float last = *last_sample;
|
|
|
|
while (dst < target) {
|
|
|
|
const int pos = ((int) idx) * chans;
|
|
|
|
const float val = inbuf[pos];
|
|
|
|
SDL_assert(pos <= finalpos);
|
|
|
|
*(dst++) = (val + last) * 0.5f;
|
|
|
|
last = val;
|
|
|
|
idx += src_incr;
|
|
|
|
}
|
|
|
|
*last_sample = last;
|
|
|
|
} else if (chans == 2) {
|
|
|
|
float last1 = last_sample[0];
|
|
|
|
float last2 = last_sample[1];
|
|
|
|
while (dst < target) {
|
|
|
|
const int pos = ((int) idx) * chans;
|
|
|
|
const float val1 = inbuf[pos];
|
|
|
|
const float val2 = inbuf[pos+1];
|
|
|
|
SDL_assert(pos <= finalpos);
|
|
|
|
*(dst++) = (val1 + last1) * 0.5f;
|
|
|
|
*(dst++) = (val2 + last2) * 0.5f;
|
|
|
|
last1 = val1;
|
|
|
|
last2 = val2;
|
|
|
|
idx += src_incr;
|
|
|
|
}
|
|
|
|
last_sample[0] = last1;
|
|
|
|
last_sample[1] = last2;
|
|
|
|
} else {
|
|
|
|
while (dst < target) {
|
|
|
|
const int pos = ((int) idx) * chans;
|
|
|
|
const float *src = &inbuf[pos];
|
|
|
|
SDL_assert(pos <= finalpos);
|
|
|
|
for (i = 0; i < chans; i++) {
|
|
|
|
const float val = *(src++);
|
|
|
|
*(dst++) = (val + last_sample[i]) * 0.5f;
|
|
|
|
last_sample[i] = val;
|
|
|
|
}
|
|
|
|
idx += src_incr;
|
2017-01-23 04:48:15 +00:00
|
|
|
}
|
2017-01-09 11:00:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-23 04:48:15 +00:00
|
|
|
return (int) ((dst - outbuf) * ((int) sizeof (float)));
|
2017-01-09 11:00:58 +00:00
|
|
|
}
|
|
|
|
|
2017-01-23 01:27:48 +00:00
|
|
|
/* We keep one special-case fast path around for an extremely common audio format. */
|
|
|
|
static int
|
|
|
|
SDL_ResampleAudioSimple_si16_c2(const double rate_incr,
|
|
|
|
Sint16 *last_sample, const Sint16 *inbuf,
|
|
|
|
const int inbuflen, Sint16 *outbuf, const int outbuflen)
|
|
|
|
{
|
|
|
|
const int chans = 2;
|
|
|
|
const int framelen = 4; /* stereo 16 bit */
|
|
|
|
const int total = (inbuflen / framelen);
|
|
|
|
const int finalpos = (total * chans) - chans;
|
|
|
|
const int dest_samples = (int)(((double)total) * rate_incr);
|
|
|
|
const double src_incr = 1.0 / rate_incr;
|
|
|
|
Sint16 *dst;
|
|
|
|
double idx;
|
|
|
|
|
|
|
|
SDL_assert((dest_samples * framelen) <= outbuflen);
|
|
|
|
SDL_assert((inbuflen % framelen) == 0);
|
|
|
|
|
|
|
|
if (rate_incr > 1.0) {
|
|
|
|
Sint16 *target = (outbuf + chans);
|
|
|
|
const Sint16 final_right = inbuf[finalpos+1];
|
|
|
|
const Sint16 final_left = inbuf[finalpos];
|
|
|
|
Sint16 earlier_right = inbuf[finalpos-1];
|
|
|
|
Sint16 earlier_left = inbuf[finalpos-2];
|
|
|
|
dst = outbuf + (dest_samples * chans);
|
|
|
|
idx = (double) total;
|
|
|
|
|
|
|
|
while (dst > target) {
|
|
|
|
const int pos = ((int) idx) * chans;
|
|
|
|
const Sint16 *src = &inbuf[pos];
|
|
|
|
const Sint16 right = *(--src);
|
|
|
|
const Sint16 left = *(--src);
|
|
|
|
SDL_assert(pos >= 0.0);
|
|
|
|
*(--dst) = (((Sint32) right) + ((Sint32) earlier_right)) >> 1;
|
|
|
|
*(--dst) = (((Sint32) left) + ((Sint32) earlier_left)) >> 1;
|
|
|
|
earlier_right = right;
|
|
|
|
earlier_left = left;
|
|
|
|
idx -= src_incr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* do last sample, interpolated against previous run's state. */
|
|
|
|
*(--dst) = (((Sint32) inbuf[1]) + ((Sint32) last_sample[1])) >> 1;
|
|
|
|
*(--dst) = (((Sint32) inbuf[0]) + ((Sint32) last_sample[0])) >> 1;
|
|
|
|
last_sample[1] = final_right;
|
|
|
|
last_sample[0] = final_left;
|
|
|
|
|
2017-01-24 05:03:36 +00:00
|
|
|
dst = (outbuf + (dest_samples * chans));
|
2017-01-23 01:27:48 +00:00
|
|
|
} else {
|
|
|
|
Sint16 *target = (outbuf + (dest_samples * chans));
|
|
|
|
dst = outbuf;
|
|
|
|
idx = 0.0;
|
|
|
|
while (dst < target) {
|
|
|
|
const int pos = ((int) idx) * chans;
|
|
|
|
const Sint16 *src = &inbuf[pos];
|
|
|
|
const Sint16 left = *(src++);
|
|
|
|
const Sint16 right = *(src++);
|
|
|
|
SDL_assert(pos <= finalpos);
|
|
|
|
*(dst++) = (((Sint32) left) + ((Sint32) last_sample[0])) >> 1;
|
|
|
|
*(dst++) = (((Sint32) right) + ((Sint32) last_sample[1])) >> 1;
|
|
|
|
last_sample[0] = left;
|
|
|
|
last_sample[1] = right;
|
|
|
|
idx += src_incr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (int) ((dst - outbuf) * ((int) sizeof (Sint16)));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void SDLCALL
|
|
|
|
SDL_ResampleCVT_si16_c2(SDL_AudioCVT *cvt, SDL_AudioFormat format)
|
|
|
|
{
|
|
|
|
const Sint16 *src = (const Sint16 *) cvt->buf;
|
|
|
|
const int srclen = cvt->len_cvt;
|
2017-01-23 21:42:47 +00:00
|
|
|
Sint16 *dst = (Sint16 *) cvt->buf;
|
|
|
|
const int dstlen = (cvt->len * cvt->len_mult);
|
2017-03-02 18:33:04 +00:00
|
|
|
Sint16 state[2];
|
|
|
|
|
|
|
|
state[0] = src[0];
|
|
|
|
state[1] = src[1];
|
2017-01-23 01:27:48 +00:00
|
|
|
|
|
|
|
SDL_assert(format == AUDIO_S16SYS);
|
|
|
|
|
|
|
|
cvt->len_cvt = SDL_ResampleAudioSimple_si16_c2(cvt->rate_incr, state, src, srclen, dst, dstlen);
|
|
|
|
if (cvt->filters[++cvt->filter_index]) {
|
|
|
|
cvt->filters[cvt->filter_index](cvt, format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-21 15:33:46 +00:00
|
|
|
|
|
|
|
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) {
|
2016-11-05 06:34:38 +00:00
|
|
|
return SDL_SetError("No buffer allocated for conversion");
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
2016-11-05 06:34:38 +00:00
|
|
|
|
2015-06-21 15:33:46 +00:00
|
|
|
/* Return okay if no conversion is necessary */
|
|
|
|
cvt->len_cvt = cvt->len;
|
|
|
|
if (cvt->filters[0] == NULL) {
|
2016-11-05 06:34:38 +00:00
|
|
|
return 0;
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Set up the conversion and go! */
|
|
|
|
cvt->filter_index = 0;
|
|
|
|
cvt->filters[0] (cvt, cvt->src_format);
|
2016-11-05 06:34:38 +00:00
|
|
|
return 0;
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
static void SDLCALL
|
|
|
|
SDL_Convert_Byteswap(SDL_AudioCVT *cvt, SDL_AudioFormat format)
|
2015-06-21 15:33:46 +00:00
|
|
|
{
|
2016-11-05 08:52:28 +00:00
|
|
|
#if DEBUG_CONVERT
|
|
|
|
printf("Converting byte order\n");
|
|
|
|
#endif
|
2016-11-05 06:34:38 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
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);
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2017-06-12 23:39:15 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
if (filter == NULL) {
|
|
|
|
return SDL_SetError("Audio filter pointer is NULL");
|
|
|
|
}
|
|
|
|
cvt->filters[cvt->filter_index++] = filter;
|
|
|
|
cvt->filters[cvt->filter_index] = NULL; /* Moving terminator */
|
|
|
|
return 0;
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
|
|
|
|
static int
|
2016-11-05 06:34:38 +00:00
|
|
|
SDL_BuildAudioTypeCVTToFloat(SDL_AudioCVT *cvt, const SDL_AudioFormat src_fmt)
|
2015-06-21 15:33:46 +00:00
|
|
|
{
|
2016-11-05 06:34:38 +00:00
|
|
|
int retval = 0; /* 0 == no conversion necessary. */
|
2015-06-21 15:33:46 +00:00
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
if ((SDL_AUDIO_ISBIGENDIAN(src_fmt) != 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN)) {
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert_Byteswap) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2016-11-05 06:34:38 +00:00
|
|
|
retval = 1; /* added a converter. */
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
if (!SDL_AUDIO_ISFLOAT(src_fmt)) {
|
2016-11-05 07:53:59 +00:00
|
|
|
const Uint16 src_bitsize = SDL_AUDIO_BITSIZE(src_fmt);
|
|
|
|
const Uint16 dst_bitsize = 32;
|
2016-11-05 06:34:38 +00:00
|
|
|
SDL_AudioFilter filter = NULL;
|
2016-11-05 07:53:59 +00:00
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
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;
|
2016-11-07 20:10:01 +00:00
|
|
|
case AUDIO_U16: filter = SDL_Convert_U16_to_F32; break;
|
2016-11-05 06:34:38 +00:00
|
|
|
case AUDIO_S32: filter = SDL_Convert_S32_to_F32; break;
|
|
|
|
default: SDL_assert(!"Unexpected audio format!"); break;
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
if (!filter) {
|
|
|
|
return SDL_SetError("No conversion available for these formats");
|
|
|
|
}
|
|
|
|
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
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);
|
|
|
|
}
|
2016-11-05 07:53:59 +00:00
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
retval = 1; /* added a converter. */
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
return retval;
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
static int
|
|
|
|
SDL_BuildAudioTypeCVTFromFloat(SDL_AudioCVT *cvt, const SDL_AudioFormat dst_fmt)
|
2015-06-21 15:33:46 +00:00
|
|
|
{
|
2016-11-05 06:34:38 +00:00
|
|
|
int retval = 0; /* 0 == no conversion necessary. */
|
|
|
|
|
|
|
|
if (!SDL_AUDIO_ISFLOAT(dst_fmt)) {
|
2016-11-05 07:56:55 +00:00
|
|
|
const Uint16 dst_bitsize = SDL_AUDIO_BITSIZE(dst_fmt);
|
|
|
|
const Uint16 src_bitsize = 32;
|
2016-11-05 06:34:38 +00:00
|
|
|
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;
|
2016-11-07 20:10:01 +00:00
|
|
|
case AUDIO_U16: filter = SDL_Convert_F32_to_U16; break;
|
2016-11-05 06:34:38 +00:00
|
|
|
case AUDIO_S32: filter = SDL_Convert_F32_to_S32; break;
|
|
|
|
default: SDL_assert(!"Unexpected audio format!"); break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!filter) {
|
|
|
|
return SDL_SetError("No conversion available for these formats");
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2016-11-05 06:34:38 +00:00
|
|
|
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. */
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((SDL_AUDIO_ISBIGENDIAN(dst_fmt) != 0) == (SDL_BYTEORDER == SDL_LIL_ENDIAN)) {
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert_Byteswap) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2016-11-05 06:34:38 +00:00
|
|
|
retval = 1; /* added a converter. */
|
|
|
|
}
|
|
|
|
|
|
|
|
return retval;
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2017-01-09 11:00:58 +00:00
|
|
|
static void
|
|
|
|
SDL_ResampleCVT(SDL_AudioCVT *cvt, const int chans, const SDL_AudioFormat format)
|
|
|
|
{
|
|
|
|
const float *src = (const float *) cvt->buf;
|
|
|
|
const int srclen = cvt->len_cvt;
|
2017-01-23 04:48:15 +00:00
|
|
|
float *dst = (float *) cvt->buf;
|
|
|
|
const int dstlen = (cvt->len * cvt->len_mult);
|
2017-01-09 21:31:57 +00:00
|
|
|
float state[8];
|
2017-01-09 11:00:58 +00:00
|
|
|
|
|
|
|
SDL_assert(format == AUDIO_F32SYS);
|
|
|
|
|
2017-01-10 04:37:52 +00:00
|
|
|
SDL_memcpy(state, src, chans*sizeof(*src));
|
2017-01-09 11:00:58 +00:00
|
|
|
|
2017-01-09 21:31:57 +00:00
|
|
|
cvt->len_cvt = SDL_ResampleAudioSimple(chans, cvt->rate_incr, state, src, srclen, dst, dstlen);
|
2017-01-09 11:00:58 +00:00
|
|
|
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. */
|
2016-11-05 06:34:38 +00:00
|
|
|
#define RESAMPLER_FUNCS(chans) \
|
|
|
|
static void SDLCALL \
|
2017-01-09 11:00:58 +00:00
|
|
|
SDL_ResampleCVT_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
|
|
|
|
SDL_ResampleCVT(cvt, chans, format); \
|
2016-11-05 06:34:38 +00:00
|
|
|
}
|
|
|
|
RESAMPLER_FUNCS(1)
|
|
|
|
RESAMPLER_FUNCS(2)
|
|
|
|
RESAMPLER_FUNCS(4)
|
|
|
|
RESAMPLER_FUNCS(6)
|
|
|
|
RESAMPLER_FUNCS(8)
|
|
|
|
#undef RESAMPLER_FUNCS
|
|
|
|
|
2017-01-06 00:12:20 +00:00
|
|
|
static SDL_AudioFilter
|
2017-01-09 11:00:58 +00:00
|
|
|
ChooseCVTResampler(const int dst_channels)
|
2017-01-06 00:12:20 +00:00
|
|
|
{
|
2017-01-09 11:00:58 +00:00
|
|
|
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;
|
2017-01-06 00:12:20 +00:00
|
|
|
}
|
|
|
|
|
2017-01-09 11:00:58 +00:00
|
|
|
return NULL;
|
2017-01-06 00:12:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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. */
|
|
|
|
}
|
|
|
|
|
2017-01-09 11:00:58 +00:00
|
|
|
filter = ChooseCVTResampler(dst_channels);
|
2017-01-06 00:12:20 +00:00
|
|
|
if (filter == NULL) {
|
|
|
|
return SDL_SetError("No conversion available for these rates");
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
|
2017-01-06 00:12:20 +00:00
|
|
|
/* Update (cvt) with filter details... */
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2017-01-06 00:12:20 +00:00
|
|
|
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);
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2017-01-06 00:12:20 +00:00
|
|
|
return 1; /* added a converter. */
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 01:35:24 +00:00
|
|
|
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)
|
|
|
|
{
|
|
|
|
switch (channels) {
|
|
|
|
case 1: /* mono */
|
|
|
|
case 2: /* stereo */
|
|
|
|
case 4: /* quad */
|
|
|
|
case 6: /* 5.1 */
|
|
|
|
return SDL_TRUE; /* supported. */
|
|
|
|
|
|
|
|
case 8: /* !!! FIXME: 7.1 */
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDL_FALSE; /* unsupported. */
|
|
|
|
}
|
|
|
|
|
2015-06-21 15:33:46 +00:00
|
|
|
|
|
|
|
/* Creates a set of audio filters to convert from one format to another.
|
|
|
|
Returns -1 if the format conversion is not supported, 0 if there's
|
|
|
|
no conversion needed, or 1 if the audio filter is set up.
|
|
|
|
*/
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
/* Sanity check target pointer */
|
|
|
|
if (cvt == NULL) {
|
|
|
|
return SDL_InvalidParamError("cvt");
|
|
|
|
}
|
|
|
|
|
2017-01-06 07:53:46 +00:00
|
|
|
/* Make sure we zero out the audio conversion before error checking */
|
|
|
|
SDL_zerop(cvt);
|
|
|
|
|
2017-06-13 01:35:24 +00:00
|
|
|
if (!SDL_SupportedAudioFormat(src_fmt)) {
|
2015-06-21 15:33:46 +00:00
|
|
|
return SDL_SetError("Invalid source format");
|
2017-06-13 01:35:24 +00:00
|
|
|
} else if (!SDL_SupportedAudioFormat(dst_fmt)) {
|
2015-06-21 15:33:46 +00:00
|
|
|
return SDL_SetError("Invalid destination format");
|
2017-06-13 01:35:24 +00:00
|
|
|
} else if (!SDL_SupportedChannelCount(src_channels)) {
|
|
|
|
return SDL_SetError("Invalid source channels");
|
|
|
|
} else if (!SDL_SupportedChannelCount(dst_channels)) {
|
|
|
|
return SDL_SetError("Invalid destination channels");
|
|
|
|
} else if (src_rate == 0) {
|
|
|
|
return SDL_SetError("Source rate is zero");
|
|
|
|
} else if (dst_rate == 0) {
|
|
|
|
return SDL_SetError("Destination rate is zero");
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
|
|
|
|
2016-11-05 08:52:28 +00:00
|
|
|
#if DEBUG_CONVERT
|
2015-06-21 15:33:46 +00:00
|
|
|
printf("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;
|
|
|
|
cvt->filters[0] = NULL;
|
|
|
|
cvt->len_mult = 1;
|
|
|
|
cvt->len_ratio = 1.0;
|
|
|
|
cvt->rate_incr = ((double) dst_rate) / ((double) src_rate);
|
|
|
|
|
2017-01-23 01:27:48 +00:00
|
|
|
/* SDL now favors float32 as its preferred internal format, and considers
|
|
|
|
everything else to be a degenerate case that we might have to make
|
|
|
|
multiple passes over the data to convert to and from float32 as
|
|
|
|
necessary. That being said, we keep one special case around for
|
|
|
|
efficiency: stereo data in Sint16 format, in the native byte order,
|
|
|
|
that only needs resampling. This is likely to be the most popular
|
|
|
|
legacy format, that apps, hardware and the OS are likely to be able
|
|
|
|
to process directly, so we handle this one case directly without
|
|
|
|
unnecessary conversions. This means that apps on embedded devices
|
|
|
|
without floating point hardware should consider aiming for this
|
|
|
|
format as well. */
|
|
|
|
if ((src_channels == 2) && (dst_channels == 2) && (src_fmt == AUDIO_S16SYS) && (dst_fmt == AUDIO_S16SYS) && (src_rate != dst_rate)) {
|
|
|
|
cvt->needed = 1;
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, SDL_ResampleCVT_si16_c2) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2017-01-23 01:27:48 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2016-11-05 06:34:38 +00:00
|
|
|
/* 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 back to native 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
|
2017-01-06 00:12:20 +00:00
|
|
|
buffer is likely to be CPU cache-friendly, avoiding the
|
2016-11-05 06:34:38 +00:00
|
|
|
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. */
|
|
|
|
|
2017-01-06 07:53:46 +00:00
|
|
|
/* 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)) {
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert_Byteswap) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2017-01-06 07:53:46 +00:00
|
|
|
cvt->needed = 1;
|
|
|
|
return 1;
|
|
|
|
}
|
2016-11-05 06:34:38 +00:00
|
|
|
}
|
|
|
|
|
2015-06-21 15:33:46 +00:00
|
|
|
/* Convert data types, if necessary. Updates (cvt). */
|
2017-01-06 07:53:46 +00:00
|
|
|
if (SDL_BuildAudioTypeCVTToFloat(cvt, src_fmt) < 0) {
|
2015-06-21 15:33:46 +00:00
|
|
|
return -1; /* shouldn't happen, but just in case... */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Channel conversion */
|
|
|
|
if (src_channels != dst_channels) {
|
|
|
|
if ((src_channels == 1) && (dst_channels > 1)) {
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertMonoToStereo) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
cvt->len_mult *= 2;
|
|
|
|
src_channels = 2;
|
|
|
|
cvt->len_ratio *= 2;
|
|
|
|
}
|
|
|
|
if ((src_channels == 2) && (dst_channels == 6)) {
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertStereoTo51) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
src_channels = 6;
|
|
|
|
cvt->len_mult *= 3;
|
|
|
|
cvt->len_ratio *= 3;
|
|
|
|
}
|
|
|
|
if ((src_channels == 2) && (dst_channels == 4)) {
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertStereoToQuad) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
src_channels = 4;
|
|
|
|
cvt->len_mult *= 2;
|
|
|
|
cvt->len_ratio *= 2;
|
|
|
|
}
|
|
|
|
while ((src_channels * 2) <= dst_channels) {
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, SDL_ConvertMonoToStereo) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
cvt->len_mult *= 2;
|
|
|
|
src_channels *= 2;
|
|
|
|
cvt->len_ratio *= 2;
|
|
|
|
}
|
|
|
|
if ((src_channels == 6) && (dst_channels <= 2)) {
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert51ToStereo) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
src_channels = 2;
|
|
|
|
cvt->len_ratio /= 3;
|
|
|
|
}
|
|
|
|
if ((src_channels == 6) && (dst_channels == 4)) {
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, SDL_Convert51ToQuad) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-06-21 15:33:46 +00:00
|
|
|
src_channels = 4;
|
|
|
|
cvt->len_ratio /= 2;
|
|
|
|
}
|
|
|
|
/* This assumes that 4 channel audio is in the format:
|
|
|
|
Left {front/back} + Right {front/back}
|
|
|
|
so converting to L/R stereo works properly.
|
|
|
|
*/
|
|
|
|
while (((src_channels % 2) == 0) &&
|
|
|
|
((src_channels / 2) >= dst_channels)) {
|
2017-01-23 05:57:19 +00:00
|
|
|
SDL_AudioFilter filter = NULL;
|
|
|
|
|
|
|
|
#if HAVE_SSE3_INTRINSICS
|
|
|
|
if (SDL_HasSSE3()) {
|
|
|
|
filter = SDL_ConvertStereoToMono_SSE3;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!filter) {
|
|
|
|
filter = SDL_ConvertStereoToMono;
|
|
|
|
}
|
|
|
|
|
2017-06-12 23:39:15 +00:00
|
|
|
if (SDL_AddAudioCVTFilter(cvt, filter) < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2017-01-23 05:57:19 +00:00
|
|
|
|
2015-06-21 15:33:46 +00:00
|
|
|
src_channels /= 2;
|
|
|
|
cvt->len_ratio /= 2;
|
|
|
|
}
|
|
|
|
if (src_channels != dst_channels) {
|
|
|
|
/* Uh oh.. */ ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Do rate conversion, if necessary. Updates (cvt). */
|
2017-01-06 07:53:46 +00:00
|
|
|
if (SDL_BuildAudioResampleCVT(cvt, dst_channels, src_rate, dst_rate) < 0) {
|
2015-06-21 15:33:46 +00:00
|
|
|
return -1; /* shouldn't happen, but just in case... */
|
|
|
|
}
|
|
|
|
|
2017-01-06 00:12:20 +00:00
|
|
|
/* Move to final data type. */
|
2017-01-06 07:53:46 +00:00
|
|
|
if (SDL_BuildAudioTypeCVTFromFloat(cvt, dst_fmt) < 0) {
|
2016-11-05 06:34:38 +00:00
|
|
|
return -1; /* shouldn't happen, but just in case... */
|
2015-06-21 15:33:46 +00:00
|
|
|
}
|
2016-11-05 06:34:38 +00:00
|
|
|
|
|
|
|
cvt->needed = (cvt->filter_index != 0);
|
2015-06-21 15:33:46 +00:00
|
|
|
return (cvt->needed);
|
|
|
|
}
|
|
|
|
|
2017-01-24 05:08:24 +00:00
|
|
|
typedef int (*SDL_ResampleAudioStreamFunc)(SDL_AudioStream *stream, const void *inbuf, const int inbuflen, void *outbuf, const int outbuflen);
|
2017-01-06 10:16:26 +00:00
|
|
|
typedef void (*SDL_ResetAudioStreamResamplerFunc)(SDL_AudioStream *stream);
|
|
|
|
typedef void (*SDL_CleanupAudioStreamResamplerFunc)(SDL_AudioStream *stream);
|
2017-01-06 00:29:38 +00:00
|
|
|
|
|
|
|
struct SDL_AudioStream
|
|
|
|
{
|
|
|
|
SDL_AudioCVT cvt_before_resampling;
|
|
|
|
SDL_AudioCVT cvt_after_resampling;
|
|
|
|
SDL_DataQueue *queue;
|
2017-01-24 05:51:33 +00:00
|
|
|
Uint8 *work_buffer_base; /* maybe unaligned pointer from SDL_realloc(). */
|
2017-01-06 00:29:38 +00:00
|
|
|
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;
|
2017-01-06 10:16:26 +00:00
|
|
|
void *resampler_state;
|
|
|
|
SDL_ResampleAudioStreamFunc resampler_func;
|
|
|
|
SDL_ResetAudioStreamResamplerFunc reset_resampler_func;
|
|
|
|
SDL_CleanupAudioStreamResamplerFunc cleanup_resampler_func;
|
2017-01-06 00:29:38 +00:00
|
|
|
};
|
|
|
|
|
2017-01-25 01:30:48 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-01-07 04:43:53 +00:00
|
|
|
#ifdef HAVE_LIBSAMPLERATE_H
|
2017-01-06 10:16:26 +00:00
|
|
|
static int
|
2017-01-24 05:08:24 +00:00
|
|
|
SDL_ResampleAudioStream_SRC(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
|
2017-01-06 10:16:26 +00:00
|
|
|
{
|
2017-01-24 05:08:24 +00:00
|
|
|
const float *inbuf = (const float *) _inbuf;
|
|
|
|
float *outbuf = (float *) _outbuf;
|
2017-01-09 11:00:58 +00:00
|
|
|
const int framelen = sizeof(float) * stream->pre_resample_channels;
|
2017-01-08 19:18:03 +00:00
|
|
|
SRC_STATE *state = (SRC_STATE *)stream->resampler_state;
|
2017-01-06 10:16:26 +00:00
|
|
|
SRC_DATA data;
|
|
|
|
int result;
|
|
|
|
|
2017-01-25 01:30:48 +00:00
|
|
|
if (inbuf == ((const float *) outbuf)) { /* libsamplerate can't work in-place. */
|
|
|
|
Uint8 *ptr = EnsureStreamBufferSize(stream, inbuflen + outbuflen);
|
|
|
|
if (ptr == NULL) {
|
|
|
|
SDL_OutOfMemory();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
SDL_memcpy(ptr + outbuflen, ptr, inbuflen);
|
|
|
|
inbuf = (const float *) (ptr + outbuflen);
|
|
|
|
outbuf = (float *) ptr;
|
|
|
|
}
|
|
|
|
|
2017-01-07 04:43:53 +00:00
|
|
|
data.data_in = (float *)inbuf; /* Older versions of libsamplerate had a non-const pointer, but didn't write to it */
|
2017-01-09 11:00:58 +00:00
|
|
|
data.input_frames = inbuflen / framelen;
|
2017-01-06 10:16:26 +00:00
|
|
|
data.input_frames_used = 0;
|
|
|
|
|
|
|
|
data.data_out = outbuf;
|
2017-01-09 11:00:58 +00:00
|
|
|
data.output_frames = outbuflen / framelen;
|
2017-01-06 10:16:26 +00:00
|
|
|
|
|
|
|
data.end_of_input = 0;
|
|
|
|
data.src_ratio = stream->rate_incr;
|
|
|
|
|
2017-01-08 19:18:03 +00:00
|
|
|
result = SRC_src_process(state, &data);
|
2017-01-06 10:16:26 +00:00
|
|
|
if (result != 0) {
|
2017-01-08 19:18:03 +00:00
|
|
|
SDL_SetError("src_process() failed: %s", SRC_src_strerror(result));
|
2017-01-06 10:16:26 +00:00
|
|
|
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)
|
|
|
|
{
|
2017-01-08 19:18:03 +00:00
|
|
|
SRC_src_reset((SRC_STATE *)stream->resampler_state);
|
2017-01-06 10:16:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
SDL_CleanupAudioStreamResampler_SRC(SDL_AudioStream *stream)
|
|
|
|
{
|
2017-01-08 19:18:03 +00:00
|
|
|
SRC_STATE *state = (SRC_STATE *)stream->resampler_state;
|
2017-01-06 10:16:26 +00:00
|
|
|
if (state) {
|
2017-01-08 19:18:03 +00:00
|
|
|
SRC_src_delete(state);
|
2017-01-06 10:16:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2017-01-08 19:18:03 +00:00
|
|
|
int result = 0;
|
|
|
|
SRC_STATE *state = NULL;
|
2017-01-06 10:16:26 +00:00
|
|
|
|
2017-01-08 19:18:03 +00:00
|
|
|
if (SRC_available) {
|
2017-01-24 20:52:22 +00:00
|
|
|
state = SRC_src_new(SRC_converter, stream->pre_resample_channels, &result);
|
2017-01-08 19:18:03 +00:00
|
|
|
if (!state) {
|
|
|
|
SDL_SetError("src_new() failed: %s", SRC_src_strerror(result));
|
|
|
|
}
|
2017-01-06 10:16:26 +00:00
|
|
|
}
|
|
|
|
|
2017-01-08 19:18:03 +00:00
|
|
|
if (!state) {
|
|
|
|
SDL_CleanupAudioStreamResampler_SRC(stream);
|
2017-01-06 10:16:26 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-01-07 04:43:53 +00:00
|
|
|
#endif /* HAVE_LIBSAMPLERATE_H */
|
2017-01-06 10:16:26 +00:00
|
|
|
|
2017-01-08 19:18:03 +00:00
|
|
|
|
2017-01-06 10:16:26 +00:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
SDL_bool resampler_seeded;
|
2017-01-24 05:08:24 +00:00
|
|
|
union
|
|
|
|
{
|
|
|
|
float f[8];
|
|
|
|
Sint16 si16[2];
|
|
|
|
} resampler_state;
|
2017-01-06 10:16:26 +00:00
|
|
|
} SDL_AudioStreamResamplerState;
|
|
|
|
|
|
|
|
static int
|
2017-01-24 05:08:24 +00:00
|
|
|
SDL_ResampleAudioStream(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
|
2017-01-06 10:16:26 +00:00
|
|
|
{
|
2017-01-24 05:08:24 +00:00
|
|
|
const float *inbuf = (const float *) _inbuf;
|
|
|
|
float *outbuf = (float *) _outbuf;
|
2017-01-06 10:16:26 +00:00
|
|
|
SDL_AudioStreamResamplerState *state = (SDL_AudioStreamResamplerState*)stream->resampler_state;
|
|
|
|
const int chans = (int)stream->pre_resample_channels;
|
|
|
|
|
2017-01-24 05:08:24 +00:00
|
|
|
SDL_assert(chans <= SDL_arraysize(state->resampler_state.f));
|
2017-01-06 10:16:26 +00:00
|
|
|
|
|
|
|
if (!state->resampler_seeded) {
|
2017-01-24 05:08:24 +00:00
|
|
|
SDL_memcpy(state->resampler_state.f, inbuf, chans * sizeof (float));
|
2017-01-06 10:16:26 +00:00
|
|
|
state->resampler_seeded = SDL_TRUE;
|
|
|
|
}
|
|
|
|
|
2017-01-24 05:08:24 +00:00
|
|
|
return SDL_ResampleAudioSimple(chans, stream->rate_incr, state->resampler_state.f, inbuf, inbuflen, outbuf, outbuflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
SDL_ResampleAudioStream_si16_c2(SDL_AudioStream *stream, const void *_inbuf, const int inbuflen, void *_outbuf, const int outbuflen)
|
|
|
|
{
|
|
|
|
const Sint16 *inbuf = (const Sint16 *) _inbuf;
|
|
|
|
Sint16 *outbuf = (Sint16 *) _outbuf;
|
|
|
|
SDL_AudioStreamResamplerState *state = (SDL_AudioStreamResamplerState*)stream->resampler_state;
|
|
|
|
|
2017-03-03 21:38:17 +00:00
|
|
|
SDL_assert(((int)stream->pre_resample_channels) <= SDL_arraysize(state->resampler_state.si16));
|
2017-01-24 05:08:24 +00:00
|
|
|
|
|
|
|
if (!state->resampler_seeded) {
|
|
|
|
state->resampler_state.si16[0] = inbuf[0];
|
|
|
|
state->resampler_state.si16[1] = inbuf[1];
|
|
|
|
state->resampler_seeded = SDL_TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDL_ResampleAudioSimple_si16_c2(stream->rate_incr, state->resampler_state.si16, inbuf, inbuflen, outbuf, outbuflen);
|
2017-01-06 10:16:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-01-08 19:17:09 +00:00
|
|
|
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)
|
2017-01-06 00:29:38 +00:00
|
|
|
{
|
|
|
|
const int packetlen = 4096; /* !!! FIXME: good enough for now. */
|
|
|
|
Uint8 pre_resample_channels;
|
|
|
|
SDL_AudioStream *retval;
|
2017-01-24 05:08:24 +00:00
|
|
|
#ifndef HAVE_LIBSAMPLERATE_H
|
|
|
|
const SDL_bool SRC_available = SDL_FALSE;
|
|
|
|
#endif
|
2017-01-06 00:29:38 +00:00
|
|
|
|
|
|
|
retval = (SDL_AudioStream *) SDL_calloc(1, sizeof (SDL_AudioStream));
|
|
|
|
if (!retval) {
|
|
|
|
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);
|
|
|
|
|
2017-02-13 21:56:41 +00:00
|
|
|
retval->src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels;
|
2017-01-06 00:29:38 +00:00
|
|
|
retval->src_format = src_format;
|
|
|
|
retval->src_channels = src_channels;
|
|
|
|
retval->src_rate = src_rate;
|
2017-02-13 21:56:41 +00:00
|
|
|
retval->dst_sample_frame_size = (SDL_AUDIO_BITSIZE(dst_format) / 8) * dst_channels;
|
2017-01-06 00:29:38 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
/* Not resampling? It's an easy conversion (and maybe not even that!). */
|
|
|
|
if (src_rate == dst_rate) {
|
|
|
|
retval->cvt_before_resampling.needed = SDL_FALSE;
|
2017-01-06 10:16:26 +00:00
|
|
|
if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, src_format, src_channels, dst_rate, dst_format, dst_channels, dst_rate) < 0) {
|
|
|
|
SDL_FreeAudioStream(retval);
|
2017-01-06 00:29:38 +00:00
|
|
|
return NULL; /* SDL_BuildAudioCVT should have called SDL_SetError. */
|
|
|
|
}
|
2017-01-24 05:08:24 +00:00
|
|
|
/* fast path special case for stereo Sint16 data that just needs resampling. */
|
|
|
|
} else if ((!SRC_available) && (src_channels == 2) && (dst_channels == 2) && (src_format == AUDIO_S16SYS) && (dst_format == AUDIO_S16SYS)) {
|
|
|
|
SDL_assert(src_rate != dst_rate);
|
|
|
|
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_si16_c2;
|
|
|
|
retval->reset_resampler_func = SDL_ResetAudioStreamResampler;
|
|
|
|
retval->cleanup_resampler_func = SDL_CleanupAudioStreamResampler;
|
2017-01-06 00:29:38 +00:00
|
|
|
} else {
|
|
|
|
/* Don't resample at first. Just get us to Float32 format. */
|
|
|
|
/* !!! FIXME: convert to int32 on devices without hardware float. */
|
2017-01-06 10:16:26 +00:00
|
|
|
if (SDL_BuildAudioCVT(&retval->cvt_before_resampling, src_format, src_channels, src_rate, AUDIO_F32SYS, pre_resample_channels, src_rate) < 0) {
|
|
|
|
SDL_FreeAudioStream(retval);
|
2017-01-06 00:29:38 +00:00
|
|
|
return NULL; /* SDL_BuildAudioCVT should have called SDL_SetError. */
|
|
|
|
}
|
|
|
|
|
2017-01-07 04:43:53 +00:00
|
|
|
#ifdef HAVE_LIBSAMPLERATE_H
|
2017-01-06 10:16:26 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-01-06 00:29:38 +00:00
|
|
|
/* Convert us to the final format after resampling. */
|
2017-01-06 10:16:26 +00:00
|
|
|
if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, AUDIO_F32SYS, pre_resample_channels, dst_rate, dst_format, dst_channels, dst_rate) < 0) {
|
|
|
|
SDL_FreeAudioStream(retval);
|
2017-01-06 00:29:38 +00:00
|
|
|
return NULL; /* SDL_BuildAudioCVT should have called SDL_SetError. */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
retval->queue = SDL_NewDataQueue(packetlen, packetlen * 2);
|
|
|
|
if (!retval->queue) {
|
2017-01-06 10:16:26 +00:00
|
|
|
SDL_FreeAudioStream(retval);
|
2017-01-06 00:29:38 +00:00
|
|
|
return NULL; /* SDL_NewDataQueue should have called SDL_SetError. */
|
|
|
|
}
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _buflen)
|
|
|
|
{
|
|
|
|
int buflen = (int) _buflen;
|
2017-01-24 15:09:29 +00:00
|
|
|
const void *origbuf = buf;
|
2017-01-06 00:29:38 +00:00
|
|
|
|
2017-01-24 05:51:33 +00:00
|
|
|
/* !!! 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. */
|
|
|
|
|
2017-01-06 00:29:38 +00:00
|
|
|
if (!stream) {
|
|
|
|
return SDL_InvalidParamError("stream");
|
|
|
|
} else if (!buf) {
|
|
|
|
return SDL_InvalidParamError("buf");
|
|
|
|
} else if (buflen == 0) {
|
|
|
|
return 0; /* nothing to do. */
|
|
|
|
} else if ((buflen % stream->src_sample_frame_size) != 0) {
|
|
|
|
return SDL_SetError("Can't add partial sample frames");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->cvt_before_resampling.needed) {
|
|
|
|
const int workbuflen = buflen * stream->cvt_before_resampling.len_mult; /* will be "* 1" if not needed */
|
2017-01-24 05:51:33 +00:00
|
|
|
Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen);
|
2017-01-06 00:29:38 +00:00
|
|
|
if (workbuf == NULL) {
|
|
|
|
return -1; /* probably out of memory. */
|
|
|
|
}
|
2017-01-24 15:09:29 +00:00
|
|
|
SDL_assert(buf == origbuf);
|
2017-01-06 00:29:38 +00:00
|
|
|
SDL_memcpy(workbuf, buf, buflen);
|
|
|
|
stream->cvt_before_resampling.buf = workbuf;
|
|
|
|
stream->cvt_before_resampling.len = buflen;
|
|
|
|
if (SDL_ConvertAudio(&stream->cvt_before_resampling) == -1) {
|
|
|
|
return -1; /* uhoh! */
|
|
|
|
}
|
|
|
|
buf = workbuf;
|
|
|
|
buflen = stream->cvt_before_resampling.len_cvt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->dst_rate != stream->src_rate) {
|
|
|
|
const int workbuflen = buflen * ((int) SDL_ceil(stream->rate_incr));
|
2017-01-24 05:51:33 +00:00
|
|
|
Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen);
|
2017-01-06 00:29:38 +00:00
|
|
|
if (workbuf == NULL) {
|
|
|
|
return -1; /* probably out of memory. */
|
|
|
|
}
|
2017-01-25 01:30:48 +00:00
|
|
|
/* don't SDL_memcpy(workbuf, buf, buflen) here; our resampler can work inplace or not,
|
|
|
|
libsamplerate needs buffers to be separate; either way, avoid a copy here if possible. */
|
|
|
|
if (buf != origbuf) {
|
|
|
|
buf = workbuf; /* in case we realloc()'d the pointer. */
|
2017-01-24 05:17:40 +00:00
|
|
|
}
|
2017-01-25 01:30:48 +00:00
|
|
|
buflen = stream->resampler_func(stream, buf, buflen, workbuf, workbuflen);
|
|
|
|
buf = EnsureStreamBufferSize(stream, workbuflen);
|
|
|
|
SDL_assert(buf != NULL); /* shouldn't be growing, just aligning. */
|
2017-01-06 00:29:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (stream->cvt_after_resampling.needed) {
|
2017-01-24 05:08:24 +00:00
|
|
|
const int workbuflen = buflen * stream->cvt_after_resampling.len_mult; /* will be "* 1" if not needed */
|
2017-01-24 05:51:33 +00:00
|
|
|
Uint8 *workbuf = EnsureStreamBufferSize(stream, workbuflen);
|
2017-01-06 00:29:38 +00:00
|
|
|
if (workbuf == NULL) {
|
|
|
|
return -1; /* probably out of memory. */
|
|
|
|
}
|
2017-01-24 15:09:29 +00:00
|
|
|
if (buf == origbuf) { /* copy if we haven't before. */
|
2017-01-24 05:17:40 +00:00
|
|
|
SDL_memcpy(workbuf, buf, buflen);
|
|
|
|
}
|
2017-01-06 00:29:38 +00:00
|
|
|
stream->cvt_after_resampling.buf = workbuf;
|
|
|
|
stream->cvt_after_resampling.len = buflen;
|
|
|
|
if (SDL_ConvertAudio(&stream->cvt_after_resampling) == -1) {
|
|
|
|
return -1; /* uhoh! */
|
|
|
|
}
|
|
|
|
buf = workbuf;
|
|
|
|
buflen = stream->cvt_after_resampling.len_cvt;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SDL_WriteToDataQueue(stream->queue, buf, buflen);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
SDL_AudioStreamClear(SDL_AudioStream *stream)
|
|
|
|
{
|
|
|
|
if (!stream) {
|
|
|
|
SDL_InvalidParamError("stream");
|
|
|
|
} else {
|
|
|
|
SDL_ClearDataQueue(stream->queue, stream->packetlen * 2);
|
2017-01-07 02:23:51 +00:00
|
|
|
if (stream->reset_resampler_func) {
|
|
|
|
stream->reset_resampler_func(stream);
|
|
|
|
}
|
2017-01-06 00:29:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* get converted/resampled data from the stream */
|
|
|
|
int
|
2017-01-06 06:02:58 +00:00
|
|
|
SDL_AudioStreamGet(SDL_AudioStream *stream, void *buf, const Uint32 len)
|
2017-01-06 00:29:38 +00:00
|
|
|
{
|
|
|
|
if (!stream) {
|
|
|
|
return SDL_InvalidParamError("stream");
|
|
|
|
} else if (!buf) {
|
|
|
|
return SDL_InvalidParamError("buf");
|
|
|
|
} else if (len == 0) {
|
|
|
|
return 0; /* nothing to do. */
|
|
|
|
} else if ((len % stream->dst_sample_frame_size) != 0) {
|
|
|
|
return SDL_SetError("Can't request partial sample frames");
|
|
|
|
}
|
|
|
|
|
2017-01-06 06:02:58 +00:00
|
|
|
return (int) SDL_ReadFromDataQueue(stream->queue, buf, len);
|
2017-01-06 00:29:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* number of converted/resampled bytes available */
|
|
|
|
int
|
|
|
|
SDL_AudioStreamAvailable(SDL_AudioStream *stream)
|
|
|
|
{
|
|
|
|
return stream ? (int) SDL_CountDataQueue(stream->queue) : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* dispose of a stream */
|
|
|
|
void
|
|
|
|
SDL_FreeAudioStream(SDL_AudioStream *stream)
|
|
|
|
{
|
|
|
|
if (stream) {
|
2017-01-06 10:16:26 +00:00
|
|
|
if (stream->cleanup_resampler_func) {
|
|
|
|
stream->cleanup_resampler_func(stream);
|
|
|
|
}
|
2017-01-06 00:29:38 +00:00
|
|
|
SDL_FreeDataQueue(stream->queue);
|
2017-01-24 05:51:33 +00:00
|
|
|
SDL_free(stream->work_buffer_base);
|
2017-01-06 00:29:38 +00:00
|
|
|
SDL_free(stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-21 15:33:46 +00:00
|
|
|
/* vi: set ts=4 sw=4 expandtab: */
|
2016-11-05 06:34:38 +00:00
|
|
|
|