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