audio: Added SDL_AudioStream. Non-power-of-two resampling now works!
parent
f12ab8f2b3
commit
30178a9b24
|
@ -547,10 +547,10 @@ SDL_RunAudio(void *devicep)
|
||||||
SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
|
SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
|
||||||
const int silence = (int) device->spec.silence;
|
const int silence = (int) device->spec.silence;
|
||||||
const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);
|
const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);
|
||||||
const int stream_len = (device->convert.needed) ? device->convert.len : device->spec.size;
|
const int stream_len = device->callbackspec.size;
|
||||||
Uint8 *stream;
|
Uint8 *stream;
|
||||||
void *udata = device->spec.userdata;
|
void *udata = device->spec.userdata;
|
||||||
void (SDLCALL *callback) (void *, Uint8 *, int) = device->spec.callback;
|
SDL_AudioCallback callback = device->spec.callback;
|
||||||
|
|
||||||
SDL_assert(!device->iscapture);
|
SDL_assert(!device->iscapture);
|
||||||
|
|
||||||
|
@ -564,16 +564,15 @@ SDL_RunAudio(void *devicep)
|
||||||
/* Loop, filling the audio buffers */
|
/* Loop, filling the audio buffers */
|
||||||
while (!SDL_AtomicGet(&device->shutdown)) {
|
while (!SDL_AtomicGet(&device->shutdown)) {
|
||||||
/* Fill the current buffer with sound */
|
/* Fill the current buffer with sound */
|
||||||
if (device->convert.needed) {
|
if (!device->stream && SDL_AtomicGet(&device->enabled)) {
|
||||||
stream = device->convert.buf;
|
|
||||||
} else if (SDL_AtomicGet(&device->enabled)) {
|
|
||||||
stream = current_audio.impl.GetDeviceBuf(device);
|
stream = current_audio.impl.GetDeviceBuf(device);
|
||||||
} else {
|
} else {
|
||||||
/* if the device isn't enabled, we still write to the
|
/* if the device isn't enabled, we still write to the
|
||||||
fake_stream, so the app's callback will fire with
|
fake_stream, so the app's callback will fire with
|
||||||
a regular frequency, in case they depend on that
|
a regular frequency, in case they depend on that
|
||||||
for timing or progress. They can use hotplug
|
for timing or progress. They can use hotplug
|
||||||
now to know if the device failed. */
|
now to know if the device failed.
|
||||||
|
Streaming playback uses fake_stream as a work buffer, too. */
|
||||||
stream = NULL;
|
stream = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -581,33 +580,45 @@ SDL_RunAudio(void *devicep)
|
||||||
stream = device->fake_stream;
|
stream = device->fake_stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* !!! FIXME: this should be LockDevice. */
|
|
||||||
if ( SDL_AtomicGet(&device->enabled) ) {
|
if ( SDL_AtomicGet(&device->enabled) ) {
|
||||||
|
/* !!! FIXME: this should be LockDevice. */
|
||||||
SDL_LockMutex(device->mixer_lock);
|
SDL_LockMutex(device->mixer_lock);
|
||||||
if (SDL_AtomicGet(&device->paused)) {
|
if (SDL_AtomicGet(&device->paused)) {
|
||||||
SDL_memset(stream, silence, stream_len);
|
SDL_memset(stream, silence, stream_len);
|
||||||
} else {
|
} else {
|
||||||
(*callback) (udata, stream, stream_len);
|
callback(udata, stream, stream_len);
|
||||||
}
|
}
|
||||||
SDL_UnlockMutex(device->mixer_lock);
|
SDL_UnlockMutex(device->mixer_lock);
|
||||||
}
|
|
||||||
|
|
||||||
/* Convert the audio if necessary */
|
|
||||||
if (device->convert.needed && SDL_AtomicGet(&device->enabled)) {
|
|
||||||
SDL_ConvertAudio(&device->convert);
|
|
||||||
stream = current_audio.impl.GetDeviceBuf(device);
|
|
||||||
if (stream == NULL) {
|
|
||||||
stream = device->fake_stream;
|
|
||||||
} else {
|
|
||||||
SDL_memcpy(stream, device->convert.buf,
|
|
||||||
device->convert.len_cvt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ready current buffer for play and change current buffer */
|
|
||||||
if (stream == device->fake_stream) {
|
|
||||||
SDL_Delay(delay);
|
|
||||||
} else {
|
} else {
|
||||||
|
SDL_memset(stream, silence, stream_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device->stream) {
|
||||||
|
/* Stream available audio to device, converting/resampling. */
|
||||||
|
/* if this fails...oh well. We'll play silence here. */
|
||||||
|
SDL_AudioStreamPut(device->stream, stream, stream_len);
|
||||||
|
|
||||||
|
while (SDL_AudioStreamAvailable(device->stream) >= device->spec.size) {
|
||||||
|
stream = SDL_AtomicGet(&device->enabled) ? current_audio.impl.GetDeviceBuf(device) : NULL;
|
||||||
|
if (stream == NULL) {
|
||||||
|
SDL_AudioStreamClear(device->stream);
|
||||||
|
SDL_Delay(delay);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
const int got = SDL_AudioStreamGet(device->stream, device->spec.size, stream, device->spec.size);
|
||||||
|
SDL_assert((got < 0) || (got == device->spec.size));
|
||||||
|
if (got != device->spec.size) {
|
||||||
|
SDL_memset(stream, device->spec.silence, device->spec.size);
|
||||||
|
}
|
||||||
|
current_audio.impl.PlayDevice(device);
|
||||||
|
current_audio.impl.WaitDevice(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (stream == device->fake_stream) {
|
||||||
|
/* nothing to do; pause like we queued a buffer to play. */
|
||||||
|
SDL_Delay(delay);
|
||||||
|
} else { /* writing directly to the device. */
|
||||||
|
/* queue this buffer and wait for it to finish playing. */
|
||||||
current_audio.impl.PlayDevice(device);
|
current_audio.impl.PlayDevice(device);
|
||||||
current_audio.impl.WaitDevice(device);
|
current_audio.impl.WaitDevice(device);
|
||||||
}
|
}
|
||||||
|
@ -628,10 +639,10 @@ SDL_CaptureAudio(void *devicep)
|
||||||
SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
|
SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
|
||||||
const int silence = (int) device->spec.silence;
|
const int silence = (int) device->spec.silence;
|
||||||
const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);
|
const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);
|
||||||
const int stream_len = (device->convert.needed) ? device->convert.len : device->spec.size;
|
const int stream_len = device->spec.size;
|
||||||
Uint8 *stream;
|
Uint8 *stream;
|
||||||
void *udata = device->spec.userdata;
|
void *udata = device->spec.userdata;
|
||||||
void (SDLCALL *callback) (void *, Uint8 *, int) = device->spec.callback;
|
SDL_AudioCallback callback = device->spec.callback;
|
||||||
|
|
||||||
SDL_assert(device->iscapture);
|
SDL_assert(device->iscapture);
|
||||||
|
|
||||||
|
@ -649,18 +660,21 @@ SDL_CaptureAudio(void *devicep)
|
||||||
|
|
||||||
if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) {
|
if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) {
|
||||||
SDL_Delay(delay); /* just so we don't cook the CPU. */
|
SDL_Delay(delay); /* just so we don't cook the CPU. */
|
||||||
|
if (device->stream) {
|
||||||
|
SDL_AudioStreamClear(device->stream);
|
||||||
|
}
|
||||||
current_audio.impl.FlushCapture(device); /* dump anything pending. */
|
current_audio.impl.FlushCapture(device); /* dump anything pending. */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fill the current buffer with sound */
|
/* Fill the current buffer with sound */
|
||||||
still_need = stream_len;
|
still_need = stream_len;
|
||||||
if (device->convert.needed) {
|
|
||||||
ptr = stream = device->convert.buf;
|
/* just use the "fake" stream to hold data read from the device. */
|
||||||
} else {
|
stream = device->fake_stream;
|
||||||
/* just use the "fake" stream to hold data read from the device. */
|
SDL_assert(stream != NULL);
|
||||||
ptr = stream = device->fake_stream;
|
|
||||||
}
|
ptr = stream;
|
||||||
|
|
||||||
/* We still read from the device when "paused" to keep the state sane,
|
/* We still read from the device when "paused" to keep the state sane,
|
||||||
and block when there isn't data so this thread isn't eating CPU.
|
and block when there isn't data so this thread isn't eating CPU.
|
||||||
|
@ -683,18 +697,32 @@ SDL_CaptureAudio(void *devicep)
|
||||||
SDL_memset(ptr, silence, still_need);
|
SDL_memset(ptr, silence, still_need);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device->convert.needed) {
|
if (device->stream) {
|
||||||
SDL_ConvertAudio(&device->convert);
|
/* if this fails...oh well. */
|
||||||
}
|
SDL_AudioStreamPut(device->stream, stream, stream_len);
|
||||||
|
|
||||||
/* !!! FIXME: this should be LockDevice. */
|
while (SDL_AudioStreamAvailable(device->stream) >= device->callbackspec.size) {
|
||||||
SDL_LockMutex(device->mixer_lock);
|
const int got = SDL_AudioStreamGet(device->stream, device->callbackspec.size, device->fake_stream, device->fake_stream_len);
|
||||||
if (SDL_AtomicGet(&device->paused)) {
|
SDL_assert((got < 0) || (got == device->callbackspec.size));
|
||||||
current_audio.impl.FlushCapture(device); /* one snuck in! */
|
if (got != device->callbackspec.size) {
|
||||||
} else {
|
SDL_memset(device->fake_stream, device->spec.silence, device->callbackspec.size);
|
||||||
(*callback)(udata, stream, stream_len);
|
}
|
||||||
|
|
||||||
|
/* !!! FIXME: this should be LockDevice. */
|
||||||
|
SDL_LockMutex(device->mixer_lock);
|
||||||
|
if (!SDL_AtomicGet(&device->paused)) {
|
||||||
|
callback(udata, device->fake_stream, device->callbackspec.size);
|
||||||
|
}
|
||||||
|
SDL_UnlockMutex(device->mixer_lock);
|
||||||
|
}
|
||||||
|
} else { /* feeding user callback directly without streaming. */
|
||||||
|
/* !!! FIXME: this should be LockDevice. */
|
||||||
|
SDL_LockMutex(device->mixer_lock);
|
||||||
|
if (!SDL_AtomicGet(&device->paused)) {
|
||||||
|
callback(udata, stream, device->callbackspec.size);
|
||||||
|
}
|
||||||
|
SDL_UnlockMutex(device->mixer_lock);
|
||||||
}
|
}
|
||||||
SDL_UnlockMutex(device->mixer_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
current_audio.impl.FlushCapture(device);
|
current_audio.impl.FlushCapture(device);
|
||||||
|
@ -929,15 +957,16 @@ close_audio_device(SDL_AudioDevice * device)
|
||||||
if (device->mixer_lock != NULL) {
|
if (device->mixer_lock != NULL) {
|
||||||
SDL_DestroyMutex(device->mixer_lock);
|
SDL_DestroyMutex(device->mixer_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_free(device->fake_stream);
|
SDL_free(device->fake_stream);
|
||||||
if (device->convert.needed) {
|
SDL_FreeAudioStream(device->stream);
|
||||||
SDL_free(device->convert.buf);
|
|
||||||
}
|
|
||||||
if (device->hidden != NULL) {
|
if (device->hidden != NULL) {
|
||||||
current_audio.impl.CloseDevice(device);
|
current_audio.impl.CloseDevice(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_FreeDataQueue(device->buffer_queue);
|
SDL_FreeDataQueue(device->buffer_queue);
|
||||||
|
|
||||||
SDL_free(device);
|
SDL_free(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1013,7 +1042,7 @@ open_audio_device(const char *devname, int iscapture,
|
||||||
SDL_AudioDeviceID id = 0;
|
SDL_AudioDeviceID id = 0;
|
||||||
SDL_AudioSpec _obtained;
|
SDL_AudioSpec _obtained;
|
||||||
SDL_AudioDevice *device;
|
SDL_AudioDevice *device;
|
||||||
SDL_bool build_cvt;
|
SDL_bool build_stream;
|
||||||
void *handle = NULL;
|
void *handle = NULL;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
|
@ -1148,69 +1177,63 @@ open_audio_device(const char *devname, int iscapture,
|
||||||
SDL_assert(device->hidden != NULL);
|
SDL_assert(device->hidden != NULL);
|
||||||
|
|
||||||
/* See if we need to do any conversion */
|
/* See if we need to do any conversion */
|
||||||
build_cvt = SDL_FALSE;
|
build_stream = SDL_FALSE;
|
||||||
if (obtained->freq != device->spec.freq) {
|
if (obtained->freq != device->spec.freq) {
|
||||||
if (allowed_changes & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) {
|
if (allowed_changes & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) {
|
||||||
obtained->freq = device->spec.freq;
|
obtained->freq = device->spec.freq;
|
||||||
} else {
|
} else {
|
||||||
build_cvt = SDL_TRUE;
|
build_stream = SDL_TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (obtained->format != device->spec.format) {
|
if (obtained->format != device->spec.format) {
|
||||||
if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) {
|
if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) {
|
||||||
obtained->format = device->spec.format;
|
obtained->format = device->spec.format;
|
||||||
} else {
|
} else {
|
||||||
build_cvt = SDL_TRUE;
|
build_stream = SDL_TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (obtained->channels != device->spec.channels) {
|
if (obtained->channels != device->spec.channels) {
|
||||||
if (allowed_changes & SDL_AUDIO_ALLOW_CHANNELS_CHANGE) {
|
if (allowed_changes & SDL_AUDIO_ALLOW_CHANNELS_CHANGE) {
|
||||||
obtained->channels = device->spec.channels;
|
obtained->channels = device->spec.channels;
|
||||||
} else {
|
} else {
|
||||||
build_cvt = SDL_TRUE;
|
build_stream = SDL_TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If the audio driver changes the buffer size, accept it.
|
/* !!! FIXME in 2.1: add SDL_AUDIO_ALLOW_SAMPLES_CHANGE flag?
|
||||||
This needs to be done after the format is modified above,
|
As of 2.0.6, we will build a stream to buffer the difference between
|
||||||
otherwise it might not have the correct buffer size.
|
what the app wants to feed and the device wants to eat, so everyone
|
||||||
|
gets their way. In prior releases, SDL would force the callback to
|
||||||
|
feed at the rate the device requested, adjusted for resampling.
|
||||||
*/
|
*/
|
||||||
if (device->spec.samples != obtained->samples) {
|
if (device->spec.samples != obtained->samples) {
|
||||||
obtained->samples = device->spec.samples;
|
build_stream = SDL_TRUE;
|
||||||
SDL_CalculateAudioSpec(obtained);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (build_cvt) {
|
SDL_CalculateAudioSpec(obtained); /* recalc after possible changes. */
|
||||||
/* Build an audio conversion block */
|
|
||||||
if (SDL_BuildAudioCVT(&device->convert,
|
device->callbackspec = *obtained;
|
||||||
obtained->format, obtained->channels,
|
|
||||||
obtained->freq,
|
if (build_stream) {
|
||||||
device->spec.format, device->spec.channels,
|
if (iscapture) {
|
||||||
device->spec.freq) < 0) {
|
device->stream = SDL_NewAudioStream(device->spec.format,
|
||||||
|
device->spec.channels, device->spec.freq,
|
||||||
|
obtained->format, obtained->channels, obtained->freq);
|
||||||
|
} else {
|
||||||
|
device->stream = SDL_NewAudioStream(obtained->format, obtained->channels,
|
||||||
|
obtained->freq, device->spec.format,
|
||||||
|
device->spec.channels, device->spec.freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device->stream) {
|
||||||
close_audio_device(device);
|
close_audio_device(device);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (device->convert.needed) {
|
|
||||||
device->convert.len = (int) (((double) device->spec.samples) /
|
|
||||||
device->convert.len_ratio);
|
|
||||||
device->convert.len *= SDL_AUDIO_BITSIZE(device->spec.format) / 8;
|
|
||||||
device->convert.len *= device->spec.channels;
|
|
||||||
|
|
||||||
device->convert.buf =
|
|
||||||
(Uint8 *) SDL_malloc(device->convert.len *
|
|
||||||
device->convert.len_mult);
|
|
||||||
if (device->convert.buf == NULL) {
|
|
||||||
close_audio_device(device);
|
|
||||||
SDL_OutOfMemory();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device->spec.callback == NULL) { /* use buffer queueing? */
|
if (device->spec.callback == NULL) { /* use buffer queueing? */
|
||||||
/* pool a few packets to start. Enough for two callbacks. */
|
/* pool a few packets to start. Enough for two callbacks. */
|
||||||
const size_t slack = ((device->convert.needed) ? device->convert.len : device->spec.size) * 2;
|
device->buffer_queue = SDL_NewDataQueue(SDL_AUDIOBUFFERQUEUE_PACKETLEN, obtained->size * 2);
|
||||||
device->buffer_queue = SDL_NewDataQueue(SDL_AUDIOBUFFERQUEUE_PACKETLEN, slack);
|
|
||||||
if (!device->buffer_queue) {
|
if (!device->buffer_queue) {
|
||||||
close_audio_device(device);
|
close_audio_device(device);
|
||||||
SDL_SetError("Couldn't create audio buffer queue");
|
SDL_SetError("Couldn't create audio buffer queue");
|
||||||
|
@ -1220,8 +1243,7 @@ open_audio_device(const char *devname, int iscapture,
|
||||||
device->spec.userdata = device;
|
device->spec.userdata = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* add it to our list of open devices. */
|
open_devices[id] = device; /* add it to our list of open devices. */
|
||||||
open_devices[id] = device;
|
|
||||||
|
|
||||||
/* Start the audio thread if necessary */
|
/* Start the audio thread if necessary */
|
||||||
if (!current_audio.impl.ProvidesOwnCallbackThread) {
|
if (!current_audio.impl.ProvidesOwnCallbackThread) {
|
||||||
|
@ -1232,13 +1254,13 @@ open_audio_device(const char *devname, int iscapture,
|
||||||
char threadname[64];
|
char threadname[64];
|
||||||
|
|
||||||
/* Allocate a fake audio buffer; only used by our internal threads. */
|
/* Allocate a fake audio buffer; only used by our internal threads. */
|
||||||
Uint32 stream_len = (device->convert.needed) ? device->convert.len_cvt : 0;
|
device->fake_stream_len = build_stream ? device->callbackspec.size : 0;
|
||||||
if (device->spec.size > stream_len) {
|
if (device->spec.size > device->fake_stream_len) {
|
||||||
stream_len = device->spec.size;
|
device->fake_stream_len = device->spec.size;
|
||||||
}
|
}
|
||||||
SDL_assert(stream_len > 0);
|
SDL_assert(device->fake_stream_len > 0);
|
||||||
|
|
||||||
device->fake_stream = (Uint8 *) SDL_malloc(stream_len);
|
device->fake_stream = (Uint8 *) SDL_malloc(device->fake_stream_len);
|
||||||
if (device->fake_stream == NULL) {
|
if (device->fake_stream == NULL) {
|
||||||
close_audio_device(device);
|
close_audio_device(device);
|
||||||
SDL_OutOfMemory();
|
SDL_OutOfMemory();
|
||||||
|
@ -1480,13 +1502,7 @@ SDL_MixAudio(Uint8 * dst, const Uint8 * src, Uint32 len, int volume)
|
||||||
/* Mix the user-level audio format */
|
/* Mix the user-level audio format */
|
||||||
SDL_AudioDevice *device = get_audio_device(1);
|
SDL_AudioDevice *device = get_audio_device(1);
|
||||||
if (device != NULL) {
|
if (device != NULL) {
|
||||||
SDL_AudioFormat format;
|
SDL_MixAudioFormat(dst, src, device->callbackspec.format, len, volume);
|
||||||
if (device->convert.needed) {
|
|
||||||
format = device->convert.src_format;
|
|
||||||
} else {
|
|
||||||
format = device->spec.format;
|
|
||||||
}
|
|
||||||
SDL_MixAudioFormat(dst, src, format, len, volume);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,4 +54,44 @@ 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_Downsample_Multiple(SDL_AudioCVT *cvt, const int channels);
|
void SDL_Downsample_Multiple(SDL_AudioCVT *cvt, const int channels);
|
||||||
|
|
||||||
|
|
||||||
|
/* SDL_AudioStream is a new audio conversion interface. It
|
||||||
|
might eventually become a public API.
|
||||||
|
The benefits vs SDL_AudioCVT:
|
||||||
|
- it can handle resampling data in chunks without generating
|
||||||
|
artifacts, when it doesn't have the complete buffer available.
|
||||||
|
- it can handle incoming data in any variable size.
|
||||||
|
- You push data as you have it, and pull it when you need it
|
||||||
|
|
||||||
|
(Note that currently this converts as data is put into the stream, so
|
||||||
|
you need to push more than a handful of bytes if you want decent
|
||||||
|
resampling. This can be changed later.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* this is opaque to the outside world. */
|
||||||
|
typedef struct SDL_AudioStream SDL_AudioStream;
|
||||||
|
|
||||||
|
/* create a new stream */
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* add data to be converted/resampled to the stream */
|
||||||
|
int SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 len);
|
||||||
|
|
||||||
|
/* get converted/resampled data from the stream */
|
||||||
|
int SDL_AudioStreamGet(SDL_AudioStream *stream, Uint32 len, void *buf, const Uint32 buflen);
|
||||||
|
|
||||||
|
/* clear any pending data in the stream without converting it. */
|
||||||
|
void SDL_AudioStreamClear(SDL_AudioStream *stream);
|
||||||
|
|
||||||
|
/* number of converted/resampled bytes available */
|
||||||
|
int SDL_AudioStreamAvailable(SDL_AudioStream *stream);
|
||||||
|
|
||||||
|
/* dispose of a stream */
|
||||||
|
void SDL_FreeAudioStream(SDL_AudioStream *stream);
|
||||||
|
|
||||||
/* vi: set ts=4 sw=4 expandtab: */
|
/* vi: set ts=4 sw=4 expandtab: */
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "SDL_audio_c.h"
|
#include "SDL_audio_c.h"
|
||||||
|
|
||||||
#include "SDL_assert.h"
|
#include "SDL_assert.h"
|
||||||
|
#include "../SDL_dataqueue.h"
|
||||||
|
|
||||||
|
|
||||||
/* Effectively mix right and left channels into a single channel */
|
/* Effectively mix right and left channels into a single channel */
|
||||||
|
@ -590,5 +591,280 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
|
||||||
return (cvt->needed);
|
return (cvt->needed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct SDL_AudioStream
|
||||||
|
{
|
||||||
|
SDL_AudioCVT cvt_before_resampling;
|
||||||
|
SDL_AudioCVT cvt_after_resampling;
|
||||||
|
SDL_DataQueue *queue;
|
||||||
|
Uint8 *work_buffer;
|
||||||
|
int work_buffer_len;
|
||||||
|
Uint8 *resample_buffer;
|
||||||
|
int resample_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;
|
||||||
|
SDL_bool resampler_seeded;
|
||||||
|
float resampler_state[8];
|
||||||
|
int packetlen;
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
const int packetlen = 4096; /* !!! FIXME: good enough for now. */
|
||||||
|
Uint8 pre_resample_channels;
|
||||||
|
SDL_AudioStream *retval;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
retval->src_sample_frame_size = SDL_AUDIO_BITSIZE(src_format) * src_channels;
|
||||||
|
retval->src_format = src_format;
|
||||||
|
retval->src_channels = src_channels;
|
||||||
|
retval->src_rate = src_rate;
|
||||||
|
retval->dst_sample_frame_size = SDL_AUDIO_BITSIZE(dst_format) * dst_channels;
|
||||||
|
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;
|
||||||
|
retval->cvt_before_resampling.len_mult = 1;
|
||||||
|
if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, src_format, src_channels, dst_rate, dst_format, dst_channels, dst_rate) == -1) {
|
||||||
|
SDL_free(retval);
|
||||||
|
return NULL; /* SDL_BuildAudioCVT should have called SDL_SetError. */
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Don't resample at first. Just get us to Float32 format. */
|
||||||
|
/* !!! FIXME: convert to int32 on devices without hardware float. */
|
||||||
|
if (SDL_BuildAudioCVT(&retval->cvt_before_resampling, src_format, src_channels, src_rate, AUDIO_F32SYS, pre_resample_channels, src_rate) == -1) {
|
||||||
|
SDL_free(retval);
|
||||||
|
return NULL; /* SDL_BuildAudioCVT should have called SDL_SetError. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert us to the final format after resampling. */
|
||||||
|
if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, AUDIO_F32SYS, pre_resample_channels, dst_rate, dst_format, dst_channels, dst_rate) == -1) {
|
||||||
|
SDL_free(retval);
|
||||||
|
return NULL; /* SDL_BuildAudioCVT should have called SDL_SetError. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
retval->queue = SDL_NewDataQueue(packetlen, packetlen * 2);
|
||||||
|
if (!retval->queue) {
|
||||||
|
SDL_free(retval);
|
||||||
|
return NULL; /* SDL_NewDataQueue should have called SDL_SetError. */
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
ResampleAudioStream(SDL_AudioStream *stream, const float *inbuf, const int inbuflen, float *outbuf, const int outbuflen)
|
||||||
|
{
|
||||||
|
/* !!! FIXME: this resampler sucks, but not much worse than our usual resampler. :) */ /* ... :( */
|
||||||
|
const int chans = (int) stream->pre_resample_channels;
|
||||||
|
const int framelen = chans * sizeof (float);
|
||||||
|
const int total = (inbuflen / framelen);
|
||||||
|
const int finalpos = total - chans;
|
||||||
|
const double src_incr = 1.0 / stream->rate_incr;
|
||||||
|
double idx = 0.0;
|
||||||
|
float *dst = outbuf;
|
||||||
|
float last_sample[SDL_arraysize(stream->resampler_state)];
|
||||||
|
int consumed = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
SDL_assert(chans <= SDL_arraysize(last_sample));
|
||||||
|
SDL_assert((inbuflen % framelen) == 0);
|
||||||
|
|
||||||
|
if (!stream->resampler_seeded) {
|
||||||
|
for (i = 0; i < chans; i++) {
|
||||||
|
stream->resampler_state[i] = inbuf[i];
|
||||||
|
}
|
||||||
|
stream->resampler_seeded = SDL_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < chans; i++) {
|
||||||
|
last_sample[i] = stream->resampler_state[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
while (consumed < total) {
|
||||||
|
const int pos = ((int) idx) * chans;
|
||||||
|
const float *src = &inbuf[(pos >= finalpos) ? finalpos : pos];
|
||||||
|
SDL_assert(dst < (outbuf + (outbuflen / framelen)));
|
||||||
|
for (i = 0; i < chans; i++) {
|
||||||
|
const float val = *(src++);
|
||||||
|
*(dst++) = (val + last_sample[i]) * 0.5f;
|
||||||
|
last_sample[i] = val;
|
||||||
|
}
|
||||||
|
consumed = pos + chans;
|
||||||
|
idx += src_incr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < chans; i++) {
|
||||||
|
stream->resampler_state[i] = last_sample[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (dst - outbuf) * sizeof (float);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Uint8 *
|
||||||
|
EnsureBufferSize(Uint8 **buf, int *len, const int newlen)
|
||||||
|
{
|
||||||
|
if (*len < newlen) {
|
||||||
|
void *ptr = SDL_realloc(*buf, newlen);
|
||||||
|
if (!ptr) {
|
||||||
|
SDL_OutOfMemory();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*buf = (Uint8 *) ptr;
|
||||||
|
*len = newlen;
|
||||||
|
}
|
||||||
|
return *buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _buflen)
|
||||||
|
{
|
||||||
|
int buflen = (int) _buflen;
|
||||||
|
|
||||||
|
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 */
|
||||||
|
Uint8 *workbuf = EnsureBufferSize(&stream->work_buffer, &stream->work_buffer_len, workbuflen);
|
||||||
|
if (workbuf == NULL) {
|
||||||
|
return -1; /* probably out of memory. */
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
float *workbuf = (float *) EnsureBufferSize(&stream->resample_buffer, &stream->resample_buffer_len, workbuflen);
|
||||||
|
if (workbuf == NULL) {
|
||||||
|
return -1; /* probably out of memory. */
|
||||||
|
}
|
||||||
|
buflen = ResampleAudioStream(stream, (float *) buf, buflen, workbuf, workbuflen);
|
||||||
|
buf = workbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream->cvt_after_resampling.needed) {
|
||||||
|
const int workbuflen = buflen * stream->cvt_before_resampling.len_mult; /* will be "* 1" if not needed */
|
||||||
|
Uint8 *workbuf;
|
||||||
|
|
||||||
|
if (buf == stream->resample_buffer) {
|
||||||
|
workbuf = EnsureBufferSize(&stream->resample_buffer, &stream->resample_buffer_len, workbuflen);
|
||||||
|
} else {
|
||||||
|
const int inplace = (buf == stream->work_buffer);
|
||||||
|
workbuf = EnsureBufferSize(&stream->work_buffer, &stream->work_buffer_len, workbuflen);
|
||||||
|
if (workbuf && !inplace) {
|
||||||
|
SDL_memcpy(workbuf, buf, buflen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workbuf == NULL) {
|
||||||
|
return -1; /* probably out of memory. */
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
stream->resampler_seeded = SDL_FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* get converted/resampled data from the stream */
|
||||||
|
int
|
||||||
|
SDL_AudioStreamGet(SDL_AudioStream *stream, Uint32 len, void *buf, const Uint32 buflen)
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDL_ReadFromDataQueue(stream->queue, buf, buflen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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) {
|
||||||
|
SDL_FreeDataQueue(stream->queue);
|
||||||
|
SDL_free(stream->work_buffer);
|
||||||
|
SDL_free(stream->resample_buffer);
|
||||||
|
SDL_free(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* vi: set ts=4 sw=4 expandtab: */
|
/* vi: set ts=4 sw=4 expandtab: */
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
typedef struct SDL_AudioDevice SDL_AudioDevice;
|
typedef struct SDL_AudioDevice SDL_AudioDevice;
|
||||||
#define _THIS SDL_AudioDevice *_this
|
#define _THIS SDL_AudioDevice *_this
|
||||||
|
|
||||||
|
typedef struct SDL_AudioStream SDL_AudioStream;
|
||||||
|
|
||||||
/* Audio targets should call this as devices are added to the system (such as
|
/* Audio targets should call this as devices are added to the system (such as
|
||||||
a USB headset being plugged in), and should also be called for
|
a USB headset being plugged in), and should also be called for
|
||||||
for every device found during DetectDevices(). */
|
for every device found during DetectDevices(). */
|
||||||
|
@ -123,15 +125,6 @@ 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
|
||||||
{
|
{
|
||||||
|
@ -139,15 +132,14 @@ struct SDL_AudioDevice
|
||||||
/* Data common to all devices */
|
/* Data common to all devices */
|
||||||
SDL_AudioDeviceID id;
|
SDL_AudioDeviceID id;
|
||||||
|
|
||||||
/* The current audio specification (shared with audio thread) */
|
/* The device's current audio specification */
|
||||||
SDL_AudioSpec spec;
|
SDL_AudioSpec spec;
|
||||||
|
|
||||||
/* An audio conversion block for audio format emulation */
|
/* The callback's expected audio specification (converted vs device's spec). */
|
||||||
SDL_AudioCVT convert;
|
SDL_AudioSpec callbackspec;
|
||||||
|
|
||||||
/* The streamer, if sample rate conversion necessitates it */
|
/* Stream that converts and resamples. NULL if not needed. */
|
||||||
int use_streamer;
|
SDL_AudioStream *stream;
|
||||||
SDL_AudioStreamer streamer;
|
|
||||||
|
|
||||||
/* Current state flags */
|
/* Current state flags */
|
||||||
SDL_atomic_t shutdown; /* true if we are signaling the play thread to end. */
|
SDL_atomic_t shutdown; /* true if we are signaling the play thread to end. */
|
||||||
|
@ -158,6 +150,9 @@ struct SDL_AudioDevice
|
||||||
/* Fake audio buffer for when the audio hardware is busy */
|
/* Fake audio buffer for when the audio hardware is busy */
|
||||||
Uint8 *fake_stream;
|
Uint8 *fake_stream;
|
||||||
|
|
||||||
|
/* Size, in bytes, of fake_stream. */
|
||||||
|
Uint32 fake_stream_len;
|
||||||
|
|
||||||
/* A mutex for locking the mixing buffers */
|
/* A mutex for locking the mixing buffers */
|
||||||
SDL_mutex *mixer_lock;
|
SDL_mutex *mixer_lock;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue