audio: More effort to improve and simplify audio resamplers.

This commit is contained in:
Ryan C. Gordon 2017-01-05 19:12:20 -05:00
parent 52130bde40
commit f12ab8f2b3
3 changed files with 128 additions and 162 deletions

View File

@ -50,9 +50,8 @@ void SDLCALL SDL_Convert_F32_to_S16(SDL_AudioCVT *cvt, SDL_AudioFormat format);
void SDLCALL SDL_Convert_F32_to_U16(SDL_AudioCVT *cvt, SDL_AudioFormat format); void SDLCALL SDL_Convert_F32_to_U16(SDL_AudioCVT *cvt, SDL_AudioFormat format);
void SDLCALL SDL_Convert_F32_to_S32(SDL_AudioCVT *cvt, SDL_AudioFormat format); void SDLCALL SDL_Convert_F32_to_S32(SDL_AudioCVT *cvt, SDL_AudioFormat format);
void SDL_Upsample_Arbitrary(SDL_AudioCVT *cvt, const int channels); void SDL_Upsample_Arbitrary(SDL_AudioCVT *cvt, const int channels);
void SDL_Upsample_Multiple(SDL_AudioCVT *cvt, const int channels);
void SDL_Downsample_Arbitrary(SDL_AudioCVT *cvt, const int channels); void SDL_Downsample_Arbitrary(SDL_AudioCVT *cvt, const int channels);
void SDL_Upsample_x2(SDL_AudioCVT *cvt, const int channels); void SDL_Downsample_Multiple(SDL_AudioCVT *cvt, const int channels);
void SDL_Upsample_x4(SDL_AudioCVT *cvt, const int channels);
void SDL_Downsample_Multiple(SDL_AudioCVT *cvt, const int multiple, const int channels);
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View File

@ -331,14 +331,43 @@ SDL_BuildAudioTypeCVTFromFloat(SDL_AudioCVT *cvt, const SDL_AudioFormat dst_fmt)
return retval; return retval;
} }
/* !!! FIXME: We only have this macro salsa because SDL_AudioCVT doesn't store
!!! FIXME: channel info or integer sample rates, so we have to have
!!! FIXME: function entry points for each supported channel count and
!!! FIXME: multiple vs arbitrary. When we rev the ABI, remove this. */
#define RESAMPLER_FUNCS(chans) \
static void SDLCALL \
SDL_Upsample_Multiple_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
SDL_assert(format == AUDIO_F32SYS); \
SDL_Upsample_Multiple(cvt, chans); \
} \
static void SDLCALL \
SDL_Upsample_Arbitrary_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
SDL_assert(format == AUDIO_F32SYS); \
SDL_Upsample_Arbitrary(cvt, chans); \
}\
static void SDLCALL \
SDL_Downsample_Multiple_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
SDL_assert(format == AUDIO_F32SYS); \
SDL_Downsample_Multiple(cvt, chans); \
} \
static void SDLCALL \
SDL_Downsample_Arbitrary_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
SDL_assert(format == AUDIO_F32SYS); \
SDL_Downsample_Arbitrary(cvt, chans); \
}
RESAMPLER_FUNCS(1)
RESAMPLER_FUNCS(2)
RESAMPLER_FUNCS(4)
RESAMPLER_FUNCS(6)
RESAMPLER_FUNCS(8)
#undef RESAMPLER_FUNCS
static int static int
SDL_FindFrequencyMultiple(const int src_rate, const int dst_rate) SDL_FindFrequencyMultiple(const int src_rate, const int dst_rate)
{ {
int retval = 0;
/* If we only built with the arbitrary resamplers, ignore multiples. */
int lo, hi; int lo, hi;
int div;
SDL_assert(src_rate != 0); SDL_assert(src_rate != 0);
SDL_assert(dst_rate != 0); SDL_assert(dst_rate != 0);
@ -352,110 +381,73 @@ SDL_FindFrequencyMultiple(const int src_rate, const int dst_rate)
hi = src_rate; hi = src_rate;
} }
/* zero means "not a supported multiple" ... we only do 2x and 4x. */
if ((hi % lo) != 0) if ((hi % lo) != 0)
return 0; /* not a multiple. */ return 0; /* not a multiple. */
div = hi / lo; return hi / lo;
retval = ((div == 2) || (div == 4)) ? div : 0;
return retval;
} }
#define RESAMPLER_FUNCS(chans) \ static SDL_AudioFilter
static void SDLCALL \ ChooseResampler(const int dst_channels, const int src_rate, const int dst_rate)
SDL_Upsample_Arbitrary_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \ {
SDL_assert(format == AUDIO_F32SYS); \ const int upsample = (src_rate < dst_rate) ? 1 : 0;
SDL_Upsample_Arbitrary(cvt, chans); \ const int multiple = SDL_FindFrequencyMultiple(src_rate, dst_rate);
}\ SDL_AudioFilter filter = NULL;
static void SDLCALL \
SDL_Downsample_Arbitrary_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \ #define PICK_CHANNEL_FILTER(upordown, resampler) switch (dst_channels) { \
SDL_assert(format == AUDIO_F32SYS); \ case 1: filter = SDL_##upordown##_##resampler##_c1; break; \
SDL_Downsample_Arbitrary(cvt, chans); \ case 2: filter = SDL_##upordown##_##resampler##_c2; break; \
} \ case 4: filter = SDL_##upordown##_##resampler##_c4; break; \
static void SDLCALL \ case 6: filter = SDL_##upordown##_##resampler##_c6; break; \
SDL_Upsample_x2_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \ case 8: filter = SDL_##upordown##_##resampler##_c8; break; \
SDL_assert(format == AUDIO_F32SYS); \ default: break; \
SDL_Upsample_x2(cvt, chans); \
} \
static void SDLCALL \
SDL_Downsample_x2_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
SDL_assert(format == AUDIO_F32SYS); \
SDL_Downsample_Multiple(cvt, 2, chans); \
} \
static void SDLCALL \
SDL_Upsample_x4_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
SDL_assert(format == AUDIO_F32SYS); \
SDL_Upsample_x4(cvt, chans); \
} \
static void SDLCALL \
SDL_Downsample_x4_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
SDL_assert(format == AUDIO_F32SYS); \
SDL_Downsample_Multiple(cvt, 4, chans); \
} }
RESAMPLER_FUNCS(1)
RESAMPLER_FUNCS(2) if (upsample) {
RESAMPLER_FUNCS(4) if (multiple) {
RESAMPLER_FUNCS(6) PICK_CHANNEL_FILTER(Upsample, Multiple);
RESAMPLER_FUNCS(8) } else {
#undef RESAMPLER_FUNCS PICK_CHANNEL_FILTER(Upsample, Arbitrary);
}
} else {
if (multiple) {
PICK_CHANNEL_FILTER(Downsample, Multiple);
} else {
PICK_CHANNEL_FILTER(Downsample, Arbitrary);
}
}
#undef PICK_CHANNEL_FILTER
return filter;
}
static int static int
SDL_BuildAudioResampleCVT(SDL_AudioCVT * cvt, int dst_channels, SDL_BuildAudioResampleCVT(SDL_AudioCVT * cvt, const int dst_channels,
int src_rate, int dst_rate) const int src_rate, const int dst_rate)
{ {
if (src_rate != dst_rate) { SDL_AudioFilter filter;
const int upsample = (src_rate < dst_rate) ? 1 : 0;
const int multiple = SDL_FindFrequencyMultiple(src_rate, dst_rate);
SDL_AudioFilter filter = NULL;
#define PICK_CHANNEL_FILTER(upordown, resampler) switch (dst_channels) { \ if (src_rate == dst_rate) {
case 1: filter = SDL_##upordown##_##resampler##_c1; break; \ return 0; /* no conversion necessary. */
case 2: filter = SDL_##upordown##_##resampler##_c2; break; \
case 4: filter = SDL_##upordown##_##resampler##_c4; break; \
case 6: filter = SDL_##upordown##_##resampler##_c6; break; \
case 8: filter = SDL_##upordown##_##resampler##_c8; break; \
default: break; \
}
if (upsample) {
if (multiple == 0) {
PICK_CHANNEL_FILTER(Upsample, Arbitrary);
} else if (multiple == 2) {
PICK_CHANNEL_FILTER(Upsample, x2);
} else if (multiple == 4) {
PICK_CHANNEL_FILTER(Upsample, x4);
}
} else {
if (multiple == 0) {
PICK_CHANNEL_FILTER(Downsample, Arbitrary);
} else if (multiple == 2) {
PICK_CHANNEL_FILTER(Downsample, x2);
} else if (multiple == 4) {
PICK_CHANNEL_FILTER(Downsample, x4);
}
}
#undef PICK_CHANNEL_FILTER
if (filter == NULL) {
return SDL_SetError("No conversion available for these rates");
}
/* Update (cvt) with filter details... */
cvt->filters[cvt->filter_index++] = filter;
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; /* added a converter. */
} }
return 0; /* no conversion necessary. */ filter = ChooseResampler(dst_channels, src_rate, dst_rate);
if (filter == NULL) {
return SDL_SetError("No conversion available for these rates");
}
/* Update (cvt) with filter details... */
cvt->filters[cvt->filter_index++] = filter;
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; /* added a converter. */
} }
@ -514,7 +506,7 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
The expectation is we can process data faster in float32 The expectation is we can process data faster in float32
(possibly with SIMD), and making several passes over the same (possibly with SIMD), and making several passes over the same
buffer in is likely to be CPU cache-friendly, avoiding the buffer is likely to be CPU cache-friendly, avoiding the
biggest performance hit in modern times. Previously we had biggest performance hit in modern times. Previously we had
(script-generated) custom converters for every data type and (script-generated) custom converters for every data type and
it was a bloat on SDL compile times and final library size. */ it was a bloat on SDL compile times and final library size. */
@ -585,11 +577,11 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
} }
/* Do rate conversion, if necessary. Updates (cvt). */ /* Do rate conversion, if necessary. Updates (cvt). */
if (SDL_BuildAudioResampleCVT(cvt, dst_channels, src_rate, dst_rate) == if (SDL_BuildAudioResampleCVT(cvt, dst_channels, src_rate, dst_rate) == -1) {
-1) {
return -1; /* shouldn't happen, but just in case... */ return -1; /* shouldn't happen, but just in case... */
} }
/* Move to final data type. */
if (SDL_BuildAudioTypeCVTFromFloat(cvt, dst_fmt) == -1) { if (SDL_BuildAudioTypeCVTFromFloat(cvt, dst_fmt) == -1) {
return -1; /* shouldn't happen, but just in case... */ return -1; /* shouldn't happen, but just in case... */
} }

View File

@ -220,14 +220,14 @@ void
SDL_Upsample_Arbitrary(SDL_AudioCVT *cvt, const int channels) SDL_Upsample_Arbitrary(SDL_AudioCVT *cvt, const int channels)
{ {
const int srcsize = cvt->len_cvt - (64 * channels); const int srcsize = cvt->len_cvt - (64 * channels);
const int dstsize = (int) (((double)(cvt->len_cvt/(channels*4))) * cvt->rate_incr) * (channels*4); const int dstsize = (int) ((((double)(cvt->len_cvt/(channels*4))) * cvt->rate_incr)) * (channels*4);
register int eps = 0; register int eps = 0;
float *dst = ((float *) (cvt->buf + dstsize)) - channels; float *dst = ((float *) (cvt->buf + dstsize)) - channels;
const float *src = ((float *) (cvt->buf + cvt->len_cvt)) - channels; const float *src = ((float *) (cvt->buf + cvt->len_cvt)) - channels;
const float *target = ((const float *) cvt->buf); const float *target = ((const float *) cvt->buf);
const size_t cpy = sizeof (float) * channels; const size_t cpy = sizeof (float) * channels;
float last_sample[8];
float sample[8]; float sample[8];
float last_sample[8];
int i; int i;
#if DEBUG_CONVERT #if DEBUG_CONVERT
@ -236,7 +236,9 @@ SDL_Upsample_Arbitrary(SDL_AudioCVT *cvt, const int channels)
SDL_assert(channels <= 8); SDL_assert(channels <= 8);
SDL_memcpy(sample, src, cpy); for (i = 0; i < channels; i++) {
sample[i] = (float) ((((double) src[i]) + ((double) src[i - channels])) * 0.5);
}
SDL_memcpy(last_sample, src, cpy); SDL_memcpy(last_sample, src, cpy);
while (dst > target) { while (dst > target) {
@ -244,11 +246,15 @@ SDL_Upsample_Arbitrary(SDL_AudioCVT *cvt, const int channels)
dst -= channels; dst -= channels;
eps += srcsize; eps += srcsize;
if ((eps << 1) >= dstsize) { if ((eps << 1) >= dstsize) {
src -= channels; if (src > target) {
for (i = 0; i < channels; i++) { src -= channels;
sample[i] = (float) ((((double) src[i]) + ((double) last_sample[i])) * 0.5); for (i = 0; i < channels; i++) {
sample[i] = (float) ((((double) src[i]) + ((double) last_sample[i])) * 0.5);
}
} else {
} }
SDL_memcpy(last_sample, sample, cpy); SDL_memcpy(last_sample, src, cpy);
eps -= dstsize; eps -= dstsize;
} }
} }
@ -291,7 +297,7 @@ SDL_Downsample_Arbitrary(SDL_AudioCVT *cvt, const int channels)
for (i = 0; i < channels; i++) { for (i = 0; i < channels; i++) {
sample[i] = (float) ((((double) src[i]) + ((double) last_sample[i])) * 0.5); sample[i] = (float) ((((double) src[i]) + ((double) last_sample[i])) * 0.5);
} }
SDL_memcpy(last_sample, sample, cpy); SDL_memcpy(last_sample, src, cpy);
eps -= srcsize; eps -= srcsize;
} }
} }
@ -303,32 +309,43 @@ SDL_Downsample_Arbitrary(SDL_AudioCVT *cvt, const int channels)
} }
void void
SDL_Upsample_x2(SDL_AudioCVT *cvt, const int channels) SDL_Upsample_Multiple(SDL_AudioCVT *cvt, const int channels)
{ {
const int dstsize = cvt->len_cvt * 2; const int multiple = (int) cvt->rate_incr;
float *dst = ((float *) (cvt->buf + dstsize)) - (channels * 2); const int dstsize = cvt->len_cvt * multiple;
float *buf = (float *) cvt->buf;
float *dst = ((float *) (cvt->buf + dstsize)) - channels;
const float *src = ((float *) (cvt->buf + cvt->len_cvt)) - channels; const float *src = ((float *) (cvt->buf + cvt->len_cvt)) - channels;
const float *target = ((const float *) cvt->buf); const float *target = buf + channels;
const size_t cpy = sizeof (float) * channels; const size_t cpy = sizeof (float) * channels;
float last_sample[8]; float last_sample[8];
int i; int i;
#if DEBUG_CONVERT #if DEBUG_CONVERT
fprintf(stderr, "Upsample (x2), %d channels.\n", channels); fprintf(stderr, "Upsample (x%d), %d channels.\n", multiple, channels);
#endif #endif
SDL_assert(channels <= 8); SDL_assert(channels <= 8);
SDL_memcpy(last_sample, src, cpy); SDL_memcpy(last_sample, src, cpy);
while (dst > target) { while (dst > target) {
SDL_assert(src >= buf);
for (i = 0; i < channels; i++) { for (i = 0; i < channels; i++) {
dst[i] = (float) ((((double)src[i]) + ((double)last_sample[i])) * 0.5); dst[i] = (float) ((((double)src[i]) + ((double)last_sample[i])) * 0.5);
} }
dst -= channels; dst -= channels;
SDL_memcpy(dst, src, cpy);
SDL_memcpy(last_sample, src, cpy); for (i = 1; i < multiple; i++) {
SDL_memcpy(dst, dst + channels, cpy);
dst -= channels;
}
src -= channels; src -= channels;
dst -= channels; if (src > buf) {
SDL_memcpy(last_sample, src - channels, cpy);
}
} }
cvt->len_cvt = dstsize; cvt->len_cvt = dstsize;
@ -338,51 +355,9 @@ SDL_Upsample_x2(SDL_AudioCVT *cvt, const int channels)
} }
void void
SDL_Upsample_x4(SDL_AudioCVT *cvt, const int channels) SDL_Downsample_Multiple(SDL_AudioCVT *cvt, const int channels)
{
const int dstsize = cvt->len_cvt * 4;
float *dst = ((float *) (cvt->buf + dstsize)) - (channels * 4);
const float *src = ((float *) (cvt->buf + cvt->len_cvt)) - channels;
const float *target = ((const float *) cvt->buf);
const size_t cpy = sizeof (float) * channels;
float last_sample[8];
int i;
#if DEBUG_CONVERT
fprintf(stderr, "Upsample (x4), %d channels.\n", channels);
#endif
SDL_assert(channels <= 8);
SDL_memcpy(last_sample, src, cpy);
while (dst > target) {
for (i = 0; i < channels; i++) {
dst[i] = (float) ((((double) src[i]) + (3.0 * ((double) last_sample[i]))) * 0.25);
}
dst -= channels;
for (i = 0; i < channels; i++) {
dst[i] = (float) ((((double) src[i]) + ((double) last_sample[i])) * 0.25);
}
dst -= channels;
for (i = 0; i < channels; i++) {
dst[i] = (float) (((3.0 * ((double) src[i])) + ((double) last_sample[i])) * 0.25);
}
dst -= channels;
SDL_memcpy(dst, src, cpy);
dst -= channels;
SDL_memcpy(last_sample, src, cpy);
src -= channels;
}
cvt->len_cvt = dstsize;
if (cvt->filters[++cvt->filter_index]) {
cvt->filters[cvt->filter_index](cvt, AUDIO_F32SYS);
}
}
void
SDL_Downsample_Multiple(SDL_AudioCVT *cvt, const int multiple, const int channels)
{ {
const int multiple = (int) (1.0 / cvt->rate_incr);
const int dstsize = cvt->len_cvt / multiple; const int dstsize = cvt->len_cvt / multiple;
float *dst = (float *) cvt->buf; float *dst = (float *) cvt->buf;
const float *src = (float *) cvt->buf; const float *src = (float *) cvt->buf;