summaryrefslogtreecommitdiffstats
path: root/dom/media/AudioBufferUtils.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/AudioBufferUtils.h')
-rw-r--r--dom/media/AudioBufferUtils.h220
1 files changed, 220 insertions, 0 deletions
diff --git a/dom/media/AudioBufferUtils.h b/dom/media/AudioBufferUtils.h
new file mode 100644
index 0000000000..3d0d1e9b6b
--- /dev/null
+++ b/dom/media/AudioBufferUtils.h
@@ -0,0 +1,220 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_SCRATCHBUFFER_H_
+#define MOZILLA_SCRATCHBUFFER_H_
+
+#include "AudioSegment.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/UniquePtr.h"
+#include "nsDebug.h"
+
+#include <algorithm>
+
+namespace mozilla {
+
+/**
+ * The classes in this file provide a interface that uses frames as a unit.
+ * However, they store their offsets in samples (because it's handy for pointer
+ * operations). Those functions can convert between the two units.
+ */
+static inline uint32_t FramesToSamples(uint32_t aChannels, uint32_t aFrames) {
+ return aFrames * aChannels;
+}
+
+static inline uint32_t SamplesToFrames(uint32_t aChannels, uint32_t aSamples) {
+ MOZ_ASSERT(!(aSamples % aChannels), "Frame alignment is wrong.");
+ return aSamples / aChannels;
+}
+
+/**
+ * Class that gets a buffer pointer from an audio callback and provides a safe
+ * interface to manipulate this buffer, and to ensure we are not missing frames
+ * by the end of the callback.
+ */
+template <typename T>
+class AudioCallbackBufferWrapper {
+ public:
+ AudioCallbackBufferWrapper()
+ : mBuffer(nullptr), mSamples(0), mSampleWriteOffset(1), mChannels(0) {}
+
+ explicit AudioCallbackBufferWrapper(uint32_t aChannels)
+ : mBuffer(nullptr),
+ mSamples(0),
+ mSampleWriteOffset(1),
+ mChannels(aChannels)
+
+ {
+ MOZ_ASSERT(aChannels);
+ }
+
+ AudioCallbackBufferWrapper& operator=(
+ const AudioCallbackBufferWrapper& aOther) {
+ MOZ_ASSERT(!aOther.mBuffer,
+ "Don't use this ctor after AudioCallbackDriver::Init");
+ MOZ_ASSERT(aOther.mSamples == 0,
+ "Don't use this ctor after AudioCallbackDriver::Init");
+ MOZ_ASSERT(aOther.mSampleWriteOffset == 1,
+ "Don't use this ctor after AudioCallbackDriver::Init");
+ MOZ_ASSERT(aOther.mChannels != 0);
+
+ mBuffer = nullptr;
+ mSamples = 0;
+ mSampleWriteOffset = 1;
+ mChannels = aOther.mChannels;
+
+ return *this;
+ }
+
+ /**
+ * Set the buffer in this wrapper. This is to be called at the beginning of
+ * the callback.
+ */
+ void SetBuffer(T* aBuffer, uint32_t aFrames) {
+ MOZ_ASSERT(!mBuffer && !mSamples, "SetBuffer called twice.");
+ mBuffer = aBuffer;
+ mSamples = FramesToSamples(mChannels, aFrames);
+ mSampleWriteOffset = 0;
+ }
+
+ /**
+ * Write some frames to the internal buffer. Free space in the buffer should
+ * be checked prior to calling these.
+ */
+ void WriteFrames(T* aBuffer, uint32_t aFrames) {
+ MOZ_ASSERT(aFrames <= Available(),
+ "Writing more that we can in the audio buffer.");
+
+ PodCopy(mBuffer + mSampleWriteOffset, aBuffer,
+ FramesToSamples(mChannels, aFrames));
+ mSampleWriteOffset += FramesToSamples(mChannels, aFrames);
+ }
+ void WriteFrames(const AudioChunk& aChunk, uint32_t aFrames) {
+ MOZ_ASSERT(aFrames <= Available(),
+ "Writing more that we can in the audio buffer.");
+
+ InterleaveAndConvertBuffer(aChunk.ChannelData<T>().Elements(), aFrames,
+ aChunk.mVolume, aChunk.ChannelCount(),
+ mBuffer + mSampleWriteOffset);
+ mSampleWriteOffset += FramesToSamples(mChannels, aFrames);
+ }
+
+ /**
+ * Number of frames that can be written to the buffer.
+ */
+ uint32_t Available() {
+ return SamplesToFrames(mChannels, mSamples - mSampleWriteOffset);
+ }
+
+ /**
+ * Check that the buffer is completly filled, and reset internal state so this
+ * instance can be reused.
+ */
+ void BufferFilled() {
+ MOZ_ASSERT(Available() == 0, "Frames should have been written");
+ MOZ_ASSERT(mBuffer, "Buffer not set.");
+ mSamples = 0;
+ mSampleWriteOffset = 0;
+ mBuffer = nullptr;
+ }
+
+ private:
+ /* This is not an owned pointer, but the pointer passed to use via the audio
+ * callback. */
+ T* mBuffer;
+ /* The number of samples of this audio buffer. */
+ uint32_t mSamples;
+ /* The position at which new samples should be written. We want to return to
+ * the audio callback iff this is equal to mSamples. */
+ uint32_t mSampleWriteOffset;
+ uint32_t mChannels;
+};
+
+/**
+ * This is a class that interfaces with the AudioCallbackBufferWrapper, and is
+ * responsible for storing the excess of data produced by the MediaTrackGraph
+ * because of different rounding constraints, to be used the next time the audio
+ * backend calls back.
+ */
+template <typename T, uint32_t BLOCK_SIZE>
+class SpillBuffer {
+ public:
+ SpillBuffer() : mBuffer(nullptr), mPosition(0), mChannels(0) {}
+
+ explicit SpillBuffer(uint32_t aChannels)
+ : mPosition(0), mChannels(aChannels) {
+ MOZ_ASSERT(aChannels);
+ mBuffer = MakeUnique<T[]>(BLOCK_SIZE * mChannels);
+ PodZero(mBuffer.get(), BLOCK_SIZE * mChannels);
+ }
+
+ SpillBuffer& operator=(SpillBuffer& aOther) {
+ MOZ_ASSERT(aOther.mPosition == 0,
+ "Don't use this ctor after AudioCallbackDriver::Init");
+ MOZ_ASSERT(aOther.mChannels != 0);
+ MOZ_ASSERT(aOther.mBuffer);
+
+ mPosition = aOther.mPosition;
+ mChannels = aOther.mChannels;
+ mBuffer = std::move(aOther.mBuffer);
+
+ return *this;
+ }
+
+ SpillBuffer& operator=(SpillBuffer&& aOther) {
+ return this->operator=(aOther);
+ }
+
+ /* Empty the spill buffer into the buffer of the audio callback. This returns
+ * the number of frames written. */
+ uint32_t Empty(AudioCallbackBufferWrapper<T>& aBuffer) {
+ uint32_t framesToWrite =
+ std::min(aBuffer.Available(), SamplesToFrames(mChannels, mPosition));
+
+ aBuffer.WriteFrames(mBuffer.get(), framesToWrite);
+
+ mPosition -= FramesToSamples(mChannels, framesToWrite);
+ // If we didn't empty the spill buffer for some reason, shift the remaining
+ // data down
+ if (mPosition > 0) {
+ MOZ_ASSERT(FramesToSamples(mChannels, framesToWrite) + mPosition <=
+ BLOCK_SIZE * mChannels);
+ PodMove(mBuffer.get(),
+ mBuffer.get() + FramesToSamples(mChannels, framesToWrite),
+ mPosition);
+ }
+
+ return framesToWrite;
+ }
+ /* Fill the spill buffer from aInput.
+ * Return the number of frames written to the spill buffer */
+ uint32_t Fill(const AudioChunk& aInput) {
+ uint32_t framesToWrite =
+ std::min(static_cast<uint32_t>(aInput.mDuration),
+ BLOCK_SIZE - SamplesToFrames(mChannels, mPosition));
+
+ MOZ_ASSERT(FramesToSamples(mChannels, framesToWrite) + mPosition <=
+ BLOCK_SIZE * mChannels);
+ InterleaveAndConvertBuffer(
+ aInput.ChannelData<T>().Elements(), framesToWrite, aInput.mVolume,
+ aInput.ChannelCount(), mBuffer.get() + mPosition);
+
+ mPosition += FramesToSamples(mChannels, framesToWrite);
+
+ return framesToWrite;
+ }
+
+ private:
+ /* The spilled data. */
+ UniquePtr<T[]> mBuffer;
+ /* The current write position, in samples, in the buffer when filling, or the
+ * amount of buffer filled when emptying. */
+ uint32_t mPosition;
+ uint32_t mChannels;
+};
+
+} // namespace mozilla
+
+#endif // MOZILLA_SCRATCHBUFFER_H_