mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
245251292d
commit
e48730f81b
@ -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, ¤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);
|
// 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;
|
||||||
|
Loading…
Reference in New Issue
Block a user