Bug 1637235 - Implement a ring buffer for audio data. r=padenot,jya
authorAlex Chronopoulos <achronop@gmail.com>
Mon, 01 Jun 2020 15:53:18 +0000
changeset 533346 7600c1bbd621e9ba857d419706b11e8b8ecbdfa0
parent 533345 a386eedf598f6604f4117d937083a05c5ac01ca9
child 533347 ddc8b742c893ea1de9700e49596f3ec5b72b608c
push id37470
push userrmaries@mozilla.com
push dateTue, 02 Jun 2020 03:24:46 +0000
treeherdermozilla-central@34aa06cff443 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot, jya
bugs1637235
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1637235 - Implement a ring buffer for audio data. r=padenot,jya Implement a ring buffer that is not thread-safe and preallocate its internal buffers. The intention is that the internal data is preallocate to any thread and then read/write operations will take place in a single thread using the memory in a ring manner. Differential Revision: https://phabricator.services.mozilla.com/D74882
dom/media/AudioBufferUtils.h
dom/media/AudioRingBuffer.cpp
dom/media/AudioRingBuffer.h
dom/media/AudioTrack.cpp
dom/media/GraphDriver.cpp
dom/media/UnderrunHandlerLinux.cpp
dom/media/gtest/TestAudioRingBuffer.cpp
dom/media/gtest/TestBufferReader.cpp
dom/media/gtest/moz.build
dom/media/moz.build
--- a/dom/media/AudioBufferUtils.h
+++ b/dom/media/AudioBufferUtils.h
@@ -3,16 +3,17 @@
  * 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
new file mode 100644
--- /dev/null
+++ b/dom/media/AudioRingBuffer.cpp
@@ -0,0 +1,470 @@
+/* -*- 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.
+   */
+  int WriteSilence(int aSamples) {
+    MOZ_ASSERT(aSamples);
+    return Write(Span<T>(), aSamples);
+  }
+
+  /**
+   * Copy `aBuffer` to the RingBuffer.
+   */
+  int 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.
+   */
+  int Write(const Span<const T>& aBuffer, int aSamples) {
+    MOZ_ASSERT(aSamples > 0 &&
+               aBuffer.Length() <= static_cast<uint32_t>(aSamples));
+
+    if (IsFull()) {
+      return 0;
+    }
+
+    int toWrite = std::min(AvailableWrite(), aSamples);
+    int part1 = std::min(Capacity() - mWriteIndex, toWrite);
+    int 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.
+   */
+  int Write(const RingBuffer& aBuffer, int aSamples) {
+    MOZ_ASSERT(aSamples);
+
+    if (IsFull()) {
+      return 0;
+    }
+
+    int toWriteThis = std::min(AvailableWrite(), aSamples);
+    int toReadThat = std::min(aBuffer.AvailableRead(), toWriteThis);
+    int part1 = std::min(aBuffer.Capacity() - aBuffer.mReadIndex, toReadThat);
+    int part2 = toReadThat - part1;
+
+    Span<T> part1Buffer = aBuffer.mStorage.Subspan(aBuffer.mReadIndex, part1);
+    int 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`.
+   */
+  int Read(const Span<T>& aBuffer) {
+    MOZ_ASSERT(!aBuffer.IsEmpty());
+    MOZ_ASSERT(aBuffer.size() <= std::numeric_limits<int>::max());
+
+    if (IsEmpty()) {
+      return 0;
+    }
+
+    int toRead = std::min(AvailableRead(), static_cast<int>(aBuffer.Length()));
+    int part1 = std::min(Capacity() - mReadIndex, toRead);
+    int 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.
+   */
+  int ReadNoCopy(std::function<int(const Span<const T>&)>&& aCallable) {
+    if (IsEmpty()) {
+      return 0;
+    }
+
+    int part1 = std::min(Capacity() - mReadIndex, AvailableRead());
+    int part2 = AvailableRead() - part1;
+
+    Span<T> part1Buffer = mStorage.Subspan(mReadIndex, part1);
+    int 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.
+   */
+  int Discard(int aSamples) {
+    MOZ_ASSERT(aSamples);
+
+    if (IsEmpty()) {
+      return 0;
+    }
+
+    int toDiscard = std::min(AvailableRead(), aSamples);
+    mReadIndex = NextIndex(mReadIndex, toDiscard);
+
+    return toDiscard;
+  }
+
+  /**
+   * Empty the ring buffer.
+   */
+  int Clear() {
+    if (IsEmpty()) {
+      return 0;
+    }
+
+    int 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.
+   */
+  int 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. */
+    int rv = mReadIndex - mWriteIndex - 1;
+    if (mWriteIndex >= mReadIndex) {
+      rv += Capacity();
+    }
+    return rv;
+  }
+
+  /**
+   * The number of samples available for reading.
+   */
+  int AvailableRead() const {
+    if (mWriteIndex >= mReadIndex) {
+      return mWriteIndex - mReadIndex;
+    }
+    return mWriteIndex + Capacity() - mReadIndex;
+  }
+
+ private:
+  int NextIndex(int aIndex, int aStep) const {
+    MOZ_ASSERT(aStep >= 0);
+    MOZ_ASSERT(aStep < Capacity());
+    MOZ_ASSERT(aIndex < Capacity());
+    return (aIndex + aStep) % Capacity();
+  }
+
+  int32_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:
+  int mReadIndex = 0;
+  int 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(int aSizeInBytes)
+    : mPtr(MakeUnique<AudioRingBufferPrivate>()) {
+  MOZ_ASSERT(aSizeInBytes > 0);
+  MOZ_ASSERT(aSizeInBytes < std::numeric_limits<int>::max());
+  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);
+}
+
+int 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);
+}
+
+int 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);
+}
+
+int AudioRingBuffer::Write(const AudioRingBuffer& aBuffer, int 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);
+}
+
+int AudioRingBuffer::WriteSilence(int 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);
+}
+
+int 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);
+}
+
+int 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);
+}
+
+int AudioRingBuffer::ReadNoCopy(
+    std::function<int(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));
+}
+
+int AudioRingBuffer::ReadNoCopy(
+    std::function<int(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));
+}
+
+int AudioRingBuffer::Discard(int 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);
+}
+
+int 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();
+}
+
+int 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();
+}
+
+int 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
new file mode 100644
--- /dev/null
+++ b/dom/media/AudioRingBuffer.h
@@ -0,0 +1,114 @@
+/* -*- 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_AUDIO_RING_BUFFER_H_
+#define MOZILLA_AUDIO_RING_BUFFER_H_
+
+#include "AudioSampleFormat.h"
+#include "mozilla/Span.h"
+
+#include <functional>
+
+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.
+ */
+class AudioRingBuffer final {
+ public:
+  explicit AudioRingBuffer(int aSizeInBytes);
+  ~AudioRingBuffer();
+
+  /**
+   * Set the sample format to either short or float. The sample format must be
+   * set before the using any other method.
+   */
+  void SetSampleFormat(AudioSampleFormat aFormat);
+
+  /**
+   * Write `aBuffer.Length()` number of samples when the format is float.
+   */
+  int Write(const Span<const float>& aBuffer);
+
+  /**
+   * Write `aBuffer.Length()` number of samples when the format is short.
+   */
+  int Write(const Span<const int16_t>& aBuffer);
+
+  /**
+   * Write `aSamples` number of samples from `aBuffer`. Note the `aBuffer` does
+   * not change.
+   */
+  int Write(const AudioRingBuffer& aBuffer, int aSamples);
+
+  /**
+   * Write `aSamples` number of zeros.
+   */
+  int WriteSilence(int aSamples);
+
+  /**
+   * Read `aBuffer.Length()` number of samples when the format is float.
+   */
+  int Read(const Span<float>& aBuffer);
+
+  /**
+   * Read `aBuffer.Length()` number of samples when the format is short.
+   */
+  int Read(const Span<int16_t>& aBuffer);
+
+  /**
+   * Read the internal buffer without extra copies when sample format is float.
+   * Check also the RingBuffer::ReadNoCopy() for more details.
+   */
+  int ReadNoCopy(std::function<int(const Span<const float>&)>&& aCallable);
+
+  /**
+   * Read the internal buffer without extra copies when sample format is short.
+   * Check also the RingBuffer::ReadNoCopy() for more details.
+   */
+  int ReadNoCopy(std::function<int(const Span<const int16_t>&)>&& aCallable);
+
+  /**
+   * Remove `aSamples` number of samples.
+   */
+  int Discard(int aSamples);
+
+  /**
+   * Remove all available samples.
+   */
+  int Clear();
+
+  /**
+   * Return true if the buffer is full.
+   */
+  bool IsFull() const;
+
+  /**
+   * Return true if the buffer is empty.
+   */
+  bool IsEmpty() const;
+
+  /**
+   * Return the number of samples available for writing.
+   */
+  int AvailableWrite() const;
+
+  /**
+   * Return the number of samples available for reading.
+   */
+  int AvailableRead() const;
+
+ private:
+  class AudioRingBufferPrivate;
+  UniquePtr<AudioRingBufferPrivate> mPtr;
+};
+
+}  // namespace mozilla
+
+#endif  // MOZILLA_AUDIO_RING_BUFFER_H_
--- a/dom/media/AudioTrack.cpp
+++ b/dom/media/AudioTrack.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 et tw=78: */
 /* 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 "mozilla/dom/AudioStreamTrack.h"
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackBinding.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 
 namespace mozilla {
 namespace dom {
 
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -1,23 +1,27 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "GraphDriver.h"
+
+#include "AudioNodeEngine.h"
 #include "mozilla/dom/AudioContext.h"
 #include "mozilla/dom/AudioDeviceInfo.h"
 #include "mozilla/dom/BaseAudioContextBinding.h"
 #include "mozilla/SchedulerGroup.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Unused.h"
 #include "mozilla/MathAlgorithms.h"
 #include "CubebDeviceEnumerator.h"
+#include "MediaTrackGraphImpl.h"
 #include "Tracing.h"
 
 #ifdef MOZ_WEBRTC
 #  include "webrtc/MediaEngineWebRTC.h"
 #endif
 
 #ifdef XP_MACOSX
 #  include <sys/sysctl.h>
@@ -1190,17 +1194,17 @@ void AudioCallbackDriver::CompleteAudioC
   MOZ_ASSERT(OnCubebOperationThread());
   auto promises = mPromisesForOperation.Lock();
   for (uint32_t i = 0; i < promises->Length(); i++) {
     TrackAndPromiseForOperation& s = promises.ref()[i];
     if ((aOperation == AsyncCubebOperation::INIT &&
          s.mOperation == dom::AudioContextOperation::Resume) ||
         (aOperation == AsyncCubebOperation::SHUTDOWN &&
          s.mOperation != dom::AudioContextOperation::Resume)) {
-      AudioContextState state;
+      dom::AudioContextState state;
       switch (s.mOperation) {
         case dom::AudioContextOperation::Resume:
           state = dom::AudioContextState::Running;
           break;
         case dom::AudioContextOperation::Suspend:
           state = dom::AudioContextState::Suspended;
           break;
         case dom::AudioContextOperation::Close:
--- a/dom/media/UnderrunHandlerLinux.cpp
+++ b/dom/media/UnderrunHandlerLinux.cpp
@@ -5,16 +5,17 @@
 
 #include <csignal>
 #include <cerrno>
 #include <pthread.h>
 
 #include <mozilla/Sprintf.h>
 #include <mozilla/Atomics.h>
 #include "audio_thread_priority.h"
+#include "nsDebug.h"
 
 namespace mozilla {
 
 Atomic<bool, MemoryOrdering::ReleaseAcquire> gRealtimeLimitReached;
 
 void UnderrunHandler(int signum) { gRealtimeLimitReached = true; }
 
 bool SoftRealTimeLimitReached() { return gRealtimeLimitReached; }
new file mode 100644
--- /dev/null
+++ b/dom/media/gtest/TestAudioRingBuffer.cpp
@@ -0,0 +1,978 @@
+/* -*- 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 "gtest/gtest.h"
+
+TEST(TestAudioRingBuffer, BasicFloat)
+{
+  AudioRingBuffer ringBuffer(11 * sizeof(float));
+  ringBuffer.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+
+  int rv = ringBuffer.WriteSilence(4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 6);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 4);
+
+  float in[4] = {.1, .2, .3, .4};
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 2);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 8);
+
+  rv = ringBuffer.WriteSilence(4);
+  EXPECT_EQ(rv, 2);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 0);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 10);
+
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 0);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 0);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 10);
+
+  float out[4] = {};
+  rv = ringBuffer.Read(MakeSpan(out, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 4);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 6);
+  for (float f : out) {
+    EXPECT_FLOAT_EQ(f, 0.0);
+  }
+
+  rv = ringBuffer.Read(MakeSpan(out, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 8);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 2);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_FLOAT_EQ(in[i], out[i]);
+  }
+
+  rv = ringBuffer.Read(MakeSpan(out, 4));
+  EXPECT_EQ(rv, 2);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < 2; ++i) {
+    EXPECT_FLOAT_EQ(out[i], 0.0);
+  }
+
+  rv = ringBuffer.Clear();
+  EXPECT_EQ(rv, 0);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+}
+
+TEST(TestAudioRingBuffer, BasicShort)
+{
+  AudioRingBuffer ringBuffer(11 * sizeof(short));
+  ringBuffer.SetSampleFormat(AUDIO_FORMAT_S16);
+
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+
+  int rv = ringBuffer.WriteSilence(4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 6);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 4);
+
+  short in[4] = {1, 2, 3, 4};
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 2);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 8);
+
+  rv = ringBuffer.WriteSilence(4);
+  EXPECT_EQ(rv, 2);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 0);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 10);
+
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 0);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 0);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 10);
+
+  short out[4] = {};
+  rv = ringBuffer.Read(MakeSpan(out, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 4);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 6);
+  for (float f : out) {
+    EXPECT_EQ(f, 0);
+  }
+
+  rv = ringBuffer.Read(MakeSpan(out, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 8);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 2);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_EQ(in[i], out[i]);
+  }
+
+  rv = ringBuffer.Read(MakeSpan(out, 4));
+  EXPECT_EQ(rv, 2);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < 2; ++i) {
+    EXPECT_EQ(out[i], 0);
+  }
+
+  rv = ringBuffer.Clear();
+  EXPECT_EQ(rv, 0);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+}
+
+TEST(TestAudioRingBuffer, BasicFloat2)
+{
+  AudioRingBuffer ringBuffer(11 * sizeof(float));
+  ringBuffer.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+
+  float in[4] = {.1, .2, .3, .4};
+  int rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 6);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 4);
+
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 2);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 8);
+
+  float out[4] = {};
+  rv = ringBuffer.Read(MakeSpan(out, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 6);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 4);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_FLOAT_EQ(in[i], out[i]);
+  }
+
+  // WriteIndex = 12
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 2);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 8);
+
+  rv = ringBuffer.Read(MakeSpan(out, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 6);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 4);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_FLOAT_EQ(in[i], out[i]);
+  }
+
+  rv = ringBuffer.Read(MakeSpan(out, 8));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_FLOAT_EQ(in[i], out[i]);
+  }
+
+  rv = ringBuffer.Read(MakeSpan(out, 8));
+  EXPECT_EQ(rv, 0);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_FLOAT_EQ(in[i], out[i]);
+  }
+
+  // WriteIndex = 16
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 6);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 4);
+
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 2);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 8);
+
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 2);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 0);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 10);
+
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 0);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 0);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 10);
+}
+
+TEST(TestAudioRingBuffer, BasicShort2)
+{
+  AudioRingBuffer ringBuffer(11 * sizeof(int16_t));
+  ringBuffer.SetSampleFormat(AUDIO_FORMAT_S16);
+
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+
+  int16_t in[4] = {1, 2, 3, 4};
+  int rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 6);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 4);
+
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 2);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 8);
+
+  int16_t out[4] = {};
+  rv = ringBuffer.Read(MakeSpan(out, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 6);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 4);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_EQ(in[i], out[i]);
+  }
+
+  // WriteIndex = 12
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 2);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 8);
+
+  rv = ringBuffer.Read(MakeSpan(out, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 6);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 4);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_EQ(in[i], out[i]);
+  }
+
+  rv = ringBuffer.Read(MakeSpan(out, 8));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_EQ(in[i], out[i]);
+  }
+
+  rv = ringBuffer.Read(MakeSpan(out, 8));
+  EXPECT_EQ(rv, 0);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_EQ(in[i], out[i]);
+  }
+
+  // WriteIndex = 16
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 6);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 4);
+
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 2);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 8);
+
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 2);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 0);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 10);
+
+  rv = ringBuffer.Write(MakeSpan(in, 4));
+  EXPECT_EQ(rv, 0);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 0);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 10);
+}
+
+TEST(TestAudioRingBuffer, NoCopyFloat)
+{
+  AudioRingBuffer ringBuffer(11 * sizeof(float));
+  ringBuffer.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+  float in[8] = {.0, .1, .2, .3, .4, .5, .6, .7};
+  ringBuffer.Write(MakeSpan(in, 6));
+  //  v ReadIndex
+  // [x0: .0, x1: .1, x2: .2, x3: .3, x4: .4,
+  //  x5: .5, x6: .0, x7: .0, x8: .0, x9: .0, x10: .0]
+
+  float out[10] = {};
+  float* out_ptr = out;
+
+  int rv = ringBuffer.ReadNoCopy([&out_ptr](const Span<const float> aInBuffer) {
+    PodMove(out_ptr, aInBuffer.data(), aInBuffer.Length());
+    out_ptr += aInBuffer.Length();
+    return aInBuffer.Length();
+  });
+  EXPECT_EQ(rv, 6);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_FLOAT_EQ(out[i], in[i]);
+  }
+
+  ringBuffer.Write(MakeSpan(in, 8));
+  // Now the buffer contains:
+  // [x0: .5, x1: .6, x2: .2, x3: .3, x4: .4,
+  //  x5: .5, x6: .0, x7: .1, x8: .2, x9: .3, x10: .4
+  //          ^ ReadIndex
+  out_ptr = out;  // reset the pointer before lambdas reuse
+  rv = ringBuffer.ReadNoCopy([&out_ptr](const Span<const float> aInBuffer) {
+    PodMove(out_ptr, aInBuffer.data(), aInBuffer.Length());
+    out_ptr += aInBuffer.Length();
+    return aInBuffer.Length();
+  });
+  EXPECT_EQ(rv, 8);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_FLOAT_EQ(out[i], in[i]);
+  }
+}
+
+TEST(TestAudioRingBuffer, NoCopyShort)
+{
+  AudioRingBuffer ringBuffer(11 * sizeof(short));
+  ringBuffer.SetSampleFormat(AUDIO_FORMAT_S16);
+
+  short in[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+  ringBuffer.Write(MakeSpan(in, 6));
+  //  v ReadIndex
+  // [x0: 0, x1: 1, x2: 2, x3: 3, x4: 4,
+  //  x5: 5, x6: 0, x7: 0, x8: 0, x9: 0, x10: 0]
+
+  short out[10] = {};
+  short* out_ptr = out;
+
+  int rv = ringBuffer.ReadNoCopy([&out_ptr](const Span<const short> aInBuffer) {
+    PodMove(out_ptr, aInBuffer.data(), aInBuffer.Length());
+    out_ptr += aInBuffer.Length();
+    return aInBuffer.Length();
+  });
+  EXPECT_EQ(rv, 6);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_EQ(out[i], in[i]);
+  }
+
+  ringBuffer.Write(MakeSpan(in, 8));
+  // Now the buffer contains:
+  // [x0: 5, x1: 6, x2: 2, x3: 3, x4: 4,
+  //  x5: 5, x6: 0, x7: 1, x8: 2, x9: 3, x10: 4
+  //          ^ ReadIndex
+  out_ptr = out;  // reset the pointer before lambdas reuse
+  rv = ringBuffer.ReadNoCopy([&out_ptr](const Span<const short> aInBuffer) {
+    PodMove(out_ptr, aInBuffer.data(), aInBuffer.Length());
+    out_ptr += aInBuffer.Length();
+    return aInBuffer.Length();
+  });
+  EXPECT_EQ(rv, 8);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_EQ(out[i], in[i]);
+  }
+}
+
+TEST(TestAudioRingBuffer, NoCopyFloat2)
+{
+  AudioRingBuffer ringBuffer(11 * sizeof(float));
+  ringBuffer.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+  float in[8] = {.0, .1, .2, .3, .4, .5, .6, .7};
+  ringBuffer.Write(MakeSpan(in, 6));
+  //  v ReadIndex
+  // [x0: .0, x1: .1, x2: .2, x3: .3, x4: .4,
+  //  x5: .5, x6: .0, x7: .0, x8: .0, x9: .0, x10: .0]
+
+  float out[10] = {};
+  float* out_ptr = out;
+  int total_frames = 3;
+
+  int rv = ringBuffer.ReadNoCopy(
+      [&out_ptr, &total_frames](const Span<const float>& aInBuffer) {
+        int inFramesUsed = std::min<int>(total_frames, aInBuffer.Length());
+        PodMove(out_ptr, aInBuffer.data(), inFramesUsed);
+        out_ptr += inFramesUsed;
+        total_frames -= inFramesUsed;
+        return inFramesUsed;
+      });
+  //                          v ReadIndex
+  // [x0: .0, x1: .1, x2: .2, x3: .3, x4: .4,
+  //  x5: .5, x6: .0, x7: .0, x8: .0, x9: .0, x10: .0]
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 7);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 3);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_FLOAT_EQ(out[i], in[i]);
+  }
+
+  total_frames = 3;
+  rv = ringBuffer.ReadNoCopy(
+      [&out_ptr, &total_frames](const Span<const float>& aInBuffer) {
+        int inFramesUsed = std::min<int>(total_frames, aInBuffer.Length());
+        PodMove(out_ptr, aInBuffer.data(), inFramesUsed);
+        out_ptr += inFramesUsed;
+        total_frames -= inFramesUsed;
+        return inFramesUsed;
+      });
+  // [x0: .0, x1: .1, x2: .2, x3: .3, x4: .4,
+  //  x5: .5, x6: .0, x7: .0, x8: .0, x9: .0, x10: .0]
+  //          ^ ReadIndex
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_FLOAT_EQ(out[i + 3], in[i + 3]);
+  }
+
+  ringBuffer.Write(MakeSpan(in, 8));
+  // Now the buffer contains:
+  // [x0: .5, x1: .6, x2: .7, x3: .3, x4: .4,
+  //  x5: .5, x6: .0, x7: .1, x8: .2, x9: .3, x10: .4
+  //          ^ ReadIndex
+
+  // reset the pointer before lambdas reuse
+  out_ptr = out;
+  total_frames = 3;
+  rv = ringBuffer.ReadNoCopy(
+      [&out_ptr, &total_frames](const Span<const float>& aInBuffer) {
+        int inFramesUsed = std::min<int>(total_frames, aInBuffer.Length());
+        PodMove(out_ptr, aInBuffer.data(), inFramesUsed);
+        out_ptr += inFramesUsed;
+        total_frames -= inFramesUsed;
+        return inFramesUsed;
+      });
+  // Now the buffer contains:
+  // [x0: .5, x1: .6, x2: .2, x3: .3, x4: .4,
+  //  x5: .5, x6: .0, x7: .1, x8: .2, x9: .3, x10: .4
+  //                                  ^ ReadIndex
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 5);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 5);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_FLOAT_EQ(out[i], in[i]);
+  }
+
+  total_frames = 3;
+  rv = ringBuffer.ReadNoCopy(
+      [&out_ptr, &total_frames](const Span<const float>& aInBuffer) {
+        int inFramesUsed = std::min<int>(total_frames, aInBuffer.Length());
+        PodMove(out_ptr, aInBuffer.data(), inFramesUsed);
+        out_ptr += inFramesUsed;
+        total_frames -= inFramesUsed;
+        return inFramesUsed;
+      });
+  // Now the buffer contains:
+  //          v ReadIndex
+  // [x0: .5, x1: .6, x2: .7, x3: .3, x4: .4,
+  //  x5: .5, x6: .0, x7: .1, x8: .2, x9: .3, x10: .4
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 8);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 2);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_FLOAT_EQ(out[i + 3], in[i + 3]);
+  }
+
+  total_frames = 3;
+  rv = ringBuffer.ReadNoCopy(
+      [&out_ptr, &total_frames](const Span<const float>& aInBuffer) {
+        int inFramesUsed = std::min<int>(total_frames, aInBuffer.Length());
+        PodMove(out_ptr, aInBuffer.data(), inFramesUsed);
+        out_ptr += inFramesUsed;
+        total_frames -= inFramesUsed;
+        return inFramesUsed;
+      });
+  // Now the buffer contains:
+  //                          v ReadIndex
+  // [x0: .5, x1: .6, x2: .7, x3: .3, x4: .4,
+  //  x5: .5, x6: .0, x7: .1, x8: .2, x9: .3, x10: .4
+  EXPECT_EQ(rv, 2);
+  EXPECT_EQ(total_frames, 1);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_FLOAT_EQ(out[i + 6], in[i + 6]);
+  }
+}
+
+TEST(TestAudioRingBuffer, NoCopyShort2)
+{
+  AudioRingBuffer ringBuffer(11 * sizeof(short));
+  ringBuffer.SetSampleFormat(AUDIO_FORMAT_S16);
+
+  short in[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+  ringBuffer.Write(MakeSpan(in, 6));
+  //  v ReadIndex
+  // [x0: 0, x1: 1, x2: 2, x3: 3, x4: 4,
+  //  x5: 5, x6: 0, x7: 0, x8: 0, x9: 0, x10: 0]
+
+  short out[10] = {};
+  short* out_ptr = out;
+  int total_frames = 3;
+
+  int rv = ringBuffer.ReadNoCopy(
+      [&out_ptr, &total_frames](const Span<const short>& aInBuffer) {
+        int inFramesUsed = std::min<int>(total_frames, aInBuffer.Length());
+        PodMove(out_ptr, aInBuffer.data(), inFramesUsed);
+        out_ptr += inFramesUsed;
+        total_frames -= inFramesUsed;
+        return inFramesUsed;
+      });
+  //                       v ReadIndex
+  // [x0: 0, x1: 1, x2: 2, x3: 3, x4: 4,
+  //  x5: 5, x6: 0, x7: 0, x8: 0, x9: 0, x10: 0]
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 7);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 3);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_EQ(out[i], in[i]);
+  }
+
+  total_frames = 3;
+  rv = ringBuffer.ReadNoCopy(
+      [&out_ptr, &total_frames](const Span<const short>& aInBuffer) {
+        int inFramesUsed = std::min<int>(total_frames, aInBuffer.Length());
+        PodMove(out_ptr, aInBuffer.data(), inFramesUsed);
+        out_ptr += inFramesUsed;
+        total_frames -= inFramesUsed;
+        return inFramesUsed;
+      });
+  // [x0: 0, x1: 1, x2: 2, x3: 3, x4: 4,
+  //  x5: 5, x6: 0, x7: 0, x8: 0, x9: 0, x10: .0]
+  //         ^ ReadIndex
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_EQ(out[i + 3], in[i + 3]);
+  }
+
+  ringBuffer.Write(MakeSpan(in, 8));
+  // Now the buffer contains:
+  // [x0: 5, x1: 6, x2: 7, x3: 3, x4: 4,
+  //  x5: 5, x6: 0, x7: 1, x8: 2, x9: 3, x10: 4
+  //         ^ ReadIndex
+
+  // reset the pointer before lambdas reuse
+  out_ptr = out;
+  total_frames = 3;
+  rv = ringBuffer.ReadNoCopy(
+      [&out_ptr, &total_frames](const Span<const short>& aInBuffer) {
+        int inFramesUsed = std::min<int>(total_frames, aInBuffer.Length());
+        PodMove(out_ptr, aInBuffer.data(), inFramesUsed);
+        out_ptr += inFramesUsed;
+        total_frames -= inFramesUsed;
+        return inFramesUsed;
+      });
+  // Now the buffer contains:
+  // [x0: 5, x1: 6, x2: 2, x3: 3, x4: 4,
+  //  x5: 5, x6: 0, x7: 1, x8: 2, x9: 3, x10: 4
+  //                              ^ ReadIndex
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 5);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 5);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_EQ(out[i], in[i]);
+  }
+
+  total_frames = 3;
+  rv = ringBuffer.ReadNoCopy(
+      [&out_ptr, &total_frames](const Span<const short>& aInBuffer) {
+        int inFramesUsed = std::min<int>(total_frames, aInBuffer.Length());
+        PodMove(out_ptr, aInBuffer.data(), inFramesUsed);
+        out_ptr += inFramesUsed;
+        total_frames -= inFramesUsed;
+        return inFramesUsed;
+      });
+  // Now the buffer contains:
+  //         v ReadIndex
+  // [x0: 5, x1: 6, x2: 7, x3: 3, x4: 4,
+  //  x5: 5, x6: 0, x7: 1, x8: 2, x9: 3, x10: 4
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 8);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 2);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_EQ(out[i + 3], in[i + 3]);
+  }
+
+  total_frames = 3;
+  rv = ringBuffer.ReadNoCopy(
+      [&out_ptr, &total_frames](const Span<const short>& aInBuffer) {
+        int inFramesUsed = std::min<int>(total_frames, aInBuffer.Length());
+        PodMove(out_ptr, aInBuffer.data(), inFramesUsed);
+        out_ptr += inFramesUsed;
+        total_frames -= inFramesUsed;
+        return inFramesUsed;
+      });
+  // Now the buffer contains:
+  //                       v ReadIndex
+  // [x0: 5, x1: 6, x2: 7, x3: 3, x4: 4,
+  //  x5: 5, x6: 0, x7: 1, x8: 2, x9: 3, x10: 4
+  EXPECT_EQ(rv, 2);
+  EXPECT_EQ(total_frames, 1);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_EQ(out[i + 6], in[i + 6]);
+  }
+}
+
+TEST(TestAudioRingBuffer, DiscardFloat)
+{
+  AudioRingBuffer ringBuffer(11 * sizeof(float));
+  ringBuffer.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+  float in[8] = {.0, .1, .2, .3, .4, .5, .6, .7};
+  ringBuffer.Write(MakeSpan(in, 8));
+
+  int rv = ringBuffer.Discard(3);
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 5);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 5);
+
+  float out[8] = {};
+  rv = ringBuffer.Read(MakeSpan(out, 3));
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 8);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 2);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_FLOAT_EQ(out[i], in[i + 3]);
+  }
+
+  rv = ringBuffer.Discard(3);
+  EXPECT_EQ(rv, 2);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+
+  ringBuffer.WriteSilence(4);
+  rv = ringBuffer.Discard(6);
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+}
+
+TEST(TestAudioRingBuffer, DiscardShort)
+{
+  AudioRingBuffer ringBuffer(11 * sizeof(short));
+  ringBuffer.SetSampleFormat(AUDIO_FORMAT_S16);
+
+  short in[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+  ringBuffer.Write(MakeSpan(in, 8));
+
+  int rv = ringBuffer.Discard(3);
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 5);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 5);
+
+  short out[8] = {};
+  rv = ringBuffer.Read(MakeSpan(out, 3));
+  EXPECT_EQ(rv, 3);
+  EXPECT_TRUE(!ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 8);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 2);
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_EQ(out[i], in[i + 3]);
+  }
+
+  rv = ringBuffer.Discard(3);
+  EXPECT_EQ(rv, 2);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+
+  ringBuffer.WriteSilence(4);
+  rv = ringBuffer.Discard(6);
+  EXPECT_EQ(rv, 4);
+  EXPECT_TRUE(ringBuffer.IsEmpty());
+  EXPECT_TRUE(!ringBuffer.IsFull());
+  EXPECT_EQ(ringBuffer.AvailableWrite(), 10);
+  EXPECT_EQ(ringBuffer.AvailableRead(), 0);
+}
+
+TEST(TestRingBuffer, WriteFromRing1)
+{
+  AudioRingBuffer ringBuffer1(11 * sizeof(float));
+  ringBuffer1.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+  AudioRingBuffer ringBuffer2(11 * sizeof(float));
+  ringBuffer2.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+  float in[4] = {.1, .2, .3, .4};
+  int rv = ringBuffer1.Write(Span<const float>(in, 4));
+  EXPECT_EQ(rv, 4);
+
+  EXPECT_EQ(ringBuffer2.AvailableRead(), 0);
+  rv = ringBuffer2.Write(ringBuffer1, 4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_EQ(ringBuffer2.AvailableRead(), 4);
+
+  float out[4] = {};
+  rv = ringBuffer2.Read(Span<float>(out, 4));
+  EXPECT_EQ(rv, 4);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_FLOAT_EQ(in[i], out[i]);
+  }
+}
+
+TEST(TestRingBuffer, WriteFromRing2)
+{
+  AudioRingBuffer ringBuffer1(11 * sizeof(float));
+  ringBuffer1.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+  AudioRingBuffer ringBuffer2(11 * sizeof(float));
+  ringBuffer2.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+  // Advance the index
+  ringBuffer2.WriteSilence(8);
+  ringBuffer2.Clear();
+
+  float in[4] = {.1, .2, .3, .4};
+  int rv = ringBuffer1.Write(Span<const float>(in, 4));
+  EXPECT_EQ(rv, 4);
+  rv = ringBuffer2.Write(ringBuffer1, 4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_EQ(ringBuffer2.AvailableRead(), 4);
+
+  float out[4] = {};
+  rv = ringBuffer2.Read(Span<float>(out, 4));
+  EXPECT_EQ(rv, 4);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_FLOAT_EQ(in[i], out[i]);
+  }
+}
+
+TEST(TestRingBuffer, WriteFromRing3)
+{
+  AudioRingBuffer ringBuffer1(11 * sizeof(float));
+  ringBuffer1.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+  AudioRingBuffer ringBuffer2(11 * sizeof(float));
+  ringBuffer2.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+  // Advance the index
+  ringBuffer2.WriteSilence(8);
+  ringBuffer2.Clear();
+  ringBuffer2.WriteSilence(4);
+  ringBuffer2.Clear();
+
+  float in[4] = {.1, .2, .3, .4};
+  int rv = ringBuffer1.Write(Span<const float>(in, 4));
+  EXPECT_EQ(rv, 4);
+  rv = ringBuffer2.Write(ringBuffer1, 4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_EQ(ringBuffer2.AvailableRead(), 4);
+
+  float out[4] = {};
+  rv = ringBuffer2.Read(Span<float>(out, 4));
+  EXPECT_EQ(rv, 4);
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_FLOAT_EQ(in[i], out[i]);
+  }
+}
+
+TEST(TestAudioRingBuffer, WriteFromRingShort)
+{
+  AudioRingBuffer ringBuffer1(11 * sizeof(short));
+  ringBuffer1.SetSampleFormat(AUDIO_FORMAT_S16);
+
+  short in[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+  int rv = ringBuffer1.Write(MakeSpan(in, 8));
+  EXPECT_EQ(rv, 8);
+
+  AudioRingBuffer ringBuffer2(11 * sizeof(short));
+  ringBuffer2.SetSampleFormat(AUDIO_FORMAT_S16);
+
+  rv = ringBuffer2.Write(ringBuffer1, 4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_EQ(ringBuffer2.AvailableRead(), 4);
+  EXPECT_EQ(ringBuffer1.AvailableRead(), 8);
+
+  short out[4] = {};
+  rv = ringBuffer2.Read(MakeSpan(out, 4));
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_EQ(out[i], in[i]);
+  }
+
+  rv = ringBuffer2.Write(ringBuffer1, 4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_EQ(ringBuffer2.AvailableRead(), 4);
+  EXPECT_EQ(ringBuffer1.AvailableRead(), 8);
+
+  ringBuffer1.Discard(4);
+  rv = ringBuffer2.Write(ringBuffer1, 4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_EQ(ringBuffer2.AvailableRead(), 8);
+  EXPECT_EQ(ringBuffer1.AvailableRead(), 4);
+
+  short out2[8] = {};
+  rv = ringBuffer2.Read(MakeSpan(out2, 8));
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_EQ(out2[i], in[i]);
+  }
+}
+
+TEST(TestAudioRingBuffer, WriteFromRingFloat)
+{
+  AudioRingBuffer ringBuffer1(11 * sizeof(float));
+  ringBuffer1.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+  float in[8] = {.0, .1, .2, .3, .4, .5, .6, .7};
+  int rv = ringBuffer1.Write(MakeSpan(in, 8));
+  EXPECT_EQ(rv, 8);
+
+  AudioRingBuffer ringBuffer2(11 * sizeof(float));
+  ringBuffer2.SetSampleFormat(AUDIO_FORMAT_FLOAT32);
+
+  rv = ringBuffer2.Write(ringBuffer1, 4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_EQ(ringBuffer2.AvailableRead(), 4);
+  EXPECT_EQ(ringBuffer1.AvailableRead(), 8);
+
+  float out[4] = {};
+  rv = ringBuffer2.Read(MakeSpan(out, 4));
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_FLOAT_EQ(out[i], in[i]);
+  }
+
+  rv = ringBuffer2.Write(ringBuffer1, 4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_EQ(ringBuffer2.AvailableRead(), 4);
+  EXPECT_EQ(ringBuffer1.AvailableRead(), 8);
+
+  ringBuffer1.Discard(4);
+  rv = ringBuffer2.Write(ringBuffer1, 4);
+  EXPECT_EQ(rv, 4);
+  EXPECT_EQ(ringBuffer2.AvailableRead(), 8);
+  EXPECT_EQ(ringBuffer1.AvailableRead(), 4);
+
+  float out2[8] = {};
+  rv = ringBuffer2.Read(MakeSpan(out2, 8));
+  for (int i = 0; i < rv; ++i) {
+    EXPECT_FLOAT_EQ(out2[i], in[i]);
+  }
+}
--- a/dom/media/gtest/TestBufferReader.cpp
+++ b/dom/media/gtest/TestBufferReader.cpp
@@ -1,16 +1,18 @@
 /* -*- 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 "gtest/gtest.h"
 #include "BufferReader.h"
 
+using namespace mozilla;
+
 TEST(BufferReader, ReaderCursor)
 {
   // Allocate a buffer and create a BufferReader.
   const size_t BUFFER_SIZE = 10;
   uint8_t buffer[BUFFER_SIZE] = {0};
 
   const uint8_t* const HEAD = reinterpret_cast<uint8_t*>(buffer);
   const uint8_t* const TAIL = HEAD + BUFFER_SIZE;
@@ -28,9 +30,9 @@ TEST(BufferReader, ReaderCursor)
 
   // Check the reading cursor of the BufferReader is correct
   // after reading and seeking.
   const uint8_t* tail = reader.Peek(0);
   const uint8_t* head = reader.Seek(0);
 
   EXPECT_EQ(head, HEAD);
   EXPECT_EQ(tail, TAIL);
-}
\ No newline at end of file
+}
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -17,16 +17,17 @@ LOCAL_INCLUDES += [
 UNIFIED_SOURCES += [
     'AudioGenerator.cpp',
     'MockMediaResource.cpp',
     'TestAudioBuffers.cpp',
     'TestAudioCallbackDriver.cpp',
     'TestAudioCompactor.cpp',
     'TestAudioMixer.cpp',
     'TestAudioPacketizer.cpp',
+    'TestAudioRingBuffer.cpp',
     'TestAudioSegment.cpp',
     'TestAudioTrackEncoder.cpp',
     'TestAudioTrackGraph.cpp',
     'TestBenchmarkStorage.cpp',
     'TestBitWriter.cpp',
     'TestBlankVideoDataCreator.cpp',
     'TestBufferReader.cpp',
     'TestDataMutex.cpp',
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -107,16 +107,17 @@ EXPORTS += [
     'AudioBufferUtils.h',
     'AudioChannelFormat.h',
     'AudioCompactor.h',
     'AudioConfig.h',
     'AudioConverter.h',
     'AudioDeviceInfo.h',
     'AudioMixer.h',
     'AudioPacketizer.h',
+    'AudioRingBuffer.h',
     'AudioSampleFormat.h',
     'AudioSegment.h',
     'AudioStream.h',
     'AutoplayPolicy.h',
     'BackgroundVideoDecodingPermissionObserver.h',
     'Benchmark.h',
     'BitReader.h',
     'BitWriter.h',
@@ -229,16 +230,17 @@ UNIFIED_SOURCES += [
     'ADTSDecoder.cpp',
     'ADTSDemuxer.cpp',
     'AudioCaptureTrack.cpp',
     'AudioChannelFormat.cpp',
     'AudioCompactor.cpp',
     'AudioConfig.cpp',
     'AudioConverter.cpp',
     'AudioDeviceInfo.cpp',
+    'AudioRingBuffer.cpp',
     'AudioSegment.cpp',
     'AudioStream.cpp',
     'AudioStreamTrack.cpp',
     'AudioTrack.cpp',
     'AudioTrackList.cpp',
     'AutoplayPolicy.cpp',
     'BackgroundVideoDecodingPermissionObserver.cpp',
     'BaseMediaResource.cpp',