Bug 793294 - Implement AudioBuffer; r=bzbarsky,smaug

This is the full implementation of the AudioBuffer object.  There are
two ways to create these objects from an audio context and this patch
implements only one of them.

The construction of the AudioBuffer object is a two step process: the
object should be created with operator new first, and then
InitializeBuffers should be called on it.  InitializeBuffers is
fallible, because it uses the JS API to create the underlying typed
arrays, but that's fine, since the length of the buffers comes from web
content, and we don't want to use infallible allocations for those
anyways.

We hold on to the JS objects from the C++ implementation, and trace
through all of those objects, so that a GC does not kill those object
without us knowing.

The buffer should be possible to manipulate from both C++ and JS, and
the C++ object probably needs to support a set of methods for the C++
callers at some point.
This commit is contained in:
Ehsan Akhgari 2012-09-21 18:42:14 -04:00
parent 5248b8f1c7
commit 5366306b68
13 changed files with 293 additions and 0 deletions

View File

@ -0,0 +1,101 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AudioBuffer.h"
#include "mozilla/dom/AudioBufferBinding.h"
#include "nsContentUtils.h"
#include "AudioContext.h"
#include "jsfriendapi.h"
#include "mozilla/ErrorResult.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(AudioBuffer)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioBuffer)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mChannels)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_DROP_JS_OBJECTS(tmp, AudioBuffer);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioBuffer)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mContext)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AudioBuffer)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
for (uint32_t i = 0; i < tmp->mChannels.Length(); ++i) {
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mChannels[i])
}
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioBuffer)
NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioBuffer)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioBuffer)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
AudioBuffer::AudioBuffer(AudioContext* aContext, uint32_t aLength,
uint32_t aSampleRate)
: mContext(aContext),
mLength(aLength),
mSampleRate(aSampleRate)
{
SetIsDOMBinding();
}
AudioBuffer::~AudioBuffer()
{
// Drop the JS object reference if we're still holding to the channels
if (mChannels.Length()) {
NS_DROP_JS_OBJECTS(this, AudioBuffer);
}
}
bool
AudioBuffer::InitializeBuffers(uint32_t aNumberOfChannels, JSContext* aJSContext)
{
if (!mChannels.SetCapacity(aNumberOfChannels)) {
return false;
}
for (uint32_t i = 0; i < aNumberOfChannels; ++i) {
JSObject* array = JS_NewFloat32Array(aJSContext, mLength);
if (!array) {
return false;
}
mChannels.AppendElement(array);
}
NS_HOLD_JS_OBJECTS(this, AudioBuffer);
return true;
}
JSObject*
AudioBuffer::WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap)
{
return AudioBufferBinding::Wrap(aCx, aScope, this, aTriedToWrap);
}
JSObject*
AudioBuffer::GetChannelData(JSContext* aJSContext, uint32_t aChannel,
ErrorResult& aRv) const
{
if (aChannel >= mChannels.Length()) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
return mChannels[aChannel];
}
}
}

View File

@ -0,0 +1,83 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#include "nsWrapperCache.h"
#include "nsCycleCollectionParticipant.h"
#include "mozilla/Attributes.h"
#include "EnableWebAudioCheck.h"
#include "nsAutoPtr.h"
#include "nsTArray.h"
#include "AudioContext.h"
struct JSContext;
struct JSObject;
namespace mozilla {
class ErrorResult;
namespace dom {
class AudioBuffer MOZ_FINAL : public nsISupports,
public nsWrapperCache,
public EnableWebAudioCheck
{
public:
AudioBuffer(AudioContext* aContext, uint32_t aLength,
uint32_t aSampleRate);
~AudioBuffer();
// This function needs to be called in order to allocate
// all of the channels. It is fallible!
bool InitializeBuffers(uint32_t aNumberOfChannels,
JSContext* aJSContext);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AudioBuffer)
AudioContext* GetParentObject() const
{
return mContext;
}
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap);
float SampleRate() const
{
return mSampleRate;
}
uint32_t Length() const
{
return mLength;
}
float Duration() const
{
return mLength / mSampleRate;
}
uint32_t NumberOfChannels() const
{
return mChannels.Length();
}
JSObject* GetChannelData(JSContext* aJSContext, uint32_t aChannel,
ErrorResult& aRv) const;
private:
nsRefPtr<AudioContext> mContext;
FallibleTArray<JSObject*> mChannels;
uint32_t mLength;
float mSampleRate;
};
}
}

View File

@ -11,6 +11,7 @@
#include "mozilla/dom/AudioContextBinding.h" #include "mozilla/dom/AudioContextBinding.h"
#include "AudioDestinationNode.h" #include "AudioDestinationNode.h"
#include "AudioBufferSourceNode.h" #include "AudioBufferSourceNode.h"
#include "AudioBuffer.h"
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -63,6 +64,19 @@ AudioContext::CreateBufferSource()
return bufferNode.forget(); return bufferNode.forget();
} }
already_AddRefed<AudioBuffer>
AudioContext::CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels,
uint32_t aLength, float aSampleRate,
ErrorResult& aRv)
{
nsRefPtr<AudioBuffer> buffer = new AudioBuffer(this, aLength, aSampleRate);
if (!buffer->InitializeBuffers(aNumberOfChannels, aJSContext)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
return buffer.forget();
}
} }
} }

View File

@ -24,6 +24,7 @@ namespace dom {
class AudioDestinationNode; class AudioDestinationNode;
class AudioBufferSourceNode; class AudioBufferSourceNode;
class AudioBuffer;
class AudioContext MOZ_FINAL : public nsISupports, class AudioContext MOZ_FINAL : public nsISupports,
public nsWrapperCache, public nsWrapperCache,
@ -55,6 +56,11 @@ public:
already_AddRefed<AudioBufferSourceNode> CreateBufferSource(); already_AddRefed<AudioBufferSourceNode> CreateBufferSource();
already_AddRefed<AudioBuffer>
CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels,
uint32_t aLength, float aSampleRate,
ErrorResult& aRv);
private: private:
nsCOMPtr<nsIDOMWindow> mWindow; nsCOMPtr<nsIDOMWindow> mWindow;
nsRefPtr<AudioDestinationNode> mDestination; nsRefPtr<AudioDestinationNode> mDestination;

View File

@ -15,6 +15,7 @@ LIBRARY_NAME := gkconwebaudio_s
LIBXUL_LIBRARY := 1 LIBXUL_LIBRARY := 1
CPPSRCS := \ CPPSRCS := \
AudioBuffer.cpp \
AudioBufferSourceNode.cpp \ AudioBufferSourceNode.cpp \
AudioContext.cpp \ AudioContext.cpp \
AudioDestinationNode.cpp \ AudioDestinationNode.cpp \
@ -25,6 +26,7 @@ CPPSRCS := \
EXPORTS_NAMESPACES := mozilla/dom EXPORTS_NAMESPACES := mozilla/dom
EXPORTS_mozilla/dom := \ EXPORTS_mozilla/dom := \
AudioBuffer.h \
AudioBufferSourceNode.h \ AudioBufferSourceNode.h \
AudioDestinationNode.h \ AudioDestinationNode.h \
AudioNode.h \ AudioNode.h \

View File

@ -11,6 +11,7 @@ relativesrcdir := @relativesrcdir@
include $(DEPTH)/config/autoconf.mk include $(DEPTH)/config/autoconf.mk
MOCHITEST_FILES := \ MOCHITEST_FILES := \
test_AudioBuffer.html \
test_AudioContext.html \ test_AudioContext.html \
$(NULL) $(NULL)

View File

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test whether we can create an AudioContext interface</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
SpecialPowers.setBoolPref("media.webaudio.enabled", true);
var ac = new mozAudioContext();
var buffer = ac.createBuffer(2, 2048, 44100);
SpecialPowers.gc(); // Make sure that our channels are accessible after GC
ok(buffer, "Buffer was allocated successfully");
is(buffer.sampleRate, 44100, "Correct sample rate");
is(buffer.length, 2048, "Correct length");
ok(Math.abs(buffer.duration - 2048 / 44100) < 0.0001, "Correct duration");
is(buffer.numberOfChannels, 2, "Correct number of channels");
for (var i = 0; i < buffer.numberOfChannels; ++i) {
var buf = buffer.getChannelData(i);
ok(buf, "Buffer index " + i + " exists");
ok(buf instanceof Float32Array, "Result is a typed array");
is(buf.length, buffer.length, "Correct length");
var foundNonZero = false;
for (var j = 0; j < buf.length; ++j) {
if (buf[j] != 0) {
foundNonZero = true;
break;
}
}
ok(!foundNonZero, "Buffer " + i + " should be initialized to 0");
}
SpecialPowers.clearUserPref("media.webaudio.enabled");
SimpleTest.finish();
});
</script>
</pre>
</body>
</html>

View File

@ -66,8 +66,12 @@
DOMInterfaces = { DOMInterfaces = {
'AudioBuffer' : {
},
'mozAudioContext': { 'mozAudioContext': {
'nativeType': 'AudioContext', 'nativeType': 'AudioContext',
'implicitJSContext': [ 'createBuffer' ],
}, },
'AudioNode' : { 'AudioNode' : {

View File

@ -6,6 +6,7 @@
#include "Skeleton.h" #include "Skeleton.h"
#include "mozilla/dom/SkeletonBinding.h" #include "mozilla/dom/SkeletonBinding.h"
#include "nsContentUtils.h"
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {

View File

@ -0,0 +1,28 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
*
* Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
[PrefControlled]
interface AudioBuffer {
readonly attribute float sampleRate;
readonly attribute long length;
// in seconds
readonly attribute float duration;
readonly attribute long numberOfChannels;
[Throws]
Float32Array getChannelData(unsigned long channel);
};

View File

@ -15,6 +15,12 @@ interface mozAudioContext {
readonly attribute AudioDestinationNode destination; readonly attribute AudioDestinationNode destination;
[Creator, Throws]
AudioBuffer createBuffer(unsigned long numberOfChannels, unsigned long length, float sampleRate);
// [Creator, Throws]
// AudioBuffer createBuffer(ArrayBuffer buffer, boolean mixToMono);
// AudioNode creation // AudioNode creation
AudioBufferSourceNode createBufferSource(); AudioBufferSourceNode createBufferSource();

View File

@ -9,6 +9,7 @@ generated_webidl_files = \
$(NULL) $(NULL)
webidl_files = \ webidl_files = \
AudioBuffer.webidl \
AudioBufferSourceNode.webidl \ AudioBufferSourceNode.webidl \
AudioContext.webidl \ AudioContext.webidl \
AudioDestinationNode.webidl \ AudioDestinationNode.webidl \

View File

@ -1024,6 +1024,8 @@ typedef uint32_t JSArrayBufferViewType;
/* /*
* Create a new typed array with nelements elements. * Create a new typed array with nelements elements.
*
* These functions (except the WithBuffer variants) fill in the array with zeros.
*/ */
extern JS_FRIEND_API(JSObject *) extern JS_FRIEND_API(JSObject *)