src/audio/emscripten/SDL_emscriptenaudio.c
author Ryan C. Gordon <icculus@icculus.org>
Tue, 02 Aug 2016 13:48:52 -0400
changeset 10238 6fa358b97f4b
parent 9998 f67cf37e9cd4
child 10255 9530fc07da6c
permissions -rw-r--r--
audio: Made some SDL_AudioDevice fields atomic.

This makes sure they're properly communicated to the audio threads.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2016 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 
    30 #include <emscripten/emscripten.h>
    31 
    32 static int
    33 copyData(_THIS)
    34 {
    35     int byte_len;
    36 
    37     if (this->hidden->write_off + this->convert.len_cvt > this->hidden->mixlen) {
    38         if (this->hidden->write_off > this->hidden->read_off) {
    39             SDL_memmove(this->hidden->mixbuf,
    40                         this->hidden->mixbuf + this->hidden->read_off,
    41                         this->hidden->mixlen - this->hidden->read_off);
    42             this->hidden->write_off = this->hidden->write_off - this->hidden->read_off;
    43         } else {
    44             this->hidden->write_off = 0;
    45         }
    46         this->hidden->read_off = 0;
    47     }
    48 
    49     SDL_memcpy(this->hidden->mixbuf + this->hidden->write_off,
    50                this->convert.buf,
    51                this->convert.len_cvt);
    52     this->hidden->write_off += this->convert.len_cvt;
    53     byte_len = this->hidden->write_off - this->hidden->read_off;
    54 
    55     return byte_len;
    56 }
    57 
    58 static void
    59 HandleAudioProcess(_THIS)
    60 {
    61     Uint8 *buf = NULL;
    62     int byte_len = 0;
    63     int bytes = SDL_AUDIO_BITSIZE(this->spec.format) / 8;
    64     int bytes_in = SDL_AUDIO_BITSIZE(this->convert.src_format) / 8;
    65 
    66     /* Only do something if audio is enabled */
    67     if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
    68         return;
    69     }
    70 
    71     if (this->convert.needed) {
    72         if (this->hidden->conv_in_len != 0) {
    73             this->convert.len = this->hidden->conv_in_len * bytes_in * this->spec.channels;
    74         }
    75 
    76         (*this->spec.callback) (this->spec.userdata,
    77                                  this->convert.buf,
    78                                  this->convert.len);
    79         SDL_ConvertAudio(&this->convert);
    80         buf = this->convert.buf;
    81         byte_len = this->convert.len_cvt;
    82 
    83         /* size mismatch*/
    84         if (byte_len != this->spec.size) {
    85             if (!this->hidden->mixbuf) {
    86                 this->hidden->mixlen = this->spec.size > byte_len ? this->spec.size * 2 : byte_len * 2;
    87                 this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen);
    88             }
    89 
    90             /* copy existing data */
    91             byte_len = copyData(this);
    92 
    93             /* read more data*/
    94             while (byte_len < this->spec.size) {
    95                 (*this->spec.callback) (this->spec.userdata,
    96                                          this->convert.buf,
    97                                          this->convert.len);
    98                 SDL_ConvertAudio(&this->convert);
    99                 byte_len = copyData(this);
   100             }
   101 
   102             byte_len = this->spec.size;
   103             buf = this->hidden->mixbuf + this->hidden->read_off;
   104             this->hidden->read_off += byte_len;
   105         }
   106 
   107     } else {
   108         if (!this->hidden->mixbuf) {
   109             this->hidden->mixlen = this->spec.size;
   110             this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen);
   111         }
   112         (*this->spec.callback) (this->spec.userdata,
   113                                  this->hidden->mixbuf,
   114                                  this->hidden->mixlen);
   115         buf = this->hidden->mixbuf;
   116         byte_len = this->hidden->mixlen;
   117     }
   118 
   119     if (buf) {
   120         EM_ASM_ARGS({
   121             var numChannels = SDL2.audio.currentOutputBuffer['numberOfChannels'];
   122             for (var c = 0; c < numChannels; ++c) {
   123                 var channelData = SDL2.audio.currentOutputBuffer['getChannelData'](c);
   124                 if (channelData.length != $1) {
   125                     throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
   126                 }
   127 
   128                 for (var j = 0; j < $1; ++j) {
   129                     channelData[j] = getValue($0 + (j*numChannels + c)*4, 'float');
   130                 }
   131             }
   132         }, buf, byte_len / bytes / this->spec.channels);
   133     }
   134 }
   135 
   136 static void
   137 Emscripten_CloseDevice(_THIS)
   138 {
   139     if (this->hidden != NULL) {
   140         if (this->hidden->mixbuf != NULL) {
   141             /* Clean up the audio buffer */
   142             SDL_free(this->hidden->mixbuf);
   143             this->hidden->mixbuf = NULL;
   144         }
   145 
   146         SDL_free(this->hidden);
   147         this->hidden = NULL;
   148     }
   149 }
   150 
   151 static int
   152 Emscripten_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
   153 {
   154     SDL_bool valid_format = SDL_FALSE;
   155     SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
   156     int i;
   157     float f;
   158     int result;
   159 
   160     while ((!valid_format) && (test_format)) {
   161         switch (test_format) {
   162         case AUDIO_F32: /* web audio only supports floats */
   163             this->spec.format = test_format;
   164 
   165             valid_format = SDL_TRUE;
   166             break;
   167         }
   168         test_format = SDL_NextAudioFormat();
   169     }
   170 
   171     if (!valid_format) {
   172         /* Didn't find a compatible format :( */
   173         return SDL_SetError("No compatible audio format!");
   174     }
   175 
   176     /* Initialize all variables that we clean on shutdown */
   177     this->hidden = (struct SDL_PrivateAudioData *)
   178         SDL_malloc((sizeof *this->hidden));
   179     if (this->hidden == NULL) {
   180         return SDL_OutOfMemory();
   181     }
   182     SDL_memset(this->hidden, 0, (sizeof *this->hidden));
   183 
   184     /* based on parts of library_sdl.js */
   185 
   186     /* create context (TODO: this puts stuff in the global namespace...)*/
   187     result = EM_ASM_INT_V({
   188         if(typeof(SDL2) === 'undefined')
   189             SDL2 = {};
   190 
   191         if(typeof(SDL2.audio) === 'undefined')
   192             SDL2.audio = {};
   193 
   194         if (!SDL2.audioContext) {
   195             if (typeof(AudioContext) !== 'undefined') {
   196                 SDL2.audioContext = new AudioContext();
   197             } else if (typeof(webkitAudioContext) !== 'undefined') {
   198                 SDL2.audioContext = new webkitAudioContext();
   199             } else {
   200                 return -1;
   201             }
   202         }
   203         return 0;
   204     });
   205     if (result < 0) {
   206         return SDL_SetError("Web Audio API is not available!");
   207     }
   208 
   209     /* limit to native freq */
   210     int sampleRate = EM_ASM_INT_V({
   211         return SDL2.audioContext['sampleRate'];
   212     });
   213 
   214     if(this->spec.freq != sampleRate) {
   215         for (i = this->spec.samples; i > 0; i--) {
   216             f = (float)i / (float)sampleRate * (float)this->spec.freq;
   217             if (SDL_floor(f) == f) {
   218                 this->hidden->conv_in_len = SDL_floor(f);
   219                 break;
   220             }
   221         }
   222 
   223         this->spec.freq = sampleRate;
   224     }
   225 
   226     SDL_CalculateAudioSpec(&this->spec);
   227 
   228     /* setup a ScriptProcessorNode */
   229     EM_ASM_ARGS({
   230         SDL2.audio.scriptProcessorNode = SDL2.audioContext['createScriptProcessor']($1, 0, $0);
   231         SDL2.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
   232             SDL2.audio.currentOutputBuffer = e['outputBuffer'];
   233             Runtime.dynCall('vi', $2, [$3]);
   234         };
   235         SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']);
   236     }, this->spec.channels, this->spec.samples, HandleAudioProcess, this);
   237     return 0;
   238 }
   239 
   240 static int
   241 Emscripten_Init(SDL_AudioDriverImpl * impl)
   242 {
   243     /* Set the function pointers */
   244     impl->OpenDevice = Emscripten_OpenDevice;
   245     impl->CloseDevice = Emscripten_CloseDevice;
   246 
   247     /* only one output */
   248     impl->OnlyHasDefaultOutputDevice = 1;
   249 
   250     /* no threads here */
   251     impl->SkipMixerLock = 1;
   252     impl->ProvidesOwnCallbackThread = 1;
   253 
   254     /* check availability */
   255     int available = EM_ASM_INT_V({
   256         if (typeof(AudioContext) !== 'undefined') {
   257             return 1;
   258         } else if (typeof(webkitAudioContext) !== 'undefined') {
   259             return 1;
   260         }
   261         return 0;
   262     });
   263 
   264     if (!available) {
   265         SDL_SetError("No audio context available");
   266     }
   267 
   268     return available;
   269 }
   270 
   271 AudioBootStrap EmscriptenAudio_bootstrap = {
   272     "emscripten", "SDL emscripten audio driver", Emscripten_Init, 0
   273 };
   274 
   275 #endif /* SDL_AUDIO_DRIVER_EMSCRIPTEN */
   276 
   277 /* vi: set ts=4 sw=4 expandtab: */