Bug 1844181 - Make it possible to resize AudioRingBuffer on the fly. r=padenot

This lets AudioDriftCorrection handle situations where it's about to overflow
the buffer. Be it because of unexpectedly high drift, very high input latency or
an increased target buffering level.

Differential Revision: https://phabricator.services.mozilla.com/D188184
This commit is contained in:
Andreas Pehrson 2023-10-09 13:02:56 +00:00
parent b0ec7e57c1
commit e926717534
3 changed files with 304 additions and 19 deletions

View File

@ -14,13 +14,15 @@ 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.
* then to use it for writing and reading values without requiring re-allocation
* or memory moving. Note that re-allocations can happen if the length of the
* buffer is explicitly set to something larger than is already allocated.
* Also 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 {
@ -29,7 +31,6 @@ class RingBuffer final {
: mStorage(ConvertToSpan(aMemoryBuffer)),
mMemoryBuffer(std::move(aMemoryBuffer)) {
MOZ_ASSERT(std::is_trivial<T>::value);
MOZ_ASSERT(!mStorage.IsEmpty());
}
/**
@ -98,8 +99,8 @@ class RingBuffer final {
* `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));
MOZ_ASSERT(aSamples > 0);
MOZ_ASSERT(aBuffer.IsEmpty() || aBuffer.Length() == aSamples);
if (IsFull()) {
return 0;
@ -263,6 +264,55 @@ class RingBuffer final {
return toDiscard;
}
/**
* Set the ring buffer to the requested size. NB: In bytes.
*
* Re-allocates memory if a larger buffer is requested than what is already
* allocated.
*/
bool SetLengthBytes(uint32_t aLengthBytes) {
MOZ_ASSERT(aLengthBytes % sizeof(T) == 0,
"Length in bytes is not a whole number of samples");
uint32_t lengthSamples = aLengthBytes / sizeof(T);
uint32_t oldLengthSamples = Capacity();
uint32_t availableRead = AvailableRead();
if (!mMemoryBuffer.SetLength(aLengthBytes)) {
return false;
}
// mStorage may now have been deallocated.
mStorage = ConvertToSpan(mMemoryBuffer);
if (mWriteIndex < mReadIndex) {
// The old data wrapped around the end of the (old) buffer. It needs to be
// moved so it is continuous.
const uint32_t toMove = mWriteIndex;
// The bit that goes between the old and the new end of the buffer.
const uint32_t toMove1 =
std::min(lengthSamples - oldLengthSamples, toMove);
{
// [0, toMove1) -> [oldLength, oldLength + toMove1).
Span<T> from1 = mStorage.Subspan(0, toMove1);
Span<T> to1 = mStorage.Subspan(oldLengthSamples, toMove1);
PodMove(to1.Elements(), from1.Elements(), toMove1);
}
// The last bit of data that starts at 0. Could be empty.
const uint32_t toMove2 = toMove - toMove1;
{
// [toMove1, toMove) -> [0, toMove2).
Span<T> from2 = mStorage.Subspan(toMove1, toMove2);
Span<T> to2 = mStorage.Subspan(0, toMove2);
PodMove(to2.Elements(), from2.Elements(), toMove2);
}
mWriteIndex = NextIndex(mReadIndex, availableRead);
}
return true;
}
/**
* 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.
@ -298,6 +348,11 @@ class RingBuffer final {
return mWriteIndex + Capacity() - mReadIndex;
}
/**
* The number of samples this ring buffer can hold.
*/
uint32_t Capacity() const { return mStorage.Length(); }
private:
uint32_t NextIndex(uint32_t aIndex, uint32_t aStep) const {
MOZ_ASSERT(aStep < Capacity());
@ -305,10 +360,8 @@ class RingBuffer final {
return (aIndex + aStep) % Capacity();
}
uint32_t Capacity() const { return mStorage.Length(); }
Span<T> ConvertToSpan(const AlignedByteBuffer& aOther) const {
MOZ_ASSERT(aOther.Length() >= sizeof(T));
MOZ_ASSERT(aOther.Length() % sizeof(T) == 0);
return Span<T>(reinterpret_cast<T*>(aOther.Data()),
aOther.Length() / sizeof(T));
}
@ -322,12 +375,14 @@ class RingBuffer final {
uint32_t mReadIndex = 0;
uint32_t mWriteIndex = 0;
/* Points to the mMemoryBuffer. */
const Span<T> mStorage;
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;
* from the RingBuffer can be pre-allocated. Note that a re-allocation will
* happen if the length in bytes is set to something larger than is already
* allocated. */
AlignedByteBuffer mMemoryBuffer;
};
/** AudioRingBuffer **/
@ -343,7 +398,6 @@ class AudioRingBuffer::AudioRingBufferPrivate {
AudioRingBuffer::AudioRingBuffer(uint32_t aSizeInBytes)
: mPtr(MakeUnique<AudioRingBufferPrivate>()) {
MOZ_ASSERT(aSizeInBytes > 0);
mPtr->mBackingBuffer.emplace(aSizeInBytes);
MOZ_ASSERT(mPtr->mBackingBuffer);
}
@ -476,6 +530,31 @@ uint32_t AudioRingBuffer::Clear() {
return mPtr->mFloatRingBuffer->Clear();
}
bool AudioRingBuffer::SetLengthBytes(uint32_t aLengthBytes) {
if (mPtr->mFloatRingBuffer) {
return mPtr->mFloatRingBuffer->SetLengthBytes(aLengthBytes);
}
if (mPtr->mIntRingBuffer) {
return mPtr->mIntRingBuffer->SetLengthBytes(aLengthBytes);
}
if (mPtr->mBackingBuffer) {
return mPtr->mBackingBuffer->SetLength(aLengthBytes);
}
MOZ_ASSERT_UNREACHABLE("Unexpected");
return true;
}
uint32_t AudioRingBuffer::Capacity() const {
if (mPtr->mFloatRingBuffer) {
return mPtr->mFloatRingBuffer->Capacity();
}
if (mPtr->mIntRingBuffer) {
return mPtr->mIntRingBuffer->Capacity();
}
MOZ_ASSERT_UNREACHABLE("Unexpected");
return 0;
}
bool AudioRingBuffer::IsFull() const {
MOZ_ASSERT(mPtr->mSampleFormat == AUDIO_FORMAT_S16 ||
mPtr->mSampleFormat == AUDIO_FORMAT_FLOAT32);

View File

@ -17,8 +17,9 @@ namespace mozilla {
* AudioRingBuffer works with audio sample format float or short. The
* implementation wrap around the RingBuffer thus it is not thread-safe. Reads
* and writes must happen in the same thread which may be different than the
* construction thread. The memory is pre-allocated in the constructor. The
* sample format has to be specified in order to be used.
* construction thread. The memory is pre-allocated in the constructor, but may
* also be re-allocated on the fly should a larger length be needed. The sample
* format has to be specified in order to be used.
*/
class AudioRingBuffer final {
public:
@ -91,6 +92,19 @@ class AudioRingBuffer final {
*/
uint32_t Clear();
/**
* Set the length of the ring buffer in bytes. Must be divisible by the sample
* size. Will not deallocate memory if the underlying buffer is large enough.
* Returns false if setting the length requires allocating memory and the
* allocation fails.
*/
bool SetLengthBytes(uint32_t aLengthBytes);
/**
* Return the number of samples this buffer can hold.
*/
uint32_t Capacity() const;
/**
* Return true if the buffer is full.
*/

View File

@ -1093,3 +1093,195 @@ TEST(TestAudioRingBuffer, PrependSilenceNoWrapShort)
EXPECT_THAT(out, ElementsAre(2, 3, 4, 5, 0, 0, 6, 7));
}
TEST(TestAudioRingBuffer, SetLengthBytesNoWrapFloat)
{
AudioRingBuffer rb(6 * sizeof(float));
rb.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
float in[5] = {.1, .2, .3, .4, .5};
uint32_t rv = rb.Write(Span(in, 5));
EXPECT_EQ(rv, 5u);
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
EXPECT_EQ(rb.Capacity(), 6u);
EXPECT_TRUE(rb.SetLengthBytes(11 * sizeof(float)));
float out[10] = {};
rv = rb.Read(Span(out, 10));
EXPECT_EQ(rv, 5u);
EXPECT_EQ(rb.AvailableRead(), 0u);
EXPECT_EQ(rb.AvailableWrite(), 10u);
EXPECT_EQ(rb.Capacity(), 11u);
EXPECT_THAT(out, ElementsAre(.1, .2, .3, .4, .5, 0, 0, 0, 0, 0));
}
TEST(TestAudioRingBuffer, SetLengthBytesNoWrapShort)
{
AudioRingBuffer rb(6 * sizeof(short));
rb.SetSampleFormat(AUDIO_FORMAT_S16);
short in[5] = {1, 2, 3, 4, 5};
uint32_t rv = rb.Write(Span(in, 5));
EXPECT_EQ(rv, 5u);
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
EXPECT_EQ(rb.Capacity(), 6u);
EXPECT_TRUE(rb.SetLengthBytes(11 * sizeof(short)));
short out[10] = {};
rv = rb.Read(Span(out, 10));
EXPECT_EQ(rv, 5u);
EXPECT_EQ(rb.AvailableRead(), 0u);
EXPECT_EQ(rb.AvailableWrite(), 10u);
EXPECT_EQ(rb.Capacity(), 11u);
EXPECT_THAT(out, ElementsAre(1, 2, 3, 4, 5, 0, 0, 0, 0, 0));
}
TEST(TestAudioRingBuffer, SetLengthBytesWrap1PartFloat)
{
AudioRingBuffer rb(6 * sizeof(float));
rb.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
EXPECT_EQ(rb.WriteSilence(3), 3u);
EXPECT_EQ(rb.AvailableRead(), 3u);
EXPECT_EQ(rb.AvailableWrite(), 2u);
EXPECT_EQ(rb.Capacity(), 6u);
float outSilence[3] = {};
EXPECT_EQ(rb.Read(Span(outSilence, 3)), 3u);
EXPECT_EQ(rb.AvailableRead(), 0u);
EXPECT_EQ(rb.AvailableWrite(), 5u);
float in[5] = {.1, .2, .3, .4, .5};
EXPECT_EQ(rb.Write(Span(in, 5)), 5u);
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
EXPECT_TRUE(rb.SetLengthBytes(11 * sizeof(float)));
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 5u);
float in2[2] = {.6, .7};
EXPECT_EQ(rb.Write(Span(in2, 2)), 2u);
EXPECT_EQ(rb.AvailableRead(), 7u);
EXPECT_EQ(rb.AvailableWrite(), 3u);
float out[10] = {};
EXPECT_EQ(rb.Read(Span(out, 10)), 7u);
EXPECT_EQ(rb.AvailableRead(), 0u);
EXPECT_EQ(rb.AvailableWrite(), 10u);
EXPECT_EQ(rb.Capacity(), 11u);
EXPECT_THAT(out, ElementsAre(.1, .2, .3, .4, .5, .6, .7, 0, 0, 0));
}
TEST(TestAudioRingBuffer, SetLengthBytesWrap1PartShort)
{
AudioRingBuffer rb(6 * sizeof(short));
rb.SetSampleFormat(AUDIO_FORMAT_S16);
EXPECT_EQ(rb.WriteSilence(3), 3u);
EXPECT_EQ(rb.AvailableRead(), 3u);
EXPECT_EQ(rb.AvailableWrite(), 2u);
EXPECT_EQ(rb.Capacity(), 6u);
short outSilence[3] = {};
EXPECT_EQ(rb.Read(Span(outSilence, 3)), 3u);
EXPECT_EQ(rb.AvailableRead(), 0u);
EXPECT_EQ(rb.AvailableWrite(), 5u);
short in[5] = {1, 2, 3, 4, 5};
EXPECT_EQ(rb.Write(Span(in, 5)), 5u);
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
EXPECT_TRUE(rb.SetLengthBytes(11 * sizeof(short)));
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 5u);
short in2[2] = {6, 7};
EXPECT_EQ(rb.Write(Span(in2, 2)), 2u);
EXPECT_EQ(rb.AvailableRead(), 7u);
EXPECT_EQ(rb.AvailableWrite(), 3u);
short out[10] = {};
EXPECT_EQ(rb.Read(Span(out, 10)), 7u);
EXPECT_EQ(rb.AvailableRead(), 0u);
EXPECT_EQ(rb.AvailableWrite(), 10u);
EXPECT_EQ(rb.Capacity(), 11u);
EXPECT_THAT(out, ElementsAre(1, 2, 3, 4, 5, 6, 7, 0, 0, 0));
}
TEST(TestAudioRingBuffer, SetLengthBytesWrap2PartsFloat)
{
AudioRingBuffer rb(6 * sizeof(float));
rb.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
EXPECT_EQ(rb.WriteSilence(3), 3u);
EXPECT_EQ(rb.AvailableRead(), 3u);
EXPECT_EQ(rb.AvailableWrite(), 2u);
EXPECT_EQ(rb.Capacity(), 6u);
float outSilence[3] = {};
EXPECT_EQ(rb.Read(Span(outSilence, 3)), 3u);
EXPECT_EQ(rb.AvailableRead(), 0u);
EXPECT_EQ(rb.AvailableWrite(), 5u);
float in[5] = {.1, .2, .3, .4, .5};
EXPECT_EQ(rb.Write(Span(in, 5)), 5u);
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
EXPECT_TRUE(rb.SetLengthBytes(8 * sizeof(float)));
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 2u);
float in2[2] = {.6, .7};
EXPECT_EQ(rb.Write(Span(in2, 2)), 2u);
EXPECT_EQ(rb.AvailableRead(), 7u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
float out[8] = {};
EXPECT_EQ(rb.Read(Span(out, 8)), 7u);
EXPECT_EQ(rb.AvailableRead(), 0u);
EXPECT_EQ(rb.AvailableWrite(), 7u);
EXPECT_EQ(rb.Capacity(), 8u);
EXPECT_THAT(out, ElementsAre(.1, .2, .3, .4, .5, .6, .7, 0));
}
TEST(TestAudioRingBuffer, SetLengthBytesWrap2PartsShort)
{
AudioRingBuffer rb(6 * sizeof(short));
rb.SetSampleFormat(AUDIO_FORMAT_S16);
EXPECT_EQ(rb.WriteSilence(3), 3u);
EXPECT_EQ(rb.AvailableRead(), 3u);
EXPECT_EQ(rb.AvailableWrite(), 2u);
EXPECT_EQ(rb.Capacity(), 6u);
short outSilence[3] = {};
EXPECT_EQ(rb.Read(Span(outSilence, 3)), 3u);
EXPECT_EQ(rb.AvailableRead(), 0u);
EXPECT_EQ(rb.AvailableWrite(), 5u);
short in[5] = {1, 2, 3, 4, 5};
EXPECT_EQ(rb.Write(Span(in, 5)), 5u);
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
EXPECT_TRUE(rb.SetLengthBytes(8 * sizeof(short)));
EXPECT_EQ(rb.AvailableRead(), 5u);
EXPECT_EQ(rb.AvailableWrite(), 2u);
short in2[2] = {6, 7};
EXPECT_EQ(rb.Write(Span(in2, 2)), 2u);
EXPECT_EQ(rb.AvailableRead(), 7u);
EXPECT_EQ(rb.AvailableWrite(), 0u);
short out[8] = {};
EXPECT_EQ(rb.Read(Span(out, 8)), 7u);
EXPECT_EQ(rb.AvailableRead(), 0u);
EXPECT_EQ(rb.AvailableWrite(), 7u);
EXPECT_EQ(rb.Capacity(), 8u);
EXPECT_THAT(out, ElementsAre(1, 2, 3, 4, 5, 6, 7, 0));
}