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