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