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 |
468 | extern "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 | */ |
479 | typedef 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 | */ |
491 | typedef struct saudio_logger { |
492 | void (*log_cb)(const char* message, void* user_data); |
493 | void* user_data; |
494 | } saudio_logger; |
495 | |
496 | typedef 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 */ |
510 | SOKOL_AUDIO_API_DECL void saudio_setup(const saudio_desc* desc); |
511 | /* shutdown sokol-audio */ |
512 | SOKOL_AUDIO_API_DECL void saudio_shutdown(void); |
513 | /* true after setup if audio backend was successfully initialized */ |
514 | SOKOL_AUDIO_API_DECL bool saudio_isvalid(void); |
515 | /* return the saudio_desc.user_data pointer */ |
516 | SOKOL_AUDIO_API_DECL void* saudio_userdata(void); |
517 | /* return a copy of the original saudio_desc struct */ |
518 | SOKOL_AUDIO_API_DECL saudio_desc saudio_query_desc(void); |
519 | /* actual sample rate */ |
520 | SOKOL_AUDIO_API_DECL int saudio_sample_rate(void); |
521 | /* return actual backend buffer size in number of frames */ |
522 | SOKOL_AUDIO_API_DECL int saudio_buffer_frames(void); |
523 | /* actual number of channels */ |
524 | SOKOL_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) */ |
526 | SOKOL_AUDIO_API_DECL bool saudio_suspended(void); |
527 | /* get current number of frames to fill packet queue */ |
528 | SOKOL_AUDIO_API_DECL int saudio_expect(void); |
529 | /* push sample frames from main thread, returns number of frames actually pushed */ |
530 | SOKOL_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++ */ |
536 | inline 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 | |
724 | typedef struct { |
725 | pthread_mutex_t mutex; |
726 | } _saudio_mutex_t; |
727 | |
728 | #elif defined(_SAUDIO_WINTHREADS) |
729 | |
730 | typedef struct { |
731 | CRITICAL_SECTION critsec; |
732 | } _saudio_mutex_t; |
733 | |
734 | #elif defined(_SAUDIO_NOTHREADS) |
735 | |
736 | typedef struct { |
737 | int dummy_mutex; |
738 | } _saudio_mutex_t; |
739 | |
740 | #endif |
741 | |
742 | /*=== DUMMY BACKEND DECLARATIONS =============================================*/ |
743 | #if defined(SOKOL_DUMMY_BACKEND) |
744 | |
745 | typedef 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 | |
754 | typedef AudioQueueRef _saudio_AudioQueueRef; |
755 | typedef AudioQueueBufferRef _saudio_AudioQueueBufferRef; |
756 | typedef AudioStreamBasicDescription _saudio_AudioStreamBasicDescription; |
757 | typedef 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 |
765 | extern "C" { |
766 | #endif |
767 | |
768 | // embedded AudioToolbox declarations |
769 | typedef uint32_t _saudio_AudioFormatID; |
770 | typedef uint32_t _saudio_AudioFormatFlags; |
771 | typedef int32_t _saudio_OSStatus; |
772 | typedef uint32_t _saudio_SMPTETimeType; |
773 | typedef uint32_t _saudio_SMPTETimeFlags; |
774 | typedef uint32_t _saudio_AudioTimeStampFlags; |
775 | typedef void* _saudio_CFRunLoopRef; |
776 | typedef void* _saudio_CFStringRef; |
777 | typedef void* _saudio_AudioQueueRef; |
778 | |
779 | #define _saudio_kAudioFormatLinearPCM ('lpcm') |
780 | #define _saudio_kLinearPCMFormatFlagIsFloat (1U << 0) |
781 | #define _saudio_kAudioFormatFlagIsPacked (1U << 3) |
782 | |
783 | typedef 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 | |
795 | typedef struct _saudio_AudioStreamPacketDescription { |
796 | int64_t mStartOffset; |
797 | uint32_t mVariableFramesInPacket; |
798 | uint32_t mDataByteSize; |
799 | } _saudio_AudioStreamPacketDescription; |
800 | |
801 | typedef 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 | |
813 | typedef 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 | |
823 | typedef 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; |
832 | typedef _saudio_AudioQueueBuffer* _saudio_AudioQueueBufferRef; |
833 | |
834 | typedef void (*_saudio_AudioQueueOutputCallback)(void* user_data, _saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer); |
835 | |
836 | extern _saudio_OSStatus AudioQueueNewOutput(const _saudio_AudioStreamBasicDescription* inFormat, _saudio_AudioQueueOutputCallback inCallbackProc, void* inUserData, _saudio_CFRunLoopRef inCallbackRunLoop, _saudio_CFStringRef inCallbackRunLoopMode, uint32_t inFlags, _saudio_AudioQueueRef* outAQ); |
837 | extern _saudio_OSStatus AudioQueueDispose(_saudio_AudioQueueRef inAQ, bool inImmediate); |
838 | extern _saudio_OSStatus AudioQueueAllocateBuffer(_saudio_AudioQueueRef inAQ, uint32_t inBufferByteSize, _saudio_AudioQueueBufferRef* outBuffer); |
839 | extern _saudio_OSStatus AudioQueueEnqueueBuffer(_saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer, uint32_t inNumPacketDescs, const _saudio_AudioStreamPacketDescription* inPacketDescs); |
840 | extern _saudio_OSStatus AudioQueueStart(_saudio_AudioQueueRef inAQ, const _saudio_AudioTimeStamp * inStartTime); |
841 | extern _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 | |
849 | typedef 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 | |
859 | typedef 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 | |
873 | typedef struct { |
874 | pthread_mutex_t mutex; |
875 | pthread_cond_t cond; |
876 | int count; |
877 | } _saudio_semaphore_t; |
878 | |
879 | typedef 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 | |
903 | typedef 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 | |
914 | typedef 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 | |
932 | typedef 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 */ |
943 | typedef 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 */ |
951 | typedef 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 */ |
964 | typedef 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 | |
980 | static _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; |
1494 | error: |
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; |
1802 | error: |
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 |
1829 | extern "C" { |
1830 | #endif |
1831 | |
1832 | EMSCRIPTEN_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 */ |
1858 | EM_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 */ |
1908 | EM_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 */ |
1922 | EM_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 */ |
1932 | EM_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 */ |
1942 | EM_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 |
1979 | extern "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 ===================================================*/ |
2225 | SOKOL_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 | |
2256 | SOKOL_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 | |
2264 | SOKOL_API_IMPL bool saudio_isvalid(void) { |
2265 | return _saudio.valid; |
2266 | } |
2267 | |
2268 | SOKOL_API_IMPL void* saudio_userdata(void) { |
2269 | return _saudio.desc.user_data; |
2270 | } |
2271 | |
2272 | SOKOL_API_IMPL saudio_desc saudio_query_desc(void) { |
2273 | return _saudio.desc; |
2274 | } |
2275 | |
2276 | SOKOL_API_IMPL int saudio_sample_rate(void) { |
2277 | return _saudio.sample_rate; |
2278 | } |
2279 | |
2280 | SOKOL_API_IMPL int saudio_buffer_frames(void) { |
2281 | return _saudio.buffer_frames; |
2282 | } |
2283 | |
2284 | SOKOL_API_IMPL int saudio_channels(void) { |
2285 | return _saudio.num_channels; |
2286 | } |
2287 | |
2288 | SOKOL_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 | |
2301 | SOKOL_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 | |
2311 | SOKOL_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 */ |