mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
Bug 901633 - Part 1 - Implement a generic audio packetizer. r=jesup
--HG-- extra : rebase_source : addc991e335f661b83a2dc0224da26a4eefa2a0d
This commit is contained in:
parent
d31516a8ec
commit
a7ae94ef7e
186
dom/media/AudioPacketizer.h
Normal file
186
dom/media/AudioPacketizer.h
Normal file
@ -0,0 +1,186 @@
|
||||
/* -*- 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>
|
||||
#include <nsAutoPtr.h>
|
||||
#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.
|
||||
*
|
||||
* Allocations of output buffer are performed by this class. Buffers can simply
|
||||
* be delete-d. This is because packets are intended to be sent off to
|
||||
* non-gecko code using normal pointers/length pairs
|
||||
*
|
||||
* 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");
|
||||
}
|
||||
|
||||
void Input(InputType* aFrames, uint32_t aFrameCount)
|
||||
{
|
||||
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();
|
||||
nsAutoPtr<InputType> oldStorage = mStorage;
|
||||
mStorage = new InputType[newLength];
|
||||
// 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];
|
||||
|
||||
// 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();
|
||||
PodZero(out + AvailableSamples(), zeros);
|
||||
samplesNeeded -= zeros;
|
||||
}
|
||||
if (ReadIndex() + samplesNeeded <= mLength) {
|
||||
ConvertAudioSamples<InputType,OutputType>(mStorage.get() + ReadIndex(),
|
||||
out,
|
||||
samplesNeeded);
|
||||
} else {
|
||||
uint32_t firstPartLength = mLength - ReadIndex();
|
||||
uint32_t secondPartLength = samplesNeeded - firstPartLength;
|
||||
ConvertAudioSamples<InputType, OutputType>(mStorage.get() + ReadIndex(),
|
||||
out,
|
||||
firstPartLength);
|
||||
ConvertAudioSamples<InputType, OutputType>(mStorage.get(),
|
||||
out + firstPartLength,
|
||||
secondPartLength);
|
||||
}
|
||||
mReadIndex += samplesNeeded;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
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
|
||||
nsAutoPtr<InputType> mStorage;
|
||||
// Length of the buffer, in samples
|
||||
uint32_t mLength;
|
||||
};
|
||||
|
||||
} // mozilla
|
||||
|
||||
#endif // AudioPacketizer_h_
|
172
dom/media/compiledtest/TestAudioPacketizer.cpp
Normal file
172
dom/media/compiledtest/TestAudioPacketizer.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
/* -*- 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 <stdint.h>
|
||||
#include <assert.h>
|
||||
#include "../AudioPacketizer.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
template<typename T>
|
||||
class AutoBuffer
|
||||
{
|
||||
public:
|
||||
AutoBuffer(size_t aLength)
|
||||
{
|
||||
mStorage = new T[aLength];
|
||||
}
|
||||
~AutoBuffer() {
|
||||
delete [] mStorage;
|
||||
}
|
||||
T* Get() {
|
||||
return mStorage;
|
||||
}
|
||||
private:
|
||||
T* mStorage;
|
||||
};
|
||||
|
||||
int16_t Sequence(int16_t* aBuffer, uint32_t aSize, uint32_t aStart = 0)
|
||||
{
|
||||
uint32_t i;
|
||||
for (i = 0; i < aSize; i++) {
|
||||
aBuffer[i] = aStart + i;
|
||||
}
|
||||
return aStart + i;
|
||||
}
|
||||
|
||||
void IsSequence(int16_t* aBuffer, uint32_t aSize, uint32_t aStart = 0)
|
||||
{
|
||||
for (uint32_t i = 0; i < aSize; i++) {
|
||||
if (aBuffer[i] != static_cast<int64_t>(aStart + i)) {
|
||||
fprintf(stderr, "Buffer is not a sequence at offset %u\n", i);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
assert("Buffer is a sequence.");
|
||||
}
|
||||
|
||||
void Zero(int16_t* aBuffer, uint32_t aSize)
|
||||
{
|
||||
for (uint32_t i = 0; i < aSize; i++) {
|
||||
if (aBuffer[i] != 0) {
|
||||
fprintf(stderr, "Buffer is not null at offset %u\n", i);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double sine(uint32_t aPhase) {
|
||||
return sin(aPhase * 2 * M_PI * 440 / 44100);
|
||||
}
|
||||
|
||||
int main() {
|
||||
for (int16_t channels = 1; channels < 2; channels++) {
|
||||
// Test that the packetizer returns zero on underrun
|
||||
{
|
||||
AudioPacketizer<int16_t, int16_t> ap(441, channels);
|
||||
for (int16_t i = 0; i < 10; i++) {
|
||||
int16_t* out = ap.Output();
|
||||
Zero(out, 441);
|
||||
delete out;
|
||||
}
|
||||
}
|
||||
// Simple test, with input/output buffer size aligned on the packet size,
|
||||
// alternating Input and Output calls.
|
||||
{
|
||||
AudioPacketizer<int16_t, int16_t> ap(441, channels);
|
||||
int16_t seqEnd = 0;
|
||||
for (int16_t i = 0; i < 10; i++) {
|
||||
AutoBuffer<int16_t> b(441 * channels);
|
||||
int16_t prevEnd = seqEnd;
|
||||
seqEnd = Sequence(b.Get(), channels * 441, prevEnd);
|
||||
ap.Input(b.Get(), 441);
|
||||
int16_t* out = ap.Output();
|
||||
IsSequence(out, 441 * channels, prevEnd);
|
||||
delete out;
|
||||
}
|
||||
}
|
||||
// Simple test, with input/output buffer size aligned on the packet size,
|
||||
// alternating two Input and Output calls.
|
||||
{
|
||||
AudioPacketizer<int16_t, int16_t> ap(441, channels);
|
||||
int16_t seqEnd = 0;
|
||||
for (int16_t i = 0; i < 10; i++) {
|
||||
AutoBuffer<int16_t> b(441 * channels);
|
||||
AutoBuffer<int16_t> b1(441 * channels);
|
||||
int16_t prevEnd0 = seqEnd;
|
||||
seqEnd = Sequence(b.Get(), 441 * channels, prevEnd0);
|
||||
int16_t prevEnd1 = seqEnd;
|
||||
seqEnd = Sequence(b1.Get(), 441 * channels, seqEnd);
|
||||
ap.Input(b.Get(), 441);
|
||||
ap.Input(b1.Get(), 441);
|
||||
int16_t* out = ap.Output();
|
||||
int16_t* out2 = ap.Output();
|
||||
IsSequence(out, 441 * channels, prevEnd0);
|
||||
IsSequence(out2, 441 * channels, prevEnd1);
|
||||
delete out;
|
||||
delete out2;
|
||||
}
|
||||
}
|
||||
// Input/output buffer size not aligned on the packet size,
|
||||
// alternating two Input and Output calls.
|
||||
{
|
||||
AudioPacketizer<int16_t, int16_t> ap(441, channels);
|
||||
int16_t prevEnd = 0;
|
||||
int16_t prevSeq = 0;
|
||||
for (int16_t i = 0; i < 10; i++) {
|
||||
AutoBuffer<int16_t> b(480 * channels);
|
||||
AutoBuffer<int16_t> b1(480 * channels);
|
||||
prevSeq = Sequence(b.Get(), 480 * channels, prevSeq);
|
||||
prevSeq = Sequence(b1.Get(), 480 * channels, prevSeq);
|
||||
ap.Input(b.Get(), 480);
|
||||
ap.Input(b1.Get(), 480);
|
||||
int16_t* out = ap.Output();
|
||||
int16_t* out2 = ap.Output();
|
||||
IsSequence(out, 441 * channels, prevEnd);
|
||||
prevEnd += 441 * channels;
|
||||
IsSequence(out2, 441 * channels, prevEnd);
|
||||
prevEnd += 441 * channels;
|
||||
delete out;
|
||||
delete out2;
|
||||
}
|
||||
printf("Available: %d\n", ap.PacketsAvailable());
|
||||
}
|
||||
|
||||
// "Real-life" test case: streaming a sine wave through a packetizer, and
|
||||
// checking that we have the right output.
|
||||
// 128 is, for example, the size of a Web Audio API block, and 441 is the
|
||||
// size of a webrtc.org packet when the sample rate is 44100 (10ms)
|
||||
{
|
||||
AudioPacketizer<int16_t, int16_t> ap(441, channels);
|
||||
AutoBuffer<int16_t> b(128 * channels);
|
||||
uint32_t phase = 0;
|
||||
uint32_t outPhase = 0;
|
||||
for (int16_t i = 0; i < 1000; i++) {
|
||||
for (int32_t j = 0; j < 128; j++) {
|
||||
for (int32_t c = 0; c < channels; c++) {
|
||||
// int16_t sinewave at 440Hz/44100Hz sample rate
|
||||
b.Get()[j * channels + c] = (2 << 14) * sine(phase);
|
||||
}
|
||||
phase++;
|
||||
}
|
||||
ap.Input(b.Get(), 128);
|
||||
while (ap.PacketsAvailable()) {
|
||||
int16_t* packet = ap.Output();
|
||||
for (uint32_t k = 0; k < ap.PacketSize(); k++) {
|
||||
for (int32_t c = 0; c < channels; c++) {
|
||||
assert(packet[k * channels + c] ==
|
||||
static_cast<int16_t>(((2 << 14) * sine(outPhase))));
|
||||
}
|
||||
outPhase++;
|
||||
}
|
||||
delete [] packet;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("OK\n");
|
||||
return 0;
|
||||
}
|
@ -6,7 +6,8 @@
|
||||
|
||||
GeckoCppUnitTests([
|
||||
'TestAudioBuffers',
|
||||
'TestAudioMixer'
|
||||
'TestAudioMixer',
|
||||
'TestAudioPacketizer'
|
||||
])
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
|
@ -101,6 +101,7 @@ EXPORTS += [
|
||||
'AudioChannelFormat.h',
|
||||
'AudioCompactor.h',
|
||||
'AudioMixer.h',
|
||||
'AudioPacketizer.h',
|
||||
'AudioSampleFormat.h',
|
||||
'AudioSegment.h',
|
||||
'AudioStream.h',
|
||||
|
Loading…
Reference in New Issue
Block a user