2015-07-30 14:51:57 +03:00
|
|
|
/* -*- 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 AudioPacketizer_h_
|
|
|
|
#define AudioPacketizer_h_
|
|
|
|
|
|
|
|
#include <mozilla/PodOperations.h>
|
|
|
|
#include <mozilla/Assertions.h>
|
2016-01-30 02:33:41 +03:00
|
|
|
#include <mozilla/UniquePtr.h>
|
2015-07-30 14:51:57 +03:00
|
|
|
#include <AudioSampleFormat.h>
|
|
|
|
|
|
|
|
// Enable this to warn when `Output` has been called but not enough data was
|
|
|
|
// buffered.
|
|
|
|
// #define LOG_PACKETIZER_UNDERRUN
|
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
/**
|
|
|
|
* This class takes arbitrary input data, and returns packets of a specific
|
|
|
|
* size. In the process, it can convert audio samples from 16bit integers to
|
|
|
|
* float (or vice-versa).
|
|
|
|
*
|
|
|
|
* Input and output, as well as length units in the public interface are
|
|
|
|
* interleaved frames.
|
|
|
|
*
|
2015-09-01 15:26:13 +03:00
|
|
|
* Allocations of output buffer can be performed by this class. Buffers can
|
|
|
|
* simply be delete-d. This is because packets are intended to be sent off to
|
2015-07-30 14:51:57 +03:00
|
|
|
* non-gecko code using normal pointers/length pairs
|
|
|
|
*
|
2015-09-01 15:26:13 +03:00
|
|
|
* Alternatively, consumers can pass in a buffer in which the output is copied.
|
|
|
|
* The buffer needs to be large enough to store a packet worth of audio.
|
|
|
|
*
|
2015-07-30 14:51:57 +03:00
|
|
|
* The implementation uses a circular buffer using absolute virtual indices.
|
|
|
|
*/
|
|
|
|
template <typename InputType, typename OutputType>
|
|
|
|
class AudioPacketizer
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
AudioPacketizer(uint32_t aPacketSize, uint32_t aChannels)
|
|
|
|
: mPacketSize(aPacketSize)
|
|
|
|
, mChannels(aChannels)
|
|
|
|
, mReadIndex(0)
|
|
|
|
, mWriteIndex(0)
|
|
|
|
// Start off with a single packet
|
|
|
|
, mStorage(new InputType[aPacketSize * aChannels])
|
|
|
|
, mLength(aPacketSize * aChannels)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aPacketSize > 0 && aChannels > 0,
|
|
|
|
"The packet size and the number of channel should be strictly positive");
|
|
|
|
}
|
|
|
|
|
2015-09-01 15:26:14 +03:00
|
|
|
void Input(const InputType* aFrames, uint32_t aFrameCount)
|
2015-07-30 14:51:57 +03:00
|
|
|
{
|
|
|
|
uint32_t inputSamples = aFrameCount * mChannels;
|
|
|
|
// Need to grow the storage. This should rarely happen, if at all, once the
|
|
|
|
// array has the right size.
|
|
|
|
if (inputSamples > EmptySlots()) {
|
|
|
|
// Calls to Input and Output are roughtly interleaved
|
|
|
|
// (Input,Output,Input,Output, etc.), or balanced
|
|
|
|
// (Input,Input,Input,Output,Output,Output), so we update the buffer to
|
|
|
|
// the exact right size in order to not waste space.
|
|
|
|
uint32_t newLength = AvailableSamples() + inputSamples;
|
|
|
|
uint32_t toCopy = AvailableSamples();
|
2016-01-30 02:33:41 +03:00
|
|
|
UniquePtr<InputType[]> oldStorage = mozilla::Move(mStorage);
|
|
|
|
mStorage = mozilla::MakeUnique<InputType[]>(newLength);
|
2015-07-30 14:51:57 +03:00
|
|
|
// Copy the old data at the beginning of the new storage.
|
|
|
|
if (WriteIndex() >= ReadIndex()) {
|
|
|
|
PodCopy(mStorage.get(),
|
|
|
|
oldStorage.get() + ReadIndex(),
|
|
|
|
AvailableSamples());
|
|
|
|
} else {
|
|
|
|
uint32_t firstPartLength = mLength - ReadIndex();
|
|
|
|
uint32_t secondPartLength = AvailableSamples() - firstPartLength;
|
|
|
|
PodCopy(mStorage.get(),
|
|
|
|
oldStorage.get() + ReadIndex(),
|
|
|
|
firstPartLength);
|
|
|
|
PodCopy(mStorage.get() + firstPartLength,
|
|
|
|
oldStorage.get(),
|
|
|
|
secondPartLength);
|
|
|
|
}
|
|
|
|
mWriteIndex = toCopy;
|
|
|
|
mReadIndex = 0;
|
|
|
|
mLength = newLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WriteIndex() + inputSamples <= mLength) {
|
|
|
|
PodCopy(mStorage.get() + WriteIndex(), aFrames, aFrameCount * mChannels);
|
|
|
|
} else {
|
|
|
|
uint32_t firstPartLength = mLength - WriteIndex();
|
|
|
|
uint32_t secondPartLength = inputSamples - firstPartLength;
|
|
|
|
PodCopy(mStorage.get() + WriteIndex(), aFrames, firstPartLength);
|
|
|
|
PodCopy(mStorage.get(), aFrames + firstPartLength, secondPartLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
mWriteIndex += inputSamples;
|
|
|
|
}
|
|
|
|
|
|
|
|
OutputType* Output()
|
|
|
|
{
|
|
|
|
uint32_t samplesNeeded = mPacketSize * mChannels;
|
|
|
|
OutputType* out = new OutputType[samplesNeeded];
|
|
|
|
|
2015-09-01 15:26:13 +03:00
|
|
|
Output(out);
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Output(OutputType* aOutputBuffer)
|
|
|
|
{
|
|
|
|
uint32_t samplesNeeded = mPacketSize * mChannels;
|
|
|
|
|
2015-07-30 14:51:57 +03:00
|
|
|
// Under-run. Pad the end of the buffer with silence.
|
|
|
|
if (AvailableSamples() < samplesNeeded) {
|
|
|
|
#ifdef LOG_PACKETIZER_UNDERRUN
|
|
|
|
char buf[256];
|
|
|
|
snprintf(buf, 256,
|
|
|
|
"AudioPacketizer %p underrun: available: %u, needed: %u\n",
|
|
|
|
this, AvailableSamples(), samplesNeeded);
|
|
|
|
NS_WARNING(buf);
|
|
|
|
#endif
|
|
|
|
uint32_t zeros = samplesNeeded - AvailableSamples();
|
2015-09-01 15:26:13 +03:00
|
|
|
PodZero(aOutputBuffer + AvailableSamples(), zeros);
|
2015-07-30 14:51:57 +03:00
|
|
|
samplesNeeded -= zeros;
|
|
|
|
}
|
|
|
|
if (ReadIndex() + samplesNeeded <= mLength) {
|
|
|
|
ConvertAudioSamples<InputType,OutputType>(mStorage.get() + ReadIndex(),
|
2015-09-01 15:26:13 +03:00
|
|
|
aOutputBuffer,
|
2015-07-30 14:51:57 +03:00
|
|
|
samplesNeeded);
|
|
|
|
} else {
|
|
|
|
uint32_t firstPartLength = mLength - ReadIndex();
|
|
|
|
uint32_t secondPartLength = samplesNeeded - firstPartLength;
|
|
|
|
ConvertAudioSamples<InputType, OutputType>(mStorage.get() + ReadIndex(),
|
2015-09-01 15:26:13 +03:00
|
|
|
aOutputBuffer,
|
2015-07-30 14:51:57 +03:00
|
|
|
firstPartLength);
|
|
|
|
ConvertAudioSamples<InputType, OutputType>(mStorage.get(),
|
2015-09-01 15:26:13 +03:00
|
|
|
aOutputBuffer + firstPartLength,
|
2015-07-30 14:51:57 +03:00
|
|
|
secondPartLength);
|
|
|
|
}
|
|
|
|
mReadIndex += samplesNeeded;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t PacketsAvailable() const {
|
|
|
|
return AvailableSamples() / mChannels / mPacketSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Empty() const {
|
|
|
|
return mWriteIndex == mReadIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Full() const {
|
|
|
|
return mWriteIndex - mReadIndex == mLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t PacketSize() const {
|
|
|
|
return mPacketSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Channels() const {
|
|
|
|
return mChannels;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
uint32_t ReadIndex() const {
|
|
|
|
return mReadIndex % mLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t WriteIndex() const {
|
|
|
|
return mWriteIndex % mLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t AvailableSamples() const {
|
|
|
|
return mWriteIndex - mReadIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t EmptySlots() const {
|
|
|
|
return mLength - AvailableSamples();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Size of one packet of audio, in frames
|
|
|
|
uint32_t mPacketSize;
|
|
|
|
// Number of channels of the stream flowing through this packetizer
|
|
|
|
uint32_t mChannels;
|
|
|
|
// Two virtual index into the buffer: the read position and the write
|
|
|
|
// position.
|
|
|
|
uint64_t mReadIndex;
|
|
|
|
uint64_t mWriteIndex;
|
|
|
|
// Storage for the samples
|
2016-01-30 02:33:41 +03:00
|
|
|
mozilla::UniquePtr<InputType[]> mStorage;
|
2015-07-30 14:51:57 +03:00
|
|
|
// Length of the buffer, in samples
|
|
|
|
uint32_t mLength;
|
|
|
|
};
|
|
|
|
|
|
|
|
} // mozilla
|
|
|
|
|
|
|
|
#endif // AudioPacketizer_h_
|