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