зеркало из https://github.com/mozilla/gecko-dev.git
224 строки
7.2 KiB
C++
224 строки
7.2 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef MOZILLA_SCRATCHBUFFER_H_
|
|
#define MOZILLA_SCRATCHBUFFER_H_
|
|
|
|
#include "mozilla/PodOperations.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "nsDebug.h"
|
|
|
|
#include <algorithm>
|
|
|
|
namespace mozilla {
|
|
|
|
/**
|
|
* The classes in this file provide a interface that uses frames as a unit.
|
|
* However, they store their offsets in samples (because it's handy for pointer
|
|
* operations). Those functions can convert between the two units.
|
|
*/
|
|
static inline uint32_t FramesToSamples(uint32_t aChannels, uint32_t aFrames) {
|
|
return aFrames * aChannels;
|
|
}
|
|
|
|
static inline uint32_t SamplesToFrames(uint32_t aChannels, uint32_t aSamples) {
|
|
MOZ_ASSERT(!(aSamples % aChannels), "Frame alignment is wrong.");
|
|
return aSamples / aChannels;
|
|
}
|
|
|
|
/**
|
|
* Class that gets a buffer pointer from an audio callback and provides a safe
|
|
* interface to manipulate this buffer, and to ensure we are not missing frames
|
|
* by the end of the callback.
|
|
*/
|
|
template <typename T>
|
|
class AudioCallbackBufferWrapper {
|
|
public:
|
|
AudioCallbackBufferWrapper()
|
|
: mBuffer(nullptr), mSamples(0), mSampleWriteOffset(1), mChannels(0) {}
|
|
|
|
explicit AudioCallbackBufferWrapper(uint32_t aChannels)
|
|
: mBuffer(nullptr),
|
|
mSamples(0),
|
|
mSampleWriteOffset(1),
|
|
mChannels(aChannels)
|
|
|
|
{
|
|
MOZ_ASSERT(aChannels);
|
|
}
|
|
|
|
AudioCallbackBufferWrapper& operator=(
|
|
const AudioCallbackBufferWrapper& aOther) {
|
|
MOZ_ASSERT(!aOther.mBuffer,
|
|
"Don't use this ctor after AudioCallbackDriver::Init");
|
|
MOZ_ASSERT(aOther.mSamples == 0,
|
|
"Don't use this ctor after AudioCallbackDriver::Init");
|
|
MOZ_ASSERT(aOther.mSampleWriteOffset == 1,
|
|
"Don't use this ctor after AudioCallbackDriver::Init");
|
|
MOZ_ASSERT(aOther.mChannels != 0);
|
|
|
|
mBuffer = nullptr;
|
|
mSamples = 0;
|
|
mSampleWriteOffset = 1;
|
|
mChannels = aOther.mChannels;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Set the buffer in this wrapper. This is to be called at the beginning of
|
|
* the callback.
|
|
*/
|
|
void SetBuffer(T* aBuffer, uint32_t aFrames) {
|
|
MOZ_ASSERT(!mBuffer && !mSamples, "SetBuffer called twice.");
|
|
mBuffer = aBuffer;
|
|
mSamples = FramesToSamples(mChannels, aFrames);
|
|
mSampleWriteOffset = 0;
|
|
}
|
|
|
|
/**
|
|
* Write some frames to the internal buffer. Free space in the buffer should
|
|
* be check prior to calling this.
|
|
*/
|
|
void WriteFrames(T* aBuffer, uint32_t aFrames) {
|
|
MOZ_ASSERT(aFrames <= Available(),
|
|
"Writing more that we can in the audio buffer.");
|
|
|
|
PodCopy(mBuffer + mSampleWriteOffset, aBuffer,
|
|
FramesToSamples(mChannels, aFrames));
|
|
mSampleWriteOffset += FramesToSamples(mChannels, aFrames);
|
|
}
|
|
|
|
/**
|
|
* Number of frames that can be written to the buffer.
|
|
*/
|
|
uint32_t Available() {
|
|
return SamplesToFrames(mChannels, mSamples - mSampleWriteOffset);
|
|
}
|
|
|
|
/**
|
|
* Check that the buffer is completly filled, and reset internal state so this
|
|
* instance can be reused.
|
|
*/
|
|
void BufferFilled() {
|
|
// It's okay to have exactly zero samples here, it can happen we have an
|
|
// audio callback driver because of a hint on MTG creation, but the
|
|
// AudioOutputStream has not been created yet, or if all the tracks have
|
|
// finished but we're still running. Note: it's also ok if we had data in
|
|
// the scratch buffer - and we usually do - and all the tracks were ended
|
|
// (no mixer callback occured).
|
|
// XXX Remove this warning, or find a way to avoid it if the mixer callback
|
|
// isn't called.
|
|
NS_WARNING_ASSERTION(
|
|
Available() == 0 || mSampleWriteOffset == 0,
|
|
"Audio Buffer is not full by the end of the callback.");
|
|
// Make sure the data returned is always set and not random!
|
|
if (Available()) {
|
|
PodZero(mBuffer + mSampleWriteOffset,
|
|
FramesToSamples(mChannels, Available()));
|
|
}
|
|
MOZ_ASSERT(mSamples, "Buffer not set.");
|
|
mSamples = 0;
|
|
mSampleWriteOffset = 0;
|
|
mBuffer = nullptr;
|
|
}
|
|
|
|
private:
|
|
/* This is not an owned pointer, but the pointer passed to use via the audio
|
|
* callback. */
|
|
T* mBuffer;
|
|
/* The number of samples of this audio buffer. */
|
|
uint32_t mSamples;
|
|
/* The position at which new samples should be written. We want to return to
|
|
* the audio callback iff this is equal to mSamples. */
|
|
uint32_t mSampleWriteOffset;
|
|
uint32_t mChannels;
|
|
};
|
|
|
|
/**
|
|
* This is a class that interfaces with the AudioCallbackBufferWrapper, and is
|
|
* responsible for storing the excess of data produced by the MediaTrackGraph
|
|
* because of different rounding constraints, to be used the next time the audio
|
|
* backend calls back.
|
|
*/
|
|
template <typename T, uint32_t BLOCK_SIZE>
|
|
class SpillBuffer {
|
|
public:
|
|
SpillBuffer() : mBuffer(nullptr), mPosition(0), mChannels(0) {}
|
|
|
|
explicit SpillBuffer(uint32_t aChannels)
|
|
: mPosition(0), mChannels(aChannels) {
|
|
MOZ_ASSERT(aChannels);
|
|
mBuffer = MakeUnique<T[]>(BLOCK_SIZE * mChannels);
|
|
PodZero(mBuffer.get(), BLOCK_SIZE * mChannels);
|
|
}
|
|
|
|
SpillBuffer& operator=(SpillBuffer& aOther) {
|
|
MOZ_ASSERT(aOther.mPosition == 0,
|
|
"Don't use this ctor after AudioCallbackDriver::Init");
|
|
MOZ_ASSERT(aOther.mChannels != 0);
|
|
MOZ_ASSERT(aOther.mBuffer);
|
|
|
|
mPosition = aOther.mPosition;
|
|
mChannels = aOther.mChannels;
|
|
mBuffer = std::move(aOther.mBuffer);
|
|
|
|
return *this;
|
|
}
|
|
|
|
SpillBuffer& operator=(SpillBuffer&& aOther) {
|
|
return this->operator=(aOther);
|
|
}
|
|
|
|
/* Empty the spill buffer into the buffer of the audio callback. This returns
|
|
* the number of frames written. */
|
|
uint32_t Empty(AudioCallbackBufferWrapper<T>& aBuffer) {
|
|
uint32_t framesToWrite =
|
|
std::min(aBuffer.Available(), SamplesToFrames(mChannels, mPosition));
|
|
|
|
aBuffer.WriteFrames(mBuffer.get(), framesToWrite);
|
|
|
|
mPosition -= FramesToSamples(mChannels, framesToWrite);
|
|
// If we didn't empty the spill buffer for some reason, shift the remaining
|
|
// data down
|
|
if (mPosition > 0) {
|
|
MOZ_ASSERT(FramesToSamples(mChannels, framesToWrite) + mPosition <=
|
|
BLOCK_SIZE * mChannels);
|
|
PodMove(mBuffer.get(),
|
|
mBuffer.get() + FramesToSamples(mChannels, framesToWrite),
|
|
mPosition);
|
|
}
|
|
|
|
return framesToWrite;
|
|
}
|
|
/* Fill the spill buffer from aInput, containing aFrames frames, return the
|
|
* number of frames written to the spill buffer */
|
|
uint32_t Fill(T* aInput, uint32_t aFrames) {
|
|
uint32_t framesToWrite =
|
|
std::min(aFrames, BLOCK_SIZE - SamplesToFrames(mChannels, mPosition));
|
|
|
|
MOZ_ASSERT(FramesToSamples(mChannels, framesToWrite) + mPosition <=
|
|
BLOCK_SIZE * mChannels);
|
|
PodCopy(mBuffer.get() + mPosition, aInput,
|
|
FramesToSamples(mChannels, framesToWrite));
|
|
|
|
mPosition += FramesToSamples(mChannels, framesToWrite);
|
|
|
|
return framesToWrite;
|
|
}
|
|
|
|
private:
|
|
/* The spilled data. */
|
|
UniquePtr<T[]> mBuffer;
|
|
/* The current write position, in samples, in the buffer when filling, or the
|
|
* amount of buffer filled when emptying. */
|
|
uint32_t mPosition;
|
|
uint32_t mChannels;
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // MOZILLA_SCRATCHBUFFER_H_
|