diff --git a/content/media/webaudio/AudioBufferSourceNode.cpp b/content/media/webaudio/AudioBufferSourceNode.cpp index 0c410f1b9f1..b63eccbf95e 100644 --- a/content/media/webaudio/AudioBufferSourceNode.cpp +++ b/content/media/webaudio/AudioBufferSourceNode.cpp @@ -68,20 +68,96 @@ public: mBuffer = aBuffer; } + // Borrow a full buffer of size WEBAUDIO_BLOCK_SIZE from the source buffer + // at offset aSourceOffset. This avoids copying memory. void BorrowFromInputBuffer(AudioChunk* aOutput, uint32_t aChannels, - uintptr_t aBufferOffset) + uintptr_t aSourceOffset) { aOutput->mDuration = WEBAUDIO_BLOCK_SIZE; aOutput->mBuffer = mBuffer; aOutput->mChannelData.SetLength(aChannels); for (uint32_t i = 0; i < aChannels; ++i) { - aOutput->mChannelData[i] = mBuffer->GetData(i) + aBufferOffset; + aOutput->mChannelData[i] = mBuffer->GetData(i) + aSourceOffset; } aOutput->mVolume = 1.0f; aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32; } + // Copy aNumberOfFrames frames from the source buffer at offset aSourceOffset + // and put it at offset aBufferOffset in the destination buffer. + void CopyFromInputBuffer(AudioChunk* aOutput, + uint32_t aChannels, + uintptr_t aSourceOffset, + uintptr_t aBufferOffset, + uint32_t aNumberOfFrames) { + for (uint32_t i = 0; i < aChannels; ++i) { + float* baseChannelData = static_cast(const_cast(aOutput->mChannelData[i])); + memcpy(baseChannelData + aBufferOffset, + mBuffer->GetData(i) + aSourceOffset, + aNumberOfFrames * sizeof(float)); + } + } + + /** + * Fill aOutput with as many zero frames as we can, and advance + * aOffsetWithinBlock and aCurrentPosition based on how many frames we write. + * This will never advance aOffsetWithinBlock past WEBAUDIO_BLOCK_SIZE or + * aCurrentPosition past aMaxPos. This function knows when it needs to + * allocate the output buffer, and also optimizes the case where it can avoid + * memory allocations. + */ + void FillWithZeroes(AudioChunk* aOutput, + uint32_t aChannels, + uint32_t* aOffsetWithinBlock, + TrackTicks* aCurrentPosition, + TrackTicks aMaxPos) + { + uint32_t numFrames = std::min(WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock, + uint32_t(aMaxPos - *aCurrentPosition)); + if (numFrames == WEBAUDIO_BLOCK_SIZE) { + aOutput->SetNull(numFrames); + } else { + if (aOutput->IsNull()) { + AllocateAudioBlock(aChannels, aOutput); + } + WriteZeroesToAudioBlock(aOutput, *aOffsetWithinBlock, numFrames); + } + *aOffsetWithinBlock += numFrames; + *aCurrentPosition += numFrames; + } + + /** + * Copy as many frames as possible from the source buffer to aOutput, and + * advance aOffsetWithinBlock and aCurrentPosition based on how many frames + * we copy. This will never advance aOffsetWithinBlock past + * WEBAUDIO_BLOCK_SIZE, or aCurrentPosition past mStop. It takes data from + * the buffer at aBufferOffset, and never takes more data than aBufferMax. + * This function knows when it needs to allocate the output buffer, and also + * optimizes the case where it can avoid memory allocations. + */ + void CopyFromBuffer(AudioChunk* aOutput, + uint32_t aChannels, + uint32_t* aOffsetWithinBlock, + TrackTicks* aCurrentPosition, + uint32_t aBufferOffset, + uint32_t aBufferMax) + { + uint32_t numFrames = std::min(std::min(WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock, + aBufferMax - aBufferOffset), + uint32_t(mStop - *aCurrentPosition)); + if (numFrames == WEBAUDIO_BLOCK_SIZE) { + BorrowFromInputBuffer(aOutput, aChannels, aBufferOffset); + } else { + if (aOutput->IsNull()) { + AllocateAudioBlock(aChannels, aOutput); + } + CopyFromInputBuffer(aOutput, aChannels, aBufferOffset, *aOffsetWithinBlock, numFrames); + } + *aOffsetWithinBlock += numFrames; + *aCurrentPosition += numFrames; + } + virtual void ProduceAudioBlock(AudioNodeStream* aStream, const AudioChunk& aInput, AudioChunk* aOutput, @@ -89,21 +165,6 @@ public: { if (!mBuffer) return; - TrackTicks currentPosition = aStream->GetCurrentPosition(); - if (currentPosition + WEBAUDIO_BLOCK_SIZE <= mStart) { - aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); - return; - } - TrackTicks endTime = std::min(mStart + mDuration, mStop); - // Don't set *aFinished just because we passed mStop. Maybe someone - // will call stop() again with a different value. - if (currentPosition + WEBAUDIO_BLOCK_SIZE >= mStart + mDuration) { - *aFinished = true; - } - if (currentPosition >= endTime || mStart >= endTime) { - aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); - return; - } uint32_t channels = mBuffer->GetChannels(); if (!channels) { @@ -111,27 +172,41 @@ public: return; } - if (currentPosition >= mStart && - currentPosition + WEBAUDIO_BLOCK_SIZE <= endTime) { - // Data is entirely within the buffer. Avoid copying it. - BorrowFromInputBuffer(aOutput, channels, - uintptr_t(currentPosition - mStart + mOffset)); - return; + uint32_t written = 0; + TrackTicks currentPosition = aStream->GetCurrentPosition(); + while (written < WEBAUDIO_BLOCK_SIZE) { + if (mStop != TRACK_TICKS_MAX && + currentPosition >= mStop) { + FillWithZeroes(aOutput, channels, &written, ¤tPosition, TRACK_TICKS_MAX); + continue; + } + if (currentPosition < mStart) { + FillWithZeroes(aOutput, channels, &written, ¤tPosition, mStart); + continue; + } + TrackTicks t = currentPosition - mStart; + if (mLoop) { + if (mOffset + t < mLoopEnd) { + CopyFromBuffer(aOutput, channels, &written, ¤tPosition, mOffset + t, mLoopEnd); + } else { + uint32_t offsetInLoop = (mOffset + t - mLoopEnd) % (mLoopEnd - mLoopStart); + CopyFromBuffer(aOutput, channels, &written, ¤tPosition, mLoopStart + offsetInLoop, mLoopEnd); + } + } else { + if (mOffset + t < mDuration) { + CopyFromBuffer(aOutput, channels, &written, ¤tPosition, mOffset + t, mDuration); + } else { + FillWithZeroes(aOutput, channels, &written, ¤tPosition, TRACK_TICKS_MAX); + } + } } - AllocateAudioBlock(channels, aOutput); - TrackTicks start = std::max(currentPosition, mStart); - TrackTicks end = std::min(currentPosition + WEBAUDIO_BLOCK_SIZE, endTime); - WriteZeroesToAudioBlock(aOutput, 0, uint32_t(start - currentPosition)); - for (uint32_t i = 0; i < channels; ++i) { - memcpy(static_cast(const_cast(aOutput->mChannelData[i])) + - uint32_t(start - currentPosition), - mBuffer->GetData(i) + - uintptr_t(start - mStart + mOffset), - uint32_t(end - start) * sizeof(float)); + // We've finished if we've gone past mStop, or if we're past mDuration when + // looping is disabled. + if (currentPosition >= mStop || + (!mLoop && currentPosition - mStart + mOffset > mDuration)) { + *aFinished = true; } - uint32_t endOffset = uint32_t(end - currentPosition); - WriteZeroesToAudioBlock(aOutput, endOffset, WEBAUDIO_BLOCK_SIZE - endOffset); } TrackTicks mStart;