1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webaudio/AudioBufferSourceNode.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,776 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "AudioBufferSourceNode.h" 1.11 +#include "mozilla/dom/AudioBufferSourceNodeBinding.h" 1.12 +#include "mozilla/dom/AudioParam.h" 1.13 +#include "nsMathUtils.h" 1.14 +#include "AudioNodeEngine.h" 1.15 +#include "AudioNodeStream.h" 1.16 +#include "AudioDestinationNode.h" 1.17 +#include "AudioParamTimeline.h" 1.18 +#include "speex/speex_resampler.h" 1.19 +#include <limits> 1.20 + 1.21 +namespace mozilla { 1.22 +namespace dom { 1.23 + 1.24 +NS_IMPL_CYCLE_COLLECTION_CLASS(AudioBufferSourceNode) 1.25 + 1.26 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioBufferSourceNode) 1.27 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBuffer) 1.28 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaybackRate) 1.29 + if (tmp->Context()) { 1.30 + // AudioNode's Unlink implementation disconnects us from the graph 1.31 + // too, but we need to do this right here to make sure that 1.32 + // UnregisterAudioBufferSourceNode can properly untangle us from 1.33 + // the possibly connected PannerNodes. 1.34 + tmp->DisconnectFromGraph(); 1.35 + tmp->Context()->UnregisterAudioBufferSourceNode(tmp); 1.36 + } 1.37 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(AudioNode) 1.38 + 1.39 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioBufferSourceNode, AudioNode) 1.40 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBuffer) 1.41 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaybackRate) 1.42 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.43 + 1.44 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioBufferSourceNode) 1.45 +NS_INTERFACE_MAP_END_INHERITING(AudioNode) 1.46 + 1.47 +NS_IMPL_ADDREF_INHERITED(AudioBufferSourceNode, AudioNode) 1.48 +NS_IMPL_RELEASE_INHERITED(AudioBufferSourceNode, AudioNode) 1.49 + 1.50 +/** 1.51 + * Media-thread playback engine for AudioBufferSourceNode. 1.52 + * Nothing is played until a non-null buffer has been set (via 1.53 + * AudioNodeStream::SetBuffer) and a non-zero mBufferEnd has been set (via 1.54 + * AudioNodeStream::SetInt32Parameter). 1.55 + */ 1.56 +class AudioBufferSourceNodeEngine : public AudioNodeEngine 1.57 +{ 1.58 +public: 1.59 + explicit AudioBufferSourceNodeEngine(AudioNode* aNode, 1.60 + AudioDestinationNode* aDestination) : 1.61 + AudioNodeEngine(aNode), 1.62 + mStart(0.0), mBeginProcessing(0), 1.63 + mStop(TRACK_TICKS_MAX), 1.64 + mResampler(nullptr), mRemainingResamplerTail(0), 1.65 + mBufferEnd(0), 1.66 + mLoopStart(0), mLoopEnd(0), 1.67 + mBufferSampleRate(0), mBufferPosition(0), mChannels(0), 1.68 + mDopplerShift(1.0f), 1.69 + mDestination(static_cast<AudioNodeStream*>(aDestination->Stream())), 1.70 + mPlaybackRateTimeline(1.0f), mLoop(false) 1.71 + {} 1.72 + 1.73 + ~AudioBufferSourceNodeEngine() 1.74 + { 1.75 + if (mResampler) { 1.76 + speex_resampler_destroy(mResampler); 1.77 + } 1.78 + } 1.79 + 1.80 + void SetSourceStream(AudioNodeStream* aSource) 1.81 + { 1.82 + mSource = aSource; 1.83 + } 1.84 + 1.85 + virtual void SetTimelineParameter(uint32_t aIndex, 1.86 + const dom::AudioParamTimeline& aValue, 1.87 + TrackRate aSampleRate) MOZ_OVERRIDE 1.88 + { 1.89 + switch (aIndex) { 1.90 + case AudioBufferSourceNode::PLAYBACKRATE: 1.91 + mPlaybackRateTimeline = aValue; 1.92 + WebAudioUtils::ConvertAudioParamToTicks(mPlaybackRateTimeline, mSource, mDestination); 1.93 + break; 1.94 + default: 1.95 + NS_ERROR("Bad AudioBufferSourceNodeEngine TimelineParameter"); 1.96 + } 1.97 + } 1.98 + virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam) 1.99 + { 1.100 + switch (aIndex) { 1.101 + case AudioBufferSourceNode::STOP: mStop = aParam; break; 1.102 + default: 1.103 + NS_ERROR("Bad AudioBufferSourceNodeEngine StreamTimeParameter"); 1.104 + } 1.105 + } 1.106 + virtual void SetDoubleParameter(uint32_t aIndex, double aParam) 1.107 + { 1.108 + switch (aIndex) { 1.109 + case AudioBufferSourceNode::START: 1.110 + MOZ_ASSERT(!mStart, "Another START?"); 1.111 + mStart = mSource->TimeFromDestinationTime(mDestination, aParam) * 1.112 + mSource->SampleRate(); 1.113 + // Round to nearest 1.114 + mBeginProcessing = mStart + 0.5; 1.115 + break; 1.116 + case AudioBufferSourceNode::DOPPLERSHIFT: 1.117 + mDopplerShift = aParam > 0 && aParam == aParam ? aParam : 1.0; 1.118 + break; 1.119 + default: 1.120 + NS_ERROR("Bad AudioBufferSourceNodeEngine double parameter."); 1.121 + }; 1.122 + } 1.123 + virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) 1.124 + { 1.125 + switch (aIndex) { 1.126 + case AudioBufferSourceNode::SAMPLE_RATE: mBufferSampleRate = aParam; break; 1.127 + case AudioBufferSourceNode::BUFFERSTART: 1.128 + if (mBufferPosition == 0) { 1.129 + mBufferPosition = aParam; 1.130 + } 1.131 + break; 1.132 + case AudioBufferSourceNode::BUFFEREND: mBufferEnd = aParam; break; 1.133 + case AudioBufferSourceNode::LOOP: mLoop = !!aParam; break; 1.134 + case AudioBufferSourceNode::LOOPSTART: mLoopStart = aParam; break; 1.135 + case AudioBufferSourceNode::LOOPEND: mLoopEnd = aParam; break; 1.136 + default: 1.137 + NS_ERROR("Bad AudioBufferSourceNodeEngine Int32Parameter"); 1.138 + } 1.139 + } 1.140 + virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer) 1.141 + { 1.142 + mBuffer = aBuffer; 1.143 + } 1.144 + 1.145 + bool BegunResampling() 1.146 + { 1.147 + return mBeginProcessing == -TRACK_TICKS_MAX; 1.148 + } 1.149 + 1.150 + void UpdateResampler(int32_t aOutRate, uint32_t aChannels) 1.151 + { 1.152 + if (mResampler && 1.153 + (aChannels != mChannels || 1.154 + // If the resampler has begun, then it will have moved 1.155 + // mBufferPosition to after the samples it has read, but it hasn't 1.156 + // output its buffered samples. Keep using the resampler, even if 1.157 + // the rates now match, so that this latent segment is output. 1.158 + (aOutRate == mBufferSampleRate && !BegunResampling()))) { 1.159 + speex_resampler_destroy(mResampler); 1.160 + mResampler = nullptr; 1.161 + mRemainingResamplerTail = 0; 1.162 + mBeginProcessing = mStart + 0.5; 1.163 + } 1.164 + 1.165 + if (aOutRate == mBufferSampleRate && !mResampler) { 1.166 + return; 1.167 + } 1.168 + 1.169 + if (!mResampler) { 1.170 + mChannels = aChannels; 1.171 + mResampler = speex_resampler_init(mChannels, mBufferSampleRate, aOutRate, 1.172 + SPEEX_RESAMPLER_QUALITY_DEFAULT, 1.173 + nullptr); 1.174 + } else { 1.175 + uint32_t currentOutSampleRate, currentInSampleRate; 1.176 + speex_resampler_get_rate(mResampler, ¤tInSampleRate, 1.177 + ¤tOutSampleRate); 1.178 + if (currentOutSampleRate == static_cast<uint32_t>(aOutRate)) { 1.179 + return; 1.180 + } 1.181 + speex_resampler_set_rate(mResampler, currentInSampleRate, aOutRate); 1.182 + } 1.183 + 1.184 + if (!BegunResampling()) { 1.185 + // Low pass filter effects from the resampler mean that samples before 1.186 + // the start time are influenced by resampling the buffer. The input 1.187 + // latency indicates half the filter width. 1.188 + int64_t inputLatency = speex_resampler_get_input_latency(mResampler); 1.189 + uint32_t ratioNum, ratioDen; 1.190 + speex_resampler_get_ratio(mResampler, &ratioNum, &ratioDen); 1.191 + // The output subsample resolution supported in aligning the resampler 1.192 + // is ratioNum. First round the start time to the nearest subsample. 1.193 + int64_t subsample = mStart * ratioNum + 0.5; 1.194 + // Now include the leading effects of the filter, and round *up* to the 1.195 + // next whole tick, because there is no effect on samples outside the 1.196 + // filter width. 1.197 + mBeginProcessing = 1.198 + (subsample - inputLatency * ratioDen + ratioNum - 1) / ratioNum; 1.199 + } 1.200 + } 1.201 + 1.202 + // Borrow a full buffer of size WEBAUDIO_BLOCK_SIZE from the source buffer 1.203 + // at offset aSourceOffset. This avoids copying memory. 1.204 + void BorrowFromInputBuffer(AudioChunk* aOutput, 1.205 + uint32_t aChannels) 1.206 + { 1.207 + aOutput->mDuration = WEBAUDIO_BLOCK_SIZE; 1.208 + aOutput->mBuffer = mBuffer; 1.209 + aOutput->mChannelData.SetLength(aChannels); 1.210 + for (uint32_t i = 0; i < aChannels; ++i) { 1.211 + aOutput->mChannelData[i] = mBuffer->GetData(i) + mBufferPosition; 1.212 + } 1.213 + aOutput->mVolume = 1.0f; 1.214 + aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32; 1.215 + } 1.216 + 1.217 + // Copy aNumberOfFrames frames from the source buffer at offset aSourceOffset 1.218 + // and put it at offset aBufferOffset in the destination buffer. 1.219 + void CopyFromInputBuffer(AudioChunk* aOutput, 1.220 + uint32_t aChannels, 1.221 + uintptr_t aOffsetWithinBlock, 1.222 + uint32_t aNumberOfFrames) { 1.223 + for (uint32_t i = 0; i < aChannels; ++i) { 1.224 + float* baseChannelData = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[i])); 1.225 + memcpy(baseChannelData + aOffsetWithinBlock, 1.226 + mBuffer->GetData(i) + mBufferPosition, 1.227 + aNumberOfFrames * sizeof(float)); 1.228 + } 1.229 + } 1.230 + 1.231 + // Resamples input data to an output buffer, according to |mBufferSampleRate| and 1.232 + // the playbackRate. 1.233 + // The number of frames consumed/produced depends on the amount of space 1.234 + // remaining in both the input and output buffer, and the playback rate (that 1.235 + // is, the ratio between the output samplerate and the input samplerate). 1.236 + void CopyFromInputBufferWithResampling(AudioNodeStream* aStream, 1.237 + AudioChunk* aOutput, 1.238 + uint32_t aChannels, 1.239 + uint32_t* aOffsetWithinBlock, 1.240 + TrackTicks* aCurrentPosition, 1.241 + int32_t aBufferMax) { 1.242 + // TODO: adjust for mStop (see bug 913854 comment 9). 1.243 + uint32_t availableInOutputBuffer = 1.244 + WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock; 1.245 + SpeexResamplerState* resampler = mResampler; 1.246 + MOZ_ASSERT(aChannels > 0); 1.247 + 1.248 + if (mBufferPosition < aBufferMax) { 1.249 + uint32_t availableInInputBuffer = aBufferMax - mBufferPosition; 1.250 + uint32_t ratioNum, ratioDen; 1.251 + speex_resampler_get_ratio(resampler, &ratioNum, &ratioDen); 1.252 + // Limit the number of input samples copied and possibly 1.253 + // format-converted for resampling by estimating how many will be used. 1.254 + // This may be a little small if still filling the resampler with 1.255 + // initial data, but we'll get called again and it will work out. 1.256 + uint32_t inputLimit = availableInOutputBuffer * ratioNum / ratioDen + 10; 1.257 + if (!BegunResampling()) { 1.258 + // First time the resampler is used. 1.259 + uint32_t inputLatency = speex_resampler_get_input_latency(resampler); 1.260 + inputLimit += inputLatency; 1.261 + // If starting after mStart, then play from the beginning of the 1.262 + // buffer, but correct for input latency. If starting before mStart, 1.263 + // then align the resampler so that the time corresponding to the 1.264 + // first input sample is mStart. 1.265 + uint32_t skipFracNum = inputLatency * ratioDen; 1.266 + double leadTicks = mStart - *aCurrentPosition; 1.267 + if (leadTicks > 0.0) { 1.268 + // Round to nearest output subsample supported by the resampler at 1.269 + // these rates. 1.270 + skipFracNum -= leadTicks * ratioNum + 0.5; 1.271 + MOZ_ASSERT(skipFracNum < INT32_MAX, "mBeginProcessing is wrong?"); 1.272 + } 1.273 + speex_resampler_set_skip_frac_num(resampler, skipFracNum); 1.274 + 1.275 + mBeginProcessing = -TRACK_TICKS_MAX; 1.276 + } 1.277 + inputLimit = std::min(inputLimit, availableInInputBuffer); 1.278 + 1.279 + for (uint32_t i = 0; true; ) { 1.280 + uint32_t inSamples = inputLimit; 1.281 + const float* inputData = mBuffer->GetData(i) + mBufferPosition; 1.282 + 1.283 + uint32_t outSamples = availableInOutputBuffer; 1.284 + float* outputData = 1.285 + static_cast<float*>(const_cast<void*>(aOutput->mChannelData[i])) + 1.286 + *aOffsetWithinBlock; 1.287 + 1.288 + WebAudioUtils::SpeexResamplerProcess(resampler, i, 1.289 + inputData, &inSamples, 1.290 + outputData, &outSamples); 1.291 + if (++i == aChannels) { 1.292 + mBufferPosition += inSamples; 1.293 + MOZ_ASSERT(mBufferPosition <= mBufferEnd || mLoop); 1.294 + *aOffsetWithinBlock += outSamples; 1.295 + *aCurrentPosition += outSamples; 1.296 + if (inSamples == availableInInputBuffer && !mLoop) { 1.297 + // We'll feed in enough zeros to empty out the resampler's memory. 1.298 + // This handles the output latency as well as capturing the low 1.299 + // pass effects of the resample filter. 1.300 + mRemainingResamplerTail = 1.301 + 2 * speex_resampler_get_input_latency(resampler) - 1; 1.302 + } 1.303 + return; 1.304 + } 1.305 + } 1.306 + } else { 1.307 + for (uint32_t i = 0; true; ) { 1.308 + uint32_t inSamples = mRemainingResamplerTail; 1.309 + uint32_t outSamples = availableInOutputBuffer; 1.310 + float* outputData = 1.311 + static_cast<float*>(const_cast<void*>(aOutput->mChannelData[i])) + 1.312 + *aOffsetWithinBlock; 1.313 + 1.314 + // AudioDataValue* for aIn selects the function that does not try to 1.315 + // copy and format-convert input data. 1.316 + WebAudioUtils::SpeexResamplerProcess(resampler, i, 1.317 + static_cast<AudioDataValue*>(nullptr), &inSamples, 1.318 + outputData, &outSamples); 1.319 + if (++i == aChannels) { 1.320 + mRemainingResamplerTail -= inSamples; 1.321 + MOZ_ASSERT(mRemainingResamplerTail >= 0); 1.322 + *aOffsetWithinBlock += outSamples; 1.323 + *aCurrentPosition += outSamples; 1.324 + break; 1.325 + } 1.326 + } 1.327 + } 1.328 + } 1.329 + 1.330 + /** 1.331 + * Fill aOutput with as many zero frames as we can, and advance 1.332 + * aOffsetWithinBlock and aCurrentPosition based on how many frames we write. 1.333 + * This will never advance aOffsetWithinBlock past WEBAUDIO_BLOCK_SIZE or 1.334 + * aCurrentPosition past aMaxPos. This function knows when it needs to 1.335 + * allocate the output buffer, and also optimizes the case where it can avoid 1.336 + * memory allocations. 1.337 + */ 1.338 + void FillWithZeroes(AudioChunk* aOutput, 1.339 + uint32_t aChannels, 1.340 + uint32_t* aOffsetWithinBlock, 1.341 + TrackTicks* aCurrentPosition, 1.342 + TrackTicks aMaxPos) 1.343 + { 1.344 + MOZ_ASSERT(*aCurrentPosition < aMaxPos); 1.345 + uint32_t numFrames = 1.346 + std::min<TrackTicks>(WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock, 1.347 + aMaxPos - *aCurrentPosition); 1.348 + if (numFrames == WEBAUDIO_BLOCK_SIZE) { 1.349 + aOutput->SetNull(numFrames); 1.350 + } else { 1.351 + if (*aOffsetWithinBlock == 0) { 1.352 + AllocateAudioBlock(aChannels, aOutput); 1.353 + } 1.354 + WriteZeroesToAudioBlock(aOutput, *aOffsetWithinBlock, numFrames); 1.355 + } 1.356 + *aOffsetWithinBlock += numFrames; 1.357 + *aCurrentPosition += numFrames; 1.358 + } 1.359 + 1.360 + /** 1.361 + * Copy as many frames as possible from the source buffer to aOutput, and 1.362 + * advance aOffsetWithinBlock and aCurrentPosition based on how many frames 1.363 + * we write. This will never advance aOffsetWithinBlock past 1.364 + * WEBAUDIO_BLOCK_SIZE, or aCurrentPosition past mStop. It takes data from 1.365 + * the buffer at aBufferOffset, and never takes more data than aBufferMax. 1.366 + * This function knows when it needs to allocate the output buffer, and also 1.367 + * optimizes the case where it can avoid memory allocations. 1.368 + */ 1.369 + void CopyFromBuffer(AudioNodeStream* aStream, 1.370 + AudioChunk* aOutput, 1.371 + uint32_t aChannels, 1.372 + uint32_t* aOffsetWithinBlock, 1.373 + TrackTicks* aCurrentPosition, 1.374 + int32_t aBufferMax) 1.375 + { 1.376 + MOZ_ASSERT(*aCurrentPosition < mStop); 1.377 + uint32_t numFrames = 1.378 + std::min(std::min<TrackTicks>(WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock, 1.379 + aBufferMax - mBufferPosition), 1.380 + mStop - *aCurrentPosition); 1.381 + if (numFrames == WEBAUDIO_BLOCK_SIZE && !mResampler) { 1.382 + MOZ_ASSERT(mBufferPosition < aBufferMax); 1.383 + BorrowFromInputBuffer(aOutput, aChannels); 1.384 + *aOffsetWithinBlock += numFrames; 1.385 + *aCurrentPosition += numFrames; 1.386 + mBufferPosition += numFrames; 1.387 + } else { 1.388 + if (*aOffsetWithinBlock == 0) { 1.389 + AllocateAudioBlock(aChannels, aOutput); 1.390 + } 1.391 + if (!mResampler) { 1.392 + MOZ_ASSERT(mBufferPosition < aBufferMax); 1.393 + CopyFromInputBuffer(aOutput, aChannels, *aOffsetWithinBlock, numFrames); 1.394 + *aOffsetWithinBlock += numFrames; 1.395 + *aCurrentPosition += numFrames; 1.396 + mBufferPosition += numFrames; 1.397 + } else { 1.398 + CopyFromInputBufferWithResampling(aStream, aOutput, aChannels, aOffsetWithinBlock, aCurrentPosition, aBufferMax); 1.399 + } 1.400 + } 1.401 + } 1.402 + 1.403 + int32_t ComputeFinalOutSampleRate(float aPlaybackRate) 1.404 + { 1.405 + // Make sure the playback rate and the doppler shift are something 1.406 + // our resampler can work with. 1.407 + int32_t rate = WebAudioUtils:: 1.408 + TruncateFloatToInt<int32_t>(mSource->SampleRate() / 1.409 + (aPlaybackRate * mDopplerShift)); 1.410 + return rate ? rate : mBufferSampleRate; 1.411 + } 1.412 + 1.413 + void UpdateSampleRateIfNeeded(uint32_t aChannels) 1.414 + { 1.415 + float playbackRate; 1.416 + 1.417 + if (mPlaybackRateTimeline.HasSimpleValue()) { 1.418 + playbackRate = mPlaybackRateTimeline.GetValue(); 1.419 + } else { 1.420 + playbackRate = mPlaybackRateTimeline.GetValueAtTime(mSource->GetCurrentPosition()); 1.421 + } 1.422 + if (playbackRate <= 0 || playbackRate != playbackRate) { 1.423 + playbackRate = 1.0f; 1.424 + } 1.425 + 1.426 + int32_t outRate = ComputeFinalOutSampleRate(playbackRate); 1.427 + UpdateResampler(outRate, aChannels); 1.428 + } 1.429 + 1.430 + virtual void ProcessBlock(AudioNodeStream* aStream, 1.431 + const AudioChunk& aInput, 1.432 + AudioChunk* aOutput, 1.433 + bool* aFinished) 1.434 + { 1.435 + if (!mBuffer || !mBufferEnd) { 1.436 + aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); 1.437 + return; 1.438 + } 1.439 + 1.440 + uint32_t channels = mBuffer->GetChannels(); 1.441 + if (!channels) { 1.442 + aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); 1.443 + return; 1.444 + } 1.445 + 1.446 + // WebKit treats the playbackRate as a k-rate parameter in their code, 1.447 + // despite the spec saying that it should be an a-rate parameter. We treat 1.448 + // it as k-rate. Spec bug: https://www.w3.org/Bugs/Public/show_bug.cgi?id=21592 1.449 + UpdateSampleRateIfNeeded(channels); 1.450 + 1.451 + uint32_t written = 0; 1.452 + TrackTicks streamPosition = aStream->GetCurrentPosition(); 1.453 + while (written < WEBAUDIO_BLOCK_SIZE) { 1.454 + if (mStop != TRACK_TICKS_MAX && 1.455 + streamPosition >= mStop) { 1.456 + FillWithZeroes(aOutput, channels, &written, &streamPosition, TRACK_TICKS_MAX); 1.457 + continue; 1.458 + } 1.459 + if (streamPosition < mBeginProcessing) { 1.460 + FillWithZeroes(aOutput, channels, &written, &streamPosition, 1.461 + mBeginProcessing); 1.462 + continue; 1.463 + } 1.464 + if (mLoop) { 1.465 + // mLoopEnd can become less than mBufferPosition when a LOOPEND engine 1.466 + // parameter is received after "loopend" is changed on the node or a 1.467 + // new buffer with lower samplerate is set. 1.468 + if (mBufferPosition >= mLoopEnd) { 1.469 + mBufferPosition = mLoopStart; 1.470 + } 1.471 + CopyFromBuffer(aStream, aOutput, channels, &written, &streamPosition, mLoopEnd); 1.472 + } else { 1.473 + if (mBufferPosition < mBufferEnd || mRemainingResamplerTail) { 1.474 + CopyFromBuffer(aStream, aOutput, channels, &written, &streamPosition, mBufferEnd); 1.475 + } else { 1.476 + FillWithZeroes(aOutput, channels, &written, &streamPosition, TRACK_TICKS_MAX); 1.477 + } 1.478 + } 1.479 + } 1.480 + 1.481 + // We've finished if we've gone past mStop, or if we're past mDuration when 1.482 + // looping is disabled. 1.483 + if (streamPosition >= mStop || 1.484 + (!mLoop && mBufferPosition >= mBufferEnd && !mRemainingResamplerTail)) { 1.485 + *aFinished = true; 1.486 + } 1.487 + } 1.488 + 1.489 + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE 1.490 + { 1.491 + // Not owned: 1.492 + // - mBuffer - shared w/ AudioNode 1.493 + // - mPlaybackRateTimeline - shared w/ AudioNode 1.494 + 1.495 + size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); 1.496 + 1.497 + // NB: We need to modify speex if we want the full memory picture, internal 1.498 + // fields that need measuring noted below. 1.499 + // - mResampler->mem 1.500 + // - mResampler->sinc_table 1.501 + // - mResampler->last_sample 1.502 + // - mResampler->magic_samples 1.503 + // - mResampler->samp_frac_num 1.504 + amount += aMallocSizeOf(mResampler); 1.505 + 1.506 + return amount; 1.507 + } 1.508 + 1.509 + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE 1.510 + { 1.511 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.512 + } 1.513 + 1.514 + double mStart; // including the fractional position between ticks 1.515 + // Low pass filter effects from the resampler mean that samples before the 1.516 + // start time are influenced by resampling the buffer. mBeginProcessing 1.517 + // includes the extent of this filter. The special value of -TRACK_TICKS_MAX 1.518 + // indicates that the resampler has begun processing. 1.519 + TrackTicks mBeginProcessing; 1.520 + TrackTicks mStop; 1.521 + nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer; 1.522 + SpeexResamplerState* mResampler; 1.523 + // mRemainingResamplerTail, like mBufferPosition, and 1.524 + // mBufferEnd, is measured in input buffer samples. 1.525 + int mRemainingResamplerTail; 1.526 + int32_t mBufferEnd; 1.527 + int32_t mLoopStart; 1.528 + int32_t mLoopEnd; 1.529 + int32_t mBufferSampleRate; 1.530 + int32_t mBufferPosition; 1.531 + uint32_t mChannels; 1.532 + float mDopplerShift; 1.533 + AudioNodeStream* mDestination; 1.534 + AudioNodeStream* mSource; 1.535 + AudioParamTimeline mPlaybackRateTimeline; 1.536 + bool mLoop; 1.537 +}; 1.538 + 1.539 +AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* aContext) 1.540 + : AudioNode(aContext, 1.541 + 2, 1.542 + ChannelCountMode::Max, 1.543 + ChannelInterpretation::Speakers) 1.544 + , mLoopStart(0.0) 1.545 + , mLoopEnd(0.0) 1.546 + // mOffset and mDuration are initialized in Start(). 1.547 + , mPlaybackRate(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), 1.548 + SendPlaybackRateToStream, 1.0f)) 1.549 + , mLoop(false) 1.550 + , mStartCalled(false) 1.551 + , mStopped(false) 1.552 +{ 1.553 + AudioBufferSourceNodeEngine* engine = new AudioBufferSourceNodeEngine(this, aContext->Destination()); 1.554 + mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::SOURCE_STREAM); 1.555 + engine->SetSourceStream(static_cast<AudioNodeStream*>(mStream.get())); 1.556 + mStream->AddMainThreadListener(this); 1.557 +} 1.558 + 1.559 +AudioBufferSourceNode::~AudioBufferSourceNode() 1.560 +{ 1.561 + if (Context()) { 1.562 + Context()->UnregisterAudioBufferSourceNode(this); 1.563 + } 1.564 +} 1.565 + 1.566 +size_t 1.567 +AudioBufferSourceNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const 1.568 +{ 1.569 + size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); 1.570 + if (mBuffer) { 1.571 + amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf); 1.572 + } 1.573 + 1.574 + amount += mPlaybackRate->SizeOfIncludingThis(aMallocSizeOf); 1.575 + return amount; 1.576 +} 1.577 + 1.578 +size_t 1.579 +AudioBufferSourceNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const 1.580 +{ 1.581 + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 1.582 +} 1.583 + 1.584 +JSObject* 1.585 +AudioBufferSourceNode::WrapObject(JSContext* aCx) 1.586 +{ 1.587 + return AudioBufferSourceNodeBinding::Wrap(aCx, this); 1.588 +} 1.589 + 1.590 +void 1.591 +AudioBufferSourceNode::Start(double aWhen, double aOffset, 1.592 + const Optional<double>& aDuration, ErrorResult& aRv) 1.593 +{ 1.594 + if (!WebAudioUtils::IsTimeValid(aWhen) || 1.595 + (aDuration.WasPassed() && !WebAudioUtils::IsTimeValid(aDuration.Value()))) { 1.596 + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 1.597 + return; 1.598 + } 1.599 + 1.600 + if (mStartCalled) { 1.601 + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1.602 + return; 1.603 + } 1.604 + mStartCalled = true; 1.605 + 1.606 + AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get()); 1.607 + if (!ns) { 1.608 + // Nothing to play, or we're already dead for some reason 1.609 + return; 1.610 + } 1.611 + 1.612 + // Remember our arguments so that we can use them when we get a new buffer. 1.613 + mOffset = aOffset; 1.614 + mDuration = aDuration.WasPassed() ? aDuration.Value() 1.615 + : std::numeric_limits<double>::min(); 1.616 + // We can't send these parameters without a buffer because we don't know the 1.617 + // buffer's sample rate or length. 1.618 + if (mBuffer) { 1.619 + SendOffsetAndDurationParametersToStream(ns); 1.620 + } 1.621 + 1.622 + // Don't set parameter unnecessarily 1.623 + if (aWhen > 0.0) { 1.624 + ns->SetDoubleParameter(START, mContext->DOMTimeToStreamTime(aWhen)); 1.625 + } 1.626 +} 1.627 + 1.628 +void 1.629 +AudioBufferSourceNode::SendBufferParameterToStream(JSContext* aCx) 1.630 +{ 1.631 + AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get()); 1.632 + MOZ_ASSERT(ns, "Why don't we have a stream here?"); 1.633 + 1.634 + if (mBuffer) { 1.635 + float rate = mBuffer->SampleRate(); 1.636 + nsRefPtr<ThreadSharedFloatArrayBufferList> data = 1.637 + mBuffer->GetThreadSharedChannelsForRate(aCx); 1.638 + ns->SetBuffer(data.forget()); 1.639 + ns->SetInt32Parameter(SAMPLE_RATE, rate); 1.640 + 1.641 + if (mStartCalled) { 1.642 + SendOffsetAndDurationParametersToStream(ns); 1.643 + } 1.644 + } else { 1.645 + ns->SetBuffer(nullptr); 1.646 + 1.647 + MarkInactive(); 1.648 + } 1.649 +} 1.650 + 1.651 +void 1.652 +AudioBufferSourceNode::SendOffsetAndDurationParametersToStream(AudioNodeStream* aStream) 1.653 +{ 1.654 + NS_ASSERTION(mBuffer && mStartCalled, 1.655 + "Only call this when we have a buffer and start() has been called"); 1.656 + 1.657 + float rate = mBuffer->SampleRate(); 1.658 + int32_t bufferEnd = mBuffer->Length(); 1.659 + int32_t offsetSamples = std::max(0, NS_lround(mOffset * rate)); 1.660 + 1.661 + // Don't set parameter unnecessarily 1.662 + if (offsetSamples > 0) { 1.663 + aStream->SetInt32Parameter(BUFFERSTART, offsetSamples); 1.664 + } 1.665 + 1.666 + if (mDuration != std::numeric_limits<double>::min()) { 1.667 + bufferEnd = std::min(bufferEnd, 1.668 + offsetSamples + NS_lround(mDuration * rate)); 1.669 + } 1.670 + aStream->SetInt32Parameter(BUFFEREND, bufferEnd); 1.671 + 1.672 + MarkActive(); 1.673 +} 1.674 + 1.675 +void 1.676 +AudioBufferSourceNode::Stop(double aWhen, ErrorResult& aRv) 1.677 +{ 1.678 + if (!WebAudioUtils::IsTimeValid(aWhen)) { 1.679 + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); 1.680 + return; 1.681 + } 1.682 + 1.683 + if (!mStartCalled) { 1.684 + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1.685 + return; 1.686 + } 1.687 + 1.688 + AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get()); 1.689 + if (!ns || !Context()) { 1.690 + // We've already stopped and had our stream shut down 1.691 + return; 1.692 + } 1.693 + 1.694 + ns->SetStreamTimeParameter(STOP, Context(), std::max(0.0, aWhen)); 1.695 +} 1.696 + 1.697 +void 1.698 +AudioBufferSourceNode::NotifyMainThreadStateChanged() 1.699 +{ 1.700 + if (mStream->IsFinished()) { 1.701 + class EndedEventDispatcher : public nsRunnable 1.702 + { 1.703 + public: 1.704 + explicit EndedEventDispatcher(AudioBufferSourceNode* aNode) 1.705 + : mNode(aNode) {} 1.706 + NS_IMETHODIMP Run() 1.707 + { 1.708 + // If it's not safe to run scripts right now, schedule this to run later 1.709 + if (!nsContentUtils::IsSafeToRunScript()) { 1.710 + nsContentUtils::AddScriptRunner(this); 1.711 + return NS_OK; 1.712 + } 1.713 + 1.714 + mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended")); 1.715 + return NS_OK; 1.716 + } 1.717 + private: 1.718 + nsRefPtr<AudioBufferSourceNode> mNode; 1.719 + }; 1.720 + if (!mStopped) { 1.721 + // Only dispatch the ended event once 1.722 + NS_DispatchToMainThread(new EndedEventDispatcher(this)); 1.723 + mStopped = true; 1.724 + } 1.725 + 1.726 + // Drop the playing reference 1.727 + // Warning: The below line might delete this. 1.728 + MarkInactive(); 1.729 + } 1.730 +} 1.731 + 1.732 +void 1.733 +AudioBufferSourceNode::SendPlaybackRateToStream(AudioNode* aNode) 1.734 +{ 1.735 + AudioBufferSourceNode* This = static_cast<AudioBufferSourceNode*>(aNode); 1.736 + SendTimelineParameterToStream(This, PLAYBACKRATE, *This->mPlaybackRate); 1.737 +} 1.738 + 1.739 +void 1.740 +AudioBufferSourceNode::SendDopplerShiftToStream(double aDopplerShift) 1.741 +{ 1.742 + SendDoubleParameterToStream(DOPPLERSHIFT, aDopplerShift); 1.743 +} 1.744 + 1.745 +void 1.746 +AudioBufferSourceNode::SendLoopParametersToStream() 1.747 +{ 1.748 + // Don't compute and set the loop parameters unnecessarily 1.749 + if (mLoop && mBuffer) { 1.750 + float rate = mBuffer->SampleRate(); 1.751 + double length = (double(mBuffer->Length()) / mBuffer->SampleRate()); 1.752 + double actualLoopStart, actualLoopEnd; 1.753 + if (mLoopStart >= 0.0 && mLoopEnd > 0.0 && 1.754 + mLoopStart < mLoopEnd) { 1.755 + MOZ_ASSERT(mLoopStart != 0.0 || mLoopEnd != 0.0); 1.756 + actualLoopStart = (mLoopStart > length) ? 0.0 : mLoopStart; 1.757 + actualLoopEnd = std::min(mLoopEnd, length); 1.758 + } else { 1.759 + actualLoopStart = 0.0; 1.760 + actualLoopEnd = length; 1.761 + } 1.762 + int32_t loopStartTicks = NS_lround(actualLoopStart * rate); 1.763 + int32_t loopEndTicks = NS_lround(actualLoopEnd * rate); 1.764 + if (loopStartTicks < loopEndTicks) { 1.765 + SendInt32ParameterToStream(LOOPSTART, loopStartTicks); 1.766 + SendInt32ParameterToStream(LOOPEND, loopEndTicks); 1.767 + SendInt32ParameterToStream(LOOP, 1); 1.768 + } else { 1.769 + // Be explicit about looping not happening if the offsets make 1.770 + // looping impossible. 1.771 + SendInt32ParameterToStream(LOOP, 0); 1.772 + } 1.773 + } else if (!mLoop) { 1.774 + SendInt32ParameterToStream(LOOP, 0); 1.775 + } 1.776 +} 1.777 + 1.778 +} 1.779 +}