SDL 2.0
SDL_emscriptenaudio.c
Go to the documentation of this file.
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "../../SDL_internal.h"
22
23#if SDL_AUDIO_DRIVER_EMSCRIPTEN
24
25#include "SDL_audio.h"
26#include "SDL_log.h"
27#include "../SDL_audio_c.h"
28#include "SDL_emscriptenaudio.h"
29#include "SDL_assert.h"
30
31#include <emscripten/emscripten.h>
32
33static void
34FeedAudioDevice(_THIS, const void *buf, const int buflen)
35{
36 const int framelen = (SDL_AUDIO_BITSIZE(this->spec.format) / 8) * this->spec.channels;
37 EM_ASM_ARGS({
38 var SDL2 = Module['SDL2'];
39 var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels'];
40 for (var c = 0; c < numChannels; ++c) {
41 var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c);
42 if (channelData.length != $1) {
43 throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
44 }
45
46 for (var j = 0; j < $1; ++j) {
47 channelData[j] = HEAPF32[$0 + ((j*numChannels + c) << 2) >> 2]; /* !!! FIXME: why are these shifts here? */
48 }
49 }
50 }, buf, buflen / framelen);
51}
52
53static void
54HandleAudioProcess(_THIS)
55{
56 SDL_AudioCallback callback = this->callbackspec.callback;
57 const int stream_len = this->callbackspec.size;
58
59 /* Only do something if audio is enabled */
60 if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
61 if (this->stream) {
63 }
64 return;
65 }
66
67 if (this->stream == NULL) { /* no conversion necessary. */
68 SDL_assert(this->spec.size == stream_len);
69 callback(this->callbackspec.userdata, this->work_buffer, stream_len);
70 } else { /* streaming/converting */
71 int got;
72 while (SDL_AudioStreamAvailable(this->stream) < ((int) this->spec.size)) {
73 callback(this->callbackspec.userdata, this->work_buffer, stream_len);
74 if (SDL_AudioStreamPut(this->stream, this->work_buffer, stream_len) == -1) {
76 SDL_AtomicSet(&this->enabled, 0);
77 break;
78 }
79 }
80
81 got = SDL_AudioStreamGet(this->stream, this->work_buffer, this->spec.size);
82 SDL_assert((got < 0) || (got == this->spec.size));
83 if (got != this->spec.size) {
84 SDL_memset(this->work_buffer, this->spec.silence, this->spec.size);
85 }
86 }
87
88 FeedAudioDevice(this, this->work_buffer, this->spec.size);
89}
90
91static void
92HandleCaptureProcess(_THIS)
93{
94 SDL_AudioCallback callback = this->callbackspec.callback;
95 const int stream_len = this->callbackspec.size;
96
97 /* Only do something if audio is enabled */
98 if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
100 return;
101 }
102
103 EM_ASM_ARGS({
104 var SDL2 = Module['SDL2'];
105 var numChannels = SDL2.capture.currentCaptureBuffer.numberOfChannels;
106 for (var c = 0; c < numChannels; ++c) {
107 var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(c);
108 if (channelData.length != $1) {
109 throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
110 }
111
112 if (numChannels == 1) { /* fastpath this a little for the common (mono) case. */
113 for (var j = 0; j < $1; ++j) {
114 setValue($0 + (j * 4), channelData[j], 'float');
115 }
116 } else {
117 for (var j = 0; j < $1; ++j) {
118 setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float');
119 }
120 }
121 }
122 }, this->work_buffer, (this->spec.size / sizeof (float)) / this->spec.channels);
123
124 /* okay, we've got an interleaved float32 array in C now. */
125
126 if (this->stream == NULL) { /* no conversion necessary. */
127 SDL_assert(this->spec.size == stream_len);
128 callback(this->callbackspec.userdata, this->work_buffer, stream_len);
129 } else { /* streaming/converting */
130 if (SDL_AudioStreamPut(this->stream, this->work_buffer, this->spec.size) == -1) {
131 SDL_AtomicSet(&this->enabled, 0);
132 }
133
134 while (SDL_AudioStreamAvailable(this->stream) >= stream_len) {
135 const int got = SDL_AudioStreamGet(this->stream, this->work_buffer, stream_len);
136 SDL_assert((got < 0) || (got == stream_len));
137 if (got != stream_len) {
138 SDL_memset(this->work_buffer, this->callbackspec.silence, stream_len);
139 }
140 callback(this->callbackspec.userdata, this->work_buffer, stream_len); /* Send it to the app. */
141 }
142 }
143}
144
145
146static void
147EMSCRIPTENAUDIO_CloseDevice(_THIS)
148{
149 EM_ASM_({
150 var SDL2 = Module['SDL2'];
151 if ($0) {
152 if (SDL2.capture.silenceTimer !== undefined) {
153 clearTimeout(SDL2.capture.silenceTimer);
154 }
155 if (SDL2.capture.stream !== undefined) {
156 var tracks = SDL2.capture.stream.getAudioTracks();
157 for (var i = 0; i < tracks.length; i++) {
158 SDL2.capture.stream.removeTrack(tracks[i]);
159 }
160 SDL2.capture.stream = undefined;
161 }
162 if (SDL2.capture.scriptProcessorNode !== undefined) {
163 SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {};
164 SDL2.capture.scriptProcessorNode.disconnect();
165 SDL2.capture.scriptProcessorNode = undefined;
166 }
167 if (SDL2.capture.mediaStreamNode !== undefined) {
168 SDL2.capture.mediaStreamNode.disconnect();
169 SDL2.capture.mediaStreamNode = undefined;
170 }
171 if (SDL2.capture.silenceBuffer !== undefined) {
172 SDL2.capture.silenceBuffer = undefined
173 }
174 SDL2.capture = undefined;
175 } else {
176 if (SDL2.audio.scriptProcessorNode != undefined) {
177 SDL2.audio.scriptProcessorNode.disconnect();
178 SDL2.audio.scriptProcessorNode = undefined;
179 }
180 SDL2.audio = undefined;
181 }
182 if ((SDL2.audioContext !== undefined) && (SDL2.audio === undefined) && (SDL2.capture === undefined)) {
183 SDL2.audioContext.close();
184 SDL2.audioContext = undefined;
185 }
186 }, this->iscapture);
187
188#if 0 /* !!! FIXME: currently not used. Can we move some stuff off the SDL2 namespace? --ryan. */
189 SDL_free(this->hidden);
190#endif
191}
192
193static int
194EMSCRIPTENAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
195{
196 SDL_bool valid_format = SDL_FALSE;
197 SDL_AudioFormat test_format;
198 int result;
199
200 /* based on parts of library_sdl.js */
201
202 /* create context */
203 result = EM_ASM_INT({
204 if(typeof(Module['SDL2']) === 'undefined') {
205 Module['SDL2'] = {};
206 }
207 var SDL2 = Module['SDL2'];
208 if (!$0) {
209 SDL2.audio = {};
210 } else {
211 SDL2.capture = {};
212 }
213
214 if (!SDL2.audioContext) {
215 if (typeof(AudioContext) !== 'undefined') {
216 SDL2.audioContext = new AudioContext();
217 } else if (typeof(webkitAudioContext) !== 'undefined') {
218 SDL2.audioContext = new webkitAudioContext();
219 }
220 }
221 return SDL2.audioContext === undefined ? -1 : 0;
222 }, iscapture);
223 if (result < 0) {
224 return SDL_SetError("Web Audio API is not available!");
225 }
226
227 test_format = SDL_FirstAudioFormat(this->spec.format);
228 while ((!valid_format) && (test_format)) {
229 switch (test_format) {
230 case AUDIO_F32: /* web audio only supports floats */
231 this->spec.format = test_format;
232
233 valid_format = SDL_TRUE;
234 break;
235 }
236 test_format = SDL_NextAudioFormat();
237 }
238
239 if (!valid_format) {
240 /* Didn't find a compatible format :( */
241 return SDL_SetError("No compatible audio format!");
242 }
243
244 /* Initialize all variables that we clean on shutdown */
245#if 0 /* !!! FIXME: currently not used. Can we move some stuff off the SDL2 namespace? --ryan. */
246 this->hidden = (struct SDL_PrivateAudioData *)
247 SDL_malloc((sizeof *this->hidden));
248 if (this->hidden == NULL) {
249 return SDL_OutOfMemory();
250 }
251 SDL_zerop(this->hidden);
252#endif
253 this->hidden = (struct SDL_PrivateAudioData *)0x1;
254
255 /* limit to native freq */
256 this->spec.freq = EM_ASM_INT_V({
257 var SDL2 = Module['SDL2'];
258 return SDL2.audioContext.sampleRate;
259 });
260
262
263 if (iscapture) {
264 /* The idea is to take the capture media stream, hook it up to an
265 audio graph where we can pass it through a ScriptProcessorNode
266 to access the raw PCM samples and push them to the SDL app's
267 callback. From there, we "process" the audio data into silence
268 and forget about it. */
269
270 /* This should, strictly speaking, use MediaRecorder for capture, but
271 this API is cleaner to use and better supported, and fires a
272 callback whenever there's enough data to fire down into the app.
273 The downside is that we are spending CPU time silencing a buffer
274 that the audiocontext uselessly mixes into any output. On the
275 upside, both of those things are not only run in native code in
276 the browser, they're probably SIMD code, too. MediaRecorder
277 feels like it's a pretty inefficient tapdance in similar ways,
278 to be honest. */
279
280 EM_ASM_({
281 var SDL2 = Module['SDL2'];
282 var have_microphone = function(stream) {
283 //console.log('SDL audio capture: we have a microphone! Replacing silence callback.');
284 if (SDL2.capture.silenceTimer !== undefined) {
285 clearTimeout(SDL2.capture.silenceTimer);
286 SDL2.capture.silenceTimer = undefined;
287 }
288 SDL2.capture.mediaStreamNode = SDL2.audioContext.createMediaStreamSource(stream);
289 SDL2.capture.scriptProcessorNode = SDL2.audioContext.createScriptProcessor($1, $0, 1);
290 SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {
291 if ((SDL2 === undefined) || (SDL2.capture === undefined)) { return; }
292 audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0);
293 SDL2.capture.currentCaptureBuffer = audioProcessingEvent.inputBuffer;
294 dynCall('vi', $2, [$3]);
295 };
296 SDL2.capture.mediaStreamNode.connect(SDL2.capture.scriptProcessorNode);
297 SDL2.capture.scriptProcessorNode.connect(SDL2.audioContext.destination);
298 SDL2.capture.stream = stream;
299 };
300
301 var no_microphone = function(error) {
302 //console.log('SDL audio capture: we DO NOT have a microphone! (' + error.name + ')...leaving silence callback running.');
303 };
304
305 /* we write silence to the audio callback until the microphone is available (user approves use, etc). */
306 SDL2.capture.silenceBuffer = SDL2.audioContext.createBuffer($0, $1, SDL2.audioContext.sampleRate);
307 SDL2.capture.silenceBuffer.getChannelData(0).fill(0.0);
308 var silence_callback = function() {
309 SDL2.capture.currentCaptureBuffer = SDL2.capture.silenceBuffer;
310 dynCall('vi', $2, [$3]);
311 };
312
313 SDL2.capture.silenceTimer = setTimeout(silence_callback, ($1 / SDL2.audioContext.sampleRate) * 1000);
314
315 if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) {
316 navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone);
317 } else if (navigator.webkitGetUserMedia !== undefined) {
318 navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone);
319 }
320 }, this->spec.channels, this->spec.samples, HandleCaptureProcess, this);
321 } else {
322 /* setup a ScriptProcessorNode */
323 EM_ASM_ARGS({
324 var SDL2 = Module['SDL2'];
325 SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0);
326 SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
327 if ((SDL2 === undefined) || (SDL2.audio === undefined)) { return; }
328 SDL2.audio.currentOutputBuffer = e['outputBuffer'];
329 dynCall('vi', $2, [$3]);
330 };
331 SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']);
332 }, this->spec.channels, this->spec.samples, HandleAudioProcess, this);
333 }
334
335 return 0;
336}
337
338static int
339EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl * impl)
340{
341 int available;
342 int capture_available;
343
344 /* Set the function pointers */
345 impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice;
346 impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice;
347
349
350 /* no threads here */
351 impl->SkipMixerLock = 1;
353
354 /* check availability */
355 available = EM_ASM_INT_V({
356 if (typeof(AudioContext) !== 'undefined') {
357 return 1;
358 } else if (typeof(webkitAudioContext) !== 'undefined') {
359 return 1;
360 }
361 return 0;
362 });
363
364 if (!available) {
365 SDL_SetError("No audio context available");
366 }
367
368 capture_available = available && EM_ASM_INT_V({
369 if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) {
370 return 1;
371 } else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') {
372 return 1;
373 }
374 return 0;
375 });
376
377 impl->HasCaptureSupport = capture_available ? SDL_TRUE : SDL_FALSE;
378 impl->OnlyHasDefaultCaptureDevice = capture_available ? SDL_TRUE : SDL_FALSE;
379
380 return available;
381}
382
384 "emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, 0
385};
386
387#endif /* SDL_AUDIO_DRIVER_EMSCRIPTEN */
388
389/* vi: set ts=4 sw=4 expandtab: */
#define _THIS
#define SDL_assert(condition)
Definition: SDL_assert.h:169
void SDL_CalculateAudioSpec(SDL_AudioSpec *spec)
Definition: SDL_audio.c:1668
SDL_AudioFormat SDL_FirstAudioFormat(SDL_AudioFormat format)
Definition: SDL_audio.c:1647
SDL_AudioFormat SDL_NextAudioFormat(void)
Definition: SDL_audio.c:1659
#define AUDIO_F32
Definition: SDL_audio.h:114
Uint16 SDL_AudioFormat
Audio format flags.
Definition: SDL_audio.h:64
#define SDL_AUDIO_BITSIZE(x)
Definition: SDL_audio.h:75
void(* SDL_AudioCallback)(void *userdata, Uint8 *stream, int len)
Definition: SDL_audio.h:163
#define SDL_AtomicSet
#define SDL_SetError
#define SDL_memset
#define SDL_AudioStreamGet
#define SDL_AudioStreamClear
#define SDL_AudioStreamAvailable
#define SDL_malloc
#define SDL_free
#define SDL_AudioStreamPut
#define SDL_AtomicGet
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 SDL_AssertionHandler void SDL_SpinLock SDL_atomic_t int int return SDL_atomic_t return void void void return void return int return SDL_AudioSpec SDL_AudioSpec return int int return return int SDL_RWops int SDL_AudioSpec Uint8 Uint32 * e
#define SDL_OutOfMemory()
Definition: SDL_error.h:52
GLuint GLuint stream
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLuint64EXT * result
const GLubyte * c
GLenum GLuint GLenum GLsizei const GLchar * buf
SDL_bool
Definition: SDL_stdinc.h:162
@ SDL_TRUE
Definition: SDL_stdinc.h:164
@ SDL_FALSE
Definition: SDL_stdinc.h:163
#define SDL_zerop(x)
Definition: SDL_stdinc.h:417
AudioBootStrap EMSCRIPTENAUDIO_bootstrap
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int int in j)
Definition: SDL_x11sym.h:50
#define NULL
Definition: begin_code.h:167
EGLImageKHR EGLint EGLint * handle
Definition: eglext.h:937
SDL_AudioSpec spec
Definition: loopwave.c:31
void(* CloseDevice)(_THIS)
Definition: SDL_sysaudio.h:78
int(* OpenDevice)(_THIS, void *handle, const char *devname, int iscapture)
Definition: SDL_sysaudio.h:68
Uint32 size
Definition: SDL_audio.h:186
Uint16 samples
Definition: SDL_audio.h:184
Uint8 channels
Definition: SDL_audio.h:182
Uint8 silence
Definition: SDL_audio.h:183
SDL_AudioFormat format
Definition: SDL_audio.h:181
int paused
Definition: testoverlay2.c:147
static Uint32 callback(Uint32 interval, void *param)
Definition: testtimer.c:34
static int available()
Definition: video.c:356