Show More
Commit Description:
Various UI improvements.
Commit Description:
Various UI improvements.
References:
File last commit:
Show/Diff file:
Action:
FNA/lib/FAudio/src/FAudio_platform_sdl2.c
544 lines | 14.7 KiB | text/x-c | CLexer
544 lines | 14.7 KiB | text/x-c | CLexer
r0 | /* FAudio - XAudio Reimplementation for FNA | |||
* | ||||
* Copyright (c) 2011-2020 Ethan Lee, Luigi Auriemma, and the MonoGame Team | ||||
* | ||||
* 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. | ||||
* | ||||
* Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com> | ||||
* | ||||
*/ | ||||
#include "FAudio_internal.h" | ||||
#include <SDL.h> | ||||
/* Mixer Thread */ | ||||
static void FAudio_INTERNAL_MixCallback(void *userdata, Uint8 *stream, int len) | ||||
{ | ||||
FAudio *audio = (FAudio*) userdata; | ||||
FAudio_zero(stream, len); | ||||
if (audio->active) | ||||
{ | ||||
FAudio_INTERNAL_UpdateEngine( | ||||
audio, | ||||
(float*) stream | ||||
); | ||||
} | ||||
} | ||||
/* Platform Functions */ | ||||
void FAudio_PlatformAddRef() | ||||
{ | ||||
/* SDL tracks ref counts for each subsystem */ | ||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) | ||||
{ | ||||
SDL_Log("SDL_INIT_AUDIO failed: %s", SDL_GetError()); | ||||
} | ||||
FAudio_INTERNAL_InitSIMDFunctions( | ||||
SDL_HasSSE2(), | ||||
SDL_HasNEON() | ||||
); | ||||
} | ||||
void FAudio_PlatformRelease() | ||||
{ | ||||
/* SDL tracks ref counts for each subsystem */ | ||||
SDL_QuitSubSystem(SDL_INIT_AUDIO); | ||||
} | ||||
void FAudio_PlatformInit( | ||||
FAudio *audio, | ||||
uint32_t flags, | ||||
uint32_t deviceIndex, | ||||
FAudioWaveFormatExtensible *mixFormat, | ||||
uint32_t *updateSize, | ||||
void** platformDevice | ||||
) { | ||||
SDL_AudioDeviceID device; | ||||
SDL_AudioSpec want, have; | ||||
FAudio_assert(mixFormat != NULL); | ||||
FAudio_assert(updateSize != NULL); | ||||
/* Build the device spec */ | ||||
want.freq = mixFormat->Format.nSamplesPerSec; | ||||
want.format = AUDIO_F32; | ||||
want.channels = mixFormat->Format.nChannels; | ||||
want.silence = 0; | ||||
want.callback = FAudio_INTERNAL_MixCallback; | ||||
want.userdata = audio; | ||||
if (flags & FAUDIO_1024_QUANTUM) | ||||
{ | ||||
/* Get the sample count for a 21.33ms frame. | ||||
* For 48KHz this should be 1024. | ||||
*/ | ||||
want.samples = (int) ( | ||||
want.freq / (1000.0 / (64.0 / 3.0)) | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
want.samples = want.freq / 100; | ||||
} | ||||
/* Open the device (or at least try to) */ | ||||
iosretry: | ||||
device = SDL_OpenAudioDevice( | ||||
deviceIndex > 0 ? SDL_GetAudioDeviceName(deviceIndex - 1, 0) : NULL, | ||||
0, | ||||
&want, | ||||
&have, | ||||
0 | ||||
); | ||||
if (device == 0) | ||||
{ | ||||
const char *err = SDL_GetError(); | ||||
SDL_Log("OpenAudioDevice failed: %s", err); | ||||
/* iOS has a weird thing where you can't open a stream when the | ||||
* app is in the background, even though the program is meant | ||||
* to be suspended and thus not trip this in the first place. | ||||
* | ||||
* Startup suspend behavior when an app is opened then closed | ||||
* is a big pile of crap, basically. | ||||
* | ||||
* Google the error code and you'll find that this has been a | ||||
* long-standing issue that nobody seems to care about. | ||||
* -flibit | ||||
*/ | ||||
if (SDL_strstr(err, "Code=561015905") != NULL) | ||||
{ | ||||
goto iosretry; | ||||
} | ||||
FAudio_assert(0 && "Failed to open audio device!"); | ||||
return; | ||||
} | ||||
/* Write up the received format for the engine */ | ||||
WriteWaveFormatExtensible(mixFormat, have.channels, have.freq); | ||||
*updateSize = have.samples; | ||||
/* SDL_AudioDeviceID is a Uint32, anybody using a 16-bit PC still? */ | ||||
*platformDevice = (void*) ((size_t) device); | ||||
/* Start the thread! */ | ||||
SDL_PauseAudioDevice(device, 0); | ||||
} | ||||
void FAudio_PlatformQuit(void* platformDevice) | ||||
{ | ||||
SDL_CloseAudioDevice((SDL_AudioDeviceID) ((size_t) platformDevice)); | ||||
} | ||||
uint32_t FAudio_PlatformGetDeviceCount() | ||||
{ | ||||
uint32_t devCount = SDL_GetNumAudioDevices(0); | ||||
if (devCount == 0) | ||||
{ | ||||
return 0; | ||||
} | ||||
return devCount + 1; /* Add one for "Default Device" */ | ||||
} | ||||
void FAudio_UTF8_To_UTF16(const char *src, uint16_t *dst, size_t len); | ||||
uint32_t FAudio_PlatformGetDeviceDetails( | ||||
uint32_t index, | ||||
FAudioDeviceDetails *details | ||||
) { | ||||
const char *name, *envvar; | ||||
int channels, rate; | ||||
FAudio_zero(details, sizeof(FAudioDeviceDetails)); | ||||
if (index >= FAudio_PlatformGetDeviceCount()) | ||||
{ | ||||
return FAUDIO_E_INVALID_CALL; | ||||
} | ||||
details->DeviceID[0] = L'0' + index; | ||||
if (index == 0) | ||||
{ | ||||
name = "Default Device"; | ||||
details->Role = FAudioGlobalDefaultDevice; | ||||
/* This variable will look like a DSound GUID or WASAPI ID, i.e. | ||||
* "{0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}" | ||||
*/ | ||||
envvar = SDL_getenv("FAUDIO_FORCE_DEFAULT_DEVICEID"); | ||||
if (envvar != NULL) | ||||
{ | ||||
FAudio_UTF8_To_UTF16( | ||||
envvar, | ||||
(uint16_t*) details->DeviceID, | ||||
sizeof(details->DeviceID) | ||||
); | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
name = SDL_GetAudioDeviceName(index - 1, 0); | ||||
details->Role = FAudioNotDefaultDevice; | ||||
} | ||||
FAudio_UTF8_To_UTF16( | ||||
name, | ||||
(uint16_t*) details->DisplayName, | ||||
sizeof(details->DisplayName) | ||||
); | ||||
/* TODO: SDL_GetAudioDeviceSpec! */ | ||||
envvar = SDL_getenv("SDL_AUDIO_FREQUENCY"); | ||||
if (!envvar || ((rate = SDL_atoi(envvar)) == 0)) | ||||
{ | ||||
rate = 48000; | ||||
} | ||||
envvar = SDL_getenv("SDL_AUDIO_CHANNELS"); | ||||
if (!envvar || ((channels = SDL_atoi(envvar)) == 0)) | ||||
{ | ||||
channels = 2; | ||||
} | ||||
WriteWaveFormatExtensible(&details->OutputFormat, channels, rate); | ||||
return 0; | ||||
} | ||||
/* Threading */ | ||||
FAudioThread FAudio_PlatformCreateThread( | ||||
FAudioThreadFunc func, | ||||
const char *name, | ||||
void* data | ||||
) { | ||||
return (FAudioThread) SDL_CreateThread( | ||||
(SDL_ThreadFunction) func, | ||||
name, | ||||
data | ||||
); | ||||
} | ||||
void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval) | ||||
{ | ||||
SDL_WaitThread((SDL_Thread*) thread, retval); | ||||
} | ||||
void FAudio_PlatformThreadPriority(FAudioThreadPriority priority) | ||||
{ | ||||
SDL_SetThreadPriority((SDL_ThreadPriority) priority); | ||||
} | ||||
uint64_t FAudio_PlatformGetThreadID(void) | ||||
{ | ||||
return (uint64_t) SDL_ThreadID(); | ||||
} | ||||
FAudioMutex FAudio_PlatformCreateMutex() | ||||
{ | ||||
return (FAudioMutex) SDL_CreateMutex(); | ||||
} | ||||
void FAudio_PlatformDestroyMutex(FAudioMutex mutex) | ||||
{ | ||||
SDL_DestroyMutex((SDL_mutex*) mutex); | ||||
} | ||||
void FAudio_PlatformLockMutex(FAudioMutex mutex) | ||||
{ | ||||
SDL_LockMutex((SDL_mutex*) mutex); | ||||
} | ||||
void FAudio_PlatformUnlockMutex(FAudioMutex mutex) | ||||
{ | ||||
SDL_UnlockMutex((SDL_mutex*) mutex); | ||||
} | ||||
void FAudio_sleep(uint32_t ms) | ||||
{ | ||||
SDL_Delay(ms); | ||||
} | ||||
/* Time */ | ||||
uint32_t FAudio_timems() | ||||
{ | ||||
return SDL_GetTicks(); | ||||
} | ||||
/* FAudio I/O */ | ||||
FAudioIOStream* FAudio_fopen(const char *path) | ||||
{ | ||||
FAudioIOStream *io = (FAudioIOStream*) FAudio_malloc( | ||||
sizeof(FAudioIOStream) | ||||
); | ||||
SDL_RWops *rwops = SDL_RWFromFile(path, "rb"); | ||||
io->data = rwops; | ||||
io->read = (FAudio_readfunc) rwops->read; | ||||
io->seek = (FAudio_seekfunc) rwops->seek; | ||||
io->close = (FAudio_closefunc) rwops->close; | ||||
io->lock = FAudio_PlatformCreateMutex(); | ||||
return io; | ||||
} | ||||
FAudioIOStream* FAudio_memopen(void *mem, int len) | ||||
{ | ||||
FAudioIOStream *io = (FAudioIOStream*) FAudio_malloc( | ||||
sizeof(FAudioIOStream) | ||||
); | ||||
SDL_RWops *rwops = SDL_RWFromMem(mem, len); | ||||
io->data = rwops; | ||||
io->read = (FAudio_readfunc) rwops->read; | ||||
io->seek = (FAudio_seekfunc) rwops->seek; | ||||
io->close = (FAudio_closefunc) rwops->close; | ||||
io->lock = FAudio_PlatformCreateMutex(); | ||||
return io; | ||||
} | ||||
uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset) | ||||
{ | ||||
SDL_RWops *rwops = (SDL_RWops*) io->data; | ||||
FAudio_assert(rwops->type == SDL_RWOPS_MEMORY); | ||||
return rwops->hidden.mem.base + offset; | ||||
} | ||||
void FAudio_close(FAudioIOStream *io) | ||||
{ | ||||
io->close(io->data); | ||||
FAudio_PlatformDestroyMutex((FAudioMutex) io->lock); | ||||
FAudio_free(io); | ||||
} | ||||
#ifdef FAUDIO_DUMP_VOICES | ||||
FAudioIOStreamOut* FAudio_fopen_out(const char *path, const char *mode) | ||||
{ | ||||
FAudioIOStreamOut *io = (FAudioIOStreamOut*) FAudio_malloc( | ||||
sizeof(FAudioIOStreamOut) | ||||
); | ||||
SDL_RWops *rwops = SDL_RWFromFile(path, mode); | ||||
io->data = rwops; | ||||
io->read = (FAudio_readfunc) rwops->read; | ||||
io->write = (FAudio_writefunc) rwops->write; | ||||
io->seek = (FAudio_seekfunc) rwops->seek; | ||||
io->size = (FAudio_sizefunc) rwops->size; | ||||
io->close = (FAudio_closefunc) rwops->close; | ||||
io->lock = FAudio_PlatformCreateMutex(); | ||||
return io; | ||||
} | ||||
void FAudio_close_out(FAudioIOStreamOut *io) | ||||
{ | ||||
io->close(io->data); | ||||
FAudio_PlatformDestroyMutex((FAudioMutex) io->lock); | ||||
FAudio_free(io); | ||||
} | ||||
#endif /* FAUDIO_DUMP_VOICES */ | ||||
/* UTF8->UTF16 Conversion, taken from PhysicsFS */ | ||||
#define UNICODE_BOGUS_CHAR_VALUE 0xFFFFFFFF | ||||
#define UNICODE_BOGUS_CHAR_CODEPOINT '?' | ||||
static uint32_t FAudio_UTF8_CodePoint(const char **_str) | ||||
{ | ||||
const char *str = *_str; | ||||
uint32_t retval = 0; | ||||
uint32_t octet = (uint32_t) ((uint8_t) *str); | ||||
uint32_t octet2, octet3, octet4; | ||||
if (octet == 0) /* null terminator, end of string. */ | ||||
return 0; | ||||
else if (octet < 128) /* one octet char: 0 to 127 */ | ||||
{ | ||||
(*_str)++; /* skip to next possible start of codepoint. */ | ||||
return octet; | ||||
} /* else if */ | ||||
else if ((octet > 127) && (octet < 192)) /* bad (starts with 10xxxxxx). */ | ||||
{ | ||||
/* | ||||
* Apparently each of these is supposed to be flagged as a bogus | ||||
* char, instead of just resyncing to the next valid codepoint. | ||||
*/ | ||||
(*_str)++; /* skip to next possible start of codepoint. */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
} /* else if */ | ||||
else if (octet < 224) /* two octets */ | ||||
{ | ||||
(*_str)++; /* advance at least one byte in case of an error */ | ||||
octet -= (128+64); | ||||
octet2 = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
*_str += 1; /* skip to next possible start of codepoint. */ | ||||
retval = ((octet << 6) | (octet2 - 128)); | ||||
if ((retval >= 0x80) && (retval <= 0x7FF)) | ||||
return retval; | ||||
} /* else if */ | ||||
else if (octet < 240) /* three octets */ | ||||
{ | ||||
(*_str)++; /* advance at least one byte in case of an error */ | ||||
octet -= (128+64+32); | ||||
octet2 = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
octet3 = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
*_str += 2; /* skip to next possible start of codepoint. */ | ||||
retval = ( ((octet << 12)) | ((octet2-128) << 6) | ((octet3-128)) ); | ||||
/* There are seven "UTF-16 surrogates" that are illegal in UTF-8. */ | ||||
switch (retval) | ||||
{ | ||||
case 0xD800: | ||||
case 0xDB7F: | ||||
case 0xDB80: | ||||
case 0xDBFF: | ||||
case 0xDC00: | ||||
case 0xDF80: | ||||
case 0xDFFF: | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
} /* switch */ | ||||
/* 0xFFFE and 0xFFFF are illegal, too, so we check them at the edge. */ | ||||
if ((retval >= 0x800) && (retval <= 0xFFFD)) | ||||
return retval; | ||||
} /* else if */ | ||||
else if (octet < 248) /* four octets */ | ||||
{ | ||||
(*_str)++; /* advance at least one byte in case of an error */ | ||||
octet -= (128+64+32+16); | ||||
octet2 = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
octet3 = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
octet4 = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet4 & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
*_str += 3; /* skip to next possible start of codepoint. */ | ||||
retval = ( ((octet << 18)) | ((octet2 - 128) << 12) | | ||||
((octet3 - 128) << 6) | ((octet4 - 128)) ); | ||||
if ((retval >= 0x10000) && (retval <= 0x10FFFF)) | ||||
return retval; | ||||
} /* else if */ | ||||
/* | ||||
* Five and six octet sequences became illegal in rfc3629. | ||||
* We throw the codepoint away, but parse them to make sure we move | ||||
* ahead the right number of bytes and don't overflow the buffer. | ||||
*/ | ||||
else if (octet < 252) /* five octets */ | ||||
{ | ||||
(*_str)++; /* advance at least one byte in case of an error */ | ||||
octet = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
octet = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
octet = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
octet = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
*_str += 4; /* skip to next possible start of codepoint. */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
} /* else if */ | ||||
else /* six octets */ | ||||
{ | ||||
(*_str)++; /* advance at least one byte in case of an error */ | ||||
octet = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
octet = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
octet = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
octet = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
octet = (uint32_t) ((uint8_t) *(++str)); | ||||
if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
*_str += 6; /* skip to next possible start of codepoint. */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
} /* else if */ | ||||
return UNICODE_BOGUS_CHAR_VALUE; | ||||
} | ||||
void FAudio_UTF8_To_UTF16(const char *src, uint16_t *dst, size_t len) | ||||
{ | ||||
len -= sizeof (uint16_t); /* save room for null char. */ | ||||
while (len >= sizeof (uint16_t)) | ||||
{ | ||||
uint32_t cp = FAudio_UTF8_CodePoint(&src); | ||||
if (cp == 0) | ||||
break; | ||||
else if (cp == UNICODE_BOGUS_CHAR_VALUE) | ||||
cp = UNICODE_BOGUS_CHAR_CODEPOINT; | ||||
if (cp > 0xFFFF) /* encode as surrogate pair */ | ||||
{ | ||||
if (len < (sizeof (uint16_t) * 2)) | ||||
break; /* not enough room for the pair, stop now. */ | ||||
cp -= 0x10000; /* Make this a 20-bit value */ | ||||
*(dst++) = 0xD800 + ((cp >> 10) & 0x3FF); | ||||
len -= sizeof (uint16_t); | ||||
cp = 0xDC00 + (cp & 0x3FF); | ||||
} /* if */ | ||||
*(dst++) = cp; | ||||
len -= sizeof (uint16_t); | ||||
} /* while */ | ||||
*dst = 0; | ||||
} | ||||
/* vim: set noexpandtab shiftwidth=8 tabstop=8: */ | ||||