diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/media/AudioRingBuffer.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/AudioRingBuffer.cpp')
-rw-r--r-- | dom/media/AudioRingBuffer.cpp | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/dom/media/AudioRingBuffer.cpp b/dom/media/AudioRingBuffer.cpp new file mode 100644 index 0000000000..917d4880a1 --- /dev/null +++ b/dom/media/AudioRingBuffer.cpp @@ -0,0 +1,471 @@ +/* -*- 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 |