Commit f07a1a5ad53f338b0a5a661a56657df1642a32c3

Ryan C. Gordon 2017-01-05T21:31:02

emscriptenaudio: Reworked to use SDL_AudioStream.

diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index 8f71b6e..94ee381 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -1243,6 +1243,21 @@ open_audio_device(const char *devname, int iscapture,
         device->spec.userdata = device;
     }
 
+    /* !!! FIXME: rename this from fake_stream */
+    /* Allocate a scratch audio buffer */
+    device->fake_stream_len = build_stream ? device->callbackspec.size : 0;
+    if (device->spec.size > device->fake_stream_len) {
+        device->fake_stream_len = device->spec.size;
+    }
+    SDL_assert(device->fake_stream_len > 0);
+
+    device->fake_stream = (Uint8 *) SDL_malloc(device->fake_stream_len);
+    if (device->fake_stream == NULL) {
+        close_audio_device(device);
+        SDL_OutOfMemory();
+        return 0;
+    }
+
     open_devices[id] = device;  /* add it to our list of open devices. */
 
     /* Start the audio thread if necessary */
@@ -1253,20 +1268,6 @@ open_audio_device(const char *devname, int iscapture,
         const size_t stacksize = is_internal_thread ? 64 * 1024 : 0;
         char threadname[64];
 
-        /* Allocate a fake audio buffer; only used by our internal threads. */
-        device->fake_stream_len = build_stream ? device->callbackspec.size : 0;
-        if (device->spec.size > device->fake_stream_len) {
-            device->fake_stream_len = device->spec.size;
-        }
-        SDL_assert(device->fake_stream_len > 0);
-
-        device->fake_stream = (Uint8 *) SDL_malloc(device->fake_stream_len);
-        if (device->fake_stream == NULL) {
-            close_audio_device(device);
-            SDL_OutOfMemory();
-            return 0;
-        }
-
         SDL_snprintf(threadname, sizeof (threadname), "SDLAudioDev%d", (int) device->id);
         device->thread = SDL_CreateThreadInternal(iscapture ? SDL_CaptureAudio : SDL_RunAudio, threadname, stacksize, device);
 
diff --git a/src/audio/emscripten/SDL_emscriptenaudio.c b/src/audio/emscripten/SDL_emscriptenaudio.c
index d3f75a5..771ccf6 100644
--- a/src/audio/emscripten/SDL_emscriptenaudio.c
+++ b/src/audio/emscripten/SDL_emscriptenaudio.c
@@ -26,175 +26,120 @@
 #include "SDL_log.h"
 #include "../SDL_audio_c.h"
 #include "SDL_emscriptenaudio.h"
+#include "SDL_assert.h"
 
 #include <emscripten/emscripten.h>
 
-static int
-copyData(_THIS)
+static void
+FeedAudioDevice(_THIS, const void *buf, const int buflen)
 {
-    int byte_len;
-
-    if (this->hidden->write_off + this->convert.len_cvt > this->hidden->mixlen) {
-        if (this->hidden->write_off > this->hidden->read_off) {
-            SDL_memmove(this->hidden->mixbuf,
-                        this->hidden->mixbuf + this->hidden->read_off,
-                        this->hidden->mixlen - this->hidden->read_off);
-            this->hidden->write_off = this->hidden->write_off - this->hidden->read_off;
-        } else {
-            this->hidden->write_off = 0;
-        }
-        this->hidden->read_off = 0;
-    }
-
-    SDL_memcpy(this->hidden->mixbuf + this->hidden->write_off,
-               this->convert.buf,
-               this->convert.len_cvt);
-    this->hidden->write_off += this->convert.len_cvt;
-    byte_len = this->hidden->write_off - this->hidden->read_off;
+    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!';
+            }
 
-    return byte_len;
+            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);
 }
 
 static void
 HandleAudioProcess(_THIS)
 {
-    Uint8 *buf = NULL;
-    int byte_len = 0;
-    int bytes = SDL_AUDIO_BITSIZE(this->spec.format) / 8;
+    SDL_AudioCallback callback = this->spec.callback;
+    const int stream_len = this->callbackspec.size;
 
     /* Only do something if audio is enabled */
     if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
+        if (this->stream) {
+            SDL_AudioStreamClear(this->stream);
+        }
         return;
     }
 
-    if (this->convert.needed) {
-        const int bytes_in = SDL_AUDIO_BITSIZE(this->convert.src_format) / 8;
-
-        if (this->hidden->conv_in_len != 0) {
-            this->convert.len = this->hidden->conv_in_len * bytes_in * this->spec.channels;
-        }
-
-        (*this->spec.callback) (this->spec.userdata,
-                                 this->convert.buf,
-                                 this->convert.len);
-        SDL_ConvertAudio(&this->convert);
-        buf = this->convert.buf;
-        byte_len = this->convert.len_cvt;
-
-        /* size mismatch*/
-        if (byte_len != this->spec.size) {
-            if (!this->hidden->mixbuf) {
-                this->hidden->mixlen = this->spec.size > byte_len ? this->spec.size * 2 : byte_len * 2;
-                this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen);
+    if (this->stream == NULL) {  /* no conversion necessary. */
+        SDL_assert(this->spec.size == stream_len);
+        callback(this->spec.userdata, this->fake_stream, stream_len);
+    } else {  /* streaming/converting */
+        int got;
+        while (SDL_AudioStreamAvailable(this->stream) < ((int) this->spec.size)) {
+            callback(this->spec.userdata, this->fake_stream, stream_len);
+            if (SDL_AudioStreamPut(this->stream, this->fake_stream, stream_len) == -1) {
+                SDL_AudioStreamClear(this->stream);
+                SDL_AtomicSet(&this->enabled, 0);
             }
-
-            /* copy existing data */
-            byte_len = copyData(this);
-
-            /* read more data*/
-            while (byte_len < this->spec.size) {
-                (*this->spec.callback) (this->spec.userdata,
-                                         this->convert.buf,
-                                         this->convert.len);
-                SDL_ConvertAudio(&this->convert);
-                byte_len = copyData(this);
-            }
-
-            byte_len = this->spec.size;
-            buf = this->hidden->mixbuf + this->hidden->read_off;
-            this->hidden->read_off += byte_len;
         }
 
-    } else {
-        if (!this->hidden->mixbuf) {
-            this->hidden->mixlen = this->spec.size;
-            this->hidden->mixbuf = SDL_malloc(this->hidden->mixlen);
+        got = SDL_AudioStreamGet(this->stream, this->spec.size, this->fake_stream, this->spec.size);
+        SDL_assert((got < 0) || (got == this->spec.size));
+        if (got != this->spec.size) {
+            SDL_memset(this->fake_stream, this->spec.silence, this->spec.size);
         }
-        (*this->spec.callback) (this->spec.userdata,
-                                 this->hidden->mixbuf,
-                                 this->hidden->mixlen);
-        buf = this->hidden->mixbuf;
-        byte_len = this->hidden->mixlen;
     }
 
-    if (buf) {
-        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!';
-                }
-
-                for (var j = 0; j < $1; ++j) {
-                    channelData[j] = HEAPF32[$0 + ((j*numChannels + c) << 2) >> 2];
-                }
-            }
-        }, buf, byte_len / bytes / this->spec.channels);
-    }
+    FeedAudioDevice(this, this->fake_stream, this->spec.size);
 }
 
 static void
 HandleCaptureProcess(_THIS)
 {
-    Uint8 *buf;
-    int buflen;
+    SDL_AudioCallback callback = this->spec.callback;
+    const int stream_len = this->callbackspec.size;
 
     /* Only do something if audio is enabled */
     if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) {
+        SDL_AudioStreamClear(this->stream);
         return;
     }
 
-    if (this->convert.needed) {
-        buf = this->convert.buf;
-        buflen = this->convert.len_cvt;
-    } else {
-        if (!this->hidden->mixbuf) {
-            this->hidden->mixbuf = (Uint8 *) SDL_malloc(this->spec.size);
-            if (!this->hidden->mixbuf) {
-                return;  /* oh well. */
-            }
-        }
-        buf = this->hidden->mixbuf;
-        buflen = this->spec.size;
-    }
-
     EM_ASM_ARGS({
         var numChannels = SDL2.capture.currentCaptureBuffer.numberOfChannels;
-        if (numChannels == 1) {  /* fastpath this a little for the common (mono) case. */
-            var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(0);
+        for (var c = 0; c < numChannels; ++c) {
+            var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(c);
             if (channelData.length != $1) {
                 throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
             }
-            for (var j = 0; j < $1; ++j) {
-                setValue($0 + (j * 4), channelData[j], 'float');
-            }
-        } else {
-            for (var c = 0; c < numChannels; ++c) {
-                var channelData = SDL2.capture.currentCaptureBuffer.getChannelData(c);
-                if (channelData.length != $1) {
-                    throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
-                }
 
+            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 {
                 for (var j = 0; j < $1; ++j) {
                     setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float');
                 }
             }
         }
-    }, buf, (this->spec.size / sizeof (float)) / this->spec.channels);
+    }, this->fake_stream, (this->spec.size / sizeof (float)) / this->spec.channels);
 
     /* okay, we've got an interleaved float32 array in C now. */
 
-    if (this->convert.needed) {
-        SDL_ConvertAudio(&this->convert);
-    }
+    if (this->stream == NULL) {  /* no conversion necessary. */
+        SDL_assert(this->spec.size == stream_len);
+        callback(this->spec.userdata, this->fake_stream, stream_len);
+    } else {  /* streaming/converting */
+        if (SDL_AudioStreamPut(this->stream, this->fake_stream, this->spec.size) == -1) {
+            SDL_AtomicSet(&this->enabled, 0);
+        }
 
-    /* Send it to the app. */
-    (*this->spec.callback) (this->spec.userdata, buf, buflen);
+        while (SDL_AudioStreamAvailable(this->stream) >= stream_len) {
+            const int got = SDL_AudioStreamGet(this->stream, stream_len, this->fake_stream, stream_len);
+            SDL_assert((got < 0) || (got == stream_len));
+            if (got != stream_len) {
+                SDL_memset(this->fake_stream, this->callbackspec.silence, stream_len);
+            }
+            callback(this->spec.userdata, this->fake_stream, stream_len);  /* Send it to the app. */
+        }
+    }
 }
 
 
-
 static void
 EMSCRIPTENAUDIO_CloseDevice(_THIS)
 {
@@ -236,8 +181,9 @@ EMSCRIPTENAUDIO_CloseDevice(_THIS)
         }
     }, this->iscapture);
 
-    SDL_free(this->hidden->mixbuf);
+#if 0  /* !!! FIXME: currently not used. Can we move some stuff off the SDL2 namespace? --ryan. */
     SDL_free(this->hidden);
+#endif
 }
 
 static int
@@ -245,8 +191,6 @@ EMSCRIPTENAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscaptu
 {
     SDL_bool valid_format = SDL_FALSE;
     SDL_AudioFormat test_format;
-    int i;
-    float f;
     int result;
 
     /* based on parts of library_sdl.js */
@@ -293,29 +237,17 @@ EMSCRIPTENAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscaptu
     }
 
     /* Initialize all variables that we clean on shutdown */
+#if 0  /* !!! FIXME: currently not used. Can we move some stuff off the SDL2 namespace? --ryan. */
     this->hidden = (struct SDL_PrivateAudioData *)
         SDL_malloc((sizeof *this->hidden));
     if (this->hidden == NULL) {
         return SDL_OutOfMemory();
     }
     SDL_zerop(this->hidden);
+#endif
 
     /* limit to native freq */
-    const int sampleRate = EM_ASM_INT_V({
-        return SDL2.audioContext.sampleRate;
-    });
-
-    if(this->spec.freq != sampleRate) {
-        for (i = this->spec.samples; i > 0; i--) {
-            f = (float)i / (float)sampleRate * (float)this->spec.freq;
-            if (SDL_floor(f) == f) {
-                this->hidden->conv_in_len = SDL_floor(f);
-                break;
-            }
-        }
-
-        this->spec.freq = sampleRate;
-    }
+    this->spec.freq = EM_ASM_INT_V({ return SDL2.audioContext.sampleRate; });
 
     SDL_CalculateAudioSpec(&this->spec);
 
diff --git a/src/audio/emscripten/SDL_emscriptenaudio.h b/src/audio/emscripten/SDL_emscriptenaudio.h
index 603f163..c059c67 100644
--- a/src/audio/emscripten/SDL_emscriptenaudio.h
+++ b/src/audio/emscripten/SDL_emscriptenaudio.h
@@ -30,12 +30,7 @@
 
 struct SDL_PrivateAudioData
 {
-    Uint8 *mixbuf;
-    Uint32 mixlen;
-
-    Uint32 conv_in_len;
-
-    Uint32 write_off, read_off;
+    int unused;
 };
 
 #endif /* _SDL_emscriptenaudio_h */