michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "AudioBuffer.h" michael@0: #include "mozilla/dom/AudioBufferBinding.h" michael@0: #include "jsfriendapi.h" michael@0: #include "mozilla/ErrorResult.h" michael@0: #include "AudioSegment.h" michael@0: #include "AudioChannelFormat.h" michael@0: #include "mozilla/PodOperations.h" michael@0: #include "mozilla/CheckedInt.h" michael@0: #include "AudioNodeEngine.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(AudioBuffer) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioBuffer) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mJSChannels) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: tmp->ClearJSChannels(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioBuffer) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AudioBuffer) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER michael@0: for (uint32_t i = 0; i < tmp->mJSChannels.Length(); ++i) { michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJSChannels[i]) michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioBuffer, AddRef) michael@0: NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioBuffer, Release) michael@0: michael@0: AudioBuffer::AudioBuffer(AudioContext* aContext, uint32_t aNumberOfChannels, michael@0: uint32_t aLength, float aSampleRate) michael@0: : mContext(aContext), michael@0: mLength(aLength), michael@0: mSampleRate(aSampleRate) michael@0: { michael@0: mJSChannels.SetCapacity(aNumberOfChannels); michael@0: SetIsDOMBinding(); michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: AudioBuffer::~AudioBuffer() michael@0: { michael@0: ClearJSChannels(); michael@0: } michael@0: michael@0: void michael@0: AudioBuffer::ClearJSChannels() michael@0: { michael@0: mJSChannels.Clear(); michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: /* static */ already_AddRefed michael@0: AudioBuffer::Create(AudioContext* aContext, uint32_t aNumberOfChannels, michael@0: uint32_t aLength, float aSampleRate, michael@0: JSContext* aJSContext, ErrorResult& aRv) michael@0: { michael@0: // Note that a buffer with zero channels is permitted here for the sake of michael@0: // AudioProcessingEvent, where channel counts must match parameters passed michael@0: // to createScriptProcessor(), one of which may be zero. michael@0: if (aSampleRate < WebAudioUtils::MinSampleRate || michael@0: aSampleRate > WebAudioUtils::MaxSampleRate || michael@0: aNumberOfChannels > WebAudioUtils::MaxChannelCount || michael@0: !aLength || aLength > INT32_MAX) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr buffer = michael@0: new AudioBuffer(aContext, aNumberOfChannels, aLength, aSampleRate); michael@0: michael@0: for (uint32_t i = 0; i < aNumberOfChannels; ++i) { michael@0: JS::Rooted array(aJSContext, michael@0: JS_NewFloat32Array(aJSContext, aLength)); michael@0: if (!array) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return nullptr; michael@0: } michael@0: buffer->mJSChannels.AppendElement(array.get()); michael@0: } michael@0: michael@0: return buffer.forget(); michael@0: } michael@0: michael@0: JSObject* michael@0: AudioBuffer::WrapObject(JSContext* aCx) michael@0: { michael@0: return AudioBufferBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: bool michael@0: AudioBuffer::RestoreJSChannelData(JSContext* aJSContext) michael@0: { michael@0: if (mSharedChannels) { michael@0: for (uint32_t i = 0; i < mJSChannels.Length(); ++i) { michael@0: const float* data = mSharedChannels->GetData(i); michael@0: // The following code first zeroes the array and then copies our data michael@0: // into it. We could avoid this with additional JS APIs to construct michael@0: // an array (or ArrayBuffer) containing initial data. michael@0: JS::Rooted array(aJSContext, michael@0: JS_NewFloat32Array(aJSContext, mLength)); michael@0: if (!array) { michael@0: return false; michael@0: } michael@0: memcpy(JS_GetFloat32ArrayData(array), data, sizeof(float)*mLength); michael@0: mJSChannels[i] = array; michael@0: } michael@0: michael@0: mSharedChannels = nullptr; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: AudioBuffer::CopyFromChannel(const Float32Array& aDestination, uint32_t aChannelNumber, michael@0: uint32_t aStartInChannel, ErrorResult& aRv) michael@0: { michael@0: aDestination.ComputeLengthAndData(); michael@0: michael@0: uint32_t length = aDestination.Length(); michael@0: CheckedInt end = aStartInChannel; michael@0: end += length; michael@0: if (aChannelNumber >= NumberOfChannels() || michael@0: !end.isValid() || end.value() > mLength) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (!mSharedChannels && JS_GetTypedArrayLength(mJSChannels[aChannelNumber]) != mLength) { michael@0: // The array was probably neutered michael@0: aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: const float* sourceData = mSharedChannels ? michael@0: mSharedChannels->GetData(aChannelNumber) : michael@0: JS_GetFloat32ArrayData(mJSChannels[aChannelNumber]); michael@0: PodMove(aDestination.Data(), sourceData + aStartInChannel, length); michael@0: } michael@0: michael@0: void michael@0: AudioBuffer::CopyToChannel(JSContext* aJSContext, const Float32Array& aSource, michael@0: uint32_t aChannelNumber, uint32_t aStartInChannel, michael@0: ErrorResult& aRv) michael@0: { michael@0: aSource.ComputeLengthAndData(); michael@0: michael@0: uint32_t length = aSource.Length(); michael@0: CheckedInt end = aStartInChannel; michael@0: end += length; michael@0: if (aChannelNumber >= NumberOfChannels() || michael@0: !end.isValid() || end.value() > mLength) { michael@0: aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (!mSharedChannels && JS_GetTypedArrayLength(mJSChannels[aChannelNumber]) != mLength) { michael@0: // The array was probably neutered michael@0: aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (!RestoreJSChannelData(aJSContext)) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return; michael@0: } michael@0: michael@0: PodMove(JS_GetFloat32ArrayData(mJSChannels[aChannelNumber]) + aStartInChannel, michael@0: aSource.Data(), length); michael@0: } michael@0: michael@0: void michael@0: AudioBuffer::SetRawChannelContents(JSContext* aJSContext, uint32_t aChannel, michael@0: float* aContents) michael@0: { michael@0: PodCopy(JS_GetFloat32ArrayData(mJSChannels[aChannel]), aContents, mLength); michael@0: } michael@0: michael@0: void michael@0: AudioBuffer::GetChannelData(JSContext* aJSContext, uint32_t aChannel, michael@0: JS::MutableHandle aRetval, michael@0: ErrorResult& aRv) michael@0: { michael@0: if (aChannel >= NumberOfChannels()) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (!RestoreJSChannelData(aJSContext)) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: return; michael@0: } michael@0: michael@0: if (mJSChannels[aChannel]) { michael@0: JS::ExposeObjectToActiveJS(mJSChannels[aChannel]); michael@0: } michael@0: aRetval.set(mJSChannels[aChannel]); michael@0: } michael@0: michael@0: static already_AddRefed michael@0: StealJSArrayDataIntoThreadSharedFloatArrayBufferList(JSContext* aJSContext, michael@0: const nsTArray& aJSArrays) michael@0: { michael@0: nsRefPtr result = michael@0: new ThreadSharedFloatArrayBufferList(aJSArrays.Length()); michael@0: for (uint32_t i = 0; i < aJSArrays.Length(); ++i) { michael@0: JS::Rooted arrayBuffer(aJSContext, michael@0: JS_GetArrayBufferViewBuffer(aJSContext, aJSArrays[i])); michael@0: uint8_t* stolenData = arrayBuffer michael@0: ? (uint8_t*) JS_StealArrayBufferContents(aJSContext, arrayBuffer) michael@0: : nullptr; michael@0: if (stolenData) { michael@0: result->SetData(i, stolenData, reinterpret_cast(stolenData)); michael@0: } else { michael@0: return nullptr; michael@0: } michael@0: } michael@0: return result.forget(); michael@0: } michael@0: michael@0: ThreadSharedFloatArrayBufferList* michael@0: AudioBuffer::GetThreadSharedChannelsForRate(JSContext* aJSContext) michael@0: { michael@0: if (!mSharedChannels) { michael@0: for (uint32_t i = 0; i < mJSChannels.Length(); ++i) { michael@0: if (mLength != JS_GetTypedArrayLength(mJSChannels[i])) { michael@0: // Probably one of the arrays was neutered michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: mSharedChannels = michael@0: StealJSArrayDataIntoThreadSharedFloatArrayBufferList(aJSContext, mJSChannels); michael@0: } michael@0: michael@0: return mSharedChannels; michael@0: } michael@0: michael@0: size_t michael@0: AudioBuffer::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = aMallocSizeOf(this); michael@0: amount += mJSChannels.SizeOfExcludingThis(aMallocSizeOf); michael@0: if (mSharedChannels) { michael@0: amount += mSharedChannels->SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: return amount; michael@0: } michael@0: michael@0: } michael@0: }