Commit 0eeb8c92 authored by Sam Lantinga's avatar Sam Lantinga

Final merge of Google Summer of Code 2008 work...

Audio Ideas - Resampling and Pitch Shifting
by Aaron Wishnick, mentored by Ryan C. Gordon

--HG--
extra : convert_revision : svn%3Ac70aab31-4412-0410-b14c-859654838e24/trunk%403165
parent 09f8ad29
...@@ -256,6 +256,68 @@ finalize_audio_entry_points(void) ...@@ -256,6 +256,68 @@ finalize_audio_entry_points(void)
#undef FILL_STUB #undef FILL_STUB
} }
/* Streaming functions (for when the input and output buffer sizes are different) */
/* Write [length] bytes from buf into the streamer */
void
SDL_StreamWrite(SDL_AudioStreamer * stream, Uint8 * buf, int length)
{
int i;
for (i = 0; i < length; ++i) {
stream->buffer[stream->write_pos] = buf[i];
++stream->write_pos;
}
}
/* Read [length] bytes out of the streamer into buf */
void
SDL_StreamRead(SDL_AudioStreamer * stream, Uint8 * buf, int length)
{
int i;
for (i = 0; i < length; ++i) {
buf[i] = stream->buffer[stream->read_pos];
++stream->read_pos;
}
}
int
SDL_StreamLength(SDL_AudioStreamer * stream)
{
return (stream->write_pos - stream->read_pos) % stream->max_len;
}
/* Initialize the stream by allocating the buffer and setting the read/write heads to the beginning */
int
SDL_StreamInit(SDL_AudioStreamer * stream, int max_len, Uint8 silence)
{
int i;
/* First try to allocate the buffer */
stream->buffer = (Uint8 *) malloc(max_len);
if (stream->buffer == NULL) {
return -1;
}
stream->max_len = max_len;
stream->read_pos = 0;
stream->write_pos = 0;
/* Zero out the buffer */
for (i = 0; i < max_len; ++i) {
stream->buffer[i] = silence;
}
}
/* Deinitialize the stream simply by freeing the buffer */
void
SDL_StreamDeinit(SDL_AudioStreamer * stream)
{
if (stream->buffer != NULL) {
free(stream->buffer);
}
}
/* The general mixing thread function */ /* The general mixing thread function */
int SDLCALL int SDLCALL
...@@ -267,6 +329,11 @@ SDL_RunAudio(void *devicep) ...@@ -267,6 +329,11 @@ SDL_RunAudio(void *devicep)
void *udata; void *udata;
void (SDLCALL * fill) (void *userdata, Uint8 * stream, int len); void (SDLCALL * fill) (void *userdata, Uint8 * stream, int len);
int silence; int silence;
int stream_max_len;
/* For streaming when the buffer sizes don't match up */
Uint8 *istream;
int istream_len;
/* Perform any thread setup */ /* Perform any thread setup */
device->threadid = SDL_ThreadID(); device->threadid = SDL_ThreadID();
...@@ -276,67 +343,188 @@ SDL_RunAudio(void *devicep) ...@@ -276,67 +343,188 @@ SDL_RunAudio(void *devicep)
fill = device->spec.callback; fill = device->spec.callback;
udata = device->spec.userdata; udata = device->spec.userdata;
/* By default do not stream */
device->use_streamer = 0;
if (device->convert.needed) { if (device->convert.needed) {
if (device->convert.src_format == AUDIO_U8) { if (device->convert.src_format == AUDIO_U8) {
silence = 0x80; silence = 0x80;
} else { } else {
silence = 0; silence = 0;
} }
stream_len = device->convert.len;
/* If the result of the conversion alters the length, i.e. resampling is being used, use the streamer */
if (device->convert.len_mult != 1 || device->convert.len_div != 1) {
/* The streamer's maximum length should be twice whichever is larger: spec.size or len_cvt */
stream_max_len = 2 * device->spec.size;
if (device->convert.len_mult > device->convert.len_div) {
stream_max_len *= device->convert.len_mult;
stream_max_len /= device->convert.len_div;
}
if (SDL_StreamInit(&device->streamer, stream_max_len, silence) <
0)
return -1;
device->use_streamer = 1;
/* istream_len should be the length of what we grab from the callback and feed to conversion,
so that we get close to spec_size. I.e. we want device.spec_size = istream_len * u / d
*/
istream_len =
device->spec.size * device->convert.len_div /
device->convert.len_mult;
}
/* stream_len = device->convert.len; */
stream_len = device->spec.size;
} else { } else {
silence = device->spec.silence; silence = device->spec.silence;
stream_len = device->spec.size; stream_len = device->spec.size;
} }
/* Loop, filling the audio buffers */ /* Determine if the streamer is necessary here */
while (device->enabled) { if (device->use_streamer == 1) {
/* This code is almost the same as the old code. The difference is, instead of reding
/* Fill the current buffer with sound */ directly from the callback into "stream", then converting and sending the audio off,
if (device->convert.needed) { we go: callback -> "istream" -> (conversion) -> streamer -> stream -> device.
if (device->convert.buf) { However, reading and writing with streamer are done separately:
stream = device->convert.buf; - We only call the callback and write to the streamer when the streamer does not
} else { contain enough samples to output to the device.
continue; - We only read from the streamer and tell the device to play when the streamer
does have enough samples to output.
This allows us to perform resampling in the conversion step, where the output of the
resampling process can be any number. We will have to see what a good size for the
stream's maximum length is, but I suspect 2*max(len_cvt, stream_len) is a good figure.
*/
while (device->enabled) {
/* Only read in audio if the streamer doesn't have enough already (if it does not have enough samples to output) */
if (SDL_StreamLength(&device->streamer) < stream_len) {
/* Set up istream */
if (device->convert.needed) {
if (device->convert.buf) {
istream = device->convert.buf;
} else {
continue;
}
} else {
istream = current_audio.impl.GetDeviceBuf(device);
if (istream == NULL) {
istream = device->fake_stream;
}
}
/* Read from the callback into the _input_ stream */
if (!device->paused) {
SDL_mutexP(device->mixer_lock);
(*fill) (udata, istream, istream_len);
SDL_mutexV(device->mixer_lock);
}
/* Convert the audio if necessary and write to the streamer */
if (device->convert.needed) {
SDL_ConvertAudio(&device->convert);
if (istream == NULL) {
istream = device->fake_stream;
}
/*SDL_memcpy(istream, device->convert.buf, device->convert.len_cvt); */
SDL_StreamWrite(&device->streamer, device->convert.buf,
device->convert.len_cvt);
} else {
SDL_StreamWrite(&device->streamer, istream, istream_len);
}
} }
} else {
stream = current_audio.impl.GetDeviceBuf(device); /* Only output audio if the streamer has enough to output */
if (stream == NULL) { if (SDL_StreamLength(&device->streamer) >= stream_len) {
stream = device->fake_stream; /* Set up the output stream */
if (device->convert.needed) {
if (device->convert.buf) {
stream = device->convert.buf;
} else {
continue;
}
} else {
stream = current_audio.impl.GetDeviceBuf(device);
if (stream == NULL) {
stream = device->fake_stream;
}
}
/* Now read from the streamer */
SDL_StreamRead(&device->streamer, stream, stream_len);
/* Ready current buffer for play and change current buffer */
if (stream != device->fake_stream) {
current_audio.impl.PlayDevice(device);
}
/* Wait for an audio buffer to become available */
if (stream == device->fake_stream) {
SDL_Delay((device->spec.samples * 1000) /
device->spec.freq);
} else {
current_audio.impl.WaitDevice(device);
}
} }
}
if (!device->paused) {
SDL_mutexP(device->mixer_lock);
(*fill) (udata, stream, stream_len);
SDL_mutexV(device->mixer_lock);
} }
} else {
/* Otherwise, do not use the streamer. This is the old code. */
/* Loop, filling the audio buffers */
while (device->enabled) {
/* Fill the current buffer with sound */
if (device->convert.needed) {
if (device->convert.buf) {
stream = device->convert.buf;
} else {
continue;
}
} else {
stream = current_audio.impl.GetDeviceBuf(device);
if (stream == NULL) {
stream = device->fake_stream;
}
}
/* Convert the audio if necessary */ if (!device->paused) {
if (device->convert.needed) { SDL_mutexP(device->mixer_lock);
SDL_ConvertAudio(&device->convert); (*fill) (udata, stream, stream_len);
stream = current_audio.impl.GetDeviceBuf(device); SDL_mutexV(device->mixer_lock);
if (stream == NULL) {
stream = device->fake_stream;
} }
SDL_memcpy(stream, device->convert.buf, device->convert.len_cvt);
}
/* Ready current buffer for play and change current buffer */ /* Convert the audio if necessary */
if (stream != device->fake_stream) { if (device->convert.needed) {
current_audio.impl.PlayDevice(device); SDL_ConvertAudio(&device->convert);
} stream = current_audio.impl.GetDeviceBuf(device);
if (stream == NULL) {
stream = device->fake_stream;
}
SDL_memcpy(stream, device->convert.buf,
device->convert.len_cvt);
}
/* Wait for an audio buffer to become available */ /* Ready current buffer for play and change current buffer */
if (stream == device->fake_stream) { if (stream != device->fake_stream) {
SDL_Delay((device->spec.samples * 1000) / device->spec.freq); current_audio.impl.PlayDevice(device);
} else { }
current_audio.impl.WaitDevice(device);
/* Wait for an audio buffer to become available */
if (stream == device->fake_stream) {
SDL_Delay((device->spec.samples * 1000) / device->spec.freq);
} else {
current_audio.impl.WaitDevice(device);
}
} }
} }
/* Wait for the audio to drain.. */ /* Wait for the audio to drain.. */
current_audio.impl.WaitDone(device); current_audio.impl.WaitDone(device);
/* If necessary, deinit the streamer */
if (device->use_streamer == 1)
SDL_StreamDeinit(&device->streamer);
return (0); return (0);
} }
......
...@@ -20,12 +20,45 @@ ...@@ -20,12 +20,45 @@
slouken@libsdl.org slouken@libsdl.org
*/ */
#include "SDL_config.h" #include "SDL_config.h"
#include <math.h>
/* Functions for audio drivers to perform runtime conversion of audio format */ /* Functions for audio drivers to perform runtime conversion of audio format */
#include "SDL_audio.h" #include "SDL_audio.h"
#include "SDL_audio_c.h" #include "SDL_audio_c.h"
#define DEBUG_CONVERT
/* These are fractional multiplication routines. That is, their inputs
are two numbers in the range [-1, 1) and the result falls in that
same range. The output is the same size as the inputs, i.e.
32-bit x 32-bit = 32-bit.
*/
/* We hope here that the right shift includes sign extension */
#ifdef SDL_HAS_64BIT_Type
#define SDL_FixMpy32(a, b) ((((Sint64)a * (Sint64)b) >> 31) & 0xffffffff)
#else
/* If we don't have the 64-bit type, do something more complicated. See http://www.8052.com/mul16.phtml or http://www.cs.uaf.edu/~cs301/notes/Chapter5/node5.html */
#define SDL_FixMpy32(a, b) ((((Sint64)a * (Sint64)b) >> 31) & 0xffffffff)
#endif
#define SDL_FixMpy16(a, b) ((((Sint32)a * (Sint32)b) >> 14) & 0xffff)
#define SDL_FixMpy8(a, b) ((((Sint16)a * (Sint16)b) >> 7) & 0xff)
/* This macro just makes the floating point filtering code not have to be a special case. */
#define SDL_FloatMpy(a, b) (a * b)
/* These macros take floating point numbers in the range [-1.0f, 1.0f) and
represent them as fixed-point numbers in that same range. There's no
checking that the floating point argument is inside the appropriate range.
*/
#define SDL_Make_1_7(a) (Sint8)(a * 128.0f)
#define SDL_Make_1_15(a) (Sint16)(a * 32768.0f)
#define SDL_Make_1_31(a) (Sint32)(a * 2147483648.0f)
#define SDL_Make_2_6(a) (Sint8)(a * 64.0f)
#define SDL_Make_2_14(a) (Sint16)(a * 16384.0f)
#define SDL_Make_2_30(a) (Sint32)(a * 1073741824.0f)
/* Effectively mix right and left channels into a single channel */ /* Effectively mix right and left channels into a single channel */
static void SDLCALL static void SDLCALL
SDL_ConvertMono(SDL_AudioCVT * cvt, SDL_AudioFormat format) SDL_ConvertMono(SDL_AudioCVT * cvt, SDL_AudioFormat format)
...@@ -1309,6 +1342,468 @@ SDL_BuildAudioTypeCVT(SDL_AudioCVT * cvt, ...@@ -1309,6 +1342,468 @@ SDL_BuildAudioTypeCVT(SDL_AudioCVT * cvt,
return 0; /* no conversion necessary. */ 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;
int i;
/* 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;
/* Copy the coefficients to the struct. If necessary, convert coefficients to fixed point, using the range (-2.0, 2.0) */
#define convert_fixed(type, fix) { \
type *cvt_coeff = (type *)cvt->coeff; \
for(i = 0; i < 6; ++i) { \
cvt_coeff[i] = fix(coeff[i]); \
} \
}
if (SDL_AUDIO_ISFLOAT(format) && SDL_AUDIO_BITSIZE(format) == 32) {
float *cvt_coeff = (float *) cvt->coeff;
for (i = 0; i < 6; ++i) {
cvt_coeff[i] = coeff[i];
}
} else {
switch (SDL_AUDIO_BITSIZE(format)) {
case 8:
convert_fixed(Uint8, SDL_Make_2_6);
break;
case 16:
convert_fixed(Uint16, SDL_Make_2_14);
break;
case 32:
convert_fixed(Uint32, SDL_Make_2_30);
break;
}
}
#ifdef DEBUG_CONVERT
#define debug_iir(type) { \
type *cvt_coeff = (type *)cvt->coeff; \
for(i = 0; i < 6; ++i) { \
printf("coeff[%u] = %f = 0x%x\n", i, coeff[i], cvt_coeff[i]); \
} \
}
if (SDL_AUDIO_ISFLOAT(format) && SDL_AUDIO_BITSIZE(format) == 32) {
float *cvt_coeff = (float *) cvt->coeff;
for (i = 0; i < 6; ++i) {
printf("coeff[%u] = %f = %f\n", i, coeff[i], cvt_coeff[i]);
}
} else {
switch (SDL_AUDIO_BITSIZE(format)) {
case 8:
debug_iir(Uint8);
break;
case 16:
debug_iir(Uint16);
break;
case 32:
debug_iir(Uint32);
break;
}
}
#undef debug_iir
#endif
/* 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;
#undef convert_fixed
}
/* Apply the lowpass IIR filter to the given SDL_AudioCVT struct */
/* This was implemented because it would be much faster than the fir filter,
but it doesn't seem to have a steep enough cutoff so we'd need several
cascaded biquads, which probably isn't a great idea. Therefore, this
function can probably be discarded.
*/
static void
SDL_FilterIIR(SDL_AudioCVT * cvt, SDL_AudioFormat format)
{
Uint32 i, n;
/* TODO: Check that n is calculated right */
n = 8 * cvt->len_cvt / SDL_AUDIO_BITSIZE(format);
/* Note that the coefficients are 2_x and the input is 1_x. Do we need to shift left at the end here? The right shift temp = buf[n] >> 1 needs to depend on whether the type is signed or not for sign extension. */
/* cvt->state_pos = 1: state[0] = x_n-1, state[1] = x_n-2, state[2] = y_n-1, state[3] - y_n-2 */
#define iir_fix(type, mult) {\
type *coeff = (type *)cvt->coeff; \
type *state = (type *)cvt->state_buf; \
type *buf = (type *)cvt->buf; \
type temp; \
for(i = 0; i < n; ++i) { \
temp = buf[i] >> 1; \
if(cvt->state_pos) { \
buf[i] = mult(coeff[0], temp) + mult(coeff[1], state[0]) + mult(coeff[2], state[1]) - mult(coeff[4], state[2]) - mult(coeff[5], state[3]); \
state[1] = temp; \
state[3] = buf[i]; \
cvt->state_pos = 0; \
} else { \
buf[i] = mult(coeff[0], temp) + mult(coeff[1], state[1]) + mult(coeff[2], state[0]) - mult(coeff[4], state[3]) - mult(coeff[5], state[2]); \
state[0] = temp; \
state[2] = buf[i]; \
cvt->state_pos = 1; \
} \
} \
}
/* Need to test to see if the previous method or this one is faster */
/*#define iir_fix(type, mult) {\
type *coeff = (type *)cvt->coeff; \
type *state = (type *)cvt->state_buf; \
type *buf = (type *)cvt->buf; \
type temp; \
for(i = 0; i < n; ++i) { \
temp = buf[i] >> 1; \
buf[i] = mult(coeff[0], temp) + mult(coeff[1], state[0]) + mult(coeff[2], state[1]) - mult(coeff[4], state[2]) - mult(coeff[5], state[3]); \
state[1] = state[0]; \
state[0] = temp; \
state[3] = state[2]; \
state[2] = buf[i]; \
} \
}*/
if (SDL_AUDIO_ISFLOAT(format) && SDL_AUDIO_BITSIZE(format) == 32) {
float *coeff = (float *) cvt->coeff;
float *state = (float *) cvt->state_buf;
float *buf = (float *) cvt->buf;
float temp;
for (i = 0; i < n; ++i) {
/* y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] - a1 * y[n-1] - a[2] * y[n-2] */
temp = buf[i];
if (cvt->state_pos) {
buf[i] =
coeff[0] * buf[n] + coeff[1] * state[0] +
coeff[2] * state[1] - coeff[4] * state[2] -
coeff[5] * state[3];
state[1] = temp;
state[3] = buf[i];
cvt->state_pos = 0;
} else {
buf[i] =
coeff[0] * buf[n] + coeff[1] * state[1] +
coeff[2] * state[0] - coeff[4] * state[3] -
coeff[5] * state[2];
state[0] = temp;
state[2] = buf[i];
cvt->state_pos = 1;
}
}
} else {
/* Treat everything as signed! */
switch (SDL_AUDIO_BITSIZE(format)) {
case 8:
iir_fix(Sint8, SDL_FixMpy8);
break;
case 16:
iir_fix(Sint16, SDL_FixMpy16);
break;
case 32:
iir_fix(Sint32, SDL_FixMpy32);
break;
}
}
#undef iir_fix
}
/* Apply the windowed sinc FIR filter to the given SDL_AudioCVT struct.
*/
static void
SDL_FilterFIR(SDL_AudioCVT * cvt, SDL_AudioFormat format)
{
int n = 8 * cvt->len_cvt / SDL_AUDIO_BITSIZE(format);
int m = cvt->len_sinc;
int i, j;
/*
Note: We can 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. However, this
depends on the zero stuffing ratio, so it may not pay off. This would
basically be a polyphase filter.
*/
/* One other way to do this fast is to look at the fir filter from a different angle:
After we zero stuff, we have input of all zeroes, except for every len_mult
sample. If we choose a sinc length equal to len_mult, then the fir filter becomes
much more simple: we're just taking a windowed sinc, shifting it to start at each
len_mult sample, and scaling it by the value of that sample. If we do this, then
we don't even need to worry about the sample histories, and the inner loop here is
unnecessary. This probably sacrifices some quality but could really speed things up as well.
*/
/* We only calculate the values of samples which are 0 (mod len_div) because
those are the only ones used. All the other ones are discarded in the
third step of resampling. This is a huge speedup. As a warning, though,
if for some reason this is used elsewhere where there are no samples discarded,
the output will not be corrrect if len_div is not 1. To make this filter a
generic FIR filter, simply remove the if statement "if(i % cvt->len_div == 0)"
around the inner loop so that every sample is processed.
*/
/* This is basically just a FIR filter. i.e. for input x_n and m coefficients,
y_n = x_n*sinc_0 + x_(n-1)*sinc_1 + x_(n-2)*sinc_2 + ... + x_(n-m+1)*sinc_(m-1)
*/
#define filter_sinc(type, mult) { \
type *sinc = (type *)cvt->coeff; \
type *state = (type *)cvt->state_buf; \
type *buf = (type *)cvt->buf; \
for(i = 0; i < n; ++i) { \
state[cvt->state_pos] = buf[i]; \
buf[i] = 0; \
if( i % cvt->len_div == 0 ) { \
for(j = 0; j < m; ++j) { \
buf[i] += mult(sinc[j], state[(cvt->state_pos + j) % m]); \
} \
}\
cvt->state_pos = (cvt->state_pos + 1) % m; \
} \
}
if (SDL_AUDIO_ISFLOAT(format) && SDL_AUDIO_BITSIZE(format) == 32) {
filter_sinc(float, SDL_FloatMpy);
} else {
switch (SDL_AUDIO_BITSIZE(format)) {
case 8:
filter_sinc(Sint8, SDL_FixMpy8);
break;
case 16:
filter_sinc(Sint16, SDL_FixMpy16);
break;
case 32:
filter_sinc(Sint32, SDL_FixMpy32);
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->coeff == NULL) {
return -1;
}
/* Set the length */
cvt->len_sinc = m + 1;
/* Allocate the floating point windowed sinc. */
fSinc = (float *) malloc((m + 1) * 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;
#ifdef DEBUG_CONVERT
printf("Lowpass cutoff frequency = %f\n", fc);
#endif
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 += fabs(fSinc[i]);
}
norm_fact = 1.0f / norm_sum;
#define convert_fixed(type, fix) { \
type *dst = (type *)cvt->coeff; \
for( i = 0; i <= m; ++i ) { \
dst[i] = fix(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->coeff;
for (i = 0; i <= m; ++i) {
fDest[i] = fSinc[i] * norm_fact;
}
} else {
switch (SDL_AUDIO_BITSIZE(format)) {
case 8:
convert_fixed(Uint8, SDL_Make_1_7);
break;
case 16:
convert_fixed(Uint16, SDL_Make_1_15);
break;
case 32:
convert_fixed(Uint32, SDL_Make_1_31);
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);
}
/* This is used to reduce the resampling ratio */
inline int
SDL_GCD(int a, int b)
{
int temp;
while (b != 0) {
temp = a % b;
a = b;
b = temp;
}
return a;
}
/* Perform proper resampling. This is pretty slow but it's the best-sounding method. */
static void SDLCALL
SDL_Resample(SDL_AudioCVT * cvt, SDL_AudioFormat format)
{
int i, j;
#ifdef DEBUG_CONVERT
printf("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)) / cvt->len_div; ++i) { \
dst[0] = src[0]; \
src += cvt->len_div; \
++dst; \
} \
}
/* Step 1: Zero stuff the conversion buffer. This upsamples by a factor of len_mult,
creating aliasing at frequencies above the original nyquist frequency.
*/
#ifdef DEBUG_CONVERT
printf("Zero-stuffing by a factor of %u\n", cvt->len_mult);
#endif
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 a windowed sinc FIR filter (lowpass filter) to remove the alias
frequencies. This is the slow part.
*/
SDL_FilterFIR(cvt, format);
/* Step 3: Now downsample by discarding samples. */
#ifdef DEBUG_CONVERT
printf("Discarding samples by a factor of %u\n", cvt->len_div);
#endif
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);
}
}
/* Creates a set of audio filters to convert from one format to another. /* Creates a set of audio filters to convert from one format to another.
...@@ -1399,6 +1894,17 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt, ...@@ -1399,6 +1894,17 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
} }
/* Do rate conversion */ /* Do rate conversion */
if (src_rate != dst_rate) {
int rate_gcd;
rate_gcd = SDL_GCD(src_rate, dst_rate);
cvt->len_mult = dst_rate / rate_gcd;
cvt->len_div = src_rate / rate_gcd;
cvt->len_ratio = (double) cvt->len_mult / (double) cvt->len_div;
cvt->filters[cvt->filter_index++] = SDL_Resample;
SDL_BuildWindowedSinc(cvt, dst_fmt, 768);
}
/*
cvt->rate_incr = 0.0; cvt->rate_incr = 0.0;
if ((src_rate / 100) != (dst_rate / 100)) { if ((src_rate / 100) != (dst_rate / 100)) {
Uint32 hi_rate, lo_rate; Uint32 hi_rate, lo_rate;
...@@ -1448,25 +1954,25 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt, ...@@ -1448,25 +1954,25 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
} }
len_mult = 2; len_mult = 2;
len_ratio = 2.0; len_ratio = 2.0;
} }*/
/* If hi_rate = lo_rate*2^x then conversion is easy */ /* If hi_rate = lo_rate*2^x then conversion is easy */
while (((lo_rate * 2) / 100) <= (hi_rate / 100)) { /* while (((lo_rate * 2) / 100) <= (hi_rate / 100)) {
cvt->filters[cvt->filter_index++] = rate_cvt; cvt->filters[cvt->filter_index++] = rate_cvt;
cvt->len_mult *= len_mult; cvt->len_mult *= len_mult;
lo_rate *= 2; lo_rate *= 2;
cvt->len_ratio *= len_ratio; cvt->len_ratio *= len_ratio;
} } */
/* We may need a slow conversion here to finish up */ /* We may need a slow conversion here to finish up */
if ((lo_rate / 100) != (hi_rate / 100)) { /* if ((lo_rate / 100) != (hi_rate / 100)) {
#if 1 #if 1 */
/* The problem with this is that if the input buffer is /* The problem with this is that if the input buffer is
say 1K, and the conversion rate is say 1.1, then the say 1K, and the conversion rate is say 1.1, then the
output buffer is 1.1K, which may not be an acceptable output buffer is 1.1K, which may not be an acceptable
buffer size for the audio driver (not a power of 2) buffer size for the audio driver (not a power of 2)
*/ */
/* For now, punt and hope the rate distortion isn't great. /* For now, punt and hope the rate distortion isn't great.
*/ */
#else /*#else
if (src_rate < dst_rate) { if (src_rate < dst_rate) {
cvt->rate_incr = (double) lo_rate / hi_rate; cvt->rate_incr = (double) lo_rate / hi_rate;
cvt->len_mult *= 2; cvt->len_mult *= 2;
...@@ -1478,7 +1984,7 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt, ...@@ -1478,7 +1984,7 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
cvt->filters[cvt->filter_index++] = SDL_RateSLOW; cvt->filters[cvt->filter_index++] = SDL_RateSLOW;
#endif #endif
} }
} }*/
/* Set up the filter information */ /* Set up the filter information */
if (cvt->filter_index != 0) { if (cvt->filter_index != 0) {
...@@ -1492,4 +1998,15 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt, ...@@ -1492,4 +1998,15 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
return (cvt->needed); return (cvt->needed);
} }
#undef SDL_FixMpy8
#undef SDL_FixMpy16
#undef SDL_FixMpy32
#undef SDL_FloatMpy
#undef SDL_Make_1_7
#undef SDL_Make_1_15
#undef SDL_Make_1_31
#undef SDL_Make_2_6
#undef SDL_Make_2_14
#undef SDL_Make_2_30
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */
...@@ -69,6 +69,15 @@ typedef struct SDL_AudioDriver ...@@ -69,6 +69,15 @@ typedef struct SDL_AudioDriver
} SDL_AudioDriver; } SDL_AudioDriver;
/* Streamer */
typedef struct
{
Uint8 *buffer;
int max_len; /* the maximum length in bytes */
int read_pos, write_pos; /* the position of the write and read heads in bytes */
} SDL_AudioStreamer;
/* Define the SDL audio driver structure */ /* Define the SDL audio driver structure */
struct SDL_AudioDevice struct SDL_AudioDevice
{ {
...@@ -81,6 +90,10 @@ struct SDL_AudioDevice ...@@ -81,6 +90,10 @@ struct SDL_AudioDevice
/* An audio conversion block for audio format emulation */ /* An audio conversion block for audio format emulation */
SDL_AudioCVT convert; SDL_AudioCVT convert;
/* The streamer, if sample rate conversion necessitates it */
int use_streamer;
SDL_AudioStreamer streamer;
/* Current state flags */ /* Current state flags */
int iscapture; int iscapture;
int enabled; int enabled;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment