Skip to content

Commit

Permalink
Fix audio crashes and leaks; add stress tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Avaer Kazmer committed Aug 5, 2018
1 parent 419d7e5 commit ff32e78
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 24 deletions.
29 changes: 20 additions & 9 deletions deps/exokit-bindings/webaudiocontext/src/AudioBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
namespace webaudio {

AudioBuffer::AudioBuffer(uint32_t sampleRate, Local<Array> buffers) : sampleRate(sampleRate), buffers(buffers) {}
AudioBuffer::~AudioBuffer() {}
AudioBuffer::~AudioBuffer() {
Local<Array> bufs = Nan::New(buffers);
int n = bufs->Length();
for (int i = 0; i < n; i++) {
bufs->Set(i,Nan::Null());
}
}
Handle<Object> AudioBuffer::Initialize(Isolate *isolate) {
Nan::EscapableHandleScope scope;

Expand Down Expand Up @@ -245,17 +251,20 @@ NAN_SETTER(AudioBufferSourceNode::BufferSetter) {
Local<Array> buffers = Nan::New(audioBuffer->buffers);
size_t numChannels = buffers->Length();
size_t numFrames = numChannels > 0 ? Local<Float32Array>::Cast(buffers->Get(0))->Length() : 0;
shared_ptr<lab::AudioBus> audioBus;

unique_ptr<float *[]> frames(new float*[numChannels]);
for (size_t i = 0; i < numChannels; i++) {
Local<Float32Array> bufferFramesFloat32Array = Local<Float32Array>::Cast(buffers->Get(i));
size_t numBufferFrames = bufferFramesFloat32Array->Length();
Local<ArrayBuffer> bufferFramesArrayBuffer = bufferFramesFloat32Array->Buffer();
frames[i] = (float *)((unsigned char *)bufferFramesArrayBuffer->GetContents().Data() + bufferFramesFloat32Array->ByteOffset());
{
unique_ptr<float *[]> frames(new float*[numChannels]);
for (size_t i = 0; i < numChannels; i++) {
Local<Float32Array> bufferFramesFloat32Array = Local<Float32Array>::Cast(buffers->Get(i));
size_t numBufferFrames = bufferFramesFloat32Array->Length();
Local<ArrayBuffer> bufferFramesArrayBuffer = bufferFramesFloat32Array->Buffer();
frames[i] = (float *)((unsigned char *)bufferFramesArrayBuffer->GetContents().Data() + bufferFramesFloat32Array->ByteOffset());
}

audioBus.reset(lab::MakeBusFromRawBuffer(audioContext->audioContext->sampleRate(), numChannels, numFrames, frames.get(), false).release());
}

shared_ptr<lab::AudioBus> audioBus(lab::MakeBusFromRawBuffer(audioContext->audioContext->sampleRate(), numChannels, numFrames, frames.get(), false).release());

{
lab::ContextRenderLock lock(audioContext->audioContext, "AudioBufferSourceNode::buffer");
audioNode->reset(lock);
Expand Down Expand Up @@ -295,8 +304,10 @@ void AudioBufferSourceNode::ProcessInMainThread(AudioBufferSourceNode *self) {

if (!self->onended.IsEmpty()) {
Local<Function> onended = Nan::New(self->onended);
self->onended.Reset();
onended->Call(Nan::Null(), 0, nullptr);
}
self->buffer.Reset();
}

}
28 changes: 13 additions & 15 deletions deps/exokit-bindings/webaudiocontext/src/AudioContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,9 @@ void AudioContext::Resume() {
NAN_METHOD(AudioContext::New) {
if (!threadInitialized) {
uv_async_init(uv_default_loop(), &threadAsync, RunInMainThread);
uv_sem_init(&threadSemaphore, 0);

atexit([]{
uv_close((uv_handle_t *)&threadAsync, nullptr);
uv_sem_destroy(&threadSemaphore);
});

threadInitialized = true;
Expand Down Expand Up @@ -475,24 +473,24 @@ NAN_GETTER(AudioContext::SampleRateGetter) {
info.GetReturnValue().Set(JS_NUM(audioContext->audioContext->sampleRate()));
}

function<void()> threadFn;
#include "LabSound/core/ConcurrentQueue.h"
struct msg_t {
function<void()> threadFn;
};
lab::concurrent_queue<msg_t> queue;
uv_async_t threadAsync;
uv_sem_t threadSemaphore;
bool threadInitialized = false;
void QueueOnMainThread(lab::ContextRenderLock &r, function<void()> &&newThreadFn) {
threadFn = std::move(newThreadFn);

{
lab::ContextRenderUnlock contextUnlock(r.context());
uv_async_send(&threadAsync);
uv_sem_wait(&threadSemaphore);
}

threadFn = function<void()>();
msg_t msg;
msg.threadFn = std::move(newThreadFn);
queue.push(msg);
uv_async_send(&threadAsync);
}
void RunInMainThread(uv_async_t *handle) {
threadFn();
uv_sem_post(&threadSemaphore);
msg_t msg;
while (queue.try_pop(msg)) {
msg.threadFn();
}
}

}
73 changes: 73 additions & 0 deletions tests/stress/audiobuffer-fast.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.js"></script>
</head>
<body>
<script>
const ctx = THREE.AudioContext.getContext();
const buffers = [];
let numLoads = 0;
let booted = false;
const numBuffers = 3;
const _load = () => {
numLoads++;
new THREE.AudioLoader().load('talknerdytome.ogg', buffer => {
buffers.push(buffer);
while (buffers.length > numBuffers) {
buffers.splice(Math.floor(Math.random() * buffers.length), 1);
}

numLoads--;

if (buffers.length >= numBuffers && !booted) {
_boot();
booted = true;
}
});
};
for (let i = 0; i < numBuffers; i++) {
_load();
}

const _boot = () => {
for (let i = 0; i < 4; i++) {
_recurse();
}
};
let ids = 0;
const _recurse = () => {
const id = ids++;
const absn = ctx.createBufferSource();
absn.buffer = buffers[Math.floor(Math.random() * buffers.length)];
absn.connect(ctx.destination);
absn.start();
console.log('started ' + id);
absn.onended = () => {
console.log('ended ' + id);
};

const _timeout = fn => {
setTimeout(fn, Math.floor(Math.random() * 20));
};
_timeout(() => {
_timeout(() => {
absn.stop();
_timeout(() => {
absn.disconnect();
});
});

if (numLoads < numBuffers) {
_load();
}

_recurse();
});
};

/* setInterval(() => {
while (Math.random() >= 0.000001) {}
}); */
</script>
</body>
</html>
74 changes: 74 additions & 0 deletions tests/stress/audiobuffer-slow.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.js"></script>
</head>
<body>
<script>
const ctx = THREE.AudioContext.getContext();
const buffers = [];
let numLoads = 0;
let booted = false;
const numBuffers = 3;
const _load = () => {
numLoads++;
new THREE.AudioLoader().load('talknerdytome.ogg', buffer => {
buffers.push(buffer);
while (buffers.length > numBuffers) {
buffers.splice(Math.floor(Math.random() * buffers.length), 1);
}

numLoads--;

if (buffers.length >= numBuffers && !booted) {
_boot();
booted = true;
}
});
};
for (let i = 0; i < numBuffers; i++) {
_load();
}

const _boot = () => {
for (let i = 0; i < 4; i++) {
_recurse();
}
};
let ids = 0;
const _recurse = () => {
const id = ids++;

const absn = ctx.createBufferSource();
absn.buffer = buffers[Math.floor(Math.random() * buffers.length)];
absn.connect(ctx.destination);
absn.start();
console.log('started ' + id);
absn.onended = () => {
console.log('onended ' + id);
};

const _timeout = fn => {
setTimeout(fn, Math.floor(Math.random() * 2000));
};
_timeout(() => {
_timeout(() => {
absn.stop();
_timeout(() => {
absn.disconnect();
});
});

if (numLoads < numBuffers) {
_load();
}

_recurse();
});
};

/* setInterval(() => {
while (Math.random() >= 0.000001) {}
}); */
</script>
</body>
</html>
Binary file added tests/stress/talknerdytome.ogg
Binary file not shown.

0 comments on commit ff32e78

Please sign in to comment.