SDL  2.0
SDL_emscriptenaudio.c
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2020 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_audio_c.h"
27 #include "SDL_emscriptenaudio.h"
28 
29 #include <emscripten/emscripten.h>
30 
31 static void
32 FeedAudioDevice(_THIS, const void *buf, const int buflen)
33 {
34  const int framelen = (SDL_AUDIO_BITSIZE(this->spec.format) / 8) * this->spec.channels;
35  EM_ASM_ARGS({
36  var SDL2 = Module['SDL2'];
37  var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels'];
38  for (var c = 0; c < numChannels; ++c) {
39  var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c);
40  if (channelData.length != $1) {
41  throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
42  }
43 
44  for (var j = 0; j < $1; ++j) {
45  channelData[j] = HEAPF32[$0 + ((j*numChannels + c) << 2) >> 2]; /* !!! FIXME: why are these shifts here? */
46  }
47  }
48  }, buf, buflen / framelen);
49 }
50 
51 static void
52 HandleAudioProcess(_THIS)
53 {
54  SDL_AudioCallback callback = this->callbackspec.callback;
55  const int stream_len = this->callbackspec.size;
56 
57  /* Only do something if audio is enabled */
58  if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
59  if (this->stream) {
61  }
62  return;
63  }
64 
65  if (this->stream == NULL) { /* no conversion necessary. */
66  SDL_assert(this->spec.size == stream_len);
67  callback(this->callbackspec.userdata, this->work_buffer, stream_len);
68  } else { /* streaming/converting */
69  int got;
70  while (SDL_AudioStreamAvailable(this->stream) < ((int) this->spec.size)) {
71  callback(this->callbackspec.userdata, this->work_buffer, stream_len);
72  if (SDL_AudioStreamPut(this->stream, this->work_buffer, stream_len) == -1) {
74  SDL_AtomicSet(&this->enabled, 0);
75  break;
76  }
77  }
78 
79  got = SDL_AudioStreamGet(this->stream, this->work_buffer, this->spec.size);
80  SDL_assert((got < 0) || (got == this->spec.size));
81  if (got != this->spec.size) {
82  SDL_memset(this->work_buffer, this->spec.silence, this->spec.size);
83  }
84  }
85 
86  FeedAudioDevice(this, this->work_buffer, this->spec.size);
87 }
88 
89 static void
90 HandleCaptureProcess(_THIS)
91 {
92  SDL_AudioCallback callback = this->callbackspec.callback;
93  const int stream_len = this->callbackspec.size;
94 
95  /* Only do something if audio is enabled */
96  if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
98  return;
99  }
100 
101  EM_ASM_ARGS({
102  var SDL2 = Module['SDL2'];
103  var numChannels = SDL2.capture.currentCaptureBuffer.numberOfChannels;
104  for (var c = 0; c < numChannels; ++c) {
105  var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(c);
106  if (channelData.length != $1) {
107  throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
108  }
109 
110  if (numChannels == 1) { /* fastpath this a little for the common (mono) case. */
111  for (var j = 0; j < $1; ++j) {
112  setValue($0 + (j * 4), channelData[j], 'float');
113  }
114  } else {
115  for (var j = 0; j < $1; ++j) {
116  setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float');
117  }
118  }
119  }
120  }, this->work_buffer, (this->spec.size / sizeof (float)) / this->spec.channels);
121 
122  /* okay, we've got an interleaved float32 array in C now. */
123 
124  if (this->stream == NULL) { /* no conversion necessary. */
125  SDL_assert(this->spec.size == stream_len);
126  callback(this->callbackspec.userdata, this->work_buffer, stream_len);
127  } else { /* streaming/converting */
128  if (SDL_AudioStreamPut(this->stream, this->work_buffer, this->spec.size) == -1) {
129  SDL_AtomicSet(&this->enabled, 0);
130  }
131 
132  while (SDL_AudioStreamAvailable(this->stream) >= stream_len) {
133  const int got = SDL_AudioStreamGet(this->stream, this->work_buffer, stream_len);
134  SDL_assert((got < 0) || (got == stream_len));
135  if (got != stream_len) {
136  SDL_memset(this->work_buffer, this->callbackspec.silence, stream_len);
137  }
138  callback(this->callbackspec.userdata, this->work_buffer, stream_len); /* Send it to the app. */
139  }
140  }
141 }
142 
143 
144 static void
145 EMSCRIPTENAUDIO_CloseDevice(_THIS)
146 {
147  EM_ASM_({
148  var SDL2 = Module['SDL2'];
149  if ($0) {
150  if (SDL2.capture.silenceTimer !== undefined) {
151  clearTimeout(SDL2.capture.silenceTimer);
152  }
153  if (SDL2.capture.stream !== undefined) {
154  var tracks = SDL2.capture.stream.getAudioTracks();
155  for (var i = 0; i < tracks.length; i++) {
156  SDL2.capture.stream.removeTrack(tracks[i]);
157  }
158  SDL2.capture.stream = undefined;
159  }
160  if (SDL2.capture.scriptProcessorNode !== undefined) {
161  SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {};
162  SDL2.capture.scriptProcessorNode.disconnect();
163  SDL2.capture.scriptProcessorNode = undefined;
164  }
165  if (SDL2.capture.mediaStreamNode !== undefined) {
166  SDL2.capture.mediaStreamNode.disconnect();
167  SDL2.capture.mediaStreamNode = undefined;
168  }
169  if (SDL2.capture.silenceBuffer !== undefined) {
170  SDL2.capture.silenceBuffer = undefined
171  }
172  SDL2.capture = undefined;
173  } else {
174  if (SDL2.audio.scriptProcessorNode != undefined) {
175  SDL2.audio.scriptProcessorNode.disconnect();
176  SDL2.audio.scriptProcessorNode = undefined;
177  }
178  SDL2.audio = undefined;
179  }
180  if ((SDL2.audioContext !== undefined) && (SDL2.audio === undefined) && (SDL2.capture === undefined)) {
181  SDL2.audioContext.close();
182  SDL2.audioContext = undefined;
183  }
184  }, this->iscapture);
185 
186 #if 0 /* !!! FIXME: currently not used. Can we move some stuff off the SDL2 namespace? --ryan. */
187  SDL_free(this->hidden);
188 #endif
189 }
190 
191 static int
192 EMSCRIPTENAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
193 {
194  SDL_bool valid_format = SDL_FALSE;
195  SDL_AudioFormat test_format;
196  int result;
197 
198  /* based on parts of library_sdl.js */
199 
200  /* create context */
201  result = EM_ASM_INT({
202  if(typeof(Module['SDL2']) === 'undefined') {
203  Module['SDL2'] = {};
204  }
205  var SDL2 = Module['SDL2'];
206  if (!$0) {
207  SDL2.audio = {};
208  } else {
209  SDL2.capture = {};
210  }
211 
212  if (!SDL2.audioContext) {
213  if (typeof(AudioContext) !== 'undefined') {
214  SDL2.audioContext = new AudioContext();
215  } else if (typeof(webkitAudioContext) !== 'undefined') {
216  SDL2.audioContext = new webkitAudioContext();
217  }
218  }
219  return SDL2.audioContext === undefined ? -1 : 0;
220  }, iscapture);
221  if (result < 0) {
222  return SDL_SetError("Web Audio API is not available!");
223  }
224 
225  test_format = SDL_FirstAudioFormat(this->spec.format);
226  while ((!valid_format) && (test_format)) {
227  switch (test_format) {
228  case AUDIO_F32: /* web audio only supports floats */
229  this->spec.format = test_format;
230 
231  valid_format = SDL_TRUE;
232  break;
233  }
234  test_format = SDL_NextAudioFormat();
235  }
236 
237  if (!valid_format) {
238  /* Didn't find a compatible format :( */
239  return SDL_SetError("No compatible audio format!");
240  }
241 
242  /* Initialize all variables that we clean on shutdown */
243 #if 0 /* !!! FIXME: currently not used. Can we move some stuff off the SDL2 namespace? --ryan. */
244  this->hidden = (struct SDL_PrivateAudioData *)
245  SDL_malloc((sizeof *this->hidden));
246  if (this->hidden == NULL) {
247  return SDL_OutOfMemory();
248  }
249  SDL_zerop(this->hidden);
250 #endif
251  this->hidden = (struct SDL_PrivateAudioData *)0x1;
252 
253  /* limit to native freq */
254  this->spec.freq = EM_ASM_INT_V({
255  var SDL2 = Module['SDL2'];
256  return SDL2.audioContext.sampleRate;
257  });
258 
260 
261  if (iscapture) {
262  /* The idea is to take the capture media stream, hook it up to an
263  audio graph where we can pass it through a ScriptProcessorNode
264  to access the raw PCM samples and push them to the SDL app's
265  callback. From there, we "process" the audio data into silence
266  and forget about it. */
267 
268  /* This should, strictly speaking, use MediaRecorder for capture, but
269  this API is cleaner to use and better supported, and fires a
270  callback whenever there's enough data to fire down into the app.
271  The downside is that we are spending CPU time silencing a buffer
272  that the audiocontext uselessly mixes into any output. On the
273  upside, both of those things are not only run in native code in
274  the browser, they're probably SIMD code, too. MediaRecorder
275  feels like it's a pretty inefficient tapdance in similar ways,
276  to be honest. */
277 
278  EM_ASM_({
279  var SDL2 = Module['SDL2'];
280  var have_microphone = function(stream) {
281  //console.log('SDL audio capture: we have a microphone! Replacing silence callback.');
282  if (SDL2.capture.silenceTimer !== undefined) {
283  clearTimeout(SDL2.capture.silenceTimer);
284  SDL2.capture.silenceTimer = undefined;
285  }
286  SDL2.capture.mediaStreamNode = SDL2.audioContext.createMediaStreamSource(stream);
287  SDL2.capture.scriptProcessorNode = SDL2.audioContext.createScriptProcessor($1, $0, 1);
288  SDL2.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {
289  if ((SDL2 === undefined) || (SDL2.capture === undefined)) { return; }
290  audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0);
291  SDL2.capture.currentCaptureBuffer = audioProcessingEvent.inputBuffer;
292  dynCall('vi', $2, [$3]);
293  };
294  SDL2.capture.mediaStreamNode.connect(SDL2.capture.scriptProcessorNode);
295  SDL2.capture.scriptProcessorNode.connect(SDL2.audioContext.destination);
296  SDL2.capture.stream = stream;
297  };
298 
299  var no_microphone = function(error) {
300  //console.log('SDL audio capture: we DO NOT have a microphone! (' + error.name + ')...leaving silence callback running.');
301  };
302 
303  /* we write silence to the audio callback until the microphone is available (user approves use, etc). */
304  SDL2.capture.silenceBuffer = SDL2.audioContext.createBuffer($0, $1, SDL2.audioContext.sampleRate);
305  SDL2.capture.silenceBuffer.getChannelData(0).fill(0.0);
306  var silence_callback = function() {
307  SDL2.capture.currentCaptureBuffer = SDL2.capture.silenceBuffer;
308  dynCall('vi', $2, [$3]);
309  };
310 
311  SDL2.capture.silenceTimer = setTimeout(silence_callback, ($1 / SDL2.audioContext.sampleRate) * 1000);
312 
313  if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) {
314  navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone);
315  } else if (navigator.webkitGetUserMedia !== undefined) {
316  navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone);
317  }
318  }, this->spec.channels, this->spec.samples, HandleCaptureProcess, this);
319  } else {
320  /* setup a ScriptProcessorNode */
321  EM_ASM_ARGS({
322  var SDL2 = Module['SDL2'];
323  SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0);
324  SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
325  if ((SDL2 === undefined) || (SDL2.audio === undefined)) { return; }
326  SDL2.audio.currentOutputBuffer = e['outputBuffer'];
327  dynCall('vi', $2, [$3]);
328  };
329  SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']);
330  }, this->spec.channels, this->spec.samples, HandleAudioProcess, this);
331  }
332 
333  return 0;
334 }
335 
336 static int
337 EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl * impl)
338 {
339  int available;
340  int capture_available;
341 
342  /* Set the function pointers */
343  impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice;
344  impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice;
345 
346  impl->OnlyHasDefaultOutputDevice = 1;
347 
348  /* no threads here */
349  impl->SkipMixerLock = 1;
350  impl->ProvidesOwnCallbackThread = 1;
351 
352  /* check availability */
353  available = EM_ASM_INT_V({
354  if (typeof(AudioContext) !== 'undefined') {
355  return 1;
356  } else if (typeof(webkitAudioContext) !== 'undefined') {
357  return 1;
358  }
359  return 0;
360  });
361 
362  if (!available) {
363  SDL_SetError("No audio context available");
364  }
365 
366  capture_available = available && EM_ASM_INT_V({
367  if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) {
368  return 1;
369  } else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') {
370  return 1;
371  }
372  return 0;
373  });
374 
375  impl->HasCaptureSupport = capture_available ? SDL_TRUE : SDL_FALSE;
376  impl->OnlyHasDefaultCaptureDevice = capture_available ? SDL_TRUE : SDL_FALSE;
377 
378  return available;
379 }
380 
382  "emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, 0
383 };
384 
385 #endif /* SDL_AUDIO_DRIVER_EMSCRIPTEN */
386 
387 /* vi: set ts=4 sw=4 expandtab: */
#define _THIS
#define SDL_assert(condition)
Definition: SDL_assert.h:171
void SDL_CalculateAudioSpec(SDL_AudioSpec *spec)
Definition: SDL_audio.c:1689
SDL_AudioFormat SDL_FirstAudioFormat(SDL_AudioFormat format)
Definition: SDL_audio.c:1650
SDL_AudioFormat SDL_NextAudioFormat(void)
Definition: SDL_audio.c:1662
#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:88
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:168
@ SDL_TRUE
Definition: SDL_stdinc.h:170
@ SDL_FALSE
Definition: SDL_stdinc.h:169
#define SDL_zerop(x)
Definition: SDL_stdinc.h:427
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:163
EGLImageKHR EGLint EGLint * handle
Definition: eglext.h:937
SDL_AudioSpec spec
Definition: loopwave.c:31
set set set set set set set macro pixldst1 abits if abits op else op endif endm macro pixldst2 abits if abits op else op endif endm macro pixldst4 abits if abits op else op endif endm macro pixldst0 abits op endm macro pixldst3 mem_operand op endm macro pixldst30 mem_operand op endm macro pixldst abits if abits elseif abits elseif abits elseif abits elseif abits pixldst0 abits else pixldst0 abits pixldst0 abits pixldst0 abits pixldst0 abits endif elseif abits else pixldst0 abits pixldst0 abits endif elseif abits else error unsupported bpp *numpix else pixst endif endm macro pixld1_s mem_operand if asr adds SRC_WIDTH_FIXED bpl add asl mov asr adds SRC_WIDTH_FIXED bpl add asl mov asr adds SRC_WIDTH_FIXED bpl add asl mov asr adds SRC_WIDTH_FIXED bpl add asl elseif asr adds SRC_WIDTH_FIXED bpl add asl mov asr adds SRC_WIDTH_FIXED bpl add asl else error unsupported endif endm macro pixld2_s mem_operand if mov asr add asl add asl mov asr sub UNIT_X add asl mov asr add asl add asl mov asr add UNIT_X add asl else pixld1_s mem_operand pixld1_s mem_operand endif endm macro pixld0_s mem_operand if asr adds SRC_WIDTH_FIXED bpl add asl elseif asr adds SRC_WIDTH_FIXED bpl add asl endif endm macro pixld_s_internal mem_operand if mem_operand pixld2_s mem_operand pixdeinterleave basereg elseif mem_operand elseif mem_operand elseif mem_operand elseif mem_operand pixld0_s mem_operand else pixld0_s mem_operand pixld0_s mem_operand pixld0_s mem_operand pixld0_s mem_operand endif elseif mem_operand else pixld0_s mem_operand pixld0_s mem_operand endif elseif mem_operand else error unsupported mem_operand if bpp mem_operand endif endm macro vuzp8 reg2 vuzp d d &reg2 endm macro vzip8 reg2 vzip d d &reg2 endm macro pixdeinterleave basereg basereg basereg basereg basereg endif endm macro pixinterleave basereg basereg basereg basereg basereg endif endm macro PF boost_increment endif if endif PF tst PF addne PF subne PF cmp ORIG_W if endif if endif if endif PF subge ORIG_W PF subges if endif if endif if endif endif endm macro cache_preload_simple endif if dst_r_bpp pld[DST_R, #(PREFETCH_DISTANCE_SIMPLE *dst_r_bpp/8)] endif if mask_bpp pld if[MASK, #(PREFETCH_DISTANCE_SIMPLE *mask_bpp/8)] endif endif endm macro fetch_mask_pixblock pixld mask_basereg pixblock_size MASK endm macro ensure_destination_ptr_alignment process_pixblock_tail_head if beq irp skip1(dst_w_bpp<=(lowbit *8)) &&((lowbit *8)<(pixblock_size *dst_w_bpp)) .if lowbit< 16 tst DST_R
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:149
static Uint32 callback(Uint32 interval, void *param)
Definition: testtimer.c:34