Skip to content

Latest commit

 

History

History
379 lines (326 loc) · 14.7 KB

SDL_emscriptenaudio.c

File metadata and controls

379 lines (326 loc) · 14.7 KB
 
1
2
/*
Simple DirectMedia Layer
Jan 3, 2018
Jan 3, 2018
3
Copyright (C) 1997-2018 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
38
39
40
41
42
43
const int framelen = (SDL_AUDIO_BITSIZE(this->spec.format) / 8) * this->spec.channels;
EM_ASM_ARGS({
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
45
46
47
48
49
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);
50
51
52
53
54
}
static void
HandleAudioProcess(_THIS)
{
May 10, 2017
May 10, 2017
55
SDL_AudioCallback callback = this->callbackspec.callback;
Jan 6, 2017
Jan 6, 2017
56
const int stream_len = this->callbackspec.size;
Aug 2, 2016
Aug 2, 2016
58
59
/* Only do something if audio is enabled */
if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
Jan 6, 2017
Jan 6, 2017
60
61
62
if (this->stream) {
SDL_AudioStreamClear(this->stream);
}
Aug 2, 2016
Aug 2, 2016
64
}
Jan 6, 2017
Jan 6, 2017
66
67
if (this->stream == NULL) { /* no conversion necessary. */
SDL_assert(this->spec.size == stream_len);
May 10, 2017
May 10, 2017
68
callback(this->callbackspec.userdata, this->work_buffer, stream_len);
Jan 6, 2017
Jan 6, 2017
69
70
71
} else { /* streaming/converting */
int got;
while (SDL_AudioStreamAvailable(this->stream) < ((int) this->spec.size)) {
May 10, 2017
May 10, 2017
72
callback(this->callbackspec.userdata, this->work_buffer, stream_len);
Jan 6, 2017
Jan 6, 2017
73
if (SDL_AudioStreamPut(this->stream, this->work_buffer, stream_len) == -1) {
Jan 6, 2017
Jan 6, 2017
74
75
SDL_AudioStreamClear(this->stream);
SDL_AtomicSet(&this->enabled, 0);
Jan 6, 2017
Jan 6, 2017
76
break;
Jan 6, 2017
Jan 6, 2017
80
got = SDL_AudioStreamGet(this->stream, this->work_buffer, this->spec.size);
Jan 6, 2017
Jan 6, 2017
81
82
SDL_assert((got < 0) || (got == this->spec.size));
if (got != this->spec.size) {
Jan 6, 2017
Jan 6, 2017
83
SDL_memset(this->work_buffer, this->spec.silence, this->spec.size);
Jan 6, 2017
Jan 6, 2017
87
FeedAudioDevice(this, this->work_buffer, this->spec.size);
Aug 9, 2016
Aug 9, 2016
90
91
92
static void
HandleCaptureProcess(_THIS)
{
May 10, 2017
May 10, 2017
93
SDL_AudioCallback callback = this->callbackspec.callback;
Jan 6, 2017
Jan 6, 2017
94
const int stream_len = this->callbackspec.size;
Aug 9, 2016
Aug 9, 2016
95
96
97
/* Only do something if audio is enabled */
if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
Jan 6, 2017
Jan 6, 2017
98
SDL_AudioStreamClear(this->stream);
Aug 9, 2016
Aug 9, 2016
99
100
101
102
103
return;
}
EM_ASM_ARGS({
var numChannels = SDL2.capture.currentCaptureBuffer.numberOfChannels;
Jan 6, 2017
Jan 6, 2017
104
105
for (var c = 0; c < numChannels; ++c) {
var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(c);
Aug 9, 2016
Aug 9, 2016
106
107
108
109
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
110
111
112
113
114
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
115
116
117
118
119
for (var j = 0; j < $1; ++j) {
setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float');
}
}
}
Jan 6, 2017
Jan 6, 2017
120
}, this->work_buffer, (this->spec.size / sizeof (float)) / this->spec.channels);
Aug 9, 2016
Aug 9, 2016
121
122
123
/* okay, we've got an interleaved float32 array in C now. */
Jan 6, 2017
Jan 6, 2017
124
125
if (this->stream == NULL) { /* no conversion necessary. */
SDL_assert(this->spec.size == stream_len);
May 10, 2017
May 10, 2017
126
callback(this->callbackspec.userdata, this->work_buffer, stream_len);
Jan 6, 2017
Jan 6, 2017
127
} else { /* streaming/converting */
Jan 6, 2017
Jan 6, 2017
128
if (SDL_AudioStreamPut(this->stream, this->work_buffer, this->spec.size) == -1) {
Jan 6, 2017
Jan 6, 2017
129
130
SDL_AtomicSet(&this->enabled, 0);
}
Aug 9, 2016
Aug 9, 2016
131
Jan 6, 2017
Jan 6, 2017
132
while (SDL_AudioStreamAvailable(this->stream) >= stream_len) {
Jan 6, 2017
Jan 6, 2017
133
const int got = SDL_AudioStreamGet(this->stream, this->work_buffer, stream_len);
Jan 6, 2017
Jan 6, 2017
134
135
SDL_assert((got < 0) || (got == stream_len));
if (got != stream_len) {
Jan 6, 2017
Jan 6, 2017
136
SDL_memset(this->work_buffer, this->callbackspec.silence, stream_len);
Jan 6, 2017
Jan 6, 2017
137
}
May 10, 2017
May 10, 2017
138
callback(this->callbackspec.userdata, this->work_buffer, stream_len); /* Send it to the app. */
Jan 6, 2017
Jan 6, 2017
139
140
}
}
Aug 9, 2016
Aug 9, 2016
141
142
143
}
Aug 12, 2016
Aug 12, 2016
145
EMSCRIPTENAUDIO_CloseDevice(_THIS)
Aug 9, 2016
Aug 9, 2016
147
148
149
150
151
EM_ASM_({
if ($0) {
if (SDL2.capture.silenceTimer !== undefined) {
clearTimeout(SDL2.capture.silenceTimer);
}
Aug 31, 2016
Aug 31, 2016
152
153
154
155
156
157
158
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
159
if (SDL2.capture.scriptProcessorNode !== undefined) {
Aug 31, 2016
Aug 31, 2016
160
SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {};
Aug 9, 2016
Aug 9, 2016
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
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
185
#if 0 /* !!! FIXME: currently not used. Can we move some stuff off the SDL2 namespace? --ryan. */
Aug 5, 2016
Aug 5, 2016
186
SDL_free(this->hidden);
Jan 6, 2017
Jan 6, 2017
187
#endif
Aug 12, 2016
Aug 12, 2016
191
EMSCRIPTENAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
192
193
{
SDL_bool valid_format = SDL_FALSE;
Aug 9, 2016
Aug 9, 2016
194
SDL_AudioFormat test_format;
Aug 9, 2016
Aug 9, 2016
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
/* based on parts of library_sdl.js */
/* create context (TODO: this puts stuff in the global namespace...)*/
result = EM_ASM_INT({
if(typeof(SDL2) === 'undefined') {
SDL2 = {};
}
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);
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
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
241
#if 0 /* !!! FIXME: currently not used. Can we move some stuff off the SDL2 namespace? --ryan. */
242
243
244
245
246
this->hidden = (struct SDL_PrivateAudioData *)
SDL_malloc((sizeof *this->hidden));
if (this->hidden == NULL) {
return SDL_OutOfMemory();
}
Aug 5, 2016
Aug 5, 2016
247
SDL_zerop(this->hidden);
Jan 6, 2017
Jan 6, 2017
248
#endif
249
250
/* limit to native freq */
Jan 6, 2017
Jan 6, 2017
251
this->spec.freq = EM_ASM_INT_V({ return SDL2.audioContext.sampleRate; });
252
253
254
SDL_CalculateAudioSpec(&this->spec);
Aug 9, 2016
Aug 9, 2016
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
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_({
var have_microphone = function(stream) {
Aug 12, 2016
Aug 12, 2016
274
275
276
277
278
//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
279
280
281
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
282
if ((SDL2 === undefined) || (SDL2.capture === undefined)) { return; }
Aug 9, 2016
Aug 9, 2016
283
284
285
286
287
288
audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0);
SDL2.capture.currentCaptureBuffer = audioProcessingEvent.inputBuffer;
Runtime.dynCall('vi', $2, [$3]);
};
SDL2.capture.mediaStreamNode.connect(SDL2.capture.scriptProcessorNode);
SDL2.capture.scriptProcessorNode.connect(SDL2.audioContext.destination);
Aug 31, 2016
Aug 31, 2016
289
SDL2.capture.stream = stream;
Aug 9, 2016
Aug 9, 2016
290
291
292
};
var no_microphone = function(error) {
Aug 12, 2016
Aug 12, 2016
293
//console.log('SDL audio capture: we DO NOT have a microphone! (' + error.name + ')...leaving silence callback running.');
Aug 9, 2016
Aug 9, 2016
294
295
296
297
298
299
300
301
302
303
};
/* 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;
Runtime.dynCall('vi', $2, [$3]);
};
Aug 10, 2016
Aug 10, 2016
304
SDL2.capture.silenceTimer = setTimeout(silence_callback, ($1 / SDL2.audioContext.sampleRate) * 1000);
Aug 9, 2016
Aug 9, 2016
305
306
307
308
309
310
311
312
313
314
315
316
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({
SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0);
SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
Sep 18, 2016
Sep 18, 2016
317
if ((SDL2 === undefined) || (SDL2.audio === undefined)) { return; }
Aug 9, 2016
Aug 9, 2016
318
319
320
321
322
323
324
SDL2.audio.currentOutputBuffer = e['outputBuffer'];
Runtime.dynCall('vi', $2, [$3]);
};
SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']);
}, this->spec.channels, this->spec.samples, HandleAudioProcess, this);
}
325
326
327
328
return 0;
}
static int
Aug 12, 2016
Aug 12, 2016
329
EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl * impl)
May 26, 2017
May 26, 2017
331
332
333
int available;
int capture_available;
334
/* Set the function pointers */
Aug 12, 2016
Aug 12, 2016
335
336
impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice;
impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice;
337
338
339
340
341
342
343
344
impl->OnlyHasDefaultOutputDevice = 1;
/* no threads here */
impl->SkipMixerLock = 1;
impl->ProvidesOwnCallbackThread = 1;
/* check availability */
May 26, 2017
May 26, 2017
345
available = EM_ASM_INT_V({
346
347
348
349
350
351
352
353
if (typeof(AudioContext) !== 'undefined') {
return 1;
} else if (typeof(webkitAudioContext) !== 'undefined') {
return 1;
}
return 0;
});
Aug 5, 2015
Aug 5, 2015
354
355
356
357
if (!available) {
SDL_SetError("No audio context available");
}
May 26, 2017
May 26, 2017
358
capture_available = available && EM_ASM_INT_V({
Aug 9, 2016
Aug 9, 2016
359
360
361
362
363
364
365
366
367
368
369
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;
370
371
372
return available;
}
Aug 12, 2016
Aug 12, 2016
373
374
AudioBootStrap EMSCRIPTENAUDIO_bootstrap = {
"emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, 0
375
376
377
378
379
};
#endif /* SDL_AUDIO_DRIVER_EMSCRIPTEN */
/* vi: set ts=4 sw=4 expandtab: */