Source code

Revision control

Other Tools

/* -*- 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/. */
#include "AudioRingBuffer.h"
#include "MediaData.h"
#include "mozilla/Assertions.h"
#include "mozilla/Maybe.h"
#include "mozilla/PodOperations.h"
namespace mozilla {
/**
* RingBuffer is used to preallocate a buffer of a specific size in bytes and
* then to use it for writing and reading values without any re-allocation or
* memory moving. Please note that the total byte size of the buffer modulo the
* size of the chosen type must be zero. The RingBuffer has been created with
* audio sample values types in mind which are integer or float. However, it
* can be used with any trivial type. It is _not_ thread-safe! The constructor
* can be called on any thread but the reads and write must happen on the same
* thread, which can be different than the construction thread.
*/
template <typename T>
class RingBuffer final {
public:
explicit RingBuffer(AlignedByteBuffer&& aMemoryBuffer)
: mStorage(ConvertToSpan(aMemoryBuffer)),
mMemoryBuffer(std::move(aMemoryBuffer)) {
MOZ_ASSERT(std::is_trivial<T>::value);
MOZ_ASSERT(!mStorage.IsEmpty());
}
/**
* Write `aSamples` number of zeros in the buffer.
*/
uint32_t WriteSilence(uint32_t aSamples) {
MOZ_ASSERT(aSamples);
return Write(Span<T>(), aSamples);
}
/**
* Copy `aBuffer` to the RingBuffer.
*/
uint32_t Write(const Span<const T>& aBuffer) {
MOZ_ASSERT(!aBuffer.IsEmpty());
return Write(aBuffer, aBuffer.Length());
}
private:
/**
* Copy `aSamples` number of elements from `aBuffer` to the RingBuffer. If
* `aBuffer` is empty append `aSamples` of zeros.
*/
uint32_t Write(const Span<const T>& aBuffer, uint32_t aSamples) {
MOZ_ASSERT(aSamples > 0 &&
aBuffer.Length() <= static_cast<uint32_t>(aSamples));
if (IsFull()) {
return 0;
}
uint32_t toWrite = std::min(AvailableWrite(), aSamples);
uint32_t part1 = std::min(Capacity() - mWriteIndex, toWrite);
uint32_t part2 = toWrite - part1;
Span<T> part1Buffer = mStorage.Subspan(mWriteIndex, part1);
Span<T> part2Buffer = mStorage.To(part2);
if (!aBuffer.IsEmpty()) {
Span<const T> fromPart1 = aBuffer.To(part1);
Span<const T> fromPart2 = aBuffer.Subspan(part1, part2);
CopySpan(part1Buffer, fromPart1);
CopySpan(part2Buffer, fromPart2);
} else {
// The aBuffer is empty, append zeros.
PodZero(part1Buffer.Elements(), part1Buffer.Length());
PodZero(part2Buffer.Elements(), part2Buffer.Length());
}
mWriteIndex = NextIndex(mWriteIndex, toWrite);
return toWrite;
}
public:
/**
* Copy `aSamples` number of elements from `aBuffer` to the RingBuffer. The
* `aBuffer` does not change.
*/
uint32_t Write(const RingBuffer& aBuffer, uint32_t aSamples) {
MOZ_ASSERT(aSamples);
if (IsFull()) {
return 0;
}
uint32_t toWriteThis = std::min(AvailableWrite(), aSamples);
uint32_t toReadThat = std::min(aBuffer.AvailableRead(), toWriteThis);
uint32_t part1 =
std::min(aBuffer.Capacity() - aBuffer.mReadIndex, toReadThat);
uint32_t part2 = toReadThat - part1;
Span<T> part1Buffer = aBuffer.mStorage.Subspan(aBuffer.mReadIndex, part1);
DebugOnly<uint32_t> ret = Write(part1Buffer);
MOZ_ASSERT(ret == part1);
if (part2) {
Span<T> part2Buffer = aBuffer.mStorage.To(part2);
ret = Write(part2Buffer);
MOZ_ASSERT(ret == part2);
}
return toReadThat;
}
/**
* Copy `aBuffer.Length()` number of elements from RingBuffer to `aBuffer`.
*/
uint32_t Read(const Span<T>& aBuffer) {
MOZ_ASSERT(!aBuffer.IsEmpty());
MOZ_ASSERT(aBuffer.size() <= std::numeric_limits<uint32_t>::max());
if (IsEmpty()) {
return 0;
}
uint32_t toRead = std::min<uint32_t>(AvailableRead(), aBuffer.Length());
uint32_t part1 = std::min(Capacity() - mReadIndex, toRead);
uint32_t part2 = toRead - part1;
Span<T> part1Buffer = mStorage.Subspan(mReadIndex, part1);
Span<T> part2Buffer = mStorage.To(part2);
Span<T> toPart1 = aBuffer.To(part1);
Span<T> toPart2 = aBuffer.Subspan(part1, part2);
CopySpan(toPart1, part1Buffer);
CopySpan(toPart2, part2Buffer);
mReadIndex = NextIndex(mReadIndex, toRead);
return toRead;
}
/**
* Provide `aCallable` that will be called with the internal linear read
* buffers and the number of samples available for reading. The `aCallable`
* will be called at most 2 times. The `aCallable` must return the number of
* samples that have been actually read. If that number is smaller than the
* available number of samples, provided in the argument, the `aCallable` will
* not be called again. The RingBuffer's available read samples will be
* decreased by the number returned from the `aCallable`.
*
* The important aspects of this method are that first, it makes it possible
* to avoid extra copies to an intermediates buffer, and second, each buffer
* provided to `aCallable is a linear piece of memory which can be used
* directly to a resampler for example.
*
* In general, the problem with ring buffers is that they cannot provide one
* linear chunk of memory so extra copies, to a linear buffer, are often
* needed. This method bridge that gap by breaking the ring buffer's
* internal read memory into linear pieces and making it available through
* the `aCallable`. In the body of the `aCallable` those buffers can be used
* directly without any copy or intermediate steps.
*/
uint32_t ReadNoCopy(
std::function<uint32_t(const Span<const T>&)>&& aCallable) {
if (IsEmpty()) {
return 0;
}
uint32_t part1 = std::min(Capacity() - mReadIndex, AvailableRead());
uint32_t part2 = AvailableRead() - part1;
Span<T> part1Buffer = mStorage.Subspan(mReadIndex, part1);
uint32_t toRead = aCallable(part1Buffer);
MOZ_ASSERT(toRead <= part1);
if (toRead == part1 && part2) {
Span<T> part2Buffer = mStorage.To(part2);
toRead += aCallable(part2Buffer);
MOZ_ASSERT(toRead <= part1 + part2);
}
mReadIndex = NextIndex(mReadIndex, toRead);
return toRead;
}
/**
* Remove the next `aSamples` number of samples from the ring buffer.
*/
uint32_t Discard(uint32_t aSamples) {
MOZ_ASSERT(aSamples);
if (IsEmpty()) {
return 0;
}
uint32_t toDiscard = std::min(AvailableRead(), aSamples);
mReadIndex = NextIndex(mReadIndex, toDiscard);
return toDiscard;
}
/**
* Empty the ring buffer.
*/
uint32_t Clear() {
if (IsEmpty()) {
return 0;
}
uint32_t toDiscard = AvailableRead();
mReadIndex = NextIndex(mReadIndex, toDiscard);
return toDiscard;
}
/**
* Returns true if the full capacity of the ring buffer is being used. When
* full any attempt to write more samples to the ring buffer will fail.
*/
bool IsFull() const { return (mWriteIndex + 1) % Capacity() == mReadIndex; }
/**
* Returns true if the ring buffer is empty. When empty any attempt to read
* more samples from the ring buffer will fail.
*/
bool IsEmpty() const { return mWriteIndex == mReadIndex; }
/**
* The number of samples available for writing.
*/
uint32_t AvailableWrite() const {
/* We subtract one element here to always keep at least one sample
* free in the buffer, to distinguish between full and empty array. */
uint32_t rv = mReadIndex - mWriteIndex - 1;
if (mWriteIndex >= mReadIndex) {
rv += Capacity();
}
return rv;
}
/**
* The number of samples available for reading.
*/
uint32_t AvailableRead() const {
if (mWriteIndex >= mReadIndex) {
return mWriteIndex - mReadIndex;
}
return mWriteIndex + Capacity() - mReadIndex;
}
private:
uint32_t NextIndex(uint32_t aIndex, uint32_t aStep) const {
MOZ_ASSERT(aStep < Capacity());
MOZ_ASSERT(aIndex < Capacity());
return (aIndex + aStep) % Capacity();
}
uint32_t Capacity() const { return mStorage.Length(); }
Span<T> ConvertToSpan(const AlignedByteBuffer& aOther) const {
MOZ_ASSERT(aOther.Length() >= sizeof(T));
return Span<T>(reinterpret_cast<T*>(aOther.Data()),
aOther.Length() / sizeof(T));
}
void CopySpan(Span<T>& aTo, const Span<const T>& aFrom) {
MOZ_ASSERT(aTo.Length() == aFrom.Length());
std::copy(aFrom.cbegin(), aFrom.cend(), aTo.begin());
}
private:
uint32_t mReadIndex = 0;
uint32_t mWriteIndex = 0;
/* Points to the mMemoryBuffer. */
const Span<T> mStorage;
/* The actual allocated memory set from outside. It is set in the ctor and it
* is not used again. It is here to control the lifetime of the memory. The
* memory is accessed through the mStorage. The idea is that the memory used
* from the RingBuffer can be pre-allocated. */
const AlignedByteBuffer mMemoryBuffer;
};
/** AudioRingBuffer **/
/* The private members of AudioRingBuffer. */
class AudioRingBuffer::AudioRingBufferPrivate {
public:
AudioSampleFormat mSampleFormat = AUDIO_FORMAT_SILENCE;
Maybe<RingBuffer<float>> mFloatRingBuffer;
Maybe<RingBuffer<int16_t>> mIntRingBuffer;
Maybe<AlignedByteBuffer> mBackingBuffer;
};
AudioRingBuffer::AudioRingBuffer(uint32_t aSizeInBytes)
: mPtr(MakeUnique<AudioRingBufferPrivate>()) {
MOZ_ASSERT(aSizeInBytes > 0);
mPtr->mBackingBuffer.emplace(aSizeInBytes);
MOZ_ASSERT(mPtr->mBackingBuffer);
}
AudioRingBuffer::~AudioRingBuffer() = default;
void AudioRingBuffer::SetSampleFormat(AudioSampleFormat aFormat) {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_SILENCE);
MOZ_ASSERT(aFormat == AUDIO_FORMAT_S16 || aFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mIntRingBuffer);
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
MOZ_ASSERT(mPtr->mBackingBuffer);
mPtr->mSampleFormat = aFormat;
if (mPtr->mSampleFormat == AUDIO_FORMAT_S16) {
mPtr->mIntRingBuffer.emplace(mPtr->mBackingBuffer.extract());
MOZ_ASSERT(!mPtr->mBackingBuffer);
return;
}
mPtr->mFloatRingBuffer.emplace(mPtr->mBackingBuffer.extract());
MOZ_ASSERT(!mPtr->mBackingBuffer);
}
uint32_t AudioRingBuffer::Write(const Span<const float>& aBuffer) {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mIntRingBuffer);
MOZ_ASSERT(!mPtr->mBackingBuffer);
return mPtr->mFloatRingBuffer->Write(aBuffer);
}
uint32_t AudioRingBuffer::Write(const Span<const int16_t>& aBuffer) {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16);
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
MOZ_ASSERT(!mPtr->mBackingBuffer);
return mPtr->mIntRingBuffer->Write(aBuffer);
}
uint32_t AudioRingBuffer::Write(const AudioRingBuffer& aBuffer,
uint32_t aSamples) {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16 ||
mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mBackingBuffer);
if (mPtr->mSampleFormat == AUDIO_FORMAT_S16) {
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
return mPtr->mIntRingBuffer->Write(aBuffer.mPtr->mIntRingBuffer.ref(),
aSamples);
}
MOZ_ASSERT(!mPtr->mIntRingBuffer);
return mPtr->mFloatRingBuffer->Write(aBuffer.mPtr->mFloatRingBuffer.ref(),
aSamples);
}
uint32_t AudioRingBuffer::WriteSilence(uint32_t aSamples) {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16 ||
mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mBackingBuffer);
if (mPtr->mSampleFormat == AUDIO_FORMAT_S16) {
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
return mPtr->mIntRingBuffer->WriteSilence(aSamples);
}
MOZ_ASSERT(!mPtr->mIntRingBuffer);
return mPtr->mFloatRingBuffer->WriteSilence(aSamples);
}
uint32_t AudioRingBuffer::Read(const Span<float>& aBuffer) {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mIntRingBuffer);
MOZ_ASSERT(!mPtr->mBackingBuffer);
return mPtr->mFloatRingBuffer->Read(aBuffer);
}
uint32_t AudioRingBuffer::Read(const Span<int16_t>& aBuffer) {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16);
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
MOZ_ASSERT(!mPtr->mBackingBuffer);
return mPtr->mIntRingBuffer->Read(aBuffer);
}
uint32_t AudioRingBuffer::ReadNoCopy(
std::function<uint32_t(const Span<const float>&)>&& aCallable) {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mIntRingBuffer);
MOZ_ASSERT(!mPtr->mBackingBuffer);
return mPtr->mFloatRingBuffer->ReadNoCopy(std::move(aCallable));
}
uint32_t AudioRingBuffer::ReadNoCopy(
std::function<uint32_t(const Span<const int16_t>&)>&& aCallable) {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16);
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
MOZ_ASSERT(!mPtr->mBackingBuffer);
return mPtr->mIntRingBuffer->ReadNoCopy(std::move(aCallable));
}
uint32_t AudioRingBuffer::Discard(uint32_t aSamples) {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16 ||
mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mBackingBuffer);
if (mPtr->mSampleFormat == AUDIO_FORMAT_S16) {
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
return mPtr->mIntRingBuffer->Discard(aSamples);
}
MOZ_ASSERT(!mPtr->mIntRingBuffer);
return mPtr->mFloatRingBuffer->Discard(aSamples);
}
uint32_t AudioRingBuffer::Clear() {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16 ||
mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mBackingBuffer);
if (mPtr->mSampleFormat == AUDIO_FORMAT_S16) {
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
MOZ_ASSERT(mPtr->mIntRingBuffer);
return mPtr->mIntRingBuffer->Clear();
}
MOZ_ASSERT(!mPtr->mIntRingBuffer);
MOZ_ASSERT(mPtr->mFloatRingBuffer);
return mPtr->mFloatRingBuffer->Clear();
}
bool AudioRingBuffer::IsFull() const {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16 ||
mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mBackingBuffer);
if (mPtr->mSampleFormat == AUDIO_FORMAT_S16) {
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
return mPtr->mIntRingBuffer->IsFull();
}
MOZ_ASSERT(!mPtr->mIntRingBuffer);
return mPtr->mFloatRingBuffer->IsFull();
}
bool AudioRingBuffer::IsEmpty() const {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16 ||
mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mBackingBuffer);
if (mPtr->mSampleFormat == AUDIO_FORMAT_S16) {
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
return mPtr->mIntRingBuffer->IsEmpty();
}
MOZ_ASSERT(!mPtr->mIntRingBuffer);
return mPtr->mFloatRingBuffer->IsEmpty();
}
uint32_t AudioRingBuffer::AvailableWrite() const {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16 ||
mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mBackingBuffer);
if (mPtr->mSampleFormat == AUDIO_FORMAT_S16) {
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
return mPtr->mIntRingBuffer->AvailableWrite();
}
MOZ_ASSERT(!mPtr->mIntRingBuffer);
return mPtr->mFloatRingBuffer->AvailableWrite();
}
uint32_t AudioRingBuffer::AvailableRead() const {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16 ||
mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(!mPtr->mBackingBuffer);
if (mPtr->mSampleFormat == AUDIO_FORMAT_S16) {
MOZ_ASSERT(!mPtr->mFloatRingBuffer);
return mPtr->mIntRingBuffer->AvailableRead();
}
MOZ_ASSERT(!mPtr->mIntRingBuffer);
return mPtr->mFloatRingBuffer->AvailableRead();
}
} // namespace mozilla