Bug 849713 - Part 5: Implement the looping logic in AudioBufferSourceNodeEngine; r=roc

The logic in this function is based around a while loop.  In every
iteration of the loop, we determine whether we need to output silence
(if we're at a position before the playback has started or after it has
stopped) or if we need to produce sound.  In each case, we call a helper
function which eagerly tries to produce as much silence or sound as
possible, while maintaining the constraints that are explained in the
comments in the code.
This commit is contained in:
Ehsan Akhgari 2013-03-10 21:02:22 -04:00
parent 245251292d
commit e48730f81b

View File

@ -68,20 +68,96 @@ public:
mBuffer = aBuffer; 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, void BorrowFromInputBuffer(AudioChunk* aOutput,
uint32_t aChannels, uint32_t aChannels,
uintptr_t aBufferOffset) uintptr_t aSourceOffset)
{ {
aOutput->mDuration = WEBAUDIO_BLOCK_SIZE; aOutput->mDuration = WEBAUDIO_BLOCK_SIZE;
aOutput->mBuffer = mBuffer; aOutput->mBuffer = mBuffer;
aOutput->mChannelData.SetLength(aChannels); aOutput->mChannelData.SetLength(aChannels);
for (uint32_t i = 0; i < aChannels; ++i) { 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->mVolume = 1.0f;
aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32; 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<float*>(const_cast<void*>(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, virtual void ProduceAudioBlock(AudioNodeStream* aStream,
const AudioChunk& aInput, const AudioChunk& aInput,
AudioChunk* aOutput, AudioChunk* aOutput,
@ -89,21 +165,6 @@ public:
{ {
if (!mBuffer) if (!mBuffer)
return; 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(); uint32_t channels = mBuffer->GetChannels();
if (!channels) { if (!channels) {
@ -111,27 +172,41 @@ public:
return; return;
} }
if (currentPosition >= mStart && uint32_t written = 0;
currentPosition + WEBAUDIO_BLOCK_SIZE <= endTime) { TrackTicks currentPosition = aStream->GetCurrentPosition();
// Data is entirely within the buffer. Avoid copying it. while (written < WEBAUDIO_BLOCK_SIZE) {
BorrowFromInputBuffer(aOutput, channels, if (mStop != TRACK_TICKS_MAX &&
uintptr_t(currentPosition - mStart + mOffset)); currentPosition >= mStop) {
return; FillWithZeroes(aOutput, channels, &written, &currentPosition, TRACK_TICKS_MAX);
continue;
}
if (currentPosition < mStart) {
FillWithZeroes(aOutput, channels, &written, &currentPosition, mStart);
continue;
}
TrackTicks t = currentPosition - mStart;
if (mLoop) {
if (mOffset + t < mLoopEnd) {
CopyFromBuffer(aOutput, channels, &written, &currentPosition, mOffset + t, mLoopEnd);
} else {
uint32_t offsetInLoop = (mOffset + t - mLoopEnd) % (mLoopEnd - mLoopStart);
CopyFromBuffer(aOutput, channels, &written, &currentPosition, mLoopStart + offsetInLoop, mLoopEnd);
}
} else {
if (mOffset + t < mDuration) {
CopyFromBuffer(aOutput, channels, &written, &currentPosition, mOffset + t, mDuration);
} else {
FillWithZeroes(aOutput, channels, &written, &currentPosition, TRACK_TICKS_MAX);
}
}
} }
AllocateAudioBlock(channels, aOutput); // We've finished if we've gone past mStop, or if we're past mDuration when
TrackTicks start = std::max(currentPosition, mStart); // looping is disabled.
TrackTicks end = std::min(currentPosition + WEBAUDIO_BLOCK_SIZE, endTime); if (currentPosition >= mStop ||
WriteZeroesToAudioBlock(aOutput, 0, uint32_t(start - currentPosition)); (!mLoop && currentPosition - mStart + mOffset > mDuration)) {
for (uint32_t i = 0; i < channels; ++i) { *aFinished = true;
memcpy(static_cast<float*>(const_cast<void*>(aOutput->mChannelData[i])) +
uint32_t(start - currentPosition),
mBuffer->GetData(i) +
uintptr_t(start - mStart + mOffset),
uint32_t(end - start) * sizeof(float));
} }
uint32_t endOffset = uint32_t(end - currentPosition);
WriteZeroesToAudioBlock(aOutput, endOffset, WEBAUDIO_BLOCK_SIZE - endOffset);
} }
TrackTicks mStart; TrackTicks mStart;