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