Skip to content

Latest commit

 

History

History
389 lines (336 loc) · 15 KB

SDL_emscriptenaudio.c

File metadata and controls

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