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