1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "AudioBufferSourceNode.h"
8 #include "nsDebug.h"
9 #include "mozilla/dom/AudioBufferSourceNodeBinding.h"
10 #include "mozilla/dom/AudioParam.h"
11 #include "mozilla/FloatingPoint.h"
12 #include "nsContentUtils.h"
13 #include "nsMathUtils.h"
14 #include "AlignmentUtils.h"
15 #include "AudioNodeEngine.h"
16 #include "AudioNodeStream.h"
17 #include "AudioDestinationNode.h"
18 #include "AudioParamTimeline.h"
19 #include <limits>
20 #include <algorithm>
21 
22 namespace mozilla {
23 namespace dom {
24 
25 NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioBufferSourceNode, AudioNode, mBuffer, mPlaybackRate, mDetune)
26 
27 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioBufferSourceNode)
28 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
29 
30 NS_IMPL_ADDREF_INHERITED(AudioBufferSourceNode, AudioNode)
31 NS_IMPL_RELEASE_INHERITED(AudioBufferSourceNode, AudioNode)
32 
33 /**
34  * Media-thread playback engine for AudioBufferSourceNode.
35  * Nothing is played until a non-null buffer has been set (via
36  * AudioNodeStream::SetBuffer) and a non-zero mBufferEnd has been set (via
37  * AudioNodeStream::SetInt32Parameter).
38  */
39 class AudioBufferSourceNodeEngine final : public AudioNodeEngine
40 {
41 public:
AudioBufferSourceNodeEngine(AudioNode * aNode,AudioDestinationNode * aDestination)42   AudioBufferSourceNodeEngine(AudioNode* aNode,
43                               AudioDestinationNode* aDestination) :
44     AudioNodeEngine(aNode),
45     mStart(0.0), mBeginProcessing(0),
46     mStop(STREAM_TIME_MAX),
47     mResampler(nullptr), mRemainingResamplerTail(0),
48     mBufferEnd(0),
49     mLoopStart(0), mLoopEnd(0),
50     mBufferPosition(0), mBufferSampleRate(0),
51     // mResamplerOutRate is initialized in UpdateResampler().
52     mChannels(0),
53     mDopplerShift(1.0f),
54     mDestination(aDestination->Stream()),
55     mPlaybackRateTimeline(1.0f),
56     mDetuneTimeline(0.0f),
57     mLoop(false)
58   {}
59 
~AudioBufferSourceNodeEngine()60   ~AudioBufferSourceNodeEngine()
61   {
62     if (mResampler) {
63       speex_resampler_destroy(mResampler);
64     }
65   }
66 
SetSourceStream(AudioNodeStream * aSource)67   void SetSourceStream(AudioNodeStream* aSource)
68   {
69     mSource = aSource;
70   }
71 
RecvTimelineEvent(uint32_t aIndex,dom::AudioTimelineEvent & aEvent)72   void RecvTimelineEvent(uint32_t aIndex,
73                          dom::AudioTimelineEvent& aEvent) override
74   {
75     MOZ_ASSERT(mDestination);
76     WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
77                                                     mDestination);
78 
79     switch (aIndex) {
80     case AudioBufferSourceNode::PLAYBACKRATE:
81       mPlaybackRateTimeline.InsertEvent<int64_t>(aEvent);
82       break;
83     case AudioBufferSourceNode::DETUNE:
84       mDetuneTimeline.InsertEvent<int64_t>(aEvent);
85       break;
86     default:
87       NS_ERROR("Bad AudioBufferSourceNodeEngine TimelineParameter");
88     }
89   }
SetStreamTimeParameter(uint32_t aIndex,StreamTime aParam)90   void SetStreamTimeParameter(uint32_t aIndex, StreamTime aParam) override
91   {
92     switch (aIndex) {
93     case AudioBufferSourceNode::STOP: mStop = aParam; break;
94     default:
95       NS_ERROR("Bad AudioBufferSourceNodeEngine StreamTimeParameter");
96     }
97   }
SetDoubleParameter(uint32_t aIndex,double aParam)98   void SetDoubleParameter(uint32_t aIndex, double aParam) override
99   {
100     switch (aIndex) {
101     case AudioBufferSourceNode::START:
102       MOZ_ASSERT(!mStart, "Another START?");
103       mStart = aParam * mDestination->SampleRate();
104       // Round to nearest
105       mBeginProcessing = mStart + 0.5;
106       break;
107     case AudioBufferSourceNode::DOPPLERSHIFT:
108       mDopplerShift = (aParam <= 0 || mozilla::IsNaN(aParam)) ? 1.0 : aParam;
109       break;
110     default:
111       NS_ERROR("Bad AudioBufferSourceNodeEngine double parameter.");
112     };
113   }
SetInt32Parameter(uint32_t aIndex,int32_t aParam)114   void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override
115   {
116     switch (aIndex) {
117     case AudioBufferSourceNode::SAMPLE_RATE:
118       MOZ_ASSERT(aParam > 0);
119       mBufferSampleRate = aParam;
120       mSource->SetActive();
121       break;
122     case AudioBufferSourceNode::BUFFERSTART:
123       MOZ_ASSERT(aParam >= 0);
124       if (mBufferPosition == 0) {
125         mBufferPosition = aParam;
126       }
127       break;
128     case AudioBufferSourceNode::BUFFEREND:
129       MOZ_ASSERT(aParam >= 0);
130       mBufferEnd = aParam;
131       break;
132     case AudioBufferSourceNode::LOOP: mLoop = !!aParam; break;
133     case AudioBufferSourceNode::LOOPSTART:
134       MOZ_ASSERT(aParam >= 0);
135       mLoopStart = aParam;
136       break;
137     case AudioBufferSourceNode::LOOPEND:
138       MOZ_ASSERT(aParam >= 0);
139       mLoopEnd = aParam;
140       break;
141     default:
142       NS_ERROR("Bad AudioBufferSourceNodeEngine Int32Parameter");
143     }
144   }
SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)145   void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer) override
146   {
147     mBuffer = aBuffer;
148   }
149 
BegunResampling()150   bool BegunResampling()
151   {
152     return mBeginProcessing == -STREAM_TIME_MAX;
153   }
154 
UpdateResampler(int32_t aOutRate,uint32_t aChannels)155   void UpdateResampler(int32_t aOutRate, uint32_t aChannels)
156   {
157     if (mResampler &&
158         (aChannels != mChannels ||
159          // If the resampler has begun, then it will have moved
160          // mBufferPosition to after the samples it has read, but it hasn't
161          // output its buffered samples.  Keep using the resampler, even if
162          // the rates now match, so that this latent segment is output.
163          (aOutRate == mBufferSampleRate && !BegunResampling()))) {
164       speex_resampler_destroy(mResampler);
165       mResampler = nullptr;
166       mRemainingResamplerTail = 0;
167       mBeginProcessing = mStart + 0.5;
168     }
169 
170     if (aChannels == 0 ||
171         (aOutRate == mBufferSampleRate && !mResampler)) {
172       mResamplerOutRate = aOutRate;
173       return;
174     }
175 
176     if (!mResampler) {
177       mChannels = aChannels;
178       mResampler = speex_resampler_init(mChannels, mBufferSampleRate, aOutRate,
179                                         SPEEX_RESAMPLER_QUALITY_MIN,
180                                         nullptr);
181     } else {
182       if (mResamplerOutRate == aOutRate) {
183         return;
184       }
185       if (speex_resampler_set_rate(mResampler, mBufferSampleRate, aOutRate) != RESAMPLER_ERR_SUCCESS) {
186         NS_ASSERTION(false, "speex_resampler_set_rate failed");
187         return;
188       }
189     }
190 
191     mResamplerOutRate = aOutRate;
192 
193     if (!BegunResampling()) {
194       // Low pass filter effects from the resampler mean that samples before
195       // the start time are influenced by resampling the buffer.  The input
196       // latency indicates half the filter width.
197       int64_t inputLatency = speex_resampler_get_input_latency(mResampler);
198       uint32_t ratioNum, ratioDen;
199       speex_resampler_get_ratio(mResampler, &ratioNum, &ratioDen);
200       // The output subsample resolution supported in aligning the resampler
201       // is ratioNum.  First round the start time to the nearest subsample.
202       int64_t subsample = mStart * ratioNum + 0.5;
203       // Now include the leading effects of the filter, and round *up* to the
204       // next whole tick, because there is no effect on samples outside the
205       // filter width.
206       mBeginProcessing =
207         (subsample - inputLatency * ratioDen + ratioNum - 1) / ratioNum;
208     }
209   }
210 
211   // Borrow a full buffer of size WEBAUDIO_BLOCK_SIZE from the source buffer
212   // at offset aSourceOffset.  This avoids copying memory.
BorrowFromInputBuffer(AudioBlock * aOutput,uint32_t aChannels)213   void BorrowFromInputBuffer(AudioBlock* aOutput,
214                              uint32_t aChannels)
215   {
216     aOutput->SetBuffer(mBuffer);
217     aOutput->mChannelData.SetLength(aChannels);
218     for (uint32_t i = 0; i < aChannels; ++i) {
219       aOutput->mChannelData[i] = mBuffer->GetData(i) + mBufferPosition;
220     }
221     aOutput->mVolume = 1.0f;
222     aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32;
223   }
224 
225   // Copy aNumberOfFrames frames from the source buffer at offset aSourceOffset
226   // and put it at offset aBufferOffset in the destination buffer.
CopyFromInputBuffer(AudioBlock * aOutput,uint32_t aChannels,uintptr_t aOffsetWithinBlock,uint32_t aNumberOfFrames)227   void CopyFromInputBuffer(AudioBlock* aOutput,
228                            uint32_t aChannels,
229                            uintptr_t aOffsetWithinBlock,
230                            uint32_t aNumberOfFrames) {
231     for (uint32_t i = 0; i < aChannels; ++i) {
232       float* baseChannelData = aOutput->ChannelFloatsForWrite(i);
233       memcpy(baseChannelData + aOffsetWithinBlock,
234              mBuffer->GetData(i) + mBufferPosition,
235              aNumberOfFrames * sizeof(float));
236     }
237   }
238 
239   // Resamples input data to an output buffer, according to |mBufferSampleRate| and
240   // the playbackRate/detune.
241   // The number of frames consumed/produced depends on the amount of space
242   // remaining in both the input and output buffer, and the playback rate (that
243   // is, the ratio between the output samplerate and the input samplerate).
CopyFromInputBufferWithResampling(AudioBlock * aOutput,uint32_t aChannels,uint32_t * aOffsetWithinBlock,uint32_t aAvailableInOutput,StreamTime * aCurrentPosition,uint32_t aBufferMax)244   void CopyFromInputBufferWithResampling(AudioBlock* aOutput,
245                                          uint32_t aChannels,
246                                          uint32_t* aOffsetWithinBlock,
247                                          uint32_t aAvailableInOutput,
248                                          StreamTime* aCurrentPosition,
249                                          uint32_t aBufferMax)
250   {
251     if (*aOffsetWithinBlock == 0) {
252       aOutput->AllocateChannels(aChannels);
253     }
254     SpeexResamplerState* resampler = mResampler;
255     MOZ_ASSERT(aChannels > 0);
256 
257     if (mBufferPosition < aBufferMax) {
258       uint32_t availableInInputBuffer = aBufferMax - mBufferPosition;
259       uint32_t ratioNum, ratioDen;
260       speex_resampler_get_ratio(resampler, &ratioNum, &ratioDen);
261       // Limit the number of input samples copied and possibly
262       // format-converted for resampling by estimating how many will be used.
263       // This may be a little small if still filling the resampler with
264       // initial data, but we'll get called again and it will work out.
265       uint32_t inputLimit = aAvailableInOutput * ratioNum / ratioDen + 10;
266       if (!BegunResampling()) {
267         // First time the resampler is used.
268         uint32_t inputLatency = speex_resampler_get_input_latency(resampler);
269         inputLimit += inputLatency;
270         // If starting after mStart, then play from the beginning of the
271         // buffer, but correct for input latency.  If starting before mStart,
272         // then align the resampler so that the time corresponding to the
273         // first input sample is mStart.
274         int64_t skipFracNum = static_cast<int64_t>(inputLatency) * ratioDen;
275         double leadTicks = mStart - *aCurrentPosition;
276         if (leadTicks > 0.0) {
277           // Round to nearest output subsample supported by the resampler at
278           // these rates.
279           int64_t leadSubsamples = leadTicks * ratioNum + 0.5;
280           MOZ_ASSERT(leadSubsamples <= skipFracNum,
281                      "mBeginProcessing is wrong?");
282           skipFracNum -= leadSubsamples;
283         }
284         speex_resampler_set_skip_frac_num(resampler,
285                                   std::min<int64_t>(skipFracNum, UINT32_MAX));
286 
287         mBeginProcessing = -STREAM_TIME_MAX;
288       }
289       inputLimit = std::min(inputLimit, availableInInputBuffer);
290 
291       for (uint32_t i = 0; true; ) {
292         uint32_t inSamples = inputLimit;
293         const float* inputData = mBuffer->GetData(i) + mBufferPosition;
294 
295         uint32_t outSamples = aAvailableInOutput;
296         float* outputData =
297           aOutput->ChannelFloatsForWrite(i) + *aOffsetWithinBlock;
298 
299         WebAudioUtils::SpeexResamplerProcess(resampler, i,
300                                              inputData, &inSamples,
301                                              outputData, &outSamples);
302         if (++i == aChannels) {
303           mBufferPosition += inSamples;
304           MOZ_ASSERT(mBufferPosition <= mBufferEnd || mLoop);
305           *aOffsetWithinBlock += outSamples;
306           *aCurrentPosition += outSamples;
307           if (inSamples == availableInInputBuffer && !mLoop) {
308             // We'll feed in enough zeros to empty out the resampler's memory.
309             // This handles the output latency as well as capturing the low
310             // pass effects of the resample filter.
311             mRemainingResamplerTail =
312               2 * speex_resampler_get_input_latency(resampler) - 1;
313           }
314           return;
315         }
316       }
317     } else {
318       for (uint32_t i = 0; true; ) {
319         uint32_t inSamples = mRemainingResamplerTail;
320         uint32_t outSamples = aAvailableInOutput;
321         float* outputData =
322           aOutput->ChannelFloatsForWrite(i) + *aOffsetWithinBlock;
323 
324         // AudioDataValue* for aIn selects the function that does not try to
325         // copy and format-convert input data.
326         WebAudioUtils::SpeexResamplerProcess(resampler, i,
327                          static_cast<AudioDataValue*>(nullptr), &inSamples,
328                          outputData, &outSamples);
329         if (++i == aChannels) {
330           MOZ_ASSERT(inSamples <= mRemainingResamplerTail);
331           mRemainingResamplerTail -= inSamples;
332           *aOffsetWithinBlock += outSamples;
333           *aCurrentPosition += outSamples;
334           break;
335         }
336       }
337     }
338   }
339 
340   /**
341    * Fill aOutput with as many zero frames as we can, and advance
342    * aOffsetWithinBlock and aCurrentPosition based on how many frames we write.
343    * This will never advance aOffsetWithinBlock past WEBAUDIO_BLOCK_SIZE or
344    * aCurrentPosition past aMaxPos.  This function knows when it needs to
345    * allocate the output buffer, and also optimizes the case where it can avoid
346    * memory allocations.
347    */
FillWithZeroes(AudioBlock * aOutput,uint32_t aChannels,uint32_t * aOffsetWithinBlock,StreamTime * aCurrentPosition,StreamTime aMaxPos)348   void FillWithZeroes(AudioBlock* aOutput,
349                       uint32_t aChannels,
350                       uint32_t* aOffsetWithinBlock,
351                       StreamTime* aCurrentPosition,
352                       StreamTime aMaxPos)
353   {
354     MOZ_ASSERT(*aCurrentPosition < aMaxPos);
355     uint32_t numFrames =
356       std::min<StreamTime>(WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock,
357                            aMaxPos - *aCurrentPosition);
358     if (numFrames == WEBAUDIO_BLOCK_SIZE || !aChannels) {
359       aOutput->SetNull(numFrames);
360     } else {
361       if (*aOffsetWithinBlock == 0) {
362         aOutput->AllocateChannels(aChannels);
363       }
364       WriteZeroesToAudioBlock(aOutput, *aOffsetWithinBlock, numFrames);
365     }
366     *aOffsetWithinBlock += numFrames;
367     *aCurrentPosition += numFrames;
368   }
369 
370   /**
371    * Copy as many frames as possible from the source buffer to aOutput, and
372    * advance aOffsetWithinBlock and aCurrentPosition based on how many frames
373    * we write.  This will never advance aOffsetWithinBlock past
374    * WEBAUDIO_BLOCK_SIZE, or aCurrentPosition past mStop.  It takes data from
375    * the buffer at aBufferOffset, and never takes more data than aBufferMax.
376    * This function knows when it needs to allocate the output buffer, and also
377    * optimizes the case where it can avoid memory allocations.
378    */
CopyFromBuffer(AudioBlock * aOutput,uint32_t aChannels,uint32_t * aOffsetWithinBlock,StreamTime * aCurrentPosition,uint32_t aBufferMax)379   void CopyFromBuffer(AudioBlock* aOutput,
380                       uint32_t aChannels,
381                       uint32_t* aOffsetWithinBlock,
382                       StreamTime* aCurrentPosition,
383                       uint32_t aBufferMax)
384   {
385     MOZ_ASSERT(*aCurrentPosition < mStop);
386     uint32_t availableInOutput =
387       std::min<StreamTime>(WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock,
388                            mStop - *aCurrentPosition);
389     if (mResampler) {
390       CopyFromInputBufferWithResampling(aOutput, aChannels,
391                                         aOffsetWithinBlock, availableInOutput,
392                                         aCurrentPosition, aBufferMax);
393       return;
394     }
395 
396     if (aChannels == 0) {
397       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
398       // There is no attempt here to limit advance so that mBufferPosition is
399       // limited to aBufferMax.  The only observable affect of skipping the
400       // check would be in the precise timing of the ended event if the loop
401       // attribute is reset after playback has looped.
402       *aOffsetWithinBlock += availableInOutput;
403       *aCurrentPosition += availableInOutput;
404       // Rounding at the start and end of the period means that fractional
405       // increments essentially accumulate if outRate remains constant.  If
406       // outRate is varying, then accumulation happens on average but not
407       // precisely.
408       TrackTicks start = *aCurrentPosition *
409         mBufferSampleRate / mResamplerOutRate;
410       TrackTicks end = (*aCurrentPosition + availableInOutput) *
411         mBufferSampleRate / mResamplerOutRate;
412       mBufferPosition += end - start;
413       return;
414     }
415 
416     uint32_t numFrames = std::min(aBufferMax - mBufferPosition,
417                                   availableInOutput);
418 
419     bool inputBufferAligned = true;
420     for (uint32_t i = 0; i < aChannels; ++i) {
421       if (!IS_ALIGNED16(mBuffer->GetData(i) + mBufferPosition)) {
422         inputBufferAligned = false;
423       }
424     }
425 
426     if (numFrames == WEBAUDIO_BLOCK_SIZE && inputBufferAligned) {
427       MOZ_ASSERT(mBufferPosition < aBufferMax);
428       BorrowFromInputBuffer(aOutput, aChannels);
429     } else {
430       if (*aOffsetWithinBlock == 0) {
431         aOutput->AllocateChannels(aChannels);
432       }
433       MOZ_ASSERT(mBufferPosition < aBufferMax);
434       CopyFromInputBuffer(aOutput, aChannels, *aOffsetWithinBlock, numFrames);
435     }
436     *aOffsetWithinBlock += numFrames;
437     *aCurrentPosition += numFrames;
438     mBufferPosition += numFrames;
439   }
440 
ComputeFinalOutSampleRate(float aPlaybackRate,float aDetune)441   int32_t ComputeFinalOutSampleRate(float aPlaybackRate, float aDetune)
442   {
443     float computedPlaybackRate = aPlaybackRate * pow(2, aDetune / 1200.f);
444     // Make sure the playback rate and the doppler shift are something
445     // our resampler can work with.
446     int32_t rate = WebAudioUtils::
447       TruncateFloatToInt<int32_t>(mSource->SampleRate() /
448                                   (computedPlaybackRate * mDopplerShift));
449     return rate ? rate : mBufferSampleRate;
450   }
451 
UpdateSampleRateIfNeeded(uint32_t aChannels,StreamTime aStreamPosition)452   void UpdateSampleRateIfNeeded(uint32_t aChannels, StreamTime aStreamPosition)
453   {
454     float playbackRate;
455     float detune;
456 
457     if (mPlaybackRateTimeline.HasSimpleValue()) {
458       playbackRate = mPlaybackRateTimeline.GetValue();
459     } else {
460       playbackRate = mPlaybackRateTimeline.GetValueAtTime(aStreamPosition);
461     }
462     if (mDetuneTimeline.HasSimpleValue()) {
463       detune = mDetuneTimeline.GetValue();
464     } else {
465       detune = mDetuneTimeline.GetValueAtTime(aStreamPosition);
466     }
467     if (playbackRate <= 0 || mozilla::IsNaN(playbackRate)) {
468       playbackRate = 1.0f;
469     }
470 
471     detune = std::min(std::max(-1200.f, detune), 1200.f);
472 
473     int32_t outRate = ComputeFinalOutSampleRate(playbackRate, detune);
474     UpdateResampler(outRate, aChannels);
475   }
476 
ProcessBlock(AudioNodeStream * aStream,GraphTime aFrom,const AudioBlock & aInput,AudioBlock * aOutput,bool * aFinished)477   void ProcessBlock(AudioNodeStream* aStream,
478                     GraphTime aFrom,
479                     const AudioBlock& aInput,
480                     AudioBlock* aOutput,
481                     bool* aFinished) override
482   {
483     if (mBufferSampleRate == 0) {
484       // start() has not yet been called or no buffer has yet been set
485       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
486       return;
487     }
488 
489     StreamTime streamPosition = mDestination->GraphTimeToStreamTime(aFrom);
490     uint32_t channels = mBuffer ? mBuffer->GetChannels() : 0;
491 
492     UpdateSampleRateIfNeeded(channels, streamPosition);
493 
494     uint32_t written = 0;
495     while (written < WEBAUDIO_BLOCK_SIZE) {
496       if (mStop != STREAM_TIME_MAX &&
497           streamPosition >= mStop) {
498         FillWithZeroes(aOutput, channels, &written, &streamPosition, STREAM_TIME_MAX);
499         continue;
500       }
501       if (streamPosition < mBeginProcessing) {
502         FillWithZeroes(aOutput, channels, &written, &streamPosition,
503                        mBeginProcessing);
504         continue;
505       }
506       if (mLoop) {
507         // mLoopEnd can become less than mBufferPosition when a LOOPEND engine
508         // parameter is received after "loopend" is changed on the node or a
509         // new buffer with lower samplerate is set.
510         if (mBufferPosition >= mLoopEnd) {
511           mBufferPosition = mLoopStart;
512         }
513         CopyFromBuffer(aOutput, channels, &written, &streamPosition, mLoopEnd);
514       } else {
515         if (mBufferPosition < mBufferEnd || mRemainingResamplerTail) {
516           CopyFromBuffer(aOutput, channels, &written, &streamPosition, mBufferEnd);
517         } else {
518           FillWithZeroes(aOutput, channels, &written, &streamPosition, STREAM_TIME_MAX);
519         }
520       }
521     }
522 
523     // We've finished if we've gone past mStop, or if we're past mDuration when
524     // looping is disabled.
525     if (streamPosition >= mStop ||
526         (!mLoop && mBufferPosition >= mBufferEnd && !mRemainingResamplerTail)) {
527       *aFinished = true;
528     }
529   }
530 
IsActive() const531   bool IsActive() const override
532   {
533     // Whether buffer has been set and start() has been called.
534     return mBufferSampleRate != 0;
535   }
536 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const537   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
538   {
539     // Not owned:
540     // - mBuffer - shared w/ AudioNode
541     // - mPlaybackRateTimeline - shared w/ AudioNode
542     // - mDetuneTimeline - shared w/ AudioNode
543 
544     size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
545 
546     // NB: We need to modify speex if we want the full memory picture, internal
547     //     fields that need measuring noted below.
548     // - mResampler->mem
549     // - mResampler->sinc_table
550     // - mResampler->last_sample
551     // - mResampler->magic_samples
552     // - mResampler->samp_frac_num
553     amount += aMallocSizeOf(mResampler);
554 
555     return amount;
556   }
557 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const558   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
559   {
560     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
561   }
562 
563   double mStart; // including the fractional position between ticks
564   // Low pass filter effects from the resampler mean that samples before the
565   // start time are influenced by resampling the buffer.  mBeginProcessing
566   // includes the extent of this filter.  The special value of -STREAM_TIME_MAX
567   // indicates that the resampler has begun processing.
568   StreamTime mBeginProcessing;
569   StreamTime mStop;
570   RefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
571   SpeexResamplerState* mResampler;
572   // mRemainingResamplerTail, like mBufferPosition, and
573   // mBufferEnd, is measured in input buffer samples.
574   uint32_t mRemainingResamplerTail;
575   uint32_t mBufferEnd;
576   uint32_t mLoopStart;
577   uint32_t mLoopEnd;
578   uint32_t mBufferPosition;
579   int32_t mBufferSampleRate;
580   int32_t mResamplerOutRate;
581   uint32_t mChannels;
582   float mDopplerShift;
583   AudioNodeStream* mDestination;
584   AudioNodeStream* mSource;
585   AudioParamTimeline mPlaybackRateTimeline;
586   AudioParamTimeline mDetuneTimeline;
587   bool mLoop;
588 };
589 
AudioBufferSourceNode(AudioContext * aContext)590 AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* aContext)
591   : AudioNode(aContext,
592               2,
593               ChannelCountMode::Max,
594               ChannelInterpretation::Speakers)
595   , mLoopStart(0.0)
596   , mLoopEnd(0.0)
597   // mOffset and mDuration are initialized in Start().
598   , mPlaybackRate(new AudioParam(this, PLAYBACKRATE, 1.0f, "playbackRate"))
599   , mDetune(new AudioParam(this, DETUNE, 0.0f, "detune"))
600   , mLoop(false)
601   , mStartCalled(false)
602 {
603   AudioBufferSourceNodeEngine* engine = new AudioBufferSourceNodeEngine(this, aContext->Destination());
604   mStream = AudioNodeStream::Create(aContext, engine,
605                                     AudioNodeStream::NEED_MAIN_THREAD_FINISHED,
606                                     aContext->Graph());
607   engine->SetSourceStream(mStream);
608   mStream->AddMainThreadListener(this);
609 }
610 
~AudioBufferSourceNode()611 AudioBufferSourceNode::~AudioBufferSourceNode()
612 {
613 }
614 
615 void
DestroyMediaStream()616 AudioBufferSourceNode::DestroyMediaStream()
617 {
618   bool hadStream = mStream;
619   if (hadStream) {
620     mStream->RemoveMainThreadListener(this);
621   }
622   AudioNode::DestroyMediaStream();
623   if (hadStream && Context()) {
624     Context()->UnregisterAudioBufferSourceNode(this);
625   }
626 }
627 
628 size_t
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const629 AudioBufferSourceNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
630 {
631   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
632 
633   /* mBuffer can be shared and is accounted for separately. */
634 
635   amount += mPlaybackRate->SizeOfIncludingThis(aMallocSizeOf);
636   amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
637   return amount;
638 }
639 
640 size_t
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const641 AudioBufferSourceNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
642 {
643   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
644 }
645 
646 JSObject*
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)647 AudioBufferSourceNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
648 {
649   return AudioBufferSourceNodeBinding::Wrap(aCx, this, aGivenProto);
650 }
651 
652 void
Start(double aWhen,double aOffset,const Optional<double> & aDuration,ErrorResult & aRv)653 AudioBufferSourceNode::Start(double aWhen, double aOffset,
654                              const Optional<double>& aDuration, ErrorResult& aRv)
655 {
656   if (!WebAudioUtils::IsTimeValid(aWhen) ||
657       (aDuration.WasPassed() && !WebAudioUtils::IsTimeValid(aDuration.Value()))) {
658     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
659     return;
660   }
661 
662   if (mStartCalled) {
663     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
664     return;
665   }
666   mStartCalled = true;
667 
668   AudioNodeStream* ns = mStream;
669   if (!ns) {
670     // Nothing to play, or we're already dead for some reason
671     return;
672   }
673 
674   // Remember our arguments so that we can use them when we get a new buffer.
675   mOffset = aOffset;
676   mDuration = aDuration.WasPassed() ? aDuration.Value()
677                                     : std::numeric_limits<double>::min();
678 
679   WEB_AUDIO_API_LOG("%f: %s %u Start(%f, %g, %g)", Context()->CurrentTime(),
680                     NodeType(), Id(), aWhen, aOffset, mDuration);
681 
682   // We can't send these parameters without a buffer because we don't know the
683   // buffer's sample rate or length.
684   if (mBuffer) {
685     SendOffsetAndDurationParametersToStream(ns);
686   }
687 
688   // Don't set parameter unnecessarily
689   if (aWhen > 0.0) {
690     ns->SetDoubleParameter(START, aWhen);
691   }
692 }
693 
694 void
SendBufferParameterToStream(JSContext * aCx)695 AudioBufferSourceNode::SendBufferParameterToStream(JSContext* aCx)
696 {
697   AudioNodeStream* ns = mStream;
698   if (!ns) {
699     return;
700   }
701 
702   if (mBuffer) {
703     RefPtr<ThreadSharedFloatArrayBufferList> data =
704       mBuffer->GetThreadSharedChannelsForRate(aCx);
705     ns->SetBuffer(data.forget());
706 
707     if (mStartCalled) {
708       SendOffsetAndDurationParametersToStream(ns);
709     }
710   } else {
711     ns->SetInt32Parameter(BUFFEREND, 0);
712     ns->SetBuffer(nullptr);
713 
714     MarkInactive();
715   }
716 }
717 
718 void
SendOffsetAndDurationParametersToStream(AudioNodeStream * aStream)719 AudioBufferSourceNode::SendOffsetAndDurationParametersToStream(AudioNodeStream* aStream)
720 {
721   NS_ASSERTION(mBuffer && mStartCalled,
722                "Only call this when we have a buffer and start() has been called");
723 
724   float rate = mBuffer->SampleRate();
725   aStream->SetInt32Parameter(SAMPLE_RATE, rate);
726 
727   int32_t bufferEnd = mBuffer->Length();
728   int32_t offsetSamples = std::max(0, NS_lround(mOffset * rate));
729 
730   // Don't set parameter unnecessarily
731   if (offsetSamples > 0) {
732     aStream->SetInt32Parameter(BUFFERSTART, offsetSamples);
733   }
734 
735   if (mDuration != std::numeric_limits<double>::min()) {
736     MOZ_ASSERT(mDuration >= 0.0); // provided by Start()
737     MOZ_ASSERT(rate >= 0.0f); // provided by AudioBuffer::Create()
738     static_assert(std::numeric_limits<double>::digits >=
739                   std::numeric_limits<decltype(bufferEnd)>::digits,
740                   "bufferEnd should be represented exactly by double");
741     // + 0.5 rounds mDuration to nearest sample when assigned to bufferEnd.
742     bufferEnd = std::min<double>(bufferEnd,
743                                  offsetSamples + mDuration * rate + 0.5);
744   }
745   aStream->SetInt32Parameter(BUFFEREND, bufferEnd);
746 
747   MarkActive();
748 }
749 
750 void
Stop(double aWhen,ErrorResult & aRv)751 AudioBufferSourceNode::Stop(double aWhen, ErrorResult& aRv)
752 {
753   if (!WebAudioUtils::IsTimeValid(aWhen)) {
754     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
755     return;
756   }
757 
758   if (!mStartCalled) {
759     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
760     return;
761   }
762 
763   WEB_AUDIO_API_LOG("%f: %s %u Stop(%f)", Context()->CurrentTime(),
764                     NodeType(), Id(), aWhen);
765 
766   AudioNodeStream* ns = mStream;
767   if (!ns || !Context()) {
768     // We've already stopped and had our stream shut down
769     return;
770   }
771 
772   ns->SetStreamTimeParameter(STOP, Context(), std::max(0.0, aWhen));
773 }
774 
775 void
NotifyMainThreadStreamFinished()776 AudioBufferSourceNode::NotifyMainThreadStreamFinished()
777 {
778   MOZ_ASSERT(mStream->IsFinished());
779 
780   class EndedEventDispatcher final : public Runnable
781   {
782   public:
783     explicit EndedEventDispatcher(AudioBufferSourceNode* aNode)
784       : mNode(aNode) {}
785     NS_IMETHOD Run() override
786     {
787       // If it's not safe to run scripts right now, schedule this to run later
788       if (!nsContentUtils::IsSafeToRunScript()) {
789         nsContentUtils::AddScriptRunner(this);
790         return NS_OK;
791       }
792 
793       mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
794       // Release stream resources.
795       mNode->DestroyMediaStream();
796       return NS_OK;
797     }
798   private:
799     RefPtr<AudioBufferSourceNode> mNode;
800   };
801 
802   NS_DispatchToMainThread(new EndedEventDispatcher(this));
803 
804   // Drop the playing reference
805   // Warning: The below line might delete this.
806   MarkInactive();
807 }
808 
809 void
SendDopplerShiftToStream(double aDopplerShift)810 AudioBufferSourceNode::SendDopplerShiftToStream(double aDopplerShift)
811 {
812   MOZ_ASSERT(mStream, "Should have disconnected panner if no stream");
813   SendDoubleParameterToStream(DOPPLERSHIFT, aDopplerShift);
814 }
815 
816 void
SendLoopParametersToStream()817 AudioBufferSourceNode::SendLoopParametersToStream()
818 {
819   if (!mStream) {
820     return;
821   }
822   // Don't compute and set the loop parameters unnecessarily
823   if (mLoop && mBuffer) {
824     float rate = mBuffer->SampleRate();
825     double length = (double(mBuffer->Length()) / mBuffer->SampleRate());
826     double actualLoopStart, actualLoopEnd;
827     if (mLoopStart >= 0.0 && mLoopEnd > 0.0 &&
828         mLoopStart < mLoopEnd) {
829       MOZ_ASSERT(mLoopStart != 0.0 || mLoopEnd != 0.0);
830       actualLoopStart = (mLoopStart > length) ? 0.0 : mLoopStart;
831       actualLoopEnd = std::min(mLoopEnd, length);
832     } else {
833       actualLoopStart = 0.0;
834       actualLoopEnd = length;
835     }
836     int32_t loopStartTicks = NS_lround(actualLoopStart * rate);
837     int32_t loopEndTicks = NS_lround(actualLoopEnd * rate);
838     if (loopStartTicks < loopEndTicks) {
839       SendInt32ParameterToStream(LOOPSTART, loopStartTicks);
840       SendInt32ParameterToStream(LOOPEND, loopEndTicks);
841       SendInt32ParameterToStream(LOOP, 1);
842     } else {
843       // Be explicit about looping not happening if the offsets make
844       // looping impossible.
845       SendInt32ParameterToStream(LOOP, 0);
846     }
847   } else {
848     SendInt32ParameterToStream(LOOP, 0);
849   }
850 }
851 
852 } // namespace dom
853 } // namespace mozilla
854