/* -*- 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 #include 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 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 check prior to calling this. */ 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); } /** * 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() { // It's okay to have exactly zero samples here, it can happen we have an // audio callback driver because of a hint on MSG creation, but the // AudioOutputStream has not been created yet, or if all the streams have finished // but we're still running. // Note: it's also ok if we had data in the scratch buffer - and we usually do - and // all the streams were ended (no mixer callback occured). // XXX Remove this warning, or find a way to avoid it if the mixer callback // isn't called. NS_WARNING_ASSERTION( Available() == 0 || mSampleWriteOffset == 0, "Audio Buffer is not full by the end of the callback."); // Make sure the data returned is always set and not random! if (Available()) { PodZero(mBuffer + mSampleWriteOffset, FramesToSamples(mChannels, Available())); } MOZ_ASSERT(mSamples, "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 MediaStreamGraph * because of different rounding constraints, to be used the next time the audio * backend calls back. */ template class SpillBuffer { public: SpillBuffer() : mBuffer(nullptr) , mPosition(0) , mChannels(0) {} explicit SpillBuffer(uint32_t aChannels) : mPosition(0) , mChannels(aChannels) { MOZ_ASSERT(aChannels); mBuffer = MakeUnique(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& 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, containing aFrames frames, return the * number of frames written to the spill buffer */ uint32_t Fill(T* aInput, uint32_t aFrames) { uint32_t framesToWrite = std::min(aFrames, BLOCK_SIZE - SamplesToFrames(mChannels, mPosition)); MOZ_ASSERT(FramesToSamples(mChannels, framesToWrite) + mPosition <= BLOCK_SIZE * mChannels); PodCopy(mBuffer.get() + mPosition, aInput, FramesToSamples(mChannels, framesToWrite)); mPosition += FramesToSamples(mChannels, framesToWrite); return framesToWrite; } private: /* The spilled data. */ UniquePtr 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_