v / thirdparty / sokol
Raw file | 2332 loc (1995 sloc) | 85.8 KB | Latest commit hash e854051c1
1#if defined(SOKOL_IMPL) && !defined(SOKOL_AUDIO_IMPL)
2#define SOKOL_AUDIO_IMPL
3#endif
4#ifndef SOKOL_AUDIO_INCLUDED
5/*
6 sokol_audio.h -- cross-platform audio-streaming API
7
8 Project URL: https://github.com/floooh/sokol
9
10 Do this:
11 #define SOKOL_IMPL or
12 #define SOKOL_AUDIO_IMPL
13 before you include this file in *one* C or C++ file to create the
14 implementation.
15
16 Optionally provide the following defines with your own implementations:
17
18 SOKOL_DUMMY_BACKEND - use a dummy backend
19 SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
20 SOKOL_AUDIO_API_DECL- public function declaration prefix (default: extern)
21 SOKOL_API_DECL - same as SOKOL_AUDIO_API_DECL
22 SOKOL_API_IMPL - public function implementation prefix (default: -)
23
24 SAUDIO_RING_MAX_SLOTS - max number of slots in the push-audio ring buffer (default 1024)
25 SAUDIO_OSX_USE_SYSTEM_HEADERS - define this to force inclusion of system headers on
26 macOS instead of using embedded CoreAudio declarations
27
28 If sokol_audio.h is compiled as a DLL, define the following before
29 including the declaration or implementation:
30
31 SOKOL_DLL
32
33 On Windows, SOKOL_DLL will define SOKOL_AUDIO_API_DECL as __declspec(dllexport)
34 or __declspec(dllimport) as needed.
35
36 Link with the following libraries:
37
38 - on macOS: AudioToolbox
39 - on iOS: AudioToolbox, AVFoundation
40 - on Linux: asound
41 - on Android: link with OpenSLES
42 - on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib
43 - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' and link with -lole32
44
45 FEATURE OVERVIEW
46 ================
47 You provide a mono- or stereo-stream of 32-bit float samples, which
48 Sokol Audio feeds into platform-specific audio backends:
49
50 - Windows: WASAPI
51 - Linux: ALSA
52 - macOS: CoreAudio
53 - iOS: CoreAudio+AVAudioSession
54 - emscripten: WebAudio with ScriptProcessorNode
55 - Android: OpenSLES
56
57 Sokol Audio will not do any buffer mixing or volume control, if you have
58 multiple independent input streams of sample data you need to perform the
59 mixing yourself before forwarding the data to Sokol Audio.
60
61 There are two mutually exclusive ways to provide the sample data:
62
63 1. Callback model: You provide a callback function, which will be called
64 when Sokol Audio needs new samples. On all platforms except emscripten,
65 this function is called from a separate thread.
66 2. Push model: Your code pushes small blocks of sample data from your
67 main loop or a thread you created. The pushed data is stored in
68 a ring buffer where it is pulled by the backend code when
69 needed.
70
71 The callback model is preferred because it is the most direct way to
72 feed sample data into the audio backends and also has less moving parts
73 (there is no ring buffer between your code and the audio backend).
74
75 Sometimes it is not possible to generate the audio stream directly in a
76 callback function running in a separate thread, for such cases Sokol Audio
77 provides the push-model as a convenience.
78
79 SOKOL AUDIO, SOLOUD AND MINIAUDIO
80 =================================
81 The WASAPI, ALSA, OpenSLES and CoreAudio backend code has been taken from the
82 SoLoud library (with some modifications, so any bugs in there are most
83 likely my fault). If you need a more fully-featured audio solution, check
84 out SoLoud, it's excellent:
85
86 https://github.com/jarikomppa/soloud
87
88 Another alternative which feature-wise is somewhere inbetween SoLoud and
89 sokol-audio might be MiniAudio:
90
91 https://github.com/mackron/miniaudio
92
93 GLOSSARY
94 ========
95 - stream buffer:
96 The internal audio data buffer, usually provided by the backend API. The
97 size of the stream buffer defines the base latency, smaller buffers have
98 lower latency but may cause audio glitches. Bigger buffers reduce or
99 eliminate glitches, but have a higher base latency.
100
101 - stream callback:
102 Optional callback function which is called by Sokol Audio when it
103 needs new samples. On Windows, macOS/iOS and Linux, this is called in
104 a separate thread, on WebAudio, this is called per-frame in the
105 browser thread.
106
107 - channel:
108 A discrete track of audio data, currently 1-channel (mono) and
109 2-channel (stereo) is supported and tested.
110
111 - sample:
112 The magnitude of an audio signal on one channel at a given time. In
113 Sokol Audio, samples are 32-bit float numbers in the range -1.0 to
114 +1.0.
115
116 - frame:
117 The tightly packed set of samples for all channels at a given time.
118 For mono 1 frame is 1 sample. For stereo, 1 frame is 2 samples.
119
120 - packet:
121 In Sokol Audio, a small chunk of audio data that is moved from the
122 main thread to the audio streaming thread in order to decouple the
123 rate at which the main thread provides new audio data, and the
124 streaming thread consuming audio data.
125
126 WORKING WITH SOKOL AUDIO
127 ========================
128 First call saudio_setup() with your preferred audio playback options.
129 In most cases you can stick with the default values, these provide
130 a good balance between low-latency and glitch-free playback
131 on all audio backends.
132
133 If you want to use the callback-model, you need to provide a stream
134 callback function either in saudio_desc.stream_cb or saudio_desc.stream_userdata_cb,
135 otherwise keep both function pointers zero-initialized.
136
137 Use push model and default playback parameters:
138
139 saudio_setup(&(saudio_desc){0});
140
141 Use stream callback model and default playback parameters:
142
143 saudio_setup(&(saudio_desc){
144 .stream_cb = my_stream_callback
145 });
146
147 The standard stream callback doesn't have a user data argument, if you want
148 that, use the alternative stream_userdata_cb and also set the user_data pointer:
149
150 saudio_setup(&(saudio_desc){
151 .stream_userdata_cb = my_stream_callback,
152 .user_data = &my_data
153 });
154
155 The following playback parameters can be provided through the
156 saudio_desc struct:
157
158 General parameters (both for stream-callback and push-model):
159
160 int sample_rate -- the sample rate in Hz, default: 44100
161 int num_channels -- number of channels, default: 1 (mono)
162 int buffer_frames -- number of frames in streaming buffer, default: 2048
163
164 The stream callback prototype (either with or without userdata):
165
166 void (*stream_cb)(float* buffer, int num_frames, int num_channels)
167 void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data)
168 Function pointer to the user-provide stream callback.
169
170 Push-model parameters:
171
172 int packet_frames -- number of frames in a packet, default: 128
173 int num_packets -- number of packets in ring buffer, default: 64
174
175 The sample_rate and num_channels parameters are only hints for the audio
176 backend, it isn't guaranteed that those are the values used for actual
177 playback.
178
179 To get the actual parameters, call the following functions after
180 saudio_setup():
181
182 int saudio_sample_rate(void)
183 int saudio_channels(void);
184
185 It's unlikely that the number of channels will be different than requested,
186 but a different sample rate isn't uncommon.
187
188 (NOTE: there's an yet unsolved issue when an audio backend might switch
189 to a different sample rate when switching output devices, for instance
190 plugging in a bluetooth headset, this case is currently not handled in
191 Sokol Audio).
192
193 You can check if audio initialization was successful with
194 saudio_isvalid(). If backend initialization failed for some reason
195 (for instance when there's no audio device in the machine), this
196 will return false. Not checking for success won't do any harm, all
197 Sokol Audio function will silently fail when called after initialization
198 has failed, so apart from missing audio output, nothing bad will happen.
199
200 Before your application exits, you should call
201
202 saudio_shutdown();
203
204 This stops the audio thread (on Linux, Windows and macOS/iOS) and
205 properly shuts down the audio backend.
206
207 THE STREAM CALLBACK MODEL
208 =========================
209 To use Sokol Audio in stream-callback-mode, provide a callback function
210 like this in the saudio_desc struct when calling saudio_setup():
211
212 void stream_cb(float* buffer, int num_frames, int num_channels) {
213 ...
214 }
215
216 Or the alternative version with a user-data argument:
217
218 void stream_userdata_cb(float* buffer, int num_frames, int num_channels, void* user_data) {
219 my_data_t* my_data = (my_data_t*) user_data;
220 ...
221 }
222
223 The job of the callback function is to fill the *buffer* with 32-bit
224 float sample values.
225
226 To output silence, fill the buffer with zeros:
227
228 void stream_cb(float* buffer, int num_frames, int num_channels) {
229 const int num_samples = num_frames * num_channels;
230 for (int i = 0; i < num_samples; i++) {
231 buffer[i] = 0.0f;
232 }
233 }
234
235 For stereo output (num_channels == 2), the samples for the left
236 and right channel are interleaved:
237
238 void stream_cb(float* buffer, int num_frames, int num_channels) {
239 assert(2 == num_channels);
240 for (int i = 0; i < num_frames; i++) {
241 buffer[2*i + 0] = ...; // left channel
242 buffer[2*i + 1] = ...; // right channel
243 }
244 }
245
246 Please keep in mind that the stream callback function is running in a
247 separate thread, if you need to share data with the main thread you need
248 to take care yourself to make the access to the shared data thread-safe!
249
250 THE PUSH MODEL
251 ==============
252 To use the push-model for providing audio data, simply don't set (keep
253 zero-initialized) the stream_cb field in the saudio_desc struct when
254 calling saudio_setup().
255
256 To provide sample data with the push model, call the saudio_push()
257 function at regular intervals (for instance once per frame). You can
258 call the saudio_expect() function to ask Sokol Audio how much room is
259 in the ring buffer, but if you provide a continuous stream of data
260 at the right sample rate, saudio_expect() isn't required (it's a simple
261 way to sync/throttle your sample generation code with the playback
262 rate though).
263
264 With saudio_push() you may need to maintain your own intermediate sample
265 buffer, since pushing individual sample values isn't very efficient.
266 The following example is from the MOD player sample in
267 sokol-samples (https://github.com/floooh/sokol-samples):
268
269 const int num_frames = saudio_expect();
270 if (num_frames > 0) {
271 const int num_samples = num_frames * saudio_channels();
272 read_samples(flt_buf, num_samples);
273 saudio_push(flt_buf, num_frames);
274 }
275
276 Another option is to ignore saudio_expect(), and just push samples as they
277 are generated in small batches. In this case you *need* to generate the
278 samples at the right sample rate:
279
280 The following example is taken from the Tiny Emulators project
281 (https://github.com/floooh/chips-test), this is for mono playback,
282 so (num_samples == num_frames):
283
284 // tick the sound generator
285 if (ay38910_tick(&sys->psg)) {
286 // new sample is ready
287 sys->sample_buffer[sys->sample_pos++] = sys->psg.sample;
288 if (sys->sample_pos == sys->num_samples) {
289 // new sample packet is ready
290 saudio_push(sys->sample_buffer, sys->num_samples);
291 sys->sample_pos = 0;
292 }
293 }
294
295 THE WEBAUDIO BACKEND
296 ====================
297 The WebAudio backend is currently using a ScriptProcessorNode callback to
298 feed the sample data into WebAudio. ScriptProcessorNode has been
299 deprecated for a while because it is running from the main thread, with
300 the default initialization parameters it works 'pretty well' though.
301 Ultimately Sokol Audio will use Audio Worklets, but this requires a few
302 more things to fall into place (Audio Worklets implemented everywhere,
303 SharedArrayBuffers enabled again, and I need to figure out a 'low-cost'
304 solution in terms of implementation effort, since Audio Worklets are
305 a lot more complex than ScriptProcessorNode if the audio data needs to come
306 from the main thread).
307
308 The WebAudio backend is automatically selected when compiling for
309 emscripten (__EMSCRIPTEN__ define exists).
310
311 https://developers.google.com/web/updates/2017/12/audio-worklet
312 https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern
313
314 "Blob URLs": https://www.html5rocks.com/en/tutorials/workers/basics/
315
316 Also see: https://blog.paul.cx/post/a-wait-free-spsc-ringbuffer-for-the-web/
317
318 THE COREAUDIO BACKEND
319 =====================
320 The CoreAudio backend is selected on macOS and iOS (__APPLE__ is defined).
321 Since the CoreAudio API is implemented in C (not Objective-C) on macOS the
322 implementation part of Sokol Audio can be included into a C source file.
323
324 However on iOS, Sokol Audio must be compiled as Objective-C due to it's
325 reliance on the AVAudioSession object. The iOS code path support both
326 being compiled with or without ARC (Automatic Reference Counting).
327
328 For thread synchronisation, the CoreAudio backend will use the
329 pthread_mutex_* functions.
330
331 The incoming floating point samples will be directly forwarded to
332 CoreAudio without further conversion.
333
334 macOS and iOS applications that use Sokol Audio need to link with
335 the AudioToolbox framework.
336
337 THE WASAPI BACKEND
338 ==================
339 The WASAPI backend is automatically selected when compiling on Windows
340 (_WIN32 is defined).
341
342 For thread synchronisation a Win32 critical section is used.
343
344 WASAPI may use a different size for its own streaming buffer then requested,
345 so the base latency may be slightly bigger. The current backend implementation
346 converts the incoming floating point sample values to signed 16-bit
347 integers.
348
349 The required Windows system DLLs are linked with #pragma comment(lib, ...),
350 so you shouldn't need to add additional linker libs in the build process
351 (otherwise this is a bug which should be fixed in sokol_audio.h).
352
353 THE ALSA BACKEND
354 ================
355 The ALSA backend is automatically selected when compiling on Linux
356 ('linux' is defined).
357
358 For thread synchronisation, the pthread_mutex_* functions are used.
359
360 Samples are directly forwarded to ALSA in 32-bit float format, no
361 further conversion is taking place.
362
363 You need to link with the 'asound' library, and the <alsa/asoundlib.h>
364 header must be present (usually both are installed with some sort
365 of ALSA development package).
366
367
368 MEMORY ALLOCATION OVERRIDE
369 ==========================
370 You can override the memory allocation functions at initialization time
371 like this:
372
373 void* my_alloc(size_t size, void* user_data) {
374 return malloc(size);
375 }
376
377 void my_free(void* ptr, void* user_data) {
378 free(ptr);
379 }
380
381 ...
382 saudio_setup(&(saudio_desc){
383 // ...
384 .allocator = {
385 .alloc = my_alloc,
386 .free = my_free,
387 .user_data = ...,
388 }
389 });
390 ...
391
392 If no overrides are provided, malloc and free will be used.
393
394 This only affects memory allocation calls done by sokol_audio.h
395 itself though, not any allocations in OS libraries.
396
397 Memory allocation will only happen on the same thread where saudio_setup()
398 was called, so you don't need to worry about thread-safety.
399
400
401 LOG FUNCTION OVERRIDE
402 =====================
403 You can override the log function at initialization time like this:
404
405 void my_log(const char* message, void* user_data) {
406 printf("saudio says: \s\n", message);
407 }
408
409 ...
410 saudio_setup(&(saudio_desc){
411 // ...
412 .logger = {
413 .log_cb = my_log,
414 .user_data = ...,
415 }
416 });
417 ...
418
419 If no overrides are provided, puts will be used on most platforms.
420 On Android, __android_log_write will be used instead.
421
422
423 LICENSE
424 =======
425
426 zlib/libpng license
427
428 Copyright (c) 2018 Andre Weissflog
429
430 This software is provided 'as-is', without any express or implied warranty.
431 In no event will the authors be held liable for any damages arising from the
432 use of this software.
433
434 Permission is granted to anyone to use this software for any purpose,
435 including commercial applications, and to alter it and redistribute it
436 freely, subject to the following restrictions:
437
438 1. The origin of this software must not be misrepresented; you must not
439 claim that you wrote the original software. If you use this software in a
440 product, an acknowledgment in the product documentation would be
441 appreciated but is not required.
442
443 2. Altered source versions must be plainly marked as such, and must not
444 be misrepresented as being the original software.
445
446 3. This notice may not be removed or altered from any source
447 distribution.
448*/
449#define SOKOL_AUDIO_INCLUDED (1)
450#include <stddef.h> // size_t
451#include <stdint.h>
452#include <stdbool.h>
453
454#if defined(SOKOL_API_DECL) && !defined(SOKOL_AUDIO_API_DECL)
455#define SOKOL_AUDIO_API_DECL SOKOL_API_DECL
456#endif
457#ifndef SOKOL_AUDIO_API_DECL
458#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_AUDIO_IMPL)
459#define SOKOL_AUDIO_API_DECL __declspec(dllexport)
460#elif defined(_WIN32) && defined(SOKOL_DLL)
461#define SOKOL_AUDIO_API_DECL __declspec(dllimport)
462#else
463#define SOKOL_AUDIO_API_DECL extern
464#endif
465#endif
466
467#ifdef __cplusplus
468extern "C" {
469#endif
470
471/*
472 saudio_allocator
473
474 Used in saudio_desc to provide custom memory-alloc and -free functions
475 to sokol_audio.h. If memory management should be overridden, both the
476 alloc and free function must be provided (e.g. it's not valid to
477 override one function but not the other).
478*/
479typedef struct saudio_allocator {
480 void* (*alloc)(size_t size, void* user_data);
481 void (*free)(void* ptr, void* user_data);
482 void* user_data;
483} saudio_allocator;
484
485/*
486 saudio_logger
487
488 Used in saudio_desc to provide custom log callbacks to sokol_audio.h.
489 Default behavior is SOKOL_LOG(message).
490*/
491typedef struct saudio_logger {
492 void (*log_cb)(const char* message, void* user_data);
493 void* user_data;
494} saudio_logger;
495
496typedef struct saudio_desc {
497 int sample_rate; // requested sample rate
498 int num_channels; // number of channels, default: 1 (mono)
499 int buffer_frames; // number of frames in streaming buffer
500 int packet_frames; // number of frames in a packet
501 int num_packets; // number of packets in packet queue
502 void (*stream_cb)(float* buffer, int num_frames, int num_channels); // optional streaming callback (no user data)
503 void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data); //... and with user data
504 void* user_data; // optional user data argument for stream_userdata_cb
505 saudio_allocator allocator; // optional allocation override functions
506 saudio_logger logger; // optional log override functions
507} saudio_desc;
508
509/* setup sokol-audio */
510SOKOL_AUDIO_API_DECL void saudio_setup(const saudio_desc* desc);
511/* shutdown sokol-audio */
512SOKOL_AUDIO_API_DECL void saudio_shutdown(void);
513/* true after setup if audio backend was successfully initialized */
514SOKOL_AUDIO_API_DECL bool saudio_isvalid(void);
515/* return the saudio_desc.user_data pointer */
516SOKOL_AUDIO_API_DECL void* saudio_userdata(void);
517/* return a copy of the original saudio_desc struct */
518SOKOL_AUDIO_API_DECL saudio_desc saudio_query_desc(void);
519/* actual sample rate */
520SOKOL_AUDIO_API_DECL int saudio_sample_rate(void);
521/* return actual backend buffer size in number of frames */
522SOKOL_AUDIO_API_DECL int saudio_buffer_frames(void);
523/* actual number of channels */
524SOKOL_AUDIO_API_DECL int saudio_channels(void);
525/* return true if audio context is currently suspended (only in WebAudio backend, all other backends return false) */
526SOKOL_AUDIO_API_DECL bool saudio_suspended(void);
527/* get current number of frames to fill packet queue */
528SOKOL_AUDIO_API_DECL int saudio_expect(void);
529/* push sample frames from main thread, returns number of frames actually pushed */
530SOKOL_AUDIO_API_DECL int saudio_push(const float* frames, int num_frames);
531
532#ifdef __cplusplus
533} /* extern "C" */
534
535/* reference-based equivalents for c++ */
536inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc); }
537
538#endif
539#endif // SOKOL_AUDIO_INCLUDED
540
541/*=== IMPLEMENTATION =========================================================*/
542#ifdef SOKOL_AUDIO_IMPL
543#define SOKOL_AUDIO_IMPL_INCLUDED (1)
544
545#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE)
546#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use saudio_desc.allocator to override memory allocation functions"
547#endif
548
549#include <stdlib.h> // alloc, free
550#include <string.h> // memset, memcpy
551#include <stddef.h> // size_t
552
553#ifndef SOKOL_API_IMPL
554 #define SOKOL_API_IMPL
555#endif
556#ifndef SOKOL_DEBUG
557 #ifndef NDEBUG
558 #define SOKOL_DEBUG
559 #endif
560#endif
561#ifndef SOKOL_ASSERT
562 #include <assert.h>
563 #define SOKOL_ASSERT(c) assert(c)
564#endif
565
566#if !defined(SOKOL_DEBUG)
567 #define SAUDIO_LOG(s)
568#else
569 #define SAUDIO_LOG(s) _saudio_log(s)
570 #ifndef SOKOL_LOG
571 #if defined(__ANDROID__)
572 #include <android/log.h>
573 #define SOKOL_LOG(s) __android_log_write(ANDROID_LOG_INFO, "SOKOL_AUDIO", s)
574 #else
575 #include <stdio.h>
576 #define SOKOL_LOG(s) puts(s)
577 #endif
578 #endif
579#endif
580
581#ifndef _SOKOL_PRIVATE
582 #if defined(__GNUC__) || defined(__clang__)
583 #define _SOKOL_PRIVATE __attribute__((unused)) static
584 #else
585 #define _SOKOL_PRIVATE static
586 #endif
587#endif
588
589#ifndef _SOKOL_UNUSED
590 #define _SOKOL_UNUSED(x) (void)(x)
591#endif
592
593// platform detection defines
594#if defined(SOKOL_DUMMY_BACKEND)
595 // nothing
596#elif defined(__APPLE__)
597 #define _SAUDIO_APPLE (1)
598 #include <TargetConditionals.h>
599 #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
600 #define _SAUDIO_IOS (1)
601 #else
602 #define _SAUDIO_MACOS (1)
603 #endif
604#elif defined(__EMSCRIPTEN__)
605 #define _SAUDIO_EMSCRIPTEN
606#elif defined(_WIN32)
607 #define _SAUDIO_WINDOWS (1)
608 #include <winapifamily.h>
609 #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP))
610 #define _SAUDIO_UWP (1)
611 #else
612 #define _SAUDIO_WIN32 (1)
613 #endif
614#elif defined(__ANDROID__)
615 #define _SAUDIO_ANDROID (1)
616#elif defined(__linux__) || defined(__unix__)
617 #define _SAUDIO_LINUX (1)
618#else
619#error "sokol_audio.h: Unknown platform"
620#endif
621
622// platform-specific headers and definitions
623#if defined(SOKOL_DUMMY_BACKEND)
624 #define _SAUDIO_NOTHREADS (1)
625#elif defined(_SAUDIO_WINDOWS)
626 #define _SAUDIO_WINTHREADS (1)
627 #ifndef WIN32_LEAN_AND_MEAN
628 #define WIN32_LEAN_AND_MEAN
629 #endif
630 #ifndef NOMINMAX
631 #define NOMINMAX
632 #endif
633 #include <windows.h>
634 #include <synchapi.h>
635 #if defined(_SAUDIO_UWP)
636 #pragma comment (lib, "WindowsApp")
637 #else
638 #pragma comment (lib, "kernel32")
639 #pragma comment (lib, "ole32")
640 #endif
641 #ifndef CINTERFACE
642 #define CINTERFACE
643 #endif
644 #ifndef COBJMACROS
645 #define COBJMACROS
646 #endif
647 #ifndef CONST_VTABLE
648 #define CONST_VTABLE
649 #endif
650 #include <mmdeviceapi.h>
651 #include <audioclient.h>
652 static const IID _saudio_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2} };
653 static const IID _saudio_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6} };
654 static const CLSID _saudio_CLSID_IMMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e} };
655 static const IID _saudio_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2} };
656 static const IID _saudio_IID_Devinterface_Audio_Render = { 0xe6327cad, 0xdcec, 0x4949, {0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2} };
657 static const IID _saudio_IID_IActivateAudioInterface_Completion_Handler = { 0x94ea2b94, 0xe9cc, 0x49e0, {0xc0, 0xff, 0xee, 0x64, 0xca, 0x8f, 0x5b, 0x90} };
658 static const GUID _saudio_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} };
659 #if defined(__cplusplus)
660 #define _SOKOL_AUDIO_WIN32COM_ID(x) (x)
661 #else
662 #define _SOKOL_AUDIO_WIN32COM_ID(x) (&x)
663 #endif
664 /* fix for Visual Studio 2015 SDKs */
665 #ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
666 #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
667 #endif
668 #ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
669 #define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
670 #endif
671 #ifdef _MSC_VER
672 #pragma warning(push)
673 #pragma warning(disable:4505) /* unreferenced local function has been removed */
674 #endif
675#elif defined(_SAUDIO_APPLE)
676 #define _SAUDIO_PTHREADS (1)
677 #include <pthread.h>
678 #if defined(_SAUDIO_IOS)
679 // always use system headers on iOS (for now at least)
680 #if !defined(SAUDIO_OSX_USE_SYSTEM_HEADERS)
681 #define SAUDIO_OSX_USE_SYSTEM_HEADERS (1)
682 #endif
683 #if !defined(__cplusplus)
684 #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields)
685 #error "sokol_audio.h on iOS requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)"
686 #endif
687 #endif
688 #include <AudioToolbox/AudioToolbox.h>
689 #include <AVFoundation/AVFoundation.h>
690 #else
691 #if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS)
692 #include <AudioToolbox/AudioToolbox.h>
693 #endif
694 #endif
695#elif defined(_SAUDIO_ANDROID)
696 #define _SAUDIO_PTHREADS (1)
697 #include <pthread.h>
698 #include "SLES/OpenSLES_Android.h"
699#elif defined(_SAUDIO_LINUX)
700 #define _SAUDIO_PTHREADS (1)
701 #include <pthread.h>
702 #define ALSA_PCM_NEW_HW_PARAMS_API
703 #include <alsa/asoundlib.h>
704#elif defined(__EMSCRIPTEN__)
705 #define _SAUDIO_NOTHREADS (1)
706 #include <emscripten/emscripten.h>
707#endif
708
709#define _saudio_def(val, def) (((val) == 0) ? (def) : (val))
710#define _saudio_def_flt(val, def) (((val) == 0.0f) ? (def) : (val))
711
712#define _SAUDIO_DEFAULT_SAMPLE_RATE (44100)
713#define _SAUDIO_DEFAULT_BUFFER_FRAMES (2048)
714#define _SAUDIO_DEFAULT_PACKET_FRAMES (128)
715#define _SAUDIO_DEFAULT_NUM_PACKETS ((_SAUDIO_DEFAULT_BUFFER_FRAMES/_SAUDIO_DEFAULT_PACKET_FRAMES)*4)
716
717#ifndef SAUDIO_RING_MAX_SLOTS
718#define SAUDIO_RING_MAX_SLOTS (1024)
719#endif
720
721/*=== MUTEX WRAPPER DECLARATIONS =============================================*/
722#if defined(_SAUDIO_PTHREADS)
723
724typedef struct {
725 pthread_mutex_t mutex;
726} _saudio_mutex_t;
727
728#elif defined(_SAUDIO_WINTHREADS)
729
730typedef struct {
731 CRITICAL_SECTION critsec;
732} _saudio_mutex_t;
733
734#elif defined(_SAUDIO_NOTHREADS)
735
736typedef struct {
737 int dummy_mutex;
738} _saudio_mutex_t;
739
740#endif
741
742/*=== DUMMY BACKEND DECLARATIONS =============================================*/
743#if defined(SOKOL_DUMMY_BACKEND)
744
745typedef struct {
746 int dummy_backend;
747} _saudio_backend_t;
748
749/*=== COREAUDIO BACKEND DECLARATIONS =========================================*/
750#elif defined(_SAUDIO_APPLE)
751
752#if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS)
753
754typedef AudioQueueRef _saudio_AudioQueueRef;
755typedef AudioQueueBufferRef _saudio_AudioQueueBufferRef;
756typedef AudioStreamBasicDescription _saudio_AudioStreamBasicDescription;
757typedef OSStatus _saudio_OSStatus;
758
759#define _saudio_kAudioFormatLinearPCM (kAudioFormatLinearPCM)
760#define _saudio_kLinearPCMFormatFlagIsFloat (kLinearPCMFormatFlagIsFloat)
761#define _saudio_kAudioFormatFlagIsPacked (kAudioFormatFlagIsPacked)
762
763#else
764#ifdef __cplusplus
765extern "C" {
766#endif
767
768// embedded AudioToolbox declarations
769typedef uint32_t _saudio_AudioFormatID;
770typedef uint32_t _saudio_AudioFormatFlags;
771typedef int32_t _saudio_OSStatus;
772typedef uint32_t _saudio_SMPTETimeType;
773typedef uint32_t _saudio_SMPTETimeFlags;
774typedef uint32_t _saudio_AudioTimeStampFlags;
775typedef void* _saudio_CFRunLoopRef;
776typedef void* _saudio_CFStringRef;
777typedef void* _saudio_AudioQueueRef;
778
779#define _saudio_kAudioFormatLinearPCM ('lpcm')
780#define _saudio_kLinearPCMFormatFlagIsFloat (1U << 0)
781#define _saudio_kAudioFormatFlagIsPacked (1U << 3)
782
783typedef struct _saudio_AudioStreamBasicDescription {
784 double mSampleRate;
785 _saudio_AudioFormatID mFormatID;
786 _saudio_AudioFormatFlags mFormatFlags;
787 uint32_t mBytesPerPacket;
788 uint32_t mFramesPerPacket;
789 uint32_t mBytesPerFrame;
790 uint32_t mChannelsPerFrame;
791 uint32_t mBitsPerChannel;
792 uint32_t mReserved;
793} _saudio_AudioStreamBasicDescription;
794
795typedef struct _saudio_AudioStreamPacketDescription {
796 int64_t mStartOffset;
797 uint32_t mVariableFramesInPacket;
798 uint32_t mDataByteSize;
799} _saudio_AudioStreamPacketDescription;
800
801typedef struct _saudio_SMPTETime {
802 int16_t mSubframes;
803 int16_t mSubframeDivisor;
804 uint32_t mCounter;
805 _saudio_SMPTETimeType mType;
806 _saudio_SMPTETimeFlags mFlags;
807 int16_t mHours;
808 int16_t mMinutes;
809 int16_t mSeconds;
810 int16_t mFrames;
811} _saudio_SMPTETime;
812
813typedef struct _saudio_AudioTimeStamp {
814 double mSampleTime;
815 uint64_t mHostTime;
816 double mRateScalar;
817 uint64_t mWordClockTime;
818 _saudio_SMPTETime mSMPTETime;
819 _saudio_AudioTimeStampFlags mFlags;
820 uint32_t mReserved;
821} _saudio_AudioTimeStamp;
822
823typedef struct _saudio_AudioQueueBuffer {
824 const uint32_t mAudioDataBytesCapacity;
825 void* const mAudioData;
826 uint32_t mAudioDataByteSize;
827 void * mUserData;
828 const uint32_t mPacketDescriptionCapacity;
829 _saudio_AudioStreamPacketDescription* const mPacketDescriptions;
830 uint32_t mPacketDescriptionCount;
831} _saudio_AudioQueueBuffer;
832typedef _saudio_AudioQueueBuffer* _saudio_AudioQueueBufferRef;
833
834typedef void (*_saudio_AudioQueueOutputCallback)(void* user_data, _saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer);
835
836extern _saudio_OSStatus AudioQueueNewOutput(const _saudio_AudioStreamBasicDescription* inFormat, _saudio_AudioQueueOutputCallback inCallbackProc, void* inUserData, _saudio_CFRunLoopRef inCallbackRunLoop, _saudio_CFStringRef inCallbackRunLoopMode, uint32_t inFlags, _saudio_AudioQueueRef* outAQ);
837extern _saudio_OSStatus AudioQueueDispose(_saudio_AudioQueueRef inAQ, bool inImmediate);
838extern _saudio_OSStatus AudioQueueAllocateBuffer(_saudio_AudioQueueRef inAQ, uint32_t inBufferByteSize, _saudio_AudioQueueBufferRef* outBuffer);
839extern _saudio_OSStatus AudioQueueEnqueueBuffer(_saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer, uint32_t inNumPacketDescs, const _saudio_AudioStreamPacketDescription* inPacketDescs);
840extern _saudio_OSStatus AudioQueueStart(_saudio_AudioQueueRef inAQ, const _saudio_AudioTimeStamp * inStartTime);
841extern _saudio_OSStatus AudioQueueStop(_saudio_AudioQueueRef inAQ, bool inImmediate);
842
843#ifdef __cplusplus
844} // extern "C"
845#endif
846
847#endif // SAUDIO_OSX_USE_SYSTEM_HEADERS
848
849typedef struct {
850 _saudio_AudioQueueRef ca_audio_queue;
851 #if defined(_SAUDIO_IOS)
852 id ca_interruption_handler;
853 #endif
854} _saudio_backend_t;
855
856/*=== ALSA BACKEND DECLARATIONS ==============================================*/
857#elif defined(_SAUDIO_LINUX)
858
859typedef struct {
860 snd_pcm_t* device;
861 float* buffer;
862 int buffer_byte_size;
863 int buffer_frames;
864 pthread_t thread;
865 bool thread_stop;
866} _saudio_backend_t;
867
868/*=== OpenSLES BACKEND DECLARATIONS ==============================================*/
869#elif defined(_SAUDIO_ANDROID)
870
871#define SAUDIO_NUM_BUFFERS 2
872
873typedef struct {
874 pthread_mutex_t mutex;
875 pthread_cond_t cond;
876 int count;
877} _saudio_semaphore_t;
878
879typedef struct {
880 SLObjectItf engine_obj;
881 SLEngineItf engine;
882 SLObjectItf output_mix_obj;
883 SLVolumeItf output_mix_vol;
884 SLDataLocator_OutputMix out_locator;
885 SLDataSink dst_data_sink;
886 SLObjectItf player_obj;
887 SLPlayItf player;
888 SLVolumeItf player_vol;
889 SLAndroidSimpleBufferQueueItf player_buffer_queue;
890
891 int16_t* output_buffers[SAUDIO_NUM_BUFFERS];
892 float* src_buffer;
893 int active_buffer;
894 _saudio_semaphore_t buffer_sem;
895 pthread_t thread;
896 volatile int thread_stop;
897 SLDataLocator_AndroidSimpleBufferQueue in_locator;
898} _saudio_backend_t;
899
900/*=== WASAPI BACKEND DECLARATIONS ============================================*/
901#elif defined(_SAUDIO_WINDOWS)
902
903typedef struct {
904 HANDLE thread_handle;
905 HANDLE buffer_end_event;
906 bool stop;
907 UINT32 dst_buffer_frames;
908 int src_buffer_frames;
909 int src_buffer_byte_size;
910 int src_buffer_pos;
911 float* src_buffer;
912} _saudio_wasapi_thread_data_t;
913
914typedef struct {
915 #if defined(_SAUDIO_UWP)
916 LPOLESTR interface_activation_audio_interface_uid_string;
917 IActivateAudioInterfaceAsyncOperation* interface_activation_operation;
918 BOOL interface_activation_success;
919 HANDLE interface_activation_mutex;
920 #else
921 IMMDeviceEnumerator* device_enumerator;
922 IMMDevice* device;
923 #endif
924 IAudioClient* audio_client;
925 IAudioRenderClient* render_client;
926 _saudio_wasapi_thread_data_t thread;
927} _saudio_backend_t;
928
929/*=== WEBAUDIO BACKEND DECLARATIONS ==========================================*/
930#elif defined(_SAUDIO_EMSCRIPTEN)
931
932typedef struct {
933 uint8_t* buffer;
934} _saudio_backend_t;
935
936#else
937#error "unknown platform"
938#endif
939
940/*=== GENERAL DECLARATIONS ===================================================*/
941
942/* a ringbuffer structure */
943typedef struct {
944 int head; // next slot to write to
945 int tail; // next slot to read from
946 int num; // number of slots in queue
947 int queue[SAUDIO_RING_MAX_SLOTS];
948} _saudio_ring_t;
949
950/* a packet FIFO structure */
951typedef struct {
952 bool valid;
953 int packet_size; /* size of a single packets in bytes(!) */
954 int num_packets; /* number of packet in fifo */
955 uint8_t* base_ptr; /* packet memory chunk base pointer (dynamically allocated) */
956 int cur_packet; /* current write-packet */
957 int cur_offset; /* current byte-offset into current write packet */
958 _saudio_mutex_t mutex; /* mutex for thread-safe access */
959 _saudio_ring_t read_queue; /* buffers with data, ready to be streamed */
960 _saudio_ring_t write_queue; /* empty buffers, ready to be pushed to */
961} _saudio_fifo_t;
962
963/* sokol-audio state */
964typedef struct {
965 bool valid;
966 void (*stream_cb)(float* buffer, int num_frames, int num_channels);
967 void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data);
968 void* user_data;
969 int sample_rate; /* sample rate */
970 int buffer_frames; /* number of frames in streaming buffer */
971 int bytes_per_frame; /* filled by backend */
972 int packet_frames; /* number of frames in a packet */
973 int num_packets; /* number of packets in packet queue */
974 int num_channels; /* actual number of channels */
975 saudio_desc desc;
976 _saudio_fifo_t fifo;
977 _saudio_backend_t backend;
978} _saudio_state_t;
979
980static _saudio_state_t _saudio;
981
982_SOKOL_PRIVATE bool _saudio_has_callback(void) {
983 return (_saudio.stream_cb || _saudio.stream_userdata_cb);
984}
985
986_SOKOL_PRIVATE void _saudio_stream_callback(float* buffer, int num_frames, int num_channels) {
987 if (_saudio.stream_cb) {
988 _saudio.stream_cb(buffer, num_frames, num_channels);
989 }
990 else if (_saudio.stream_userdata_cb) {
991 _saudio.stream_userdata_cb(buffer, num_frames, num_channels, _saudio.user_data);
992 }
993}
994
995/*=== MEMORY HELPERS ========================================================*/
996_SOKOL_PRIVATE void _saudio_clear(void* ptr, size_t size) {
997 SOKOL_ASSERT(ptr && (size > 0));
998 memset(ptr, 0, size);
999}
1000
1001_SOKOL_PRIVATE void* _saudio_malloc(size_t size) {
1002 SOKOL_ASSERT(size > 0);
1003 void* ptr;
1004 if (_saudio.desc.allocator.alloc) {
1005 ptr = _saudio.desc.allocator.alloc(size, _saudio.desc.allocator.user_data);
1006 }
1007 else {
1008 ptr = malloc(size);
1009 }
1010 SOKOL_ASSERT(ptr);
1011 return ptr;
1012}
1013
1014_SOKOL_PRIVATE void* _saudio_malloc_clear(size_t size) {
1015 void* ptr = _saudio_malloc(size);
1016 _saudio_clear(ptr, size);
1017 return ptr;
1018}
1019
1020_SOKOL_PRIVATE void _saudio_free(void* ptr) {
1021 if (_saudio.desc.allocator.free) {
1022 _saudio.desc.allocator.free(ptr, _saudio.desc.allocator.user_data);
1023 }
1024 else {
1025 free(ptr);
1026 }
1027}
1028
1029#if defined(SOKOL_DEBUG)
1030_SOKOL_PRIVATE void _saudio_log(const char* msg) {
1031 SOKOL_ASSERT(msg);
1032 if (_saudio.desc.logger.log_cb) {
1033 _saudio.desc.logger.log_cb(msg, _saudio.desc.logger.user_data);
1034 } else {
1035 SOKOL_LOG(msg);
1036 }
1037}
1038#endif
1039
1040/*=== MUTEX IMPLEMENTATION ===================================================*/
1041#if defined(_SAUDIO_NOTHREADS)
1042
1043_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { (void)m; }
1044_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { (void)m; }
1045_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { (void)m; }
1046_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { (void)m; }
1047
1048#elif defined(_SAUDIO_PTHREADS)
1049
1050_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) {
1051 pthread_mutexattr_t attr;
1052 pthread_mutexattr_init(&attr);
1053 pthread_mutex_init(&m->mutex, &attr);
1054}
1055
1056_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) {
1057 pthread_mutex_destroy(&m->mutex);
1058}
1059
1060_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) {
1061 pthread_mutex_lock(&m->mutex);
1062}
1063
1064_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) {
1065 pthread_mutex_unlock(&m->mutex);
1066}
1067
1068#elif defined(_SAUDIO_WINTHREADS)
1069
1070_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) {
1071 InitializeCriticalSection(&m->critsec);
1072}
1073
1074_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) {
1075 DeleteCriticalSection(&m->critsec);
1076}
1077
1078_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) {
1079 EnterCriticalSection(&m->critsec);
1080}
1081
1082_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) {
1083 LeaveCriticalSection(&m->critsec);
1084}
1085#else
1086#error "unknown platform!"
1087#endif
1088
1089/*=== RING-BUFFER QUEUE IMPLEMENTATION =======================================*/
1090_SOKOL_PRIVATE int _saudio_ring_idx(_saudio_ring_t* ring, int i) {
1091 return (i % ring->num);
1092}
1093
1094_SOKOL_PRIVATE void _saudio_ring_init(_saudio_ring_t* ring, int num_slots) {
1095 SOKOL_ASSERT((num_slots + 1) <= SAUDIO_RING_MAX_SLOTS);
1096 ring->head = 0;
1097 ring->tail = 0;
1098 /* one slot reserved to detect 'full' vs 'empty' */
1099 ring->num = num_slots + 1;
1100}
1101
1102_SOKOL_PRIVATE bool _saudio_ring_full(_saudio_ring_t* ring) {
1103 return _saudio_ring_idx(ring, ring->head + 1) == ring->tail;
1104}
1105
1106_SOKOL_PRIVATE bool _saudio_ring_empty(_saudio_ring_t* ring) {
1107 return ring->head == ring->tail;
1108}
1109
1110_SOKOL_PRIVATE int _saudio_ring_count(_saudio_ring_t* ring) {
1111 int count;
1112 if (ring->head >= ring->tail) {
1113 count = ring->head - ring->tail;
1114 }
1115 else {
1116 count = (ring->head + ring->num) - ring->tail;
1117 }
1118 SOKOL_ASSERT(count < ring->num);
1119 return count;
1120}
1121
1122_SOKOL_PRIVATE void _saudio_ring_enqueue(_saudio_ring_t* ring, int val) {
1123 SOKOL_ASSERT(!_saudio_ring_full(ring));
1124 ring->queue[ring->head] = val;
1125 ring->head = _saudio_ring_idx(ring, ring->head + 1);
1126}
1127
1128_SOKOL_PRIVATE int _saudio_ring_dequeue(_saudio_ring_t* ring) {
1129 SOKOL_ASSERT(!_saudio_ring_empty(ring));
1130 int val = ring->queue[ring->tail];
1131 ring->tail = _saudio_ring_idx(ring, ring->tail + 1);
1132 return val;
1133}
1134
1135/*--- a packet fifo for queueing audio data from main thread ----------------*/
1136_SOKOL_PRIVATE void _saudio_fifo_init_mutex(_saudio_fifo_t* fifo) {
1137 /* this must be called before initializing both the backend and the fifo itself! */
1138 _saudio_mutex_init(&fifo->mutex);
1139}
1140
1141_SOKOL_PRIVATE void _saudio_fifo_init(_saudio_fifo_t* fifo, int packet_size, int num_packets) {
1142 /* NOTE: there's a chicken-egg situation during the init phase where the
1143 streaming thread must be started before the fifo is actually initialized,
1144 thus the fifo init must already be protected from access by the fifo_read() func.
1145 */
1146 _saudio_mutex_lock(&fifo->mutex);
1147 SOKOL_ASSERT((packet_size > 0) && (num_packets > 0));
1148 fifo->packet_size = packet_size;
1149 fifo->num_packets = num_packets;
1150 fifo->base_ptr = (uint8_t*) _saudio_malloc((size_t)(packet_size * num_packets));
1151 fifo->cur_packet = -1;
1152 fifo->cur_offset = 0;
1153 _saudio_ring_init(&fifo->read_queue, num_packets);
1154 _saudio_ring_init(&fifo->write_queue, num_packets);
1155 for (int i = 0; i < num_packets; i++) {
1156 _saudio_ring_enqueue(&fifo->write_queue, i);
1157 }
1158 SOKOL_ASSERT(_saudio_ring_full(&fifo->write_queue));
1159 SOKOL_ASSERT(_saudio_ring_count(&fifo->write_queue) == num_packets);
1160 SOKOL_ASSERT(_saudio_ring_empty(&fifo->read_queue));
1161 SOKOL_ASSERT(_saudio_ring_count(&fifo->read_queue) == 0);
1162 fifo->valid = true;
1163 _saudio_mutex_unlock(&fifo->mutex);
1164}
1165
1166_SOKOL_PRIVATE void _saudio_fifo_shutdown(_saudio_fifo_t* fifo) {
1167 SOKOL_ASSERT(fifo->base_ptr);
1168 _saudio_free(fifo->base_ptr);
1169 fifo->base_ptr = 0;
1170 fifo->valid = false;
1171 _saudio_mutex_destroy(&fifo->mutex);
1172}
1173
1174_SOKOL_PRIVATE int _saudio_fifo_writable_bytes(_saudio_fifo_t* fifo) {
1175 _saudio_mutex_lock(&fifo->mutex);
1176 int num_bytes = (_saudio_ring_count(&fifo->write_queue) * fifo->packet_size);
1177 if (fifo->cur_packet != -1) {
1178 num_bytes += fifo->packet_size - fifo->cur_offset;
1179 }
1180 _saudio_mutex_unlock(&fifo->mutex);
1181 SOKOL_ASSERT((num_bytes >= 0) && (num_bytes <= (fifo->num_packets * fifo->packet_size)));
1182 return num_bytes;
1183}
1184
1185/* write new data to the write queue, this is called from main thread */
1186_SOKOL_PRIVATE int _saudio_fifo_write(_saudio_fifo_t* fifo, const uint8_t* ptr, int num_bytes) {
1187 /* returns the number of bytes written, this will be smaller then requested
1188 if the write queue runs full
1189 */
1190 int all_to_copy = num_bytes;
1191 while (all_to_copy > 0) {
1192 /* need to grab a new packet? */
1193 if (fifo->cur_packet == -1) {
1194 _saudio_mutex_lock(&fifo->mutex);
1195 if (!_saudio_ring_empty(&fifo->write_queue)) {
1196 fifo->cur_packet = _saudio_ring_dequeue(&fifo->write_queue);
1197 }
1198 _saudio_mutex_unlock(&fifo->mutex);
1199 SOKOL_ASSERT(fifo->cur_offset == 0);
1200 }
1201 /* append data to current write packet */
1202 if (fifo->cur_packet != -1) {
1203 int to_copy = all_to_copy;
1204 const int max_copy = fifo->packet_size - fifo->cur_offset;
1205 if (to_copy > max_copy) {
1206 to_copy = max_copy;
1207 }
1208 uint8_t* dst = fifo->base_ptr + fifo->cur_packet * fifo->packet_size + fifo->cur_offset;
1209 memcpy(dst, ptr, (size_t)to_copy);
1210 ptr += to_copy;
1211 fifo->cur_offset += to_copy;
1212 all_to_copy -= to_copy;
1213 SOKOL_ASSERT(fifo->cur_offset <= fifo->packet_size);
1214 SOKOL_ASSERT(all_to_copy >= 0);
1215 }
1216 else {
1217 /* early out if we're starving */
1218 int bytes_copied = num_bytes - all_to_copy;
1219 SOKOL_ASSERT((bytes_copied >= 0) && (bytes_copied < num_bytes));
1220 return bytes_copied;
1221 }
1222 /* if write packet is full, push to read queue */
1223 if (fifo->cur_offset == fifo->packet_size) {
1224 _saudio_mutex_lock(&fifo->mutex);
1225 _saudio_ring_enqueue(&fifo->read_queue, fifo->cur_packet);
1226 _saudio_mutex_unlock(&fifo->mutex);
1227 fifo->cur_packet = -1;
1228 fifo->cur_offset = 0;
1229 }
1230 }
1231 SOKOL_ASSERT(all_to_copy == 0);
1232 return num_bytes;
1233}
1234
1235/* read queued data, this is called form the stream callback (maybe separate thread) */
1236_SOKOL_PRIVATE int _saudio_fifo_read(_saudio_fifo_t* fifo, uint8_t* ptr, int num_bytes) {
1237 /* NOTE: fifo_read might be called before the fifo is properly initialized */
1238 _saudio_mutex_lock(&fifo->mutex);
1239 int num_bytes_copied = 0;
1240 if (fifo->valid) {
1241 SOKOL_ASSERT(0 == (num_bytes % fifo->packet_size));
1242 SOKOL_ASSERT(num_bytes <= (fifo->packet_size * fifo->num_packets));
1243 const int num_packets_needed = num_bytes / fifo->packet_size;
1244 uint8_t* dst = ptr;
1245 /* either pull a full buffer worth of data, or nothing */
1246 if (_saudio_ring_count(&fifo->read_queue) >= num_packets_needed) {
1247 for (int i = 0; i < num_packets_needed; i++) {
1248 int packet_index = _saudio_ring_dequeue(&fifo->read_queue);
1249 _saudio_ring_enqueue(&fifo->write_queue, packet_index);
1250 const uint8_t* src = fifo->base_ptr + packet_index * fifo->packet_size;
1251 memcpy(dst, src, (size_t)fifo->packet_size);
1252 dst += fifo->packet_size;
1253 num_bytes_copied += fifo->packet_size;
1254 }
1255 SOKOL_ASSERT(num_bytes == num_bytes_copied);
1256 }
1257 }
1258 _saudio_mutex_unlock(&fifo->mutex);
1259 return num_bytes_copied;
1260}
1261
1262/*=== DUMMY BACKEND IMPLEMENTATION ===========================================*/
1263#if defined(SOKOL_DUMMY_BACKEND)
1264_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1265 _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
1266 return true;
1267};
1268_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { };
1269
1270/*=== COREAUDIO BACKEND IMPLEMENTATION =======================================*/
1271#elif defined(_SAUDIO_APPLE)
1272
1273#if defined(_SAUDIO_IOS)
1274#if __has_feature(objc_arc)
1275#define _SAUDIO_OBJC_RELEASE(obj) { obj = nil; }
1276#else
1277#define _SAUDIO_OBJC_RELEASE(obj) { [obj release]; obj = nil; }
1278#endif
1279
1280@interface _saudio_interruption_handler : NSObject { }
1281@end
1282
1283@implementation _saudio_interruption_handler
1284-(id)init {
1285 self = [super init];
1286 AVAudioSession* session = [AVAudioSession sharedInstance];
1287 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:session];
1288 return self;
1289}
1290
1291-(void)dealloc {
1292 [self remove_handler];
1293 #if !__has_feature(objc_arc)
1294 [super dealloc];
1295 #endif
1296}
1297
1298-(void)remove_handler {
1299 [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVAudioSessionInterruptionNotification" object:nil];
1300}
1301
1302-(void)handle_interruption:(NSNotification*)notification {
1303 AVAudioSession* session = [AVAudioSession sharedInstance];
1304 SOKOL_ASSERT(session);
1305 NSDictionary* dict = notification.userInfo;
1306 SOKOL_ASSERT(dict);
1307 NSInteger type = [[dict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];
1308 switch (type) {
1309 case AVAudioSessionInterruptionTypeBegan:
1310 AudioQueuePause(_saudio.backend.ca_audio_queue);
1311 [session setActive:false error:nil];
1312 break;
1313 case AVAudioSessionInterruptionTypeEnded:
1314 [session setActive:true error:nil];
1315 AudioQueueStart(_saudio.backend.ca_audio_queue, NULL);
1316 break;
1317 default:
1318 break;
1319 }
1320}
1321@end
1322#endif // _SAUDIO_IOS
1323
1324/* NOTE: the buffer data callback is called on a separate thread! */
1325_SOKOL_PRIVATE void _saudio_coreaudio_callback(void* user_data, _saudio_AudioQueueRef queue, _saudio_AudioQueueBufferRef buffer) {
1326 _SOKOL_UNUSED(user_data);
1327 if (_saudio_has_callback()) {
1328 const int num_frames = (int)buffer->mAudioDataByteSize / _saudio.bytes_per_frame;
1329 const int num_channels = _saudio.num_channels;
1330 _saudio_stream_callback((float*)buffer->mAudioData, num_frames, num_channels);
1331 }
1332 else {
1333 uint8_t* ptr = (uint8_t*)buffer->mAudioData;
1334 int num_bytes = (int) buffer->mAudioDataByteSize;
1335 if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) {
1336 /* not enough read data available, fill the entire buffer with silence */
1337 _saudio_clear(ptr, (size_t)num_bytes);
1338 }
1339 }
1340 AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
1341}
1342
1343_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1344 SOKOL_ASSERT(0 == _saudio.backend.ca_audio_queue);
1345
1346 #if defined(_SAUDIO_IOS)
1347 /* activate audio session */
1348 AVAudioSession* session = [AVAudioSession sharedInstance];
1349 SOKOL_ASSERT(session != nil);
1350 [session setCategory: AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
1351 [session setActive:true error:nil];
1352
1353 /* create interruption handler */
1354 _saudio.backend.ca_interruption_handler = [[_saudio_interruption_handler alloc] init];
1355 #endif // _SAUDIO_IOS
1356
1357 /* create an audio queue with fp32 samples */
1358 _saudio_AudioStreamBasicDescription fmt;
1359 _saudio_clear(&fmt, sizeof(fmt));
1360 fmt.mSampleRate = (double) _saudio.sample_rate;
1361 fmt.mFormatID = _saudio_kAudioFormatLinearPCM;
1362 fmt.mFormatFlags = _saudio_kLinearPCMFormatFlagIsFloat | _saudio_kAudioFormatFlagIsPacked;
1363 fmt.mFramesPerPacket = 1;
1364 fmt.mChannelsPerFrame = (uint32_t) _saudio.num_channels;
1365 fmt.mBytesPerFrame = (uint32_t)sizeof(float) * (uint32_t)_saudio.num_channels;
1366 fmt.mBytesPerPacket = fmt.mBytesPerFrame;
1367 fmt.mBitsPerChannel = 32;
1368 _saudio_OSStatus res = AudioQueueNewOutput(&fmt, _saudio_coreaudio_callback, 0, NULL, NULL, 0, &_saudio.backend.ca_audio_queue);
1369 SOKOL_ASSERT((res == 0) && _saudio.backend.ca_audio_queue); (void)res;
1370
1371 /* create 2 audio buffers */
1372 for (int i = 0; i < 2; i++) {
1373 _saudio_AudioQueueBufferRef buf = NULL;
1374 const uint32_t buf_byte_size = (uint32_t)_saudio.buffer_frames * fmt.mBytesPerFrame;
1375 res = AudioQueueAllocateBuffer(_saudio.backend.ca_audio_queue, buf_byte_size, &buf);
1376 SOKOL_ASSERT((res == 0) && buf); (void)res;
1377 buf->mAudioDataByteSize = buf_byte_size;
1378 _saudio_clear(buf->mAudioData, buf->mAudioDataByteSize);
1379 AudioQueueEnqueueBuffer(_saudio.backend.ca_audio_queue, buf, 0, NULL);
1380 }
1381
1382 /* init or modify actual playback parameters */
1383 _saudio.bytes_per_frame = (int)fmt.mBytesPerFrame;
1384
1385 /* ...and start playback */
1386 res = AudioQueueStart(_saudio.backend.ca_audio_queue, NULL);
1387 SOKOL_ASSERT(0 == res); (void)res;
1388
1389 return true;
1390}
1391
1392_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1393 AudioQueueStop(_saudio.backend.ca_audio_queue, true);
1394 AudioQueueDispose(_saudio.backend.ca_audio_queue, false);
1395 _saudio.backend.ca_audio_queue = NULL;
1396 #if defined(_SAUDIO_IOS)
1397 /* remove interruption handler */
1398 if (_saudio.backend.ca_interruption_handler != nil) {
1399 [_saudio.backend.ca_interruption_handler remove_handler];
1400 _SAUDIO_OBJC_RELEASE(_saudio.backend.ca_interruption_handler);
1401 }
1402 /* deactivate audio session */
1403 AVAudioSession* session = [AVAudioSession sharedInstance];
1404 SOKOL_ASSERT(session);
1405 [session setActive:false error:nil];;
1406 #endif // _SAUDIO_IOS
1407}
1408
1409/*=== ALSA BACKEND IMPLEMENTATION ============================================*/
1410#elif defined(_SAUDIO_LINUX)
1411
1412/* the streaming callback runs in a separate thread */
1413_SOKOL_PRIVATE void* _saudio_alsa_cb(void* param) {
1414 _SOKOL_UNUSED(param);
1415 while (!_saudio.backend.thread_stop) {
1416 /* snd_pcm_writei() will be blocking until it needs data */
1417 int write_res = snd_pcm_writei(_saudio.backend.device, _saudio.backend.buffer, (snd_pcm_uframes_t)_saudio.backend.buffer_frames);
1418 if (write_res < 0) {
1419 /* underrun occurred */
1420 snd_pcm_prepare(_saudio.backend.device);
1421 }
1422 else {
1423 /* fill the streaming buffer with new data */
1424 if (_saudio_has_callback()) {
1425 _saudio_stream_callback(_saudio.backend.buffer, _saudio.backend.buffer_frames, _saudio.num_channels);
1426 }
1427 else {
1428 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.buffer, _saudio.backend.buffer_byte_size)) {
1429 /* not enough read data available, fill the entire buffer with silence */
1430 _saudio_clear(_saudio.backend.buffer, (size_t)_saudio.backend.buffer_byte_size);
1431 }
1432 }
1433 }
1434 }
1435 return 0;
1436}
1437
1438_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1439 int dir; uint32_t rate;
1440 int rc = snd_pcm_open(&_saudio.backend.device, "default", SND_PCM_STREAM_PLAYBACK, 0);
1441 if (rc < 0) {
1442 SAUDIO_LOG("sokol_audio.h: snd_pcm_open() failed");
1443 return false;
1444 }
1445
1446 /* configuration works by restricting the 'configuration space' step
1447 by step, we require all parameters except the sample rate to
1448 match perfectly
1449 */
1450 snd_pcm_hw_params_t* params = 0;
1451 snd_pcm_hw_params_alloca(¶ms);
1452 snd_pcm_hw_params_any(_saudio.backend.device, params);
1453 snd_pcm_hw_params_set_access(_saudio.backend.device, params, SND_PCM_ACCESS_RW_INTERLEAVED);
1454 if (0 > snd_pcm_hw_params_set_format(_saudio.backend.device, params, SND_PCM_FORMAT_FLOAT_LE)) {
1455 SAUDIO_LOG("sokol_audio.h: float samples not supported");
1456 goto error;
1457 }
1458 if (0 > snd_pcm_hw_params_set_buffer_size(_saudio.backend.device, params, (snd_pcm_uframes_t)_saudio.buffer_frames)) {
1459 SAUDIO_LOG("sokol_audio.h: requested buffer size not supported");
1460 goto error;
1461 }
1462 if (0 > snd_pcm_hw_params_set_channels(_saudio.backend.device, params, (uint32_t)_saudio.num_channels)) {
1463 SAUDIO_LOG("sokol_audio.h: requested channel count not supported");
1464 goto error;
1465 }
1466 /* let ALSA pick a nearby sampling rate */
1467 rate = (uint32_t) _saudio.sample_rate;
1468 dir = 0;
1469 if (0 > snd_pcm_hw_params_set_rate_near(_saudio.backend.device, params, &rate, &dir)) {
1470 SAUDIO_LOG("sokol_audio.h: snd_pcm_hw_params_set_rate_near() failed");
1471 goto error;
1472 }
1473 if (0 > snd_pcm_hw_params(_saudio.backend.device, params)) {
1474 SAUDIO_LOG("sokol_audio.h: snd_pcm_hw_params() failed");
1475 goto error;
1476 }
1477
1478 /* read back actual sample rate and channels */
1479 _saudio.sample_rate = (int)rate;
1480 _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
1481
1482 /* allocate the streaming buffer */
1483 _saudio.backend.buffer_byte_size = _saudio.buffer_frames * _saudio.bytes_per_frame;
1484 _saudio.backend.buffer_frames = _saudio.buffer_frames;
1485 _saudio.backend.buffer = (float*) _saudio_malloc_clear((size_t)_saudio.backend.buffer_byte_size);
1486
1487 /* create the buffer-streaming start thread */
1488 if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_alsa_cb, 0)) {
1489 SAUDIO_LOG("sokol_audio.h: pthread_create() failed");
1490 goto error;
1491 }
1492
1493 return true;
1494error:
1495 if (_saudio.backend.device) {
1496 snd_pcm_close(_saudio.backend.device);
1497 _saudio.backend.device = 0;
1498 }
1499 return false;
1500};
1501
1502_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1503 SOKOL_ASSERT(_saudio.backend.device);
1504 _saudio.backend.thread_stop = true;
1505 pthread_join(_saudio.backend.thread, 0);
1506 snd_pcm_drain(_saudio.backend.device);
1507 snd_pcm_close(_saudio.backend.device);
1508 _saudio_free(_saudio.backend.buffer);
1509};
1510
1511/*=== WASAPI BACKEND IMPLEMENTATION ==========================================*/
1512#elif defined(_SAUDIO_WINDOWS)
1513
1514#if defined(_SAUDIO_UWP)
1515/* Minimal implementation of an IActivateAudioInterfaceCompletionHandler COM object in plain C.
1516 Meant to be a static singleton (always one reference when add/remove reference)
1517 and implements IUnknown and IActivateAudioInterfaceCompletionHandler when queryinterface'd
1518
1519 Do not know why but IActivateAudioInterfaceCompletionHandler's GUID is not the one system queries for,
1520 so I'm advertising the one actually requested.
1521*/
1522_SOKOL_PRIVATE HRESULT STDMETHODCALLTYPE _saudio_interface_completion_handler_queryinterface(IActivateAudioInterfaceCompletionHandler* instance, REFIID riid, void** ppvObject) {
1523 if (!ppvObject) {
1524 return E_POINTER;
1525 }
1526
1527 if (IsEqualIID(riid, _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IActivateAudioInterface_Completion_Handler)) || IsEqualIID(riid, _SOKOL_AUDIO_WIN32COM_ID(IID_IUnknown)))
1528 {
1529 *ppvObject = (void*)instance;
1530 return S_OK;
1531 }
1532
1533 *ppvObject = NULL;
1534 return E_NOINTERFACE;
1535}
1536
1537_SOKOL_PRIVATE ULONG STDMETHODCALLTYPE _saudio_interface_completion_handler_addref_release(IActivateAudioInterfaceCompletionHandler* instance) {
1538 _SOKOL_UNUSED(instance);
1539 return 1;
1540}
1541
1542_SOKOL_PRIVATE HRESULT STDMETHODCALLTYPE _saudio_backend_activate_audio_interface_cb(IActivateAudioInterfaceCompletionHandler* instance, IActivateAudioInterfaceAsyncOperation* activateOperation) {
1543 _SOKOL_UNUSED(instance);
1544 WaitForSingleObject(_saudio.backend.interface_activation_mutex, INFINITE);
1545 _saudio.backend.interface_activation_success = TRUE;
1546 HRESULT activation_result;
1547 if (FAILED(activateOperation->lpVtbl->GetActivateResult(activateOperation, &activation_result, (IUnknown**)(&_saudio.backend.audio_client))) || FAILED(activation_result)) {
1548 _saudio.backend.interface_activation_success = FALSE;
1549 }
1550
1551 ReleaseMutex(_saudio.backend.interface_activation_mutex);
1552 return S_OK;
1553}
1554#endif // _SAUDIO_UWP
1555
1556/* fill intermediate buffer with new data and reset buffer_pos */
1557_SOKOL_PRIVATE void _saudio_wasapi_fill_buffer(void) {
1558 if (_saudio_has_callback()) {
1559 _saudio_stream_callback(_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_frames, _saudio.num_channels);
1560 }
1561 else {
1562 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_byte_size)) {
1563 /* not enough read data available, fill the entire buffer with silence */
1564 _saudio_clear(_saudio.backend.thread.src_buffer, (size_t)_saudio.backend.thread.src_buffer_byte_size);
1565 }
1566 }
1567}
1568
1569_SOKOL_PRIVATE int _saudio_wasapi_min(int a, int b) {
1570 return (a < b) ? a : b;
1571}
1572
1573_SOKOL_PRIVATE void _saudio_wasapi_submit_buffer(int num_frames) {
1574 BYTE* wasapi_buffer = 0;
1575 if (FAILED(IAudioRenderClient_GetBuffer(_saudio.backend.render_client, num_frames, &wasapi_buffer))) {
1576 return;
1577 }
1578 SOKOL_ASSERT(wasapi_buffer);
1579
1580 /* copy samples to WASAPI buffer, refill source buffer if needed */
1581 int num_remaining_samples = num_frames * _saudio.num_channels;
1582 int buffer_pos = _saudio.backend.thread.src_buffer_pos;
1583 const int buffer_size_in_samples = _saudio.backend.thread.src_buffer_byte_size / (int)sizeof(float);
1584 float* dst = (float*)wasapi_buffer;
1585 const float* dst_end = dst + num_remaining_samples;
1586 _SOKOL_UNUSED(dst_end); // suppress unused warning in release mode
1587 const float* src = _saudio.backend.thread.src_buffer;
1588
1589 while (num_remaining_samples > 0) {
1590 if (0 == buffer_pos) {
1591 _saudio_wasapi_fill_buffer();
1592 }
1593 const int samples_to_copy = _saudio_wasapi_min(num_remaining_samples, buffer_size_in_samples - buffer_pos);
1594 SOKOL_ASSERT((buffer_pos + samples_to_copy) <= buffer_size_in_samples);
1595 SOKOL_ASSERT((dst + samples_to_copy) <= dst_end);
1596 memcpy(dst, &src[buffer_pos], (size_t)samples_to_copy * sizeof(float));
1597 num_remaining_samples -= samples_to_copy;
1598 SOKOL_ASSERT(num_remaining_samples >= 0);
1599 buffer_pos += samples_to_copy;
1600 dst += samples_to_copy;
1601
1602 SOKOL_ASSERT(buffer_pos <= buffer_size_in_samples);
1603 if (buffer_pos == buffer_size_in_samples) {
1604 buffer_pos = 0;
1605 }
1606 }
1607 _saudio.backend.thread.src_buffer_pos = buffer_pos;
1608 IAudioRenderClient_ReleaseBuffer(_saudio.backend.render_client, num_frames, 0);
1609}
1610
1611_SOKOL_PRIVATE DWORD WINAPI _saudio_wasapi_thread_fn(LPVOID param) {
1612 (void)param;
1613 _saudio_wasapi_submit_buffer(_saudio.backend.thread.src_buffer_frames);
1614 IAudioClient_Start(_saudio.backend.audio_client);
1615 while (!_saudio.backend.thread.stop) {
1616 WaitForSingleObject(_saudio.backend.thread.buffer_end_event, INFINITE);
1617 UINT32 padding = 0;
1618 if (FAILED(IAudioClient_GetCurrentPadding(_saudio.backend.audio_client, &padding))) {
1619 continue;
1620 }
1621 SOKOL_ASSERT(_saudio.backend.thread.dst_buffer_frames >= padding);
1622 int num_frames = (int)_saudio.backend.thread.dst_buffer_frames - (int)padding;
1623 if (num_frames > 0) {
1624 _saudio_wasapi_submit_buffer(num_frames);
1625 }
1626 }
1627 return 0;
1628}
1629
1630_SOKOL_PRIVATE void _saudio_wasapi_release(void) {
1631 if (_saudio.backend.thread.src_buffer) {
1632 _saudio_free(_saudio.backend.thread.src_buffer);
1633 _saudio.backend.thread.src_buffer = 0;
1634 }
1635 if (_saudio.backend.render_client) {
1636 IAudioRenderClient_Release(_saudio.backend.render_client);
1637 _saudio.backend.render_client = 0;
1638 }
1639 if (_saudio.backend.audio_client) {
1640 IAudioClient_Release(_saudio.backend.audio_client);
1641 _saudio.backend.audio_client = 0;
1642 }
1643 #if defined(_SAUDIO_UWP)
1644 if (_saudio.backend.interface_activation_audio_interface_uid_string) {
1645 CoTaskMemFree(_saudio.backend.interface_activation_audio_interface_uid_string);
1646 _saudio.backend.interface_activation_audio_interface_uid_string = 0;
1647 }
1648 if (_saudio.backend.interface_activation_operation) {
1649 IActivateAudioInterfaceAsyncOperation_Release(_saudio.backend.interface_activation_operation);
1650 _saudio.backend.interface_activation_operation = 0;
1651 }
1652 #else
1653 if (_saudio.backend.device) {
1654 IMMDevice_Release(_saudio.backend.device);
1655 _saudio.backend.device = 0;
1656 }
1657 if (_saudio.backend.device_enumerator) {
1658 IMMDeviceEnumerator_Release(_saudio.backend.device_enumerator);
1659 _saudio.backend.device_enumerator = 0;
1660 }
1661 #endif
1662 if (0 != _saudio.backend.thread.buffer_end_event) {
1663 CloseHandle(_saudio.backend.thread.buffer_end_event);
1664 _saudio.backend.thread.buffer_end_event = 0;
1665 }
1666}
1667
1668_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1669 REFERENCE_TIME dur;
1670 /* UWP Threads are CoInitialized by default with a different threading model, and this call fails
1671 See https://github.com/Microsoft/cppwinrt/issues/6#issuecomment-253930637 */
1672 #if defined(_SAUDIO_WIN32)
1673 /* CoInitializeEx could have been called elsewhere already, in which
1674 case the function returns with S_FALSE (thus it does not make much
1675 sense to check the result)
1676 */
1677 HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
1678 _SOKOL_UNUSED(hr);
1679 #endif
1680 _saudio.backend.thread.buffer_end_event = CreateEvent(0, FALSE, FALSE, 0);
1681 if (0 == _saudio.backend.thread.buffer_end_event) {
1682 SAUDIO_LOG("sokol_audio wasapi: failed to create buffer_end_event");
1683 goto error;
1684 }
1685 #if defined(_SAUDIO_UWP)
1686 _saudio.backend.interface_activation_mutex = CreateMutexA(NULL, FALSE, "interface_activation_mutex");
1687 if (_saudio.backend.interface_activation_mutex == NULL) {
1688 SAUDIO_LOG("sokol_audio wasapi: failed to create interface activation mutex");
1689 goto error;
1690 }
1691 if (FAILED(StringFromIID(_SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_Devinterface_Audio_Render), &_saudio.backend.interface_activation_audio_interface_uid_string))) {
1692 SAUDIO_LOG("sokol_audio wasapi: failed to get default audio device ID string");
1693 goto error;
1694 }
1695
1696 /* static instance of the fake COM object */
1697 static IActivateAudioInterfaceCompletionHandlerVtbl completion_handler_interface_vtable = {
1698 _saudio_interface_completion_handler_queryinterface,
1699 _saudio_interface_completion_handler_addref_release,
1700 _saudio_interface_completion_handler_addref_release,
1701 _saudio_backend_activate_audio_interface_cb
1702 };
1703 static IActivateAudioInterfaceCompletionHandler completion_handler_interface = { &completion_handler_interface_vtable };
1704
1705 if (FAILED(ActivateAudioInterfaceAsync(_saudio.backend.interface_activation_audio_interface_uid_string, _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient), NULL, &completion_handler_interface, &_saudio.backend.interface_activation_operation))) {
1706 SAUDIO_LOG("sokol_audio wasapi: failed to get default audio device ID string");
1707 goto error;
1708 }
1709 while (!(_saudio.backend.audio_client)) {
1710 if (WaitForSingleObject(_saudio.backend.interface_activation_mutex, 10) != WAIT_TIMEOUT) {
1711 ReleaseMutex(_saudio.backend.interface_activation_mutex);
1712 }
1713 }
1714
1715 if (!(_saudio.backend.interface_activation_success)) {
1716 SAUDIO_LOG("sokol_audio wasapi: interface activation failed. Unable to get audio client");
1717 goto error;
1718 }
1719
1720 #else
1721 if (FAILED(CoCreateInstance(_SOKOL_AUDIO_WIN32COM_ID(_saudio_CLSID_IMMDeviceEnumerator),
1722 0, CLSCTX_ALL,
1723 _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IMMDeviceEnumerator),
1724 (void**)&_saudio.backend.device_enumerator)))
1725 {
1726 SAUDIO_LOG("sokol_audio wasapi: failed to create device enumerator");
1727 goto error;
1728 }
1729 if (FAILED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(_saudio.backend.device_enumerator,
1730 eRender, eConsole,
1731 &_saudio.backend.device)))
1732 {
1733 SAUDIO_LOG("sokol_audio wasapi: GetDefaultAudioEndPoint failed");
1734 goto error;
1735 }
1736 if (FAILED(IMMDevice_Activate(_saudio.backend.device,
1737 _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient),
1738 CLSCTX_ALL, 0,
1739 (void**)&_saudio.backend.audio_client)))
1740 {
1741 SAUDIO_LOG("sokol_audio wasapi: device activate failed");
1742 goto error;
1743 }
1744 #endif
1745
1746 WAVEFORMATEXTENSIBLE fmtex;
1747 _saudio_clear(&fmtex, sizeof(fmtex));
1748 fmtex.Format.nChannels = (WORD)_saudio.num_channels;
1749 fmtex.Format.nSamplesPerSec = (DWORD)_saudio.sample_rate;
1750 fmtex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
1751 fmtex.Format.wBitsPerSample = 32;
1752 fmtex.Format.nBlockAlign = (fmtex.Format.nChannels * fmtex.Format.wBitsPerSample) / 8;
1753 fmtex.Format.nAvgBytesPerSec = fmtex.Format.nSamplesPerSec * fmtex.Format.nBlockAlign;
1754 fmtex.Format.cbSize = 22; /* WORD + DWORD + GUID */
1755 fmtex.Samples.wValidBitsPerSample = 32;
1756 if (_saudio.num_channels == 1) {
1757 fmtex.dwChannelMask = SPEAKER_FRONT_CENTER;
1758 }
1759 else {
1760 fmtex.dwChannelMask = SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT;
1761 }
1762 fmtex.SubFormat = _saudio_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1763 dur = (REFERENCE_TIME)
1764 (((double)_saudio.buffer_frames) / (((double)_saudio.sample_rate) * (1.0/10000000.0)));
1765 if (FAILED(IAudioClient_Initialize(_saudio.backend.audio_client,
1766 AUDCLNT_SHAREMODE_SHARED,
1767 AUDCLNT_STREAMFLAGS_EVENTCALLBACK|AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
1768 dur, 0, (WAVEFORMATEX*)&fmtex, 0)))
1769 {
1770 SAUDIO_LOG("sokol_audio wasapi: audio client initialize failed");
1771 goto error;
1772 }
1773 if (FAILED(IAudioClient_GetBufferSize(_saudio.backend.audio_client, &_saudio.backend.thread.dst_buffer_frames))) {
1774 SAUDIO_LOG("sokol_audio wasapi: audio client get buffer size failed");
1775 goto error;
1776 }
1777 if (FAILED(IAudioClient_GetService(_saudio.backend.audio_client,
1778 _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioRenderClient),
1779 (void**)&_saudio.backend.render_client)))
1780 {
1781 SAUDIO_LOG("sokol_audio wasapi: audio client GetService failed");
1782 goto error;
1783 }
1784 if (FAILED(IAudioClient_SetEventHandle(_saudio.backend.audio_client, _saudio.backend.thread.buffer_end_event))) {
1785 SAUDIO_LOG("sokol_audio wasapi: audio client SetEventHandle failed");
1786 goto error;
1787 }
1788 _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
1789 _saudio.backend.thread.src_buffer_frames = _saudio.buffer_frames;
1790 _saudio.backend.thread.src_buffer_byte_size = _saudio.backend.thread.src_buffer_frames * _saudio.bytes_per_frame;
1791
1792 /* allocate an intermediate buffer for sample format conversion */
1793 _saudio.backend.thread.src_buffer = (float*) _saudio_malloc((size_t)_saudio.backend.thread.src_buffer_byte_size);
1794
1795 /* create streaming thread */
1796 _saudio.backend.thread.thread_handle = CreateThread(NULL, 0, _saudio_wasapi_thread_fn, 0, 0, 0);
1797 if (0 == _saudio.backend.thread.thread_handle) {
1798 SAUDIO_LOG("sokol_audio wasapi: CreateThread failed");
1799 goto error;
1800 }
1801 return true;
1802error:
1803 _saudio_wasapi_release();
1804 return false;
1805}
1806
1807_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1808 if (_saudio.backend.thread.thread_handle) {
1809 _saudio.backend.thread.stop = true;
1810 SetEvent(_saudio.backend.thread.buffer_end_event);
1811 WaitForSingleObject(_saudio.backend.thread.thread_handle, INFINITE);
1812 CloseHandle(_saudio.backend.thread.thread_handle);
1813 _saudio.backend.thread.thread_handle = 0;
1814 }
1815 if (_saudio.backend.audio_client) {
1816 IAudioClient_Stop(_saudio.backend.audio_client);
1817 }
1818 _saudio_wasapi_release();
1819
1820 #if defined(_SAUDIO_WIN32)
1821 CoUninitialize();
1822 #endif
1823}
1824
1825/*=== EMSCRIPTEN BACKEND IMPLEMENTATION ======================================*/
1826#elif defined(_SAUDIO_EMSCRIPTEN)
1827
1828#ifdef __cplusplus
1829extern "C" {
1830#endif
1831
1832EMSCRIPTEN_KEEPALIVE int _saudio_emsc_pull(int num_frames) {
1833 SOKOL_ASSERT(_saudio.backend.buffer);
1834 if (num_frames == _saudio.buffer_frames) {
1835 if (_saudio_has_callback()) {
1836 _saudio_stream_callback((float*)_saudio.backend.buffer, num_frames, _saudio.num_channels);
1837 }
1838 else {
1839 const int num_bytes = num_frames * _saudio.bytes_per_frame;
1840 if (0 == _saudio_fifo_read(&_saudio.fifo, _saudio.backend.buffer, num_bytes)) {
1841 /* not enough read data available, fill the entire buffer with silence */
1842 _saudio_clear(_saudio.backend.buffer, (size_t)num_bytes);
1843 }
1844 }
1845 int res = (int) _saudio.backend.buffer;
1846 return res;
1847 }
1848 else {
1849 return 0;
1850 }
1851}
1852
1853#ifdef __cplusplus
1854} /* extern "C" */
1855#endif
1856
1857/* setup the WebAudio context and attach a ScriptProcessorNode */
1858EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size), {
1859 Module._saudio_context = null;
1860 Module._saudio_node = null;
1861 if (typeof AudioContext !== 'undefined') {
1862 Module._saudio_context = new AudioContext({
1863 sampleRate: sample_rate,
1864 latencyHint: 'interactive',
1865 });
1866 }
1867 else {
1868 Module._saudio_context = null;
1869 console.log('sokol_audio.h: no WebAudio support');
1870 }
1871 if (Module._saudio_context) {
1872 console.log('sokol_audio.h: sample rate ', Module._saudio_context.sampleRate);
1873 Module._saudio_node = Module._saudio_context.createScriptProcessor(buffer_size, 0, num_channels);
1874 Module._saudio_node.onaudioprocess = (event) => {
1875 const num_frames = event.outputBuffer.length;
1876 const ptr = __saudio_emsc_pull(num_frames);
1877 if (ptr) {
1878 const num_channels = event.outputBuffer.numberOfChannels;
1879 for (let chn = 0; chn < num_channels; chn++) {
1880 const chan = event.outputBuffer.getChannelData(chn);
1881 for (let i = 0; i < num_frames; i++) {
1882 chan[i] = HEAPF32[(ptr>>2) + ((num_channels*i)+chn)]
1883 }
1884 }
1885 }
1886 };
1887 Module._saudio_node.connect(Module._saudio_context.destination);
1888
1889 // in some browsers, WebAudio needs to be activated on a user action
1890 const resume_webaudio = () => {
1891 if (Module._saudio_context) {
1892 if (Module._saudio_context.state === 'suspended') {
1893 Module._saudio_context.resume();
1894 }
1895 }
1896 };
1897 document.addEventListener('click', resume_webaudio, {once:true});
1898 document.addEventListener('touchstart', resume_webaudio, {once:true});
1899 document.addEventListener('keydown', resume_webaudio, {once:true});
1900 return 1;
1901 }
1902 else {
1903 return 0;
1904 }
1905});
1906
1907/* shutdown the WebAudioContext and ScriptProcessorNode */
1908EM_JS(void, saudio_js_shutdown, (void), {
1909 \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F
1910 const ctx = Module._saudio_context;
1911 if (ctx !== null) {
1912 if (Module._saudio_node) {
1913 Module._saudio_node.disconnect();
1914 }
1915 ctx.close();
1916 Module._saudio_context = null;
1917 Module._saudio_node = null;
1918 }
1919});
1920
1921/* get the actual sample rate back from the WebAudio context */
1922EM_JS(int, saudio_js_sample_rate, (void), {
1923 if (Module._saudio_context) {
1924 return Module._saudio_context.sampleRate;
1925 }
1926 else {
1927 return 0;
1928 }
1929});
1930
1931/* get the actual buffer size in number of frames */
1932EM_JS(int, saudio_js_buffer_frames, (void), {
1933 if (Module._saudio_node) {
1934 return Module._saudio_node.bufferSize;
1935 }
1936 else {
1937 return 0;
1938 }
1939});
1940
1941/* return 1 if the WebAudio context is currently suspended, else 0 */
1942EM_JS(int, saudio_js_suspended, (void), {
1943 if (Module._saudio_context) {
1944 if (Module._saudio_context.state === 'suspended') {
1945 return 1;
1946 }
1947 else {
1948 return 0;
1949 }
1950 }
1951});
1952
1953_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1954 if (saudio_js_init(_saudio.sample_rate, _saudio.num_channels, _saudio.buffer_frames)) {
1955 _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels;
1956 _saudio.sample_rate = saudio_js_sample_rate();
1957 _saudio.buffer_frames = saudio_js_buffer_frames();
1958 const size_t buf_size = (size_t) (_saudio.buffer_frames * _saudio.bytes_per_frame);
1959 _saudio.backend.buffer = (uint8_t*) _saudio_malloc(buf_size);
1960 return true;
1961 }
1962 else {
1963 return false;
1964 }
1965}
1966
1967_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1968 saudio_js_shutdown();
1969 if (_saudio.backend.buffer) {
1970 _saudio_free(_saudio.backend.buffer);
1971 _saudio.backend.buffer = 0;
1972 }
1973}
1974
1975/*=== ANDROID BACKEND IMPLEMENTATION ======================================*/
1976#elif defined(_SAUDIO_ANDROID)
1977
1978#ifdef __cplusplus
1979extern "C" {
1980#endif
1981
1982_SOKOL_PRIVATE void _saudio_semaphore_init(_saudio_semaphore_t* sem) {
1983 sem->count = 0;
1984 int r = pthread_mutex_init(&sem->mutex, NULL);
1985 SOKOL_ASSERT(r == 0);
1986
1987 r = pthread_cond_init(&sem->cond, NULL);
1988 SOKOL_ASSERT(r == 0);
1989
1990 (void)(r);
1991}
1992
1993_SOKOL_PRIVATE void _saudio_semaphore_destroy(_saudio_semaphore_t* sem)
1994{
1995 pthread_cond_destroy(&sem->cond);
1996 pthread_mutex_destroy(&sem->mutex);
1997}
1998
1999_SOKOL_PRIVATE void _saudio_semaphore_post(_saudio_semaphore_t* sem, int count)
2000{
2001 int r = pthread_mutex_lock(&sem->mutex);
2002 SOKOL_ASSERT(r == 0);
2003
2004 for (int ii = 0; ii < count; ii++) {
2005 r = pthread_cond_signal(&sem->cond);
2006 SOKOL_ASSERT(r == 0);
2007 }
2008
2009 sem->count += count;
2010 r = pthread_mutex_unlock(&sem->mutex);
2011 SOKOL_ASSERT(r == 0);
2012
2013 (void)(r);
2014}
2015
2016_SOKOL_PRIVATE bool _saudio_semaphore_wait(_saudio_semaphore_t* sem)
2017{
2018 int r = pthread_mutex_lock(&sem->mutex);
2019 SOKOL_ASSERT(r == 0);
2020
2021 while (r == 0 && sem->count <= 0) {
2022 r = pthread_cond_wait(&sem->cond, &sem->mutex);
2023 }
2024
2025 bool ok = (r == 0);
2026 if (ok) {
2027 --sem->count;
2028 }
2029 r = pthread_mutex_unlock(&sem->mutex);
2030 (void)(r);
2031 return ok;
2032}
2033
2034/* fill intermediate buffer with new data and reset buffer_pos */
2035_SOKOL_PRIVATE void _saudio_opensles_fill_buffer(void) {
2036 int src_buffer_frames = _saudio.buffer_frames;
2037 if (_saudio_has_callback()) {
2038 _saudio_stream_callback(_saudio.backend.src_buffer, src_buffer_frames, _saudio.num_channels);
2039 }
2040 else {
2041 const int src_buffer_byte_size = src_buffer_frames * _saudio.num_channels * (int)sizeof(float);
2042 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.src_buffer, src_buffer_byte_size)) {
2043 /* not enough read data available, fill the entire buffer with silence */
2044 _saudio_clear(_saudio.backend.src_buffer, (size_t)src_buffer_byte_size);
2045 }
2046 }
2047}
2048
2049_SOKOL_PRIVATE void SLAPIENTRY _saudio_opensles_play_cb(SLPlayItf player, void *context, SLuint32 event) {
2050 (void)(context);
2051 (void)(player);
2052
2053 if (event & SL_PLAYEVENT_HEADATEND) {
2054 _saudio_semaphore_post(&_saudio.backend.buffer_sem, 1);
2055 }
2056}
2057
2058_SOKOL_PRIVATE void* _saudio_opensles_thread_fn(void* param) {
2059 _SOKOL_UNUSED(param);
2060 while (!_saudio.backend.thread_stop) {
2061 /* get next output buffer, advance, next buffer. */
2062 int16_t* out_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer];
2063 _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_NUM_BUFFERS;
2064 int16_t* next_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer];
2065
2066 /* queue this buffer */
2067 const int buffer_size_bytes = _saudio.buffer_frames * _saudio.num_channels * (int)sizeof(short);
2068 (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, out_buffer, (SLuint32)buffer_size_bytes);
2069
2070 /* fill the next buffer */
2071 _saudio_opensles_fill_buffer();
2072 const int num_samples = _saudio.num_channels * _saudio.buffer_frames;
2073 for (int i = 0; i < num_samples; ++i) {
2074 next_buffer[i] = (int16_t) (_saudio.backend.src_buffer[i] * 0x7FFF);
2075 }
2076
2077 _saudio_semaphore_wait(&_saudio.backend.buffer_sem);
2078 }
2079
2080 return 0;
2081}
2082
2083_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
2084 _saudio.backend.thread_stop = 1;
2085 pthread_join(_saudio.backend.thread, 0);
2086
2087 if (_saudio.backend.player_obj) {
2088 (*_saudio.backend.player_obj)->Destroy(_saudio.backend.player_obj);
2089 }
2090
2091 if (_saudio.backend.output_mix_obj) {
2092 (*_saudio.backend.output_mix_obj)->Destroy(_saudio.backend.output_mix_obj);
2093 }
2094
2095 if (_saudio.backend.engine_obj) {
2096 (*_saudio.backend.engine_obj)->Destroy(_saudio.backend.engine_obj);
2097 }
2098
2099 for (int i = 0; i < SAUDIO_NUM_BUFFERS; i++) {
2100 _saudio_free(_saudio.backend.output_buffers[i]);
2101 }
2102 _saudio_free(_saudio.backend.src_buffer);
2103}
2104
2105_SOKOL_PRIVATE bool _saudio_backend_init(void) {
2106 _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels;
2107
2108 for (int i = 0; i < SAUDIO_NUM_BUFFERS; ++i) {
2109 const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames;
2110 _saudio.backend.output_buffers[i] = (int16_t*) _saudio_malloc_clear((size_t)buffer_size_bytes);
2111 }
2112
2113 {
2114 const int buffer_size_bytes = _saudio.bytes_per_frame * _saudio.buffer_frames;
2115 _saudio.backend.src_buffer = (float*) _saudio_malloc_clear((size_t)buffer_size_bytes);
2116 }
2117
2118 /* Create engine */
2119 const SLEngineOption opts[] = { { SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE } };
2120 if (slCreateEngine(&_saudio.backend.engine_obj, 1, opts, 0, NULL, NULL ) != SL_RESULT_SUCCESS) {
2121 SAUDIO_LOG("sokol_audio opensles: slCreateEngine failed");
2122 _saudio_backend_shutdown();
2123 return false;
2124 }
2125
2126 (*_saudio.backend.engine_obj)->Realize(_saudio.backend.engine_obj, SL_BOOLEAN_FALSE);
2127 if ((*_saudio.backend.engine_obj)->GetInterface(_saudio.backend.engine_obj, SL_IID_ENGINE, &_saudio.backend.engine) != SL_RESULT_SUCCESS) {
2128 SAUDIO_LOG("sokol_audio opensles: GetInterface->Engine failed");
2129 _saudio_backend_shutdown();
2130 return false;
2131 }
2132
2133 /* Create output mix. */
2134 {
2135 const SLInterfaceID ids[] = { SL_IID_VOLUME };
2136 const SLboolean req[] = { SL_BOOLEAN_FALSE };
2137
2138 if( (*_saudio.backend.engine)->CreateOutputMix(_saudio.backend.engine, &_saudio.backend.output_mix_obj, 1, ids, req) != SL_RESULT_SUCCESS)
2139 {
2140 SAUDIO_LOG("sokol_audio opensles: CreateOutputMix failed");
2141 _saudio_backend_shutdown();
2142 return false;
2143 }
2144 (*_saudio.backend.output_mix_obj)->Realize(_saudio.backend.output_mix_obj, SL_BOOLEAN_FALSE);
2145
2146 if((*_saudio.backend.output_mix_obj)->GetInterface(_saudio.backend.output_mix_obj, SL_IID_VOLUME, &_saudio.backend.output_mix_vol) != SL_RESULT_SUCCESS) {
2147 SAUDIO_LOG("sokol_audio opensles: GetInterface->OutputMixVol failed");
2148 }
2149 }
2150
2151 /* android buffer queue */
2152 _saudio.backend.in_locator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
2153 _saudio.backend.in_locator.numBuffers = SAUDIO_NUM_BUFFERS;
2154
2155 /* data format */
2156 SLDataFormat_PCM format;
2157 format.formatType = SL_DATAFORMAT_PCM;
2158 format.numChannels = (SLuint32)_saudio.num_channels;
2159 format.samplesPerSec = (SLuint32) (_saudio.sample_rate * 1000);
2160 format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
2161 format.containerSize = 16;
2162 format.endianness = SL_BYTEORDER_LITTLEENDIAN;
2163
2164 if (_saudio.num_channels == 2) {
2165 format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
2166 } else {
2167 format.channelMask = SL_SPEAKER_FRONT_CENTER;
2168 }
2169
2170 SLDataSource src;
2171 src.pLocator = &_saudio.backend.in_locator;
2172 src.pFormat = &format;
2173
2174 /* Output mix. */
2175 _saudio.backend.out_locator.locatorType = SL_DATALOCATOR_OUTPUTMIX;
2176 _saudio.backend.out_locator.outputMix = _saudio.backend.output_mix_obj;
2177
2178 _saudio.backend.dst_data_sink.pLocator = &_saudio.backend.out_locator;
2179 _saudio.backend.dst_data_sink.pFormat = NULL;
2180
2181 /* setup player */
2182 {
2183 const SLInterfaceID ids[] = { SL_IID_VOLUME, SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
2184 const SLboolean req[] = { SL_BOOLEAN_FALSE, SL_BOOLEAN_TRUE };
2185
2186 (*_saudio.backend.engine)->CreateAudioPlayer(_saudio.backend.engine, &_saudio.backend.player_obj, &src, &_saudio.backend.dst_data_sink, sizeof(ids) / sizeof(ids[0]), ids, req);
2187
2188 (*_saudio.backend.player_obj)->Realize(_saudio.backend.player_obj, SL_BOOLEAN_FALSE);
2189
2190 (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_PLAY, &_saudio.backend.player);
2191 (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_VOLUME, &_saudio.backend.player_vol);
2192
2193 (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &_saudio.backend.player_buffer_queue);
2194 }
2195
2196 /* begin */
2197 {
2198 const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames;
2199 (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, _saudio.backend.output_buffers[0], (SLuint32)buffer_size_bytes);
2200 _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_NUM_BUFFERS;
2201
2202 (*_saudio.backend.player)->RegisterCallback(_saudio.backend.player, _saudio_opensles_play_cb, NULL);
2203 (*_saudio.backend.player)->SetCallbackEventsMask(_saudio.backend.player, SL_PLAYEVENT_HEADATEND);
2204 (*_saudio.backend.player)->SetPlayState(_saudio.backend.player, SL_PLAYSTATE_PLAYING);
2205 }
2206
2207 /* create the buffer-streaming start thread */
2208 if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_opensles_thread_fn, 0)) {
2209 _saudio_backend_shutdown();
2210 return false;
2211 }
2212
2213 return true;
2214}
2215
2216#ifdef __cplusplus
2217} /* extern "C" */
2218#endif
2219
2220#else
2221#error "unsupported platform"
2222#endif
2223
2224/*=== PUBLIC API FUNCTIONS ===================================================*/
2225SOKOL_API_IMPL void saudio_setup(const saudio_desc* desc) {
2226 SOKOL_ASSERT(!_saudio.valid);
2227 SOKOL_ASSERT(desc);
2228 SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free));
2229 _saudio_clear(&_saudio, sizeof(_saudio));
2230 _saudio.desc = *desc;
2231 _saudio.stream_cb = desc->stream_cb;
2232 _saudio.stream_userdata_cb = desc->stream_userdata_cb;
2233 _saudio.user_data = desc->user_data;
2234 _saudio.sample_rate = _saudio_def(_saudio.desc.sample_rate, _SAUDIO_DEFAULT_SAMPLE_RATE);
2235 _saudio.buffer_frames = _saudio_def(_saudio.desc.buffer_frames, _SAUDIO_DEFAULT_BUFFER_FRAMES);
2236 _saudio.packet_frames = _saudio_def(_saudio.desc.packet_frames, _SAUDIO_DEFAULT_PACKET_FRAMES);
2237 _saudio.num_packets = _saudio_def(_saudio.desc.num_packets, _SAUDIO_DEFAULT_NUM_PACKETS);
2238 _saudio.num_channels = _saudio_def(_saudio.desc.num_channels, 1);
2239 _saudio_fifo_init_mutex(&_saudio.fifo);
2240 if (_saudio_backend_init()) {
2241 /* the backend might not support the requested exact buffer size,
2242 make sure the actual buffer size is still a multiple of
2243 the requested packet size
2244 */
2245 if (0 != (_saudio.buffer_frames % _saudio.packet_frames)) {
2246 SAUDIO_LOG("sokol_audio.h: actual backend buffer size isn't multiple of requested packet size");
2247 _saudio_backend_shutdown();
2248 return;
2249 }
2250 SOKOL_ASSERT(_saudio.bytes_per_frame > 0);
2251 _saudio_fifo_init(&_saudio.fifo, _saudio.packet_frames * _saudio.bytes_per_frame, _saudio.num_packets);
2252 _saudio.valid = true;
2253 }
2254}
2255
2256SOKOL_API_IMPL void saudio_shutdown(void) {
2257 if (_saudio.valid) {
2258 _saudio_backend_shutdown();
2259 _saudio_fifo_shutdown(&_saudio.fifo);
2260 _saudio.valid = false;
2261 }
2262}
2263
2264SOKOL_API_IMPL bool saudio_isvalid(void) {
2265 return _saudio.valid;
2266}
2267
2268SOKOL_API_IMPL void* saudio_userdata(void) {
2269 return _saudio.desc.user_data;
2270}
2271
2272SOKOL_API_IMPL saudio_desc saudio_query_desc(void) {
2273 return _saudio.desc;
2274}
2275
2276SOKOL_API_IMPL int saudio_sample_rate(void) {
2277 return _saudio.sample_rate;
2278}
2279
2280SOKOL_API_IMPL int saudio_buffer_frames(void) {
2281 return _saudio.buffer_frames;
2282}
2283
2284SOKOL_API_IMPL int saudio_channels(void) {
2285 return _saudio.num_channels;
2286}
2287
2288SOKOL_API_IMPL bool saudio_suspended(void) {
2289 #if defined(_SAUDIO_EMSCRIPTEN)
2290 if (_saudio.valid) {
2291 return 1 == saudio_js_suspended();
2292 }
2293 else {
2294 return false;
2295 }
2296 #else
2297 return false;
2298 #endif
2299}
2300
2301SOKOL_API_IMPL int saudio_expect(void) {
2302 if (_saudio.valid) {
2303 const int num_frames = _saudio_fifo_writable_bytes(&_saudio.fifo) / _saudio.bytes_per_frame;
2304 return num_frames;
2305 }
2306 else {
2307 return 0;
2308 }
2309}
2310
2311SOKOL_API_IMPL int saudio_push(const float* frames, int num_frames) {
2312 SOKOL_ASSERT(frames && (num_frames > 0));
2313 if (_saudio.valid) {
2314 const int num_bytes = num_frames * _saudio.bytes_per_frame;
2315 const int num_written = _saudio_fifo_write(&_saudio.fifo, (const uint8_t*)frames, num_bytes);
2316 return num_written / _saudio.bytes_per_frame;
2317 }
2318 else {
2319 return 0;
2320 }
2321}
2322
2323#undef _saudio_def
2324#undef _saudio_def_flt
2325
2326#if defined(_SAUDIO_WINDOWS)
2327#ifdef _MSC_VER
2328#pragma warning(pop)
2329#endif
2330#endif
2331
2332#endif /* SOKOL_AUDIO_IMPL */