src/audio/SDL_audiocvt.c
branchgsoc2008_audio_resampling
changeset 2655 b8e736c8a5a8
parent 2130 3ee59c43d784
child 2656 dd74182b3c3c
     1.1 --- a/src/audio/SDL_audiocvt.c	Wed Apr 23 06:26:21 2008 +0000
     1.2 +++ b/src/audio/SDL_audiocvt.c	Wed Jun 18 04:51:10 2008 +0000
     1.3 @@ -20,6 +20,7 @@
     1.4      slouken@libsdl.org
     1.5  */
     1.6  #include "SDL_config.h"
     1.7 +#include <math.h>
     1.8  
     1.9  /* Functions for audio drivers to perform runtime conversion of audio format */
    1.10  
    1.11 @@ -1229,6 +1230,79 @@
    1.12      }
    1.13  }
    1.14  
    1.15 +/* Perform proper resampling */
    1.16 +static void SDLCALL
    1.17 +SDL_Resample(SDL_AudioCVT * cvt, SDL_AudioFormat format)
    1.18 +{
    1.19 +    int i, j;
    1.20 +
    1.21 +#ifdef DEBUG_CONVERT
    1.22 +    fprintf(stderr, "Converting audio rate via proper resampling (mono)\n");
    1.23 +#endif
    1.24 +
    1.25 +#define zerostuff_mono(type) { \
    1.26 +        const type *src = (const type *) (cvt->buf + cvt->len_cvt); \
    1.27 +        type *dst = (type *) (cvt->buf + (cvt->len_cvt * cvt->len_mult)); \
    1.28 +        for (i = cvt->len_cvt / sizeof (type); i; --i) { \
    1.29 +            src--; \
    1.30 +            dst[-1] = src[0]; \
    1.31 +			for( j = -cvt->len_mult; j < -1; ++j ) { \
    1.32 +				dst[j] = 0; \
    1.33 +			} \
    1.34 +            dst -= cvt->len_mult; \
    1.35 +        } \
    1.36 +    }
    1.37 +	
    1.38 +#define discard_mono(type) { \
    1.39 +        const type *src = (const type *) (cvt->buf); \
    1.40 +        type *dst = (type *) (cvt->buf); \
    1.41 +        for (i = 0; i < cvt->len_cvt / sizeof (type); ++i) { \
    1.42 +            dst[0] = src[0]; \
    1.43 +            src += cvt->len_div; \
    1.44 +            ++dst; \
    1.45 +        } \
    1.46 +    }
    1.47 +
    1.48 +	// Step 1: Zero stuff the conversion buffer
    1.49 +    switch (SDL_AUDIO_BITSIZE(format)) {
    1.50 +    case 8:
    1.51 +        zerostuff_mono(Uint8);
    1.52 +        break;
    1.53 +    case 16:
    1.54 +        zerostuff_mono(Uint16);
    1.55 +        break;
    1.56 +    case 32:
    1.57 +        zerostuff_mono(Uint32);
    1.58 +        break;
    1.59 +    }
    1.60 +	
    1.61 +	cvt->len_cvt *= cvt->len_mult;
    1.62 +
    1.63 +	// Step 2: Use either a windowed sinc FIR filter or IIR lowpass filter to remove all alias frequencies
    1.64 +	
    1.65 +	// Step 3: Discard unnecessary samples
    1.66 +    switch (SDL_AUDIO_BITSIZE(format)) {
    1.67 +    case 8:
    1.68 +        discard_mono(Uint8);
    1.69 +        break;
    1.70 +    case 16:
    1.71 +        discard_mono(Uint16);
    1.72 +        break;
    1.73 +    case 32:
    1.74 +        discard_mono(Uint32);
    1.75 +        break;
    1.76 +    }
    1.77 +	
    1.78 +#undef zerostuff_mono
    1.79 +#undef discard_mono
    1.80 +
    1.81 +    cvt->len_cvt /= cvt->len_div;
    1.82 +	
    1.83 +    if (cvt->filters[++cvt->filter_index]) {
    1.84 +        cvt->filters[cvt->filter_index] (cvt, format);
    1.85 +    }
    1.86 +}
    1.87 +
    1.88  int
    1.89  SDL_ConvertAudio(SDL_AudioCVT * cvt)
    1.90  {
    1.91 @@ -1309,6 +1383,177 @@
    1.92      return 0;                   /* no conversion necessary. */
    1.93  }
    1.94  
    1.95 +/* Generate the necessary IIR lowpass coefficients for resampling.
    1.96 +   Assume that the SDL_AudioCVT struct is already set up with
    1.97 +   the correct values for len_mult and len_div, and use the
    1.98 +   type of dst_format. Also assume the buffer is allocated.
    1.99 +   Note the buffer needs to be 6 units long.
   1.100 +   For now, use RBJ's cookbook coefficients. It might be more
   1.101 +   optimal to create a Butterworth filter, but this is more difficult.
   1.102 +*/
   1.103 +int SDL_BuildIIRLowpass(SDL_AudioCVT * cvt, SDL_AudioFormat format) {
   1.104 +	float fc;			/* cutoff frequency */
   1.105 +	float coeff[6];		/* floating point iir coefficients b0, b1, b2, a0, a1, a2 */
   1.106 +	float scale;
   1.107 +	float w0, alpha, cosw0;
   1.108 +	
   1.109 +	/* The higher Q is, the higher CUTOFF can be. Need to find a good balance to avoid aliasing */
   1.110 +	static const float Q = 5.0f;
   1.111 +	static const float CUTOFF = 0.4f;
   1.112 +	
   1.113 +	fc = (cvt->len_mult > cvt->len_div) ? CUTOFF / (float)cvt->len_mult : CUTOFF / (float)cvt->len_div;
   1.114 +	
   1.115 +	w0 = 2.0f * M_PI * fc;
   1.116 +	cosw0 = cosf(w0);
   1.117 +	alpha = sin(w0) / (2.0f * Q);
   1.118 +	
   1.119 +	/* Compute coefficients, normalizing by a0 */
   1.120 +	scale = 1.0f / (1.0f + alpha);
   1.121 +	
   1.122 +	coeff[0] = (1.0f - cosw0) / 2.0f * scale;
   1.123 +	coeff[1] = (1.0f - cosw0) * scale;
   1.124 +	coeff[2] = coeff[0];
   1.125 +	
   1.126 +	coeff[3] = 1.0f;	/* a0 is normalized to 1 */
   1.127 +	coeff[4] = -2.0f * cosw0 * scale;
   1.128 +	coeff[5] = (1.0f - alpha) * scale;
   1.129 +	
   1.130 +	/* Convert coefficients to fixed point, using the range (-2.0, 2.0) */
   1.131 +	
   1.132 +	/* Initialize the state buffer to all zeroes, and set initial position */
   1.133 +	memset(cvt->state_buf, 0, 4 * SDL_AUDIO_BITSIZE(format) / 4);
   1.134 +	cvt->state_pos = 0;
   1.135 +}
   1.136 +
   1.137 +/* Apply the windowed sinc FIR filter to the given SDL_AudioCVT struct */
   1.138 +int SDL_FilterFIR(SDL_AudioCVT * cvt, SDL_AudioFormat format) {
   1.139 +	int n = cvt->len_cvt / (SDL_AUDIO_BITSIZE(format) / 4);
   1.140 +	int m = cvt->len_sinc;
   1.141 +	int i, j;
   1.142 +	
   1.143 +	/* Note: this makes use of the symmetry of the sinc filter.
   1.144 +	   We can also make a big optimization here by taking advantage
   1.145 +	   of the fact that the signal is zero stuffed, so we can do
   1.146 +	   significantly fewer multiplications and additions.
   1.147 +	*/
   1.148 +#define filter_sinc(type, shift_bits) { \
   1.149 +			type *sinc = (type *)cvt->sinc; \
   1.150 +			type *state = (type *)cvt->state_buf; \
   1.151 +			type *buf = (type *)cvt->buf; \
   1.152 +			for(i = 0; i < n; ++i) { \
   1.153 +				state[cvt->state_pos++] = buf[i] >> shift_bits; \
   1.154 +				if(cvt->state_pos == m) cvt->state_pos = 0; \
   1.155 +				buf[i] = 0; \
   1.156 +				for(j = 0; j < m;  ++j) { \
   1.157 +					buf[i] += state[j] * sinc[j]; \
   1.158 +				} \
   1.159 +			} \
   1.160 +		}
   1.161 +			
   1.162 +	switch (SDL_AUDIO_BITSIZE(format)) {
   1.163 +		case 8:
   1.164 +			filter_sinc(Uint8, 4);
   1.165 +			break;
   1.166 +		case 16:
   1.167 +			filter_sinc(Uint16, 8);
   1.168 +			break;
   1.169 +		case 32:
   1.170 +			filter_sinc(Uint32, 16);
   1.171 +			break;
   1.172 +	}
   1.173 +	
   1.174 +#undef filter_sinc
   1.175 +			
   1.176 +}
   1.177 +
   1.178 +/* Generate the necessary windowed sinc filter for resampling.
   1.179 +   Assume that the SDL_AudioCVT struct is already set up with
   1.180 +   the correct values for len_mult and len_div, and use the
   1.181 +   type of dst_format. Also assume the buffer is allocated.
   1.182 +   Note the buffer needs to be m+1 units long.
   1.183 +*/
   1.184 +int
   1.185 +SDL_BuildWindowedSinc(SDL_AudioCVT * cvt, SDL_AudioFormat format, unsigned int m) {
   1.186 +	float fScale;		/* scale factor for fixed point */
   1.187 +	float *fSinc;		/* floating point sinc buffer, to be converted to fixed point */
   1.188 +	float fc;			/* cutoff frequency */
   1.189 +	float two_pi_fc, two_pi_over_m, four_pi_over_m, m_over_two;
   1.190 +	float norm_sum, norm_fact;
   1.191 +	unsigned int i;
   1.192 +
   1.193 +	/* Check that the buffer is allocated */
   1.194 +	if( cvt->sinc == NULL ) {
   1.195 +		return -1;
   1.196 +	}
   1.197 +
   1.198 +	/* Set the length */
   1.199 +	cvt->len_sinc = m;
   1.200 +	
   1.201 +	/* Allocate the floating point windowed sinc. */
   1.202 +	fSinc = (float *)malloc(m * sizeof(float));
   1.203 +	if( fSinc == NULL ) {
   1.204 +		return -1;
   1.205 +	}
   1.206 +	
   1.207 +	/* Set up the filter parameters */
   1.208 +	fc = (cvt->len_mult > cvt->len_div) ? 0.5f / (float)cvt->len_mult : 0.5f / (float)cvt->len_div;
   1.209 +	two_pi_fc = 2.0f * M_PI * fc;
   1.210 +	two_pi_over_m = 2.0f * M_PI / (float)m;
   1.211 +	four_pi_over_m = 2.0f * two_pi_over_m;
   1.212 +	m_over_two = (float)m / 2.0f;
   1.213 +	norm_sum = 0.0f;
   1.214 +	
   1.215 +	for(i = 0; i <= m; ++i ) {
   1.216 +		if( i == m/2 ) {
   1.217 +			fSinc[i] = two_pi_fc;
   1.218 +		} else {
   1.219 +			fSinc[i] = sinf(two_pi_fc * ((float)i - m_over_two)) / ((float)i - m_over_two);
   1.220 +			/* Apply blackman window */
   1.221 +			fSinc[i] *= 0.42f - 0.5f * cosf(two_pi_over_m * (float)i) + 0.08f * cosf(four_pi_over_m * (float)i);
   1.222 +		}
   1.223 +		norm_sum += abs(fSinc[i]);
   1.224 +	}
   1.225 +
   1.226 +	/* Now normalize and convert to fixed point. We scale everything to half the precision
   1.227 +	   of whatever datatype we're using, for example, 16 bit data means we use 8 bits */
   1.228 +	
   1.229 +#define convert_fixed(type, size) { \
   1.230 +		norm_fact = size / norm_sum; \
   1.231 +		type *dst = (type *)cvt->sinc; \
   1.232 +		for( i = 0; i <= m; ++i ) { \
   1.233 +			dst[i] = (type)(fSinc[i] * norm_fact); \
   1.234 +		} \
   1.235 +	}
   1.236 +	
   1.237 +	/* If we're using floating point, we only need to normalize */
   1.238 +	if(SDL_AUDIO_ISFLOAT(format) && SDL_AUDIO_BITSIZE(format) == 32) {
   1.239 +		float *fDest = (float *)cvt->sinc;
   1.240 +		norm_fact = 1.0f / norm_sum;
   1.241 +		for(i = 0; i <= m; ++i) {
   1.242 +			fDest[i] = fSinc[i] * norm_fact;
   1.243 +		}
   1.244 +	} else {
   1.245 +		switch (SDL_AUDIO_BITSIZE(format)) {
   1.246 +			case 8:
   1.247 +				convert_fixed(Uint8, 0x0e);
   1.248 +				break;
   1.249 +			case 16:
   1.250 +				convert_fixed(Uint16, 0xfe);
   1.251 +				break;
   1.252 +			case 32:
   1.253 +				convert_fixed(Uint32, 0xfffe);
   1.254 +				break;
   1.255 +		}
   1.256 +	}
   1.257 +	
   1.258 +	/* Initialize the state buffer to all zeroes, and set initial position */
   1.259 +	memset(cvt->state_buf, 0, cvt->len_sinc * SDL_AUDIO_BITSIZE(format) / 4);
   1.260 +	cvt->state_pos = 0;
   1.261 +	
   1.262 +	/* Clean up */
   1.263 +#undef convert_fixed
   1.264 +	free(fSinc);
   1.265 +}
   1.266  
   1.267  
   1.268  /* Creates a set of audio filters to convert from one format to another.