Mercurial > sdl-ios-xcode
diff src/audio/SDL_audiocvt.c @ 2655:b8e736c8a5a8 gsoc2008_audio_resampling
Added beginnings of resampling code.
author | Aaron Wishnick <schnarf@gmail.com> |
---|---|
date | Wed, 18 Jun 2008 04:51:10 +0000 |
parents | 3ee59c43d784 |
children | dd74182b3c3c |
line wrap: on
line diff
--- a/src/audio/SDL_audiocvt.c Wed Apr 23 06:26:21 2008 +0000 +++ b/src/audio/SDL_audiocvt.c Wed Jun 18 04:51:10 2008 +0000 @@ -20,6 +20,7 @@ slouken@libsdl.org */ #include "SDL_config.h" +#include <math.h> /* Functions for audio drivers to perform runtime conversion of audio format */ @@ -1229,6 +1230,79 @@ } } +/* Perform proper resampling */ +static void SDLCALL +SDL_Resample(SDL_AudioCVT * cvt, SDL_AudioFormat format) +{ + int i, j; + +#ifdef DEBUG_CONVERT + fprintf(stderr, "Converting audio rate via proper resampling (mono)\n"); +#endif + +#define zerostuff_mono(type) { \ + const type *src = (const type *) (cvt->buf + cvt->len_cvt); \ + type *dst = (type *) (cvt->buf + (cvt->len_cvt * cvt->len_mult)); \ + for (i = cvt->len_cvt / sizeof (type); i; --i) { \ + src--; \ + dst[-1] = src[0]; \ + for( j = -cvt->len_mult; j < -1; ++j ) { \ + dst[j] = 0; \ + } \ + dst -= cvt->len_mult; \ + } \ + } + +#define discard_mono(type) { \ + const type *src = (const type *) (cvt->buf); \ + type *dst = (type *) (cvt->buf); \ + for (i = 0; i < cvt->len_cvt / sizeof (type); ++i) { \ + dst[0] = src[0]; \ + src += cvt->len_div; \ + ++dst; \ + } \ + } + + // Step 1: Zero stuff the conversion buffer + switch (SDL_AUDIO_BITSIZE(format)) { + case 8: + zerostuff_mono(Uint8); + break; + case 16: + zerostuff_mono(Uint16); + break; + case 32: + zerostuff_mono(Uint32); + break; + } + + cvt->len_cvt *= cvt->len_mult; + + // Step 2: Use either a windowed sinc FIR filter or IIR lowpass filter to remove all alias frequencies + + // Step 3: Discard unnecessary samples + switch (SDL_AUDIO_BITSIZE(format)) { + case 8: + discard_mono(Uint8); + break; + case 16: + discard_mono(Uint16); + break; + case 32: + discard_mono(Uint32); + break; + } + +#undef zerostuff_mono +#undef discard_mono + + cvt->len_cvt /= cvt->len_div; + + if (cvt->filters[++cvt->filter_index]) { + cvt->filters[cvt->filter_index] (cvt, format); + } +} + int SDL_ConvertAudio(SDL_AudioCVT * cvt) { @@ -1309,6 +1383,177 @@ return 0; /* no conversion necessary. */ } +/* Generate the necessary IIR lowpass coefficients for resampling. + Assume that the SDL_AudioCVT struct is already set up with + the correct values for len_mult and len_div, and use the + type of dst_format. Also assume the buffer is allocated. + Note the buffer needs to be 6 units long. + For now, use RBJ's cookbook coefficients. It might be more + optimal to create a Butterworth filter, but this is more difficult. +*/ +int SDL_BuildIIRLowpass(SDL_AudioCVT * cvt, SDL_AudioFormat format) { + float fc; /* cutoff frequency */ + float coeff[6]; /* floating point iir coefficients b0, b1, b2, a0, a1, a2 */ + float scale; + float w0, alpha, cosw0; + + /* The higher Q is, the higher CUTOFF can be. Need to find a good balance to avoid aliasing */ + static const float Q = 5.0f; + static const float CUTOFF = 0.4f; + + fc = (cvt->len_mult > cvt->len_div) ? CUTOFF / (float)cvt->len_mult : CUTOFF / (float)cvt->len_div; + + w0 = 2.0f * M_PI * fc; + cosw0 = cosf(w0); + alpha = sin(w0) / (2.0f * Q); + + /* Compute coefficients, normalizing by a0 */ + scale = 1.0f / (1.0f + alpha); + + coeff[0] = (1.0f - cosw0) / 2.0f * scale; + coeff[1] = (1.0f - cosw0) * scale; + coeff[2] = coeff[0]; + + coeff[3] = 1.0f; /* a0 is normalized to 1 */ + coeff[4] = -2.0f * cosw0 * scale; + coeff[5] = (1.0f - alpha) * scale; + + /* Convert coefficients to fixed point, using the range (-2.0, 2.0) */ + + /* Initialize the state buffer to all zeroes, and set initial position */ + memset(cvt->state_buf, 0, 4 * SDL_AUDIO_BITSIZE(format) / 4); + cvt->state_pos = 0; +} + +/* Apply the windowed sinc FIR filter to the given SDL_AudioCVT struct */ +int SDL_FilterFIR(SDL_AudioCVT * cvt, SDL_AudioFormat format) { + int n = cvt->len_cvt / (SDL_AUDIO_BITSIZE(format) / 4); + int m = cvt->len_sinc; + int i, j; + + /* Note: this makes use of the symmetry of the sinc filter. + We can also make a big optimization here by taking advantage + of the fact that the signal is zero stuffed, so we can do + significantly fewer multiplications and additions. + */ +#define filter_sinc(type, shift_bits) { \ + type *sinc = (type *)cvt->sinc; \ + type *state = (type *)cvt->state_buf; \ + type *buf = (type *)cvt->buf; \ + for(i = 0; i < n; ++i) { \ + state[cvt->state_pos++] = buf[i] >> shift_bits; \ + if(cvt->state_pos == m) cvt->state_pos = 0; \ + buf[i] = 0; \ + for(j = 0; j < m; ++j) { \ + buf[i] += state[j] * sinc[j]; \ + } \ + } \ + } + + switch (SDL_AUDIO_BITSIZE(format)) { + case 8: + filter_sinc(Uint8, 4); + break; + case 16: + filter_sinc(Uint16, 8); + break; + case 32: + filter_sinc(Uint32, 16); + break; + } + +#undef filter_sinc + +} + +/* Generate the necessary windowed sinc filter for resampling. + Assume that the SDL_AudioCVT struct is already set up with + the correct values for len_mult and len_div, and use the + type of dst_format. Also assume the buffer is allocated. + Note the buffer needs to be m+1 units long. +*/ +int +SDL_BuildWindowedSinc(SDL_AudioCVT * cvt, SDL_AudioFormat format, unsigned int m) { + float fScale; /* scale factor for fixed point */ + float *fSinc; /* floating point sinc buffer, to be converted to fixed point */ + float fc; /* cutoff frequency */ + float two_pi_fc, two_pi_over_m, four_pi_over_m, m_over_two; + float norm_sum, norm_fact; + unsigned int i; + + /* Check that the buffer is allocated */ + if( cvt->sinc == NULL ) { + return -1; + } + + /* Set the length */ + cvt->len_sinc = m; + + /* Allocate the floating point windowed sinc. */ + fSinc = (float *)malloc(m * sizeof(float)); + if( fSinc == NULL ) { + return -1; + } + + /* Set up the filter parameters */ + fc = (cvt->len_mult > cvt->len_div) ? 0.5f / (float)cvt->len_mult : 0.5f / (float)cvt->len_div; + two_pi_fc = 2.0f * M_PI * fc; + two_pi_over_m = 2.0f * M_PI / (float)m; + four_pi_over_m = 2.0f * two_pi_over_m; + m_over_two = (float)m / 2.0f; + norm_sum = 0.0f; + + for(i = 0; i <= m; ++i ) { + if( i == m/2 ) { + fSinc[i] = two_pi_fc; + } else { + fSinc[i] = sinf(two_pi_fc * ((float)i - m_over_two)) / ((float)i - m_over_two); + /* Apply blackman window */ + fSinc[i] *= 0.42f - 0.5f * cosf(two_pi_over_m * (float)i) + 0.08f * cosf(four_pi_over_m * (float)i); + } + norm_sum += abs(fSinc[i]); + } + + /* Now normalize and convert to fixed point. We scale everything to half the precision + of whatever datatype we're using, for example, 16 bit data means we use 8 bits */ + +#define convert_fixed(type, size) { \ + norm_fact = size / norm_sum; \ + type *dst = (type *)cvt->sinc; \ + for( i = 0; i <= m; ++i ) { \ + dst[i] = (type)(fSinc[i] * norm_fact); \ + } \ + } + + /* If we're using floating point, we only need to normalize */ + if(SDL_AUDIO_ISFLOAT(format) && SDL_AUDIO_BITSIZE(format) == 32) { + float *fDest = (float *)cvt->sinc; + norm_fact = 1.0f / norm_sum; + for(i = 0; i <= m; ++i) { + fDest[i] = fSinc[i] * norm_fact; + } + } else { + switch (SDL_AUDIO_BITSIZE(format)) { + case 8: + convert_fixed(Uint8, 0x0e); + break; + case 16: + convert_fixed(Uint16, 0xfe); + break; + case 32: + convert_fixed(Uint32, 0xfffe); + break; + } + } + + /* Initialize the state buffer to all zeroes, and set initial position */ + memset(cvt->state_buf, 0, cvt->len_sinc * SDL_AUDIO_BITSIZE(format) / 4); + cvt->state_pos = 0; + + /* Clean up */ +#undef convert_fixed + free(fSinc); +} /* Creates a set of audio filters to convert from one format to another.