| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "../SDL_internal.h" |
| |
| /* Allow access to a raw mixing buffer */ |
| |
| #include "SDL.h" |
| #include "SDL_audio.h" |
| #include "SDL_audio_c.h" |
| #include "SDL_audiomem.h" |
| #include "SDL_sysaudio.h" |
| |
| #define _THIS SDL_AudioDevice *_this |
| |
| static SDL_AudioDriver current_audio; |
| static SDL_AudioDevice *open_devices[16]; |
| |
| /* !!! FIXME: These are wordy and unlocalized... */ |
| #define DEFAULT_OUTPUT_DEVNAME "System audio output device" |
| #define DEFAULT_INPUT_DEVNAME "System audio capture device" |
| |
| |
| /* |
| * Not all of these will be compiled and linked in, but it's convenient |
| * to have a complete list here and saves yet-another block of #ifdefs... |
| * Please see bootstrap[], below, for the actual #ifdef mess. |
| */ |
| extern AudioBootStrap BSD_AUDIO_bootstrap; |
| extern AudioBootStrap DSP_bootstrap; |
| extern AudioBootStrap ALSA_bootstrap; |
| extern AudioBootStrap PULSEAUDIO_bootstrap; |
| extern AudioBootStrap QSAAUDIO_bootstrap; |
| extern AudioBootStrap SUNAUDIO_bootstrap; |
| extern AudioBootStrap ARTS_bootstrap; |
| extern AudioBootStrap ESD_bootstrap; |
| extern AudioBootStrap NAS_bootstrap; |
| extern AudioBootStrap XAUDIO2_bootstrap; |
| extern AudioBootStrap DSOUND_bootstrap; |
| extern AudioBootStrap WINMM_bootstrap; |
| extern AudioBootStrap PAUDIO_bootstrap; |
| extern AudioBootStrap HAIKUAUDIO_bootstrap; |
| extern AudioBootStrap COREAUDIO_bootstrap; |
| extern AudioBootStrap SNDMGR_bootstrap; |
| extern AudioBootStrap DISKAUD_bootstrap; |
| extern AudioBootStrap DUMMYAUD_bootstrap; |
| extern AudioBootStrap DCAUD_bootstrap; |
| extern AudioBootStrap DART_bootstrap; |
| extern AudioBootStrap NDSAUD_bootstrap; |
| extern AudioBootStrap FUSIONSOUND_bootstrap; |
| extern AudioBootStrap ANDROIDAUD_bootstrap; |
| extern AudioBootStrap PSPAUD_bootstrap; |
| extern AudioBootStrap SNDIO_bootstrap; |
| |
| /* Available audio drivers */ |
| static const AudioBootStrap *const bootstrap[] = { |
| #if SDL_AUDIO_DRIVER_PULSEAUDIO |
| &PULSEAUDIO_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_ALSA |
| &ALSA_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_SNDIO |
| &SNDIO_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_BSD |
| &BSD_AUDIO_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_OSS |
| &DSP_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_QSA |
| &QSAAUDIO_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_SUNAUDIO |
| &SUNAUDIO_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_ARTS |
| &ARTS_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_ESD |
| &ESD_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_NAS |
| &NAS_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_XAUDIO2 |
| &XAUDIO2_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_DSOUND |
| &DSOUND_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_WINMM |
| &WINMM_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_PAUDIO |
| &PAUDIO_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_HAIKU |
| &HAIKUAUDIO_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_COREAUDIO |
| &COREAUDIO_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_DISK |
| &DISKAUD_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_DUMMY |
| &DUMMYAUD_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_FUSIONSOUND |
| &FUSIONSOUND_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_ANDROID |
| &ANDROIDAUD_bootstrap, |
| #endif |
| #if SDL_AUDIO_DRIVER_PSP |
| &PSPAUD_bootstrap, |
| #endif |
| NULL |
| }; |
| |
| static SDL_AudioDevice * |
| get_audio_device(SDL_AudioDeviceID id) |
| { |
| id--; |
| if ((id >= SDL_arraysize(open_devices)) || (open_devices[id] == NULL)) { |
| SDL_SetError("Invalid audio device ID"); |
| return NULL; |
| } |
| |
| return open_devices[id]; |
| } |
| |
| |
| /* stubs for audio drivers that don't need a specific entry point... */ |
| static void |
| SDL_AudioDetectDevices_Default(int iscapture, SDL_AddAudioDevice addfn) |
| { /* no-op. */ |
| } |
| |
| static void |
| SDL_AudioThreadInit_Default(_THIS) |
| { /* no-op. */ |
| } |
| |
| static void |
| SDL_AudioWaitDevice_Default(_THIS) |
| { /* no-op. */ |
| } |
| |
| static void |
| SDL_AudioPlayDevice_Default(_THIS) |
| { /* no-op. */ |
| } |
| |
| static Uint8 * |
| SDL_AudioGetDeviceBuf_Default(_THIS) |
| { |
| return NULL; |
| } |
| |
| static void |
| SDL_AudioWaitDone_Default(_THIS) |
| { /* no-op. */ |
| } |
| |
| static void |
| SDL_AudioCloseDevice_Default(_THIS) |
| { /* no-op. */ |
| } |
| |
| static void |
| SDL_AudioDeinitialize_Default(void) |
| { /* no-op. */ |
| } |
| |
| static int |
| SDL_AudioOpenDevice_Default(_THIS, const char *devname, int iscapture) |
| { |
| return -1; |
| } |
| |
| static void |
| SDL_AudioLockDevice_Default(SDL_AudioDevice * device) |
| { |
| if (device->thread && (SDL_ThreadID() == device->threadid)) { |
| return; |
| } |
| SDL_LockMutex(device->mixer_lock); |
| } |
| |
| static void |
| SDL_AudioUnlockDevice_Default(SDL_AudioDevice * device) |
| { |
| if (device->thread && (SDL_ThreadID() == device->threadid)) { |
| return; |
| } |
| SDL_UnlockMutex(device->mixer_lock); |
| } |
| |
| |
| static void |
| finalize_audio_entry_points(void) |
| { |
| /* |
| * Fill in stub functions for unused driver entry points. This lets us |
| * blindly call them without having to check for validity first. |
| */ |
| |
| #define FILL_STUB(x) \ |
| if (current_audio.impl.x == NULL) { \ |
| current_audio.impl.x = SDL_Audio##x##_Default; \ |
| } |
| FILL_STUB(DetectDevices); |
| FILL_STUB(OpenDevice); |
| FILL_STUB(ThreadInit); |
| FILL_STUB(WaitDevice); |
| FILL_STUB(PlayDevice); |
| FILL_STUB(GetDeviceBuf); |
| FILL_STUB(WaitDone); |
| FILL_STUB(CloseDevice); |
| FILL_STUB(LockDevice); |
| FILL_STUB(UnlockDevice); |
| FILL_STUB(Deinitialize); |
| #undef FILL_STUB |
| } |
| |
| /* Streaming functions (for when the input and output buffer sizes are different) */ |
| /* Write [length] bytes from buf into the streamer */ |
| static 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 */ |
| static 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; |
| } |
| } |
| |
| static 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 */ |
| #if 0 |
| static int |
| SDL_StreamInit(SDL_AudioStreamer * stream, int max_len, Uint8 silence) |
| { |
| /* First try to allocate the buffer */ |
| stream->buffer = (Uint8 *) SDL_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 */ |
| SDL_memset(stream->buffer, silence, max_len); |
| |
| return 0; |
| } |
| #endif |
| |
| /* Deinitialize the stream simply by freeing the buffer */ |
| static void |
| SDL_StreamDeinit(SDL_AudioStreamer * stream) |
| { |
| SDL_free(stream->buffer); |
| } |
| |
| #if defined(ANDROID) |
| #include <android/log.h> |
| #endif |
| |
| /* The general mixing thread function */ |
| int SDLCALL |
| SDL_RunAudio(void *devicep) |
| { |
| SDL_AudioDevice *device = (SDL_AudioDevice *) devicep; |
| Uint8 *stream; |
| int stream_len; |
| void *udata; |
| void (SDLCALL * fill) (void *userdata, Uint8 * stream, int len); |
| Uint32 delay; |
| /* For streaming when the buffer sizes don't match up */ |
| Uint8 *istream; |
| int istream_len = 0; |
| |
| /* The audio mixing is always a high priority thread */ |
| SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); |
| |
| /* Perform any thread setup */ |
| device->threadid = SDL_ThreadID(); |
| current_audio.impl.ThreadInit(device); |
| |
| /* Set up the mixing function */ |
| fill = device->spec.callback; |
| udata = device->spec.userdata; |
| |
| /* By default do not stream */ |
| device->use_streamer = 0; |
| |
| if (device->convert.needed) { |
| #if 0 /* !!! FIXME: I took len_div out of the structure. Use rate_incr instead? */ |
| /* 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; |
| } |
| #endif |
| stream_len = device->convert.len; |
| } else { |
| stream_len = device->spec.size; |
| } |
| |
| /* Calculate the delay while paused */ |
| delay = ((device->spec.samples * 1000) / device->spec.freq); |
| |
| /* Determine if the streamer is necessary here */ |
| if (device->use_streamer == 1) { |
| /* This code is almost the same as the old code. The difference is, instead of reading |
| directly from the callback into "stream", then converting and sending the audio off, |
| we go: callback -> "istream" -> (conversion) -> streamer -> stream -> device. |
| However, reading and writing with streamer are done separately: |
| - We only call the callback and write to the streamer when the streamer does not |
| contain enough samples to output to the device. |
| - 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) { |
| |
| if (device->paused) { |
| SDL_Delay(delay); |
| continue; |
| } |
| |
| /* 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 { |
| /* FIXME: Ryan, this is probably wrong. I imagine we don't want to get |
| * a device buffer both here and below in the stream output. |
| */ |
| istream = current_audio.impl.GetDeviceBuf(device); |
| if (istream == NULL) { |
| istream = device->fake_stream; |
| } |
| } |
| |
| /* Read from the callback into the _input_ stream */ |
| SDL_LockMutex(device->mixer_lock); |
| (*fill) (udata, istream, istream_len); |
| SDL_UnlockMutex(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); |
| } |
| } |
| |
| /* Only output audio if the streamer has enough to output */ |
| if (SDL_StreamLength(&device->streamer) >= stream_len) { |
| /* 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 */ |
| current_audio.impl.WaitDevice(device); |
| } else { |
| SDL_Delay(delay); |
| } |
| } |
| |
| } |
| } else { |
| /* Otherwise, do not use the streamer. This is the old code. */ |
| const int silence = (int) device->spec.silence; |
| |
| /* 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; |
| } |
| } |
| |
| SDL_LockMutex(device->mixer_lock); |
| if (device->paused) { |
| SDL_memset(stream, silence, stream_len); |
| } else { |
| (*fill) (udata, stream, stream_len); |
| } |
| SDL_UnlockMutex(device->mixer_lock); |
| |
| /* Convert the audio if necessary */ |
| if (device->convert.needed) { |
| 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); |
| } |
| |
| /* 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 */ |
| current_audio.impl.WaitDevice(device); |
| } else { |
| SDL_Delay(delay); |
| } |
| } |
| } |
| |
| /* Wait for the audio to drain.. */ |
| current_audio.impl.WaitDone(device); |
| |
| /* If necessary, deinit the streamer */ |
| if (device->use_streamer == 1) |
| SDL_StreamDeinit(&device->streamer); |
| |
| return (0); |
| } |
| |
| |
| static SDL_AudioFormat |
| SDL_ParseAudioFormat(const char *string) |
| { |
| #define CHECK_FMT_STRING(x) if (SDL_strcmp(string, #x) == 0) return AUDIO_##x |
| CHECK_FMT_STRING(U8); |
| CHECK_FMT_STRING(S8); |
| CHECK_FMT_STRING(U16LSB); |
| CHECK_FMT_STRING(S16LSB); |
| CHECK_FMT_STRING(U16MSB); |
| CHECK_FMT_STRING(S16MSB); |
| CHECK_FMT_STRING(U16SYS); |
| CHECK_FMT_STRING(S16SYS); |
| CHECK_FMT_STRING(U16); |
| CHECK_FMT_STRING(S16); |
| CHECK_FMT_STRING(S32LSB); |
| CHECK_FMT_STRING(S32MSB); |
| CHECK_FMT_STRING(S32SYS); |
| CHECK_FMT_STRING(S32); |
| CHECK_FMT_STRING(F32LSB); |
| CHECK_FMT_STRING(F32MSB); |
| CHECK_FMT_STRING(F32SYS); |
| CHECK_FMT_STRING(F32); |
| #undef CHECK_FMT_STRING |
| return 0; |
| } |
| |
| int |
| SDL_GetNumAudioDrivers(void) |
| { |
| return (SDL_arraysize(bootstrap) - 1); |
| } |
| |
| const char * |
| SDL_GetAudioDriver(int index) |
| { |
| if (index >= 0 && index < SDL_GetNumAudioDrivers()) { |
| return (bootstrap[index]->name); |
| } |
| return (NULL); |
| } |
| |
| int |
| SDL_AudioInit(const char *driver_name) |
| { |
| int i = 0; |
| int initialized = 0; |
| int tried_to_init = 0; |
| |
| if (SDL_WasInit(SDL_INIT_AUDIO)) { |
| SDL_AudioQuit(); /* shutdown driver if already running. */ |
| } |
| |
| SDL_memset(¤t_audio, '\0', sizeof(current_audio)); |
| SDL_memset(open_devices, '\0', sizeof(open_devices)); |
| |
| /* Select the proper audio driver */ |
| if (driver_name == NULL) { |
| driver_name = SDL_getenv("SDL_AUDIODRIVER"); |
| } |
| |
| for (i = 0; (!initialized) && (bootstrap[i]); ++i) { |
| /* make sure we should even try this driver before doing so... */ |
| const AudioBootStrap *backend = bootstrap[i]; |
| if ((driver_name && (SDL_strncasecmp(backend->name, driver_name, SDL_strlen(driver_name)) != 0)) || |
| (!driver_name && backend->demand_only)) { |
| continue; |
| } |
| |
| tried_to_init = 1; |
| SDL_memset(¤t_audio, 0, sizeof(current_audio)); |
| current_audio.name = backend->name; |
| current_audio.desc = backend->desc; |
| initialized = backend->init(¤t_audio.impl); |
| } |
| |
| if (!initialized) { |
| /* specific drivers will set the error message if they fail... */ |
| if (!tried_to_init) { |
| if (driver_name) { |
| SDL_SetError("Audio target '%s' not available", driver_name); |
| } else { |
| SDL_SetError("No available audio device"); |
| } |
| } |
| |
| SDL_memset(¤t_audio, 0, sizeof(current_audio)); |
| return (-1); /* No driver was available, so fail. */ |
| } |
| |
| finalize_audio_entry_points(); |
| |
| return (0); |
| } |
| |
| /* |
| * Get the current audio driver name |
| */ |
| const char * |
| SDL_GetCurrentAudioDriver() |
| { |
| return current_audio.name; |
| } |
| |
| static void |
| free_device_list(char ***devices, int *devCount) |
| { |
| int i = *devCount; |
| if ((i > 0) && (*devices != NULL)) { |
| while (i--) { |
| SDL_free((*devices)[i]); |
| } |
| } |
| |
| SDL_free(*devices); |
| |
| *devices = NULL; |
| *devCount = 0; |
| } |
| |
| static |
| void SDL_AddCaptureAudioDevice(const char *_name) |
| { |
| char *name = NULL; |
| void *ptr = SDL_realloc(current_audio.inputDevices, |
| (current_audio.inputDeviceCount+1) * sizeof(char*)); |
| if (ptr == NULL) { |
| return; /* oh well. */ |
| } |
| |
| current_audio.inputDevices = (char **) ptr; |
| name = SDL_strdup(_name); /* if this returns NULL, that's okay. */ |
| current_audio.inputDevices[current_audio.inputDeviceCount++] = name; |
| } |
| |
| static |
| void SDL_AddOutputAudioDevice(const char *_name) |
| { |
| char *name = NULL; |
| void *ptr = SDL_realloc(current_audio.outputDevices, |
| (current_audio.outputDeviceCount+1) * sizeof(char*)); |
| if (ptr == NULL) { |
| return; /* oh well. */ |
| } |
| |
| current_audio.outputDevices = (char **) ptr; |
| name = SDL_strdup(_name); /* if this returns NULL, that's okay. */ |
| current_audio.outputDevices[current_audio.outputDeviceCount++] = name; |
| } |
| |
| |
| int |
| SDL_GetNumAudioDevices(int iscapture) |
| { |
| int retval = 0; |
| |
| if (!SDL_WasInit(SDL_INIT_AUDIO)) { |
| return -1; |
| } |
| |
| if ((iscapture) && (!current_audio.impl.HasCaptureSupport)) { |
| return 0; |
| } |
| |
| if ((iscapture) && (current_audio.impl.OnlyHasDefaultInputDevice)) { |
| return 1; |
| } |
| |
| if ((!iscapture) && (current_audio.impl.OnlyHasDefaultOutputDevice)) { |
| return 1; |
| } |
| |
| if (iscapture) { |
| free_device_list(¤t_audio.inputDevices, |
| ¤t_audio.inputDeviceCount); |
| current_audio.impl.DetectDevices(iscapture, SDL_AddCaptureAudioDevice); |
| retval = current_audio.inputDeviceCount; |
| } else { |
| free_device_list(¤t_audio.outputDevices, |
| ¤t_audio.outputDeviceCount); |
| current_audio.impl.DetectDevices(iscapture, SDL_AddOutputAudioDevice); |
| retval = current_audio.outputDeviceCount; |
| } |
| |
| return retval; |
| } |
| |
| |
| const char * |
| SDL_GetAudioDeviceName(int index, int iscapture) |
| { |
| if (!SDL_WasInit(SDL_INIT_AUDIO)) { |
| SDL_SetError("Audio subsystem is not initialized"); |
| return NULL; |
| } |
| |
| if ((iscapture) && (!current_audio.impl.HasCaptureSupport)) { |
| SDL_SetError("No capture support"); |
| return NULL; |
| } |
| |
| if (index < 0) { |
| goto no_such_device; |
| } |
| |
| if ((iscapture) && (current_audio.impl.OnlyHasDefaultInputDevice)) { |
| if (index > 0) { |
| goto no_such_device; |
| } |
| return DEFAULT_INPUT_DEVNAME; |
| } |
| |
| if ((!iscapture) && (current_audio.impl.OnlyHasDefaultOutputDevice)) { |
| if (index > 0) { |
| goto no_such_device; |
| } |
| return DEFAULT_OUTPUT_DEVNAME; |
| } |
| |
| if (iscapture) { |
| if (index >= current_audio.inputDeviceCount) { |
| goto no_such_device; |
| } |
| return current_audio.inputDevices[index]; |
| } else { |
| if (index >= current_audio.outputDeviceCount) { |
| goto no_such_device; |
| } |
| return current_audio.outputDevices[index]; |
| } |
| |
| no_such_device: |
| SDL_SetError("No such device"); |
| return NULL; |
| } |
| |
| |
| static void |
| close_audio_device(SDL_AudioDevice * device) |
| { |
| device->enabled = 0; |
| if (device->thread != NULL) { |
| SDL_WaitThread(device->thread, NULL); |
| } |
| if (device->mixer_lock != NULL) { |
| SDL_DestroyMutex(device->mixer_lock); |
| } |
| SDL_FreeAudioMem(device->fake_stream); |
| if (device->convert.needed) { |
| SDL_FreeAudioMem(device->convert.buf); |
| } |
| if (device->opened) { |
| current_audio.impl.CloseDevice(device); |
| device->opened = 0; |
| } |
| SDL_FreeAudioMem(device); |
| } |
| |
| |
| /* |
| * Sanity check desired AudioSpec for SDL_OpenAudio() in (orig). |
| * Fills in a sanitized copy in (prepared). |
| * Returns non-zero if okay, zero on fatal parameters in (orig). |
| */ |
| static int |
| prepare_audiospec(const SDL_AudioSpec * orig, SDL_AudioSpec * prepared) |
| { |
| SDL_memcpy(prepared, orig, sizeof(SDL_AudioSpec)); |
| |
| if (orig->callback == NULL) { |
| SDL_SetError("SDL_OpenAudio() passed a NULL callback"); |
| return 0; |
| } |
| |
| if (orig->freq == 0) { |
| const char *env = SDL_getenv("SDL_AUDIO_FREQUENCY"); |
| if ((!env) || ((prepared->freq = SDL_atoi(env)) == 0)) { |
| prepared->freq = 22050; /* a reasonable default */ |
| } |
| } |
| |
| if (orig->format == 0) { |
| const char *env = SDL_getenv("SDL_AUDIO_FORMAT"); |
| if ((!env) || ((prepared->format = SDL_ParseAudioFormat(env)) == 0)) { |
| prepared->format = AUDIO_S16; /* a reasonable default */ |
| } |
| } |
| |
| switch (orig->channels) { |
| case 0:{ |
| const char *env = SDL_getenv("SDL_AUDIO_CHANNELS"); |
| if ((!env) || ((prepared->channels = (Uint8) SDL_atoi(env)) == 0)) { |
| prepared->channels = 2; /* a reasonable default */ |
| } |
| break; |
| } |
| case 1: /* Mono */ |
| case 2: /* Stereo */ |
| case 4: /* surround */ |
| case 6: /* surround with center and lfe */ |
| break; |
| default: |
| SDL_SetError("Unsupported number of audio channels."); |
| return 0; |
| } |
| |
| if (orig->samples == 0) { |
| const char *env = SDL_getenv("SDL_AUDIO_SAMPLES"); |
| if ((!env) || ((prepared->samples = (Uint16) SDL_atoi(env)) == 0)) { |
| /* Pick a default of ~46 ms at desired frequency */ |
| /* !!! FIXME: remove this when the non-Po2 resampling is in. */ |
| const int samples = (prepared->freq / 1000) * 46; |
| int power2 = 1; |
| while (power2 < samples) { |
| power2 *= 2; |
| } |
| prepared->samples = power2; |
| } |
| } |
| |
| /* Calculate the silence and size of the audio specification */ |
| SDL_CalculateAudioSpec(prepared); |
| |
| return 1; |
| } |
| |
| |
| static SDL_AudioDeviceID |
| open_audio_device(const char *devname, int iscapture, |
| const SDL_AudioSpec * desired, SDL_AudioSpec * obtained, |
| int allowed_changes, int min_id) |
| { |
| SDL_AudioDeviceID id = 0; |
| SDL_AudioSpec _obtained; |
| SDL_AudioDevice *device; |
| SDL_bool build_cvt; |
| int i = 0; |
| |
| if (!SDL_WasInit(SDL_INIT_AUDIO)) { |
| SDL_SetError("Audio subsystem is not initialized"); |
| return 0; |
| } |
| |
| if ((iscapture) && (!current_audio.impl.HasCaptureSupport)) { |
| SDL_SetError("No capture support"); |
| return 0; |
| } |
| |
| if (!obtained) { |
| obtained = &_obtained; |
| } |
| if (!prepare_audiospec(desired, obtained)) { |
| return 0; |
| } |
| |
| /* If app doesn't care about a specific device, let the user override. */ |
| if (devname == NULL) { |
| devname = SDL_getenv("SDL_AUDIO_DEVICE_NAME"); |
| } |
| |
| /* |
| * Catch device names at the high level for the simple case... |
| * This lets us have a basic "device enumeration" for systems that |
| * don't have multiple devices, but makes sure the device name is |
| * always NULL when it hits the low level. |
| * |
| * Also make sure that the simple case prevents multiple simultaneous |
| * opens of the default system device. |
| */ |
| |
| if ((iscapture) && (current_audio.impl.OnlyHasDefaultInputDevice)) { |
| if ((devname) && (SDL_strcmp(devname, DEFAULT_INPUT_DEVNAME) != 0)) { |
| SDL_SetError("No such device"); |
| return 0; |
| } |
| devname = NULL; |
| |
| for (i = 0; i < SDL_arraysize(open_devices); i++) { |
| if ((open_devices[i]) && (open_devices[i]->iscapture)) { |
| SDL_SetError("Audio device already open"); |
| return 0; |
| } |
| } |
| } |
| |
| if ((!iscapture) && (current_audio.impl.OnlyHasDefaultOutputDevice)) { |
| if ((devname) && (SDL_strcmp(devname, DEFAULT_OUTPUT_DEVNAME) != 0)) { |
| SDL_SetError("No such device"); |
| return 0; |
| } |
| devname = NULL; |
| |
| for (i = 0; i < SDL_arraysize(open_devices); i++) { |
| if ((open_devices[i]) && (!open_devices[i]->iscapture)) { |
| SDL_SetError("Audio device already open"); |
| return 0; |
| } |
| } |
| } |
| |
| device = (SDL_AudioDevice *) SDL_AllocAudioMem(sizeof(SDL_AudioDevice)); |
| if (device == NULL) { |
| SDL_OutOfMemory(); |
| return 0; |
| } |
| SDL_memset(device, '\0', sizeof(SDL_AudioDevice)); |
| device->spec = *obtained; |
| device->enabled = 1; |
| device->paused = 1; |
| device->iscapture = iscapture; |
| |
| /* Create a semaphore for locking the sound buffers */ |
| if (!current_audio.impl.SkipMixerLock) { |
| device->mixer_lock = SDL_CreateMutex(); |
| if (device->mixer_lock == NULL) { |
| close_audio_device(device); |
| SDL_SetError("Couldn't create mixer lock"); |
| return 0; |
| } |
| } |
| |
| /* force a device detection if we haven't done one yet. */ |
| if ( ((iscapture) && (current_audio.inputDevices == NULL)) || |
| ((!iscapture) && (current_audio.outputDevices == NULL)) ) |
| SDL_GetNumAudioDevices(iscapture); |
| |
| if (current_audio.impl.OpenDevice(device, devname, iscapture) < 0) { |
| close_audio_device(device); |
| return 0; |
| } |
| device->opened = 1; |
| |
| /* Allocate a fake audio memory buffer */ |
| device->fake_stream = (Uint8 *)SDL_AllocAudioMem(device->spec.size); |
| if (device->fake_stream == NULL) { |
| close_audio_device(device); |
| SDL_OutOfMemory(); |
| return 0; |
| } |
| |
| /* See if we need to do any conversion */ |
| build_cvt = SDL_FALSE; |
| if (obtained->freq != device->spec.freq) { |
| if (allowed_changes & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) { |
| obtained->freq = device->spec.freq; |
| } else { |
| build_cvt = SDL_TRUE; |
| } |
| } |
| if (obtained->format != device->spec.format) { |
| if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) { |
| obtained->format = device->spec.format; |
| } else { |
| build_cvt = SDL_TRUE; |
| } |
| } |
| if (obtained->channels != device->spec.channels) { |
| if (allowed_changes & SDL_AUDIO_ALLOW_CHANNELS_CHANGE) { |
| obtained->channels = device->spec.channels; |
| } else { |
| build_cvt = SDL_TRUE; |
| } |
| } |
| |
| /* If the audio driver changes the buffer size, accept it. |
| This needs to be done after the format is modified above, |
| otherwise it might not have the correct buffer size. |
| */ |
| if (device->spec.samples != obtained->samples) { |
| obtained->samples = device->spec.samples; |
| SDL_CalculateAudioSpec(obtained); |
| } |
| |
| if (build_cvt) { |
| /* Build an audio conversion block */ |
| if (SDL_BuildAudioCVT(&device->convert, |
| obtained->format, obtained->channels, |
| obtained->freq, |
| device->spec.format, device->spec.channels, |
| device->spec.freq) < 0) { |
| close_audio_device(device); |
| return 0; |
| } |
| if (device->convert.needed) { |
| device->convert.len = (int) (((double) device->spec.size) / |
| device->convert.len_ratio); |
| |
| device->convert.buf = |
| (Uint8 *) SDL_AllocAudioMem(device->convert.len * |
| device->convert.len_mult); |
| if (device->convert.buf == NULL) { |
| close_audio_device(device); |
| SDL_OutOfMemory(); |
| return 0; |
| } |
| } |
| } |
| |
| /* Find an available device ID and store the structure... */ |
| for (id = min_id - 1; id < SDL_arraysize(open_devices); id++) { |
| if (open_devices[id] == NULL) { |
| open_devices[id] = device; |
| break; |
| } |
| } |
| |
| if (id == SDL_arraysize(open_devices)) { |
| SDL_SetError("Too many open audio devices"); |
| close_audio_device(device); |
| return 0; |
| } |
| |
| /* Start the audio thread if necessary */ |
| if (!current_audio.impl.ProvidesOwnCallbackThread) { |
| /* Start the audio thread */ |
| char name[64]; |
| SDL_snprintf(name, sizeof (name), "SDLAudioDev%d", (int) (id + 1)); |
| /* !!! FIXME: this is nasty. */ |
| #if defined(__WIN32__) && !defined(HAVE_LIBC) |
| #undef SDL_CreateThread |
| #if SDL_DYNAMIC_API |
| device->thread = SDL_CreateThread_REAL(SDL_RunAudio, name, device, NULL, NULL); |
| #else |
| device->thread = SDL_CreateThread(SDL_RunAudio, name, device, NULL, NULL); |
| #endif |
| #else |
| device->thread = SDL_CreateThread(SDL_RunAudio, name, device); |
| #endif |
| if (device->thread == NULL) { |
| SDL_CloseAudioDevice(id + 1); |
| SDL_SetError("Couldn't create audio thread"); |
| return 0; |
| } |
| } |
| |
| return id + 1; |
| } |
| |
| |
| int |
| SDL_OpenAudio(SDL_AudioSpec * desired, SDL_AudioSpec * obtained) |
| { |
| SDL_AudioDeviceID id = 0; |
| |
| /* Start up the audio driver, if necessary. This is legacy behaviour! */ |
| if (!SDL_WasInit(SDL_INIT_AUDIO)) { |
| if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { |
| return (-1); |
| } |
| } |
| |
| /* SDL_OpenAudio() is legacy and can only act on Device ID #1. */ |
| if (open_devices[0] != NULL) { |
| SDL_SetError("Audio device is already opened"); |
| return (-1); |
| } |
| |
| if (obtained) { |
| id = open_audio_device(NULL, 0, desired, obtained, |
| SDL_AUDIO_ALLOW_ANY_CHANGE, 1); |
| } else { |
| id = open_audio_device(NULL, 0, desired, desired, 0, 1); |
| } |
| |
| SDL_assert((id == 0) || (id == 1)); |
| return ((id == 0) ? -1 : 0); |
| } |
| |
| SDL_AudioDeviceID |
| SDL_OpenAudioDevice(const char *device, int iscapture, |
| const SDL_AudioSpec * desired, SDL_AudioSpec * obtained, |
| int allowed_changes) |
| { |
| return open_audio_device(device, iscapture, desired, obtained, |
| allowed_changes, 2); |
| } |
| |
| SDL_AudioStatus |
| SDL_GetAudioDeviceStatus(SDL_AudioDeviceID devid) |
| { |
| SDL_AudioDevice *device = get_audio_device(devid); |
| SDL_AudioStatus status = SDL_AUDIO_STOPPED; |
| if (device && device->enabled) { |
| if (device->paused) { |
| status = SDL_AUDIO_PAUSED; |
| } else { |
| status = SDL_AUDIO_PLAYING; |
| } |
| } |
| return (status); |
| } |
| |
| |
| SDL_AudioStatus |
| SDL_GetAudioStatus(void) |
| { |
| return SDL_GetAudioDeviceStatus(1); |
| } |
| |
| void |
| SDL_PauseAudioDevice(SDL_AudioDeviceID devid, int pause_on) |
| { |
| SDL_AudioDevice *device = get_audio_device(devid); |
| if (device) { |
| current_audio.impl.LockDevice(device); |
| device->paused = pause_on; |
| current_audio.impl.UnlockDevice(device); |
| } |
| } |
| |
| void |
| SDL_PauseAudio(int pause_on) |
| { |
| SDL_PauseAudioDevice(1, pause_on); |
| } |
| |
| |
| void |
| SDL_LockAudioDevice(SDL_AudioDeviceID devid) |
| { |
| /* Obtain a lock on the mixing buffers */ |
| SDL_AudioDevice *device = get_audio_device(devid); |
| if (device) { |
| current_audio.impl.LockDevice(device); |
| } |
| } |
| |
| void |
| SDL_LockAudio(void) |
| { |
| SDL_LockAudioDevice(1); |
| } |
| |
| void |
| SDL_UnlockAudioDevice(SDL_AudioDeviceID devid) |
| { |
| /* Obtain a lock on the mixing buffers */ |
| SDL_AudioDevice *device = get_audio_device(devid); |
| if (device) { |
| current_audio.impl.UnlockDevice(device); |
| } |
| } |
| |
| void |
| SDL_UnlockAudio(void) |
| { |
| SDL_UnlockAudioDevice(1); |
| } |
| |
| void |
| SDL_CloseAudioDevice(SDL_AudioDeviceID devid) |
| { |
| SDL_AudioDevice *device = get_audio_device(devid); |
| if (device) { |
| close_audio_device(device); |
| open_devices[devid - 1] = NULL; |
| } |
| } |
| |
| void |
| SDL_CloseAudio(void) |
| { |
| SDL_CloseAudioDevice(1); |
| } |
| |
| void |
| SDL_AudioQuit(void) |
| { |
| SDL_AudioDeviceID i; |
| |
| if (!current_audio.name) { /* not initialized?! */ |
| return; |
| } |
| |
| for (i = 0; i < SDL_arraysize(open_devices); i++) { |
| if (open_devices[i] != NULL) { |
| SDL_CloseAudioDevice(i+1); |
| } |
| } |
| |
| /* Free the driver data */ |
| current_audio.impl.Deinitialize(); |
| free_device_list(¤t_audio.outputDevices, |
| ¤t_audio.outputDeviceCount); |
| free_device_list(¤t_audio.inputDevices, |
| ¤t_audio.inputDeviceCount); |
| SDL_memset(¤t_audio, '\0', sizeof(current_audio)); |
| SDL_memset(open_devices, '\0', sizeof(open_devices)); |
| } |
| |
| #define NUM_FORMATS 10 |
| static int format_idx; |
| static int format_idx_sub; |
| static SDL_AudioFormat format_list[NUM_FORMATS][NUM_FORMATS] = { |
| {AUDIO_U8, AUDIO_S8, AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_U16LSB, |
| AUDIO_U16MSB, AUDIO_S32LSB, AUDIO_S32MSB, AUDIO_F32LSB, AUDIO_F32MSB}, |
| {AUDIO_S8, AUDIO_U8, AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_U16LSB, |
| AUDIO_U16MSB, AUDIO_S32LSB, AUDIO_S32MSB, AUDIO_F32LSB, AUDIO_F32MSB}, |
| {AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_S32LSB, |
| AUDIO_S32MSB, AUDIO_F32LSB, AUDIO_F32MSB, AUDIO_U8, AUDIO_S8}, |
| {AUDIO_S16MSB, AUDIO_S16LSB, AUDIO_U16MSB, AUDIO_U16LSB, AUDIO_S32MSB, |
| AUDIO_S32LSB, AUDIO_F32MSB, AUDIO_F32LSB, AUDIO_U8, AUDIO_S8}, |
| {AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_S16LSB, AUDIO_S16MSB, AUDIO_S32LSB, |
| AUDIO_S32MSB, AUDIO_F32LSB, AUDIO_F32MSB, AUDIO_U8, AUDIO_S8}, |
| {AUDIO_U16MSB, AUDIO_U16LSB, AUDIO_S16MSB, AUDIO_S16LSB, AUDIO_S32MSB, |
| AUDIO_S32LSB, AUDIO_F32MSB, AUDIO_F32LSB, AUDIO_U8, AUDIO_S8}, |
| {AUDIO_S32LSB, AUDIO_S32MSB, AUDIO_F32LSB, AUDIO_F32MSB, AUDIO_S16LSB, |
| AUDIO_S16MSB, AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_U8, AUDIO_S8}, |
| {AUDIO_S32MSB, AUDIO_S32LSB, AUDIO_F32MSB, AUDIO_F32LSB, AUDIO_S16MSB, |
| AUDIO_S16LSB, AUDIO_U16MSB, AUDIO_U16LSB, AUDIO_U8, AUDIO_S8}, |
| {AUDIO_F32LSB, AUDIO_F32MSB, AUDIO_S32LSB, AUDIO_S32MSB, AUDIO_S16LSB, |
| AUDIO_S16MSB, AUDIO_U16LSB, AUDIO_U16MSB, AUDIO_U8, AUDIO_S8}, |
| {AUDIO_F32MSB, AUDIO_F32LSB, AUDIO_S32MSB, AUDIO_S32LSB, AUDIO_S16MSB, |
| AUDIO_S16LSB, AUDIO_U16MSB, AUDIO_U16LSB, AUDIO_U8, AUDIO_S8}, |
| }; |
| |
| SDL_AudioFormat |
| SDL_FirstAudioFormat(SDL_AudioFormat format) |
| { |
| for (format_idx = 0; format_idx < NUM_FORMATS; ++format_idx) { |
| if (format_list[format_idx][0] == format) { |
| break; |
| } |
| } |
| format_idx_sub = 0; |
| return (SDL_NextAudioFormat()); |
| } |
| |
| SDL_AudioFormat |
| SDL_NextAudioFormat(void) |
| { |
| if ((format_idx == NUM_FORMATS) || (format_idx_sub == NUM_FORMATS)) { |
| return (0); |
| } |
| return (format_list[format_idx][format_idx_sub++]); |
| } |
| |
| void |
| SDL_CalculateAudioSpec(SDL_AudioSpec * spec) |
| { |
| switch (spec->format) { |
| case AUDIO_U8: |
| spec->silence = 0x80; |
| break; |
| default: |
| spec->silence = 0x00; |
| break; |
| } |
| spec->size = SDL_AUDIO_BITSIZE(spec->format) / 8; |
| spec->size *= spec->channels; |
| spec->size *= spec->samples; |
| } |
| |
| |
| /* |
| * Moved here from SDL_mixer.c, since it relies on internals of an opened |
| * audio device (and is deprecated, by the way!). |
| */ |
| void |
| SDL_MixAudio(Uint8 * dst, const Uint8 * src, Uint32 len, int volume) |
| { |
| /* Mix the user-level audio format */ |
| SDL_AudioDevice *device = get_audio_device(1); |
| if (device != NULL) { |
| SDL_AudioFormat format; |
| if (device->convert.needed) { |
| format = device->convert.src_format; |
| } else { |
| format = device->spec.format; |
| } |
| SDL_MixAudioFormat(dst, src, format, len, volume); |
| } |
| } |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |