1 /* FAudio - XAudio Reimplementation for FNA
2 *
3 * Copyright (c) 2011-2021 Ethan Lee, Luigi Auriemma, and the MonoGame Team
4 *
5 * This software is provided 'as-is', without any express or implied warranty.
6 * In no event will the authors be held liable for any damages arising from
7 * the use of this software.
8 *
9 * Permission is granted to anyone to use this software for any purpose,
10 * including commercial applications, and to alter it and redistribute it
11 * freely, subject to the following restrictions:
12 *
13 * 1. The origin of this software must not be misrepresented; you must not
14 * claim that you wrote the original software. If you use this software in a
15 * product, an acknowledgment in the product documentation would be
16 * appreciated but is not required.
17 *
18 * 2. Altered source versions must be plainly marked as such, and must not be
19 * misrepresented as being the original software.
20 *
21 * 3. This notice may not be removed or altered from any source distribution.
22 *
23 * Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com>
24 *
25 */
26
27 #include "FAudio_internal.h"
28
29 #include <SDL.h>
30
31 #if !SDL_VERSION_ATLEAST(2, 0, 9)
32 #error "SDL version older than 2.0.9"
33 #endif /* !SDL_VERSION_ATLEAST */
34
35 /* Mixer Thread */
36
FAudio_INTERNAL_MixCallback(void * userdata,Uint8 * stream,int len)37 static void FAudio_INTERNAL_MixCallback(void *userdata, Uint8 *stream, int len)
38 {
39 FAudio *audio = (FAudio*) userdata;
40
41 FAudio_zero(stream, len);
42 if (audio->active)
43 {
44 FAudio_INTERNAL_UpdateEngine(
45 audio,
46 (float*) stream
47 );
48 }
49 }
50
51 /* Platform Functions */
52
FAudio_PlatformAddRef()53 void FAudio_PlatformAddRef()
54 {
55 /* SDL tracks ref counts for each subsystem */
56 if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
57 {
58 SDL_Log("SDL_INIT_AUDIO failed: %s", SDL_GetError());
59 }
60 FAudio_INTERNAL_InitSIMDFunctions(
61 SDL_HasSSE2(),
62 SDL_HasNEON()
63 );
64 }
65
FAudio_PlatformRelease()66 void FAudio_PlatformRelease()
67 {
68 /* SDL tracks ref counts for each subsystem */
69 SDL_QuitSubSystem(SDL_INIT_AUDIO);
70 }
71
FAudio_PlatformInit(FAudio * audio,uint32_t flags,uint32_t deviceIndex,FAudioWaveFormatExtensible * mixFormat,uint32_t * updateSize,void ** platformDevice)72 void FAudio_PlatformInit(
73 FAudio *audio,
74 uint32_t flags,
75 uint32_t deviceIndex,
76 FAudioWaveFormatExtensible *mixFormat,
77 uint32_t *updateSize,
78 void** platformDevice
79 ) {
80 SDL_AudioDeviceID device;
81 SDL_AudioSpec want, have;
82 const char *driver;
83 int changes = 0;
84
85 FAudio_assert(mixFormat != NULL);
86 FAudio_assert(updateSize != NULL);
87
88 /* Build the device spec */
89 want.freq = mixFormat->Format.nSamplesPerSec;
90 want.format = AUDIO_F32;
91 want.channels = mixFormat->Format.nChannels;
92 want.silence = 0;
93 want.callback = FAudio_INTERNAL_MixCallback;
94 want.userdata = audio;
95 if (flags & FAUDIO_1024_QUANTUM)
96 {
97 /* Get the sample count for a 21.33ms frame.
98 * For 48KHz this should be 1024.
99 */
100 want.samples = (int) (
101 want.freq / (1000.0 / (64.0 / 3.0))
102 );
103 }
104 else
105 {
106 want.samples = want.freq / 100;
107 }
108
109 /* FIXME: SDL bug!
110 * The PulseAudio backend does this annoying thing where it halves the
111 * buffer size to prevent latency issues:
112 *
113 * https://hg.libsdl.org/SDL/file/df343364c6c5/src/audio/pulseaudio/SDL_pulseaudio.c#l577
114 *
115 * To get the _actual_ quantum size we want, we just double the buffer
116 * size and allow SDL to set the quantum size back to normal.
117 * -flibit
118 */
119 driver = SDL_GetCurrentAudioDriver();
120 if (SDL_strcmp(driver, "pulseaudio") == 0)
121 {
122 want.samples *= 2;
123 changes = SDL_AUDIO_ALLOW_SAMPLES_CHANGE;
124 }
125
126 /* FIXME: SDL bug!
127 * The most common backends support varying samples values, but many
128 * require a power-of-two value, which XAudio2 is not a fan of.
129 * Normally SDL creates an intermediary stream to handle this, but this
130 * has not been written yet:
131 * https://bugzilla.libsdl.org/show_bug.cgi?id=5136
132 * -flibit
133 */
134 else if ( SDL_strcmp(driver, "emscripten") == 0 ||
135 SDL_strcmp(driver, "dsp") == 0 )
136 {
137 want.samples -= 1;
138 want.samples |= want.samples >> 1;
139 want.samples |= want.samples >> 2;
140 want.samples |= want.samples >> 4;
141 want.samples |= want.samples >> 8;
142 want.samples |= want.samples >> 16;
143 want.samples += 1;
144 SDL_Log(
145 "Forcing FAudio quantum to a power-of-two.\n"
146 "You don't actually want this, it's technically a bug:\n"
147 "https://bugzilla.libsdl.org/show_bug.cgi?id=5136"
148 );
149 }
150
151 /* SDL audio with arts/dsp backends expect ^2, so round up.
152 * For 48KHz this should be 512.
153 * https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
154 */
155 want.samples--;
156 want.samples |= want.samples >> 1;
157 want.samples |= want.samples >> 2;
158 want.samples |= want.samples >> 4;
159 want.samples |= want.samples >> 8;
160 want.samples |= want.samples >> 16;
161 want.samples++;
162
163 /* Open the device (or at least try to) */
164 iosretry:
165 device = SDL_OpenAudioDevice(
166 deviceIndex > 0 ? SDL_GetAudioDeviceName(deviceIndex - 1, 0) : NULL,
167 0,
168 &want,
169 &have,
170 changes
171 );
172 if (device == 0)
173 {
174 const char *err = SDL_GetError();
175 SDL_Log("OpenAudioDevice failed: %s", err);
176
177 /* iOS has a weird thing where you can't open a stream when the
178 * app is in the background, even though the program is meant
179 * to be suspended and thus not trip this in the first place.
180 *
181 * Startup suspend behavior when an app is opened then closed
182 * is a big pile of crap, basically.
183 *
184 * Google the error code and you'll find that this has been a
185 * long-standing issue that nobody seems to care about.
186 * -flibit
187 */
188 if (SDL_strstr(err, "Code=561015905") != NULL)
189 {
190 goto iosretry;
191 }
192
193 FAudio_assert(0 && "Failed to open audio device!");
194 return;
195 }
196
197 /* Write up the received format for the engine */
198 WriteWaveFormatExtensible(
199 mixFormat,
200 have.channels,
201 have.freq,
202 &DATAFORMAT_SUBTYPE_IEEE_FLOAT
203 );
204 *updateSize = have.samples;
205
206 /* SDL_AudioDeviceID is a Uint32, anybody using a 16-bit PC still? */
207 *platformDevice = (void*) ((size_t) device);
208
209 /* Start the thread! */
210 SDL_PauseAudioDevice(device, 0);
211 }
212
FAudio_PlatformQuit(void * platformDevice)213 void FAudio_PlatformQuit(void* platformDevice)
214 {
215 SDL_CloseAudioDevice((SDL_AudioDeviceID) ((size_t) platformDevice));
216 }
217
FAudio_PlatformGetDeviceCount()218 uint32_t FAudio_PlatformGetDeviceCount()
219 {
220 uint32_t devCount = SDL_GetNumAudioDevices(0);
221 if (devCount == 0)
222 {
223 return 0;
224 }
225 return devCount + 1; /* Add one for "Default Device" */
226 }
227
228 void FAudio_UTF8_To_UTF16(const char *src, uint16_t *dst, size_t len);
229
FAudio_PlatformGetDeviceDetails(uint32_t index,FAudioDeviceDetails * details)230 uint32_t FAudio_PlatformGetDeviceDetails(
231 uint32_t index,
232 FAudioDeviceDetails *details
233 ) {
234 const char *name, *envvar;
235 int channels, rate;
236
237 FAudio_zero(details, sizeof(FAudioDeviceDetails));
238 if (index >= FAudio_PlatformGetDeviceCount())
239 {
240 return FAUDIO_E_INVALID_CALL;
241 }
242
243 details->DeviceID[0] = L'0' + index;
244 if (index == 0)
245 {
246 name = "Default Device";
247 details->Role = FAudioGlobalDefaultDevice;
248
249 /* This variable will look like a DSound GUID or WASAPI ID, i.e.
250 * "{0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}"
251 */
252 envvar = SDL_getenv("FAUDIO_FORCE_DEFAULT_DEVICEID");
253 if (envvar != NULL)
254 {
255 FAudio_UTF8_To_UTF16(
256 envvar,
257 (uint16_t*) details->DeviceID,
258 sizeof(details->DeviceID)
259 );
260 }
261 }
262 else
263 {
264 name = SDL_GetAudioDeviceName(index - 1, 0);
265 details->Role = FAudioNotDefaultDevice;
266 }
267 FAudio_UTF8_To_UTF16(
268 name,
269 (uint16_t*) details->DisplayName,
270 sizeof(details->DisplayName)
271 );
272
273 /* TODO: SDL_GetAudioDeviceSpec! */
274 envvar = SDL_getenv("SDL_AUDIO_FREQUENCY");
275 if (!envvar || ((rate = SDL_atoi(envvar)) == 0))
276 {
277 rate = 48000;
278 }
279 envvar = SDL_getenv("SDL_AUDIO_CHANNELS");
280 if (!envvar || ((channels = SDL_atoi(envvar)) == 0))
281 {
282 channels = 2;
283 }
284 WriteWaveFormatExtensible(
285 &details->OutputFormat,
286 channels,
287 rate,
288 &DATAFORMAT_SUBTYPE_PCM
289 );
290 return 0;
291 }
292
293 /* Threading */
294
FAudio_PlatformCreateThread(FAudioThreadFunc func,const char * name,void * data)295 FAudioThread FAudio_PlatformCreateThread(
296 FAudioThreadFunc func,
297 const char *name,
298 void* data
299 ) {
300 return (FAudioThread) SDL_CreateThread(
301 (SDL_ThreadFunction) func,
302 name,
303 data
304 );
305 }
306
FAudio_PlatformWaitThread(FAudioThread thread,int32_t * retval)307 void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval)
308 {
309 SDL_WaitThread((SDL_Thread*) thread, retval);
310 }
311
FAudio_PlatformThreadPriority(FAudioThreadPriority priority)312 void FAudio_PlatformThreadPriority(FAudioThreadPriority priority)
313 {
314 SDL_SetThreadPriority((SDL_ThreadPriority) priority);
315 }
316
FAudio_PlatformGetThreadID(void)317 uint64_t FAudio_PlatformGetThreadID(void)
318 {
319 return (uint64_t) SDL_ThreadID();
320 }
321
FAudio_PlatformCreateMutex()322 FAudioMutex FAudio_PlatformCreateMutex()
323 {
324 return (FAudioMutex) SDL_CreateMutex();
325 }
326
FAudio_PlatformDestroyMutex(FAudioMutex mutex)327 void FAudio_PlatformDestroyMutex(FAudioMutex mutex)
328 {
329 SDL_DestroyMutex((SDL_mutex*) mutex);
330 }
331
FAudio_PlatformLockMutex(FAudioMutex mutex)332 void FAudio_PlatformLockMutex(FAudioMutex mutex)
333 {
334 SDL_LockMutex((SDL_mutex*) mutex);
335 }
336
FAudio_PlatformUnlockMutex(FAudioMutex mutex)337 void FAudio_PlatformUnlockMutex(FAudioMutex mutex)
338 {
339 SDL_UnlockMutex((SDL_mutex*) mutex);
340 }
341
FAudio_sleep(uint32_t ms)342 void FAudio_sleep(uint32_t ms)
343 {
344 SDL_Delay(ms);
345 }
346
347 /* Time */
348
FAudio_timems()349 uint32_t FAudio_timems()
350 {
351 return SDL_GetTicks();
352 }
353
354 /* FAudio I/O */
355
FAudio_fopen(const char * path)356 FAudioIOStream* FAudio_fopen(const char *path)
357 {
358 FAudioIOStream *io = (FAudioIOStream*) FAudio_malloc(
359 sizeof(FAudioIOStream)
360 );
361 SDL_RWops *rwops = SDL_RWFromFile(path, "rb");
362 io->data = rwops;
363 io->read = (FAudio_readfunc) rwops->read;
364 io->seek = (FAudio_seekfunc) rwops->seek;
365 io->close = (FAudio_closefunc) rwops->close;
366 io->lock = FAudio_PlatformCreateMutex();
367 return io;
368 }
369
FAudio_memopen(void * mem,int len)370 FAudioIOStream* FAudio_memopen(void *mem, int len)
371 {
372 FAudioIOStream *io = (FAudioIOStream*) FAudio_malloc(
373 sizeof(FAudioIOStream)
374 );
375 SDL_RWops *rwops = SDL_RWFromMem(mem, len);
376 io->data = rwops;
377 io->read = (FAudio_readfunc) rwops->read;
378 io->seek = (FAudio_seekfunc) rwops->seek;
379 io->close = (FAudio_closefunc) rwops->close;
380 io->lock = FAudio_PlatformCreateMutex();
381 return io;
382 }
383
FAudio_memptr(FAudioIOStream * io,size_t offset)384 uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset)
385 {
386 SDL_RWops *rwops = (SDL_RWops*) io->data;
387 FAudio_assert(rwops->type == SDL_RWOPS_MEMORY);
388 return rwops->hidden.mem.base + offset;
389 }
390
FAudio_close(FAudioIOStream * io)391 void FAudio_close(FAudioIOStream *io)
392 {
393 io->close(io->data);
394 FAudio_PlatformDestroyMutex((FAudioMutex) io->lock);
395 FAudio_free(io);
396 }
397
398 #ifdef FAUDIO_DUMP_VOICES
FAudio_fopen_out(const char * path,const char * mode)399 FAudioIOStreamOut* FAudio_fopen_out(const char *path, const char *mode)
400 {
401 FAudioIOStreamOut *io = (FAudioIOStreamOut*) FAudio_malloc(
402 sizeof(FAudioIOStreamOut)
403 );
404 SDL_RWops *rwops = SDL_RWFromFile(path, mode);
405 io->data = rwops;
406 io->read = (FAudio_readfunc) rwops->read;
407 io->write = (FAudio_writefunc) rwops->write;
408 io->seek = (FAudio_seekfunc) rwops->seek;
409 io->size = (FAudio_sizefunc) rwops->size;
410 io->close = (FAudio_closefunc) rwops->close;
411 io->lock = FAudio_PlatformCreateMutex();
412 return io;
413 }
414
FAudio_close_out(FAudioIOStreamOut * io)415 void FAudio_close_out(FAudioIOStreamOut *io)
416 {
417 io->close(io->data);
418 FAudio_PlatformDestroyMutex((FAudioMutex) io->lock);
419 FAudio_free(io);
420 }
421 #endif /* FAUDIO_DUMP_VOICES */
422
423 /* UTF8->UTF16 Conversion, taken from PhysicsFS */
424
425 #define UNICODE_BOGUS_CHAR_VALUE 0xFFFFFFFF
426 #define UNICODE_BOGUS_CHAR_CODEPOINT '?'
427
FAudio_UTF8_CodePoint(const char ** _str)428 static uint32_t FAudio_UTF8_CodePoint(const char **_str)
429 {
430 const char *str = *_str;
431 uint32_t retval = 0;
432 uint32_t octet = (uint32_t) ((uint8_t) *str);
433 uint32_t octet2, octet3, octet4;
434
435 if (octet == 0) /* null terminator, end of string. */
436 return 0;
437
438 else if (octet < 128) /* one octet char: 0 to 127 */
439 {
440 (*_str)++; /* skip to next possible start of codepoint. */
441 return octet;
442 } /* else if */
443
444 else if ((octet > 127) && (octet < 192)) /* bad (starts with 10xxxxxx). */
445 {
446 /*
447 * Apparently each of these is supposed to be flagged as a bogus
448 * char, instead of just resyncing to the next valid codepoint.
449 */
450 (*_str)++; /* skip to next possible start of codepoint. */
451 return UNICODE_BOGUS_CHAR_VALUE;
452 } /* else if */
453
454 else if (octet < 224) /* two octets */
455 {
456 (*_str)++; /* advance at least one byte in case of an error */
457 octet -= (128+64);
458 octet2 = (uint32_t) ((uint8_t) *(++str));
459 if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
460 return UNICODE_BOGUS_CHAR_VALUE;
461
462 *_str += 1; /* skip to next possible start of codepoint. */
463 retval = ((octet << 6) | (octet2 - 128));
464 if ((retval >= 0x80) && (retval <= 0x7FF))
465 return retval;
466 } /* else if */
467
468 else if (octet < 240) /* three octets */
469 {
470 (*_str)++; /* advance at least one byte in case of an error */
471 octet -= (128+64+32);
472 octet2 = (uint32_t) ((uint8_t) *(++str));
473 if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
474 return UNICODE_BOGUS_CHAR_VALUE;
475
476 octet3 = (uint32_t) ((uint8_t) *(++str));
477 if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
478 return UNICODE_BOGUS_CHAR_VALUE;
479
480 *_str += 2; /* skip to next possible start of codepoint. */
481 retval = ( ((octet << 12)) | ((octet2-128) << 6) | ((octet3-128)) );
482
483 /* There are seven "UTF-16 surrogates" that are illegal in UTF-8. */
484 switch (retval)
485 {
486 case 0xD800:
487 case 0xDB7F:
488 case 0xDB80:
489 case 0xDBFF:
490 case 0xDC00:
491 case 0xDF80:
492 case 0xDFFF:
493 return UNICODE_BOGUS_CHAR_VALUE;
494 } /* switch */
495
496 /* 0xFFFE and 0xFFFF are illegal, too, so we check them at the edge. */
497 if ((retval >= 0x800) && (retval <= 0xFFFD))
498 return retval;
499 } /* else if */
500
501 else if (octet < 248) /* four octets */
502 {
503 (*_str)++; /* advance at least one byte in case of an error */
504 octet -= (128+64+32+16);
505 octet2 = (uint32_t) ((uint8_t) *(++str));
506 if ((octet2 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
507 return UNICODE_BOGUS_CHAR_VALUE;
508
509 octet3 = (uint32_t) ((uint8_t) *(++str));
510 if ((octet3 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
511 return UNICODE_BOGUS_CHAR_VALUE;
512
513 octet4 = (uint32_t) ((uint8_t) *(++str));
514 if ((octet4 & (128+64)) != 128) /* Format isn't 10xxxxxx? */
515 return UNICODE_BOGUS_CHAR_VALUE;
516
517 *_str += 3; /* skip to next possible start of codepoint. */
518 retval = ( ((octet << 18)) | ((octet2 - 128) << 12) |
519 ((octet3 - 128) << 6) | ((octet4 - 128)) );
520 if ((retval >= 0x10000) && (retval <= 0x10FFFF))
521 return retval;
522 } /* else if */
523
524 /*
525 * Five and six octet sequences became illegal in rfc3629.
526 * We throw the codepoint away, but parse them to make sure we move
527 * ahead the right number of bytes and don't overflow the buffer.
528 */
529
530 else if (octet < 252) /* five octets */
531 {
532 (*_str)++; /* advance at least one byte in case of an error */
533 octet = (uint32_t) ((uint8_t) *(++str));
534 if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
535 return UNICODE_BOGUS_CHAR_VALUE;
536
537 octet = (uint32_t) ((uint8_t) *(++str));
538 if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
539 return UNICODE_BOGUS_CHAR_VALUE;
540
541 octet = (uint32_t) ((uint8_t) *(++str));
542 if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
543 return UNICODE_BOGUS_CHAR_VALUE;
544
545 octet = (uint32_t) ((uint8_t) *(++str));
546 if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
547 return UNICODE_BOGUS_CHAR_VALUE;
548
549 *_str += 4; /* skip to next possible start of codepoint. */
550 return UNICODE_BOGUS_CHAR_VALUE;
551 } /* else if */
552
553 else /* six octets */
554 {
555 (*_str)++; /* advance at least one byte in case of an error */
556 octet = (uint32_t) ((uint8_t) *(++str));
557 if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
558 return UNICODE_BOGUS_CHAR_VALUE;
559
560 octet = (uint32_t) ((uint8_t) *(++str));
561 if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
562 return UNICODE_BOGUS_CHAR_VALUE;
563
564 octet = (uint32_t) ((uint8_t) *(++str));
565 if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
566 return UNICODE_BOGUS_CHAR_VALUE;
567
568 octet = (uint32_t) ((uint8_t) *(++str));
569 if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
570 return UNICODE_BOGUS_CHAR_VALUE;
571
572 octet = (uint32_t) ((uint8_t) *(++str));
573 if ((octet & (128+64)) != 128) /* Format isn't 10xxxxxx? */
574 return UNICODE_BOGUS_CHAR_VALUE;
575
576 *_str += 6; /* skip to next possible start of codepoint. */
577 return UNICODE_BOGUS_CHAR_VALUE;
578 } /* else if */
579
580 return UNICODE_BOGUS_CHAR_VALUE;
581 }
582
FAudio_UTF8_To_UTF16(const char * src,uint16_t * dst,size_t len)583 void FAudio_UTF8_To_UTF16(const char *src, uint16_t *dst, size_t len)
584 {
585 len -= sizeof (uint16_t); /* save room for null char. */
586 while (len >= sizeof (uint16_t))
587 {
588 uint32_t cp = FAudio_UTF8_CodePoint(&src);
589 if (cp == 0)
590 break;
591 else if (cp == UNICODE_BOGUS_CHAR_VALUE)
592 cp = UNICODE_BOGUS_CHAR_CODEPOINT;
593
594 if (cp > 0xFFFF) /* encode as surrogate pair */
595 {
596 if (len < (sizeof (uint16_t) * 2))
597 break; /* not enough room for the pair, stop now. */
598
599 cp -= 0x10000; /* Make this a 20-bit value */
600
601 *(dst++) = 0xD800 + ((cp >> 10) & 0x3FF);
602 len -= sizeof (uint16_t);
603
604 cp = 0xDC00 + (cp & 0x3FF);
605 } /* if */
606
607 *(dst++) = cp;
608 len -= sizeof (uint16_t);
609 } /* while */
610
611 *dst = 0;
612 }
613
614 /* vim: set noexpandtab shiftwidth=8 tabstop=8: */
615