Backed out 3 changesets (bug 1637235) for causing bustages in TestDynamicResampler.cpp CLOSED TREE

Backed out changeset 705f2d35af32 (bug 1637235)
Backed out changeset d32ea183ab01 (bug 1637235)
Backed out changeset 81012bc3b3ac (bug 1637235)
This commit is contained in:
Noemi Erli 2020-06-01 17:39:50 +03:00
Родитель b2e6e5eb9d
Коммит e8580c92df
16 изменённых файлов: 2 добавлений и 4439 удалений

Просмотреть файл

@ -8,7 +8,6 @@
#include "mozilla/PodOperations.h"
#include "mozilla/UniquePtr.h"
#include "nsDebug.h"
#include <algorithm>

Просмотреть файл

@ -1,193 +0,0 @@
/* -*- 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_DRIFT_CORRECTION_H_
#define MOZILLA_AUDIO_DRIFT_CORRECTION_H_
#include "DynamicResampler.h"
namespace mozilla {
/**
* ClockDrift calculates the diverge of the source clock from the nominal
* (provided) rate compared to the target clock, which is considered the master
* clock. In the case of different sampling rates, it is assumed that resampling
* will take place so the returned correction is estimated after the resampling.
* That means that resampling is taken into account in the calculations but it
* does appear in the correction. The correction must be applied to the top of
* the resampling.
*
* It works by measuring the incoming, the outgoing frames, and the amount of
* buffered data and estimates the correction needed. The correction logic has
* been created with two things in mind. First, not to run out of frames because
* that means the audio will glitch. Second, not to change the correction very
* often because this will result in a change in the resampling ratio. The
* resampler recreates its internal memory when the ratio changes which has a
* performance impact.
*
* The pref `media.clock drift.buffering` can be used to configure the desired
* internal buffering. Right now it is at 5ms. But it can be increased if there
* are audio quality problems.
*/
class ClockDrift final {
public:
/**
* Provide the nominal source and the target sample rate.
*/
ClockDrift(int32_t aSourceRate, int32_t aTargetRate)
: mSourceRate(aSourceRate),
mTargetRate(aTargetRate),
mDesiredBuffering(5 * mSourceRate / 100) {
if (Preferences::HasUserValue("media.clockdrift.buffering")) {
int msecs = Preferences::GetInt("media.clockdrift.buffering");
mDesiredBuffering = msecs * mSourceRate / 100;
}
}
/**
* The correction in the form of a ratio. A correction of 0.98 means that the
* target is 2% slower compared to the source or 1.03 which means that the
* target is 3% faster than the source.
*/
float GetCorrection() { return mCorrection; }
/**
* Update the available source frames, target frames, and the current
* buffering, in every iteration. If the condition are met a new correction is
* calculated. A new correction is calculated in the following cases:
* 1. Every 100 iterations which mean every 100 calls of this method.
* 2. Every time we run out of buffered frames (less than 2ms).
* In addition to that, the correction is clamped to 10% to avoid sound
* distortion so the result will be in [0.9, 1.1].
*/
void UpdateClock(int aSourceClock, int aTargetClock, int aBufferedFrames) {
if (mIterations == mAdjustementWindow) {
CalculateCorrection(aBufferedFrames);
} else if (aBufferedFrames < 2 * mSourceRate / 100 /*20ms*/) {
BufferedFramesCorrection(aBufferedFrames);
}
mTargetClock += aTargetClock;
mSourceClock += aSourceClock;
++mIterations;
}
private:
void CalculateCorrection(int aBufferedFrames) {
// We want to maintain 4 ms buffered
int32_t bufferedFramesDiff = aBufferedFrames - mDesiredBuffering;
int32_t resampledSourceClock = mSourceClock + bufferedFramesDiff;
if (mTargetRate != mSourceRate) {
resampledSourceClock =
resampledSourceClock *
(static_cast<float>(mTargetRate) / static_cast<float>(mSourceRate));
}
mCorrection = (float)mTargetClock / resampledSourceClock;
// Clamp to ragnge [0.9, 1.1] to avoid distortion
mCorrection = std::min(std::max(mCorrection, 0.9f), 1.1f);
// If previous correction slightly smaller (-1%) ignore it to avoid
// recalculations. Don't do it when is greater (+1%) to avoid risking
// running out of frames.
if (mPreviousCorrection - mCorrection <= 0.01 &&
mPreviousCorrection - mCorrection > 0) {
mCorrection = mPreviousCorrection;
}
mPreviousCorrection = mCorrection;
// Reset the counters to preper for the new period.
mIterations = 0;
mTargetClock = 0;
mSourceClock = 0;
}
void BufferedFramesCorrection(int aBufferedFrames) {
int32_t bufferedFramesDiff = aBufferedFrames - mDesiredBuffering;
int32_t resampledSourceClock = mSourceRate + bufferedFramesDiff;
if (mTargetRate != mSourceRate) {
resampledSourceClock = resampledSourceClock *
(static_cast<float>(mTargetRate) / mSourceRate);
}
MOZ_ASSERT(mTargetRate > resampledSourceClock);
mPreviousCorrection = mCorrection;
mCorrection +=
static_cast<float>(mTargetRate) / resampledSourceClock - 1.0f;
// Clamp to range [0.9, 1.1] to avoid distortion
mCorrection = std::min(std::max(mCorrection, 0.9f), 1.1f);
}
private:
const int32_t mSourceRate;
const int32_t mTargetRate;
float mCorrection = 1.0;
float mPreviousCorrection = 1.0;
const int32_t mAdjustementWindow = 100;
int32_t mDesiredBuffering; // defult: 5ms
int32_t mSourceClock = 0;
int32_t mTargetClock = 0;
int32_t mIterations = 0;
};
/**
* Correct the drift between two independent clocks, the source, and the target
* clock. The target clock is the master clock so the correction syncs the drift
* of the source clock to the target. The nominal sampling rates of source and
* target must be provided. If the source and the target operate in different
* sample rate the drift correction will be performed on the top of resampling
* from the source rate to the target rate.
*
* It works with AudioSegment in order to be able to be used from the
* MediaTrackGraph/MediaTrack. The audio buffers are pre-allocated so there is
* no new allocation takes place during operation. The preallocation capacity is
* 100ms for input and 100ms for output. The class consists of ClockDrift and
* AudioResampler check there for more details.
*
* The class is not thread-safe. The construction can happen in any thread but
* the member method must be used in a single thread that can be different than
* the construction thread. Appropriate for being used in the high priority
* audio thread.
*/
class AudioDriftCorrection final {
public:
AudioDriftCorrection(int32_t aSourceRate, int32_t aTargetRate)
: mClockDrift(aSourceRate, aTargetRate),
mResampler(aSourceRate, aTargetRate, aTargetRate / 20 /*50ms*/),
mTargetRate(aTargetRate) {}
/**
* The source audio frames and request the number of target audio frames must
* be provided. The duration of the source and the output is considered as the
* source clock and the target clock. The input is buffered internally so some
* latency exists. The returned AudioSegment must be cleaned up because the
* internal buffer will be reused after 100ms. If the drift correction (and
* possible resampling) is not possible due to lack of input data an empty
* AudioSegment will be returned. Not thread-safe.
*/
AudioSegment RequestFrames(const AudioSegment& aInput, int aOutputFrames) {
// Very important to go first since the Dynamic will get the sample format
// from the chunk.
if (aInput.GetDuration()) {
// Always go through the resampler because the clock might shift later.
mResampler.AppendInput(aInput);
}
mClockDrift.UpdateClock(aInput.GetDuration(), aOutputFrames,
mResampler.InputDuration());
TrackRate receivingRate = mTargetRate * mClockDrift.GetCorrection();
// Update resampler's rate if there is a new correction.
mResampler.UpdateOutRate(receivingRate);
// If it does not have enough frames the result will be an empty segment.
return mResampler.Resample(aOutputFrames);
}
private:
ClockDrift mClockDrift;
AudioResampler mResampler;
const int32_t mTargetRate;
};
}; // namespace mozilla
#endif /* MOZILLA_AUDIO_DRIFT_CORRECTION_H_ */

Просмотреть файл

@ -1,470 +0,0 @@
/* -*- 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

Просмотреть файл

@ -1,114 +0,0 @@
/* -*- 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_

Просмотреть файл

@ -4,7 +4,6 @@
* 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"

Просмотреть файл

@ -1,461 +0,0 @@
/* -*- 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 "DynamicResampler.h"
namespace mozilla {
DynamicResampler::DynamicResampler(int aInRate, int aOutRate,
uint32_t aPreBufferFrames)
: mInRate(aInRate), mOutRate(aOutRate), mPreBufferFrames(aPreBufferFrames) {
MOZ_ASSERT(aInRate);
MOZ_ASSERT(aOutRate);
UpdateResampler(mOutRate, STEREO);
}
DynamicResampler::~DynamicResampler() {
if (mResampler) {
speex_resampler_destroy(mResampler);
}
}
void DynamicResampler::SetSampleFormat(AudioSampleFormat aFormat) {
MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_SILENCE);
MOZ_ASSERT(aFormat == AUDIO_FORMAT_S16 || aFormat == AUDIO_FORMAT_FLOAT32);
mSampleFormat = aFormat;
for (AudioRingBuffer& b : mInternalInBuffer) {
b.SetSampleFormat(mSampleFormat);
}
if (mPreBufferFrames) {
AppendInputSilence(mPreBufferFrames);
}
}
bool DynamicResampler::Resample(float* aOutBuffer, uint32_t* aOutFrames,
int aChannelIndex) {
MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_FLOAT32);
return ResampleInternal(aOutBuffer, aOutFrames, aChannelIndex);
}
bool DynamicResampler::Resample(int16_t* aOutBuffer, uint32_t* aOutFrames,
int aChannelIndex) {
MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_S16);
return ResampleInternal(aOutBuffer, aOutFrames, aChannelIndex);
}
void DynamicResampler::ResampleInternal(const float* aInBuffer,
uint32_t* aInFrames, float* aOutBuffer,
uint32_t* aOutFrames,
int aChannelIndex) {
MOZ_ASSERT(mResampler);
MOZ_ASSERT(mChannels);
MOZ_ASSERT(mInRate);
MOZ_ASSERT(mOutRate);
MOZ_ASSERT(aInBuffer);
MOZ_ASSERT(aInFrames);
MOZ_ASSERT(*aInFrames > 0);
MOZ_ASSERT(aOutBuffer);
MOZ_ASSERT(aOutFrames);
MOZ_ASSERT(*aOutFrames > 0);
MOZ_ASSERT(aChannelIndex >= 0);
MOZ_ASSERT(aChannelIndex <= mChannels);
#ifdef DEBUG
int rv =
#endif
speex_resampler_process_float(mResampler, aChannelIndex, aInBuffer,
aInFrames, aOutBuffer, aOutFrames);
MOZ_ASSERT(rv == RESAMPLER_ERR_SUCCESS);
}
void DynamicResampler::ResampleInternal(const int16_t* aInBuffer,
uint32_t* aInFrames,
int16_t* aOutBuffer,
uint32_t* aOutFrames,
int aChannelIndex) {
MOZ_ASSERT(mResampler);
MOZ_ASSERT(mChannels);
MOZ_ASSERT(mInRate);
MOZ_ASSERT(mOutRate);
MOZ_ASSERT(aInBuffer);
MOZ_ASSERT(aInFrames);
MOZ_ASSERT(*aInFrames > 0);
MOZ_ASSERT(aOutBuffer);
MOZ_ASSERT(aOutFrames);
MOZ_ASSERT(*aOutFrames > 0);
MOZ_ASSERT(aChannelIndex >= 0);
MOZ_ASSERT(aChannelIndex <= mChannels);
#ifdef DEBUG
int rv =
#endif
speex_resampler_process_int(mResampler, aChannelIndex, aInBuffer,
aInFrames, aOutBuffer, aOutFrames);
MOZ_ASSERT(rv == RESAMPLER_ERR_SUCCESS);
}
void DynamicResampler::UpdateResampler(int aOutRate, int aChannels) {
MOZ_ASSERT(aOutRate);
MOZ_ASSERT(aChannels);
if (mChannels != aChannels) {
mResampler = speex_resampler_init(aChannels, mInRate, aOutRate,
SPEEX_RESAMPLER_QUALITY_MIN, nullptr);
MOZ_ASSERT(mResampler);
mChannels = aChannels;
mOutRate = aOutRate;
// Between mono and stereo changes, keep always allocated 2 channels to
// avoid reallocations in the most common case.
if ((mChannels == STEREO || mChannels == 1) &&
mInternalInBuffer.Length() == STEREO) {
// Don't worry if format is not set it will write silence then.
if ((mSampleFormat == AUDIO_FORMAT_S16 ||
mSampleFormat == AUDIO_FORMAT_FLOAT32) &&
mChannels == STEREO) {
// The mono channel is allways up to date. When we are going from mono
// to stereo upmix the mono to stereo channel
int bufferedDuration = mInternalInBuffer[0].AvailableRead();
mInternalInBuffer[1].Clear();
if (bufferedDuration) {
mInternalInBuffer[1].Write(mInternalInBuffer[0], bufferedDuration);
}
}
// Maintain stereo size
mInputTail.SetLength(STEREO);
WarmUpResampler(false);
return;
}
// upmix or downmix, for now just clear but it has to be updated
// because allocates and this is executed in audio thread.
mInternalInBuffer.Clear();
for (int i = 0; i < mChannels; ++i) {
// Pre-allocate something big, 100ms of audio.
AudioRingBuffer* b =
mInternalInBuffer.AppendElement(sizeof(float) * mInRate / 10);
if (mSampleFormat != AUDIO_FORMAT_SILENCE) {
// In ctor this update is not needed
b->SetSampleFormat(mSampleFormat);
}
}
mInputTail.SetLength(mChannels);
return;
}
if (mOutRate != aOutRate) {
// If the rates was the same the resampler was not being used so warm up.
if (mOutRate == mInRate) {
WarmUpResampler(true);
}
#ifdef DEBUG
int rv =
#endif
speex_resampler_set_rate(mResampler, mInRate, aOutRate);
MOZ_ASSERT(rv == RESAMPLER_ERR_SUCCESS);
mOutRate = aOutRate;
}
}
void DynamicResampler::WarmUpResampler(bool aSkipLatency) {
MOZ_ASSERT(mInputTail.Length());
for (int i = 0; i < mChannels; ++i) {
if (!mInputTail[i].Length()) {
continue;
}
uint32_t inFrames = mInputTail[i].Length();
uint32_t outFrames = 5 * TailBuffer::MAXSIZE; // something big
if (mSampleFormat == AUDIO_FORMAT_S16) {
short outBuffer[5 * TailBuffer::MAXSIZE] = {};
ResampleInternal(mInputTail[i].Buffer<short>(), &inFrames, outBuffer,
&outFrames, i);
MOZ_ASSERT(inFrames == (uint32_t)mInputTail[i].Length());
} else {
float outBuffer[100] = {};
ResampleInternal(mInputTail[i].Buffer<float>(), &inFrames, outBuffer,
&outFrames, i);
MOZ_ASSERT(inFrames == (uint32_t)mInputTail[i].Length());
}
}
if (aSkipLatency) {
int inputLatency = speex_resampler_get_input_latency(mResampler);
MOZ_ASSERT(inputLatency > 0);
uint32_t ratioNum, ratioDen;
speex_resampler_get_ratio(mResampler, &ratioNum, &ratioDen);
// Ratio at this point is one so only skip the input latency. No special
// calculations are needed.
speex_resampler_set_skip_frac_num(mResampler, inputLatency * ratioDen);
}
}
void DynamicResampler::AppendInput(const nsTArray<const float*>& aInBuffer,
uint32_t aInFrames) {
MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_FLOAT32);
AppendInputInternal(aInBuffer, aInFrames);
}
void DynamicResampler::AppendInput(const nsTArray<const int16_t*>& aInBuffer,
uint32_t aInFrames) {
MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_S16);
AppendInputInternal(aInBuffer, aInFrames);
}
bool DynamicResampler::EnoughInFrames(uint32_t aOutFrames,
int aChannelIndex) const {
if (mInRate == mOutRate) {
return InFramesBuffered(aChannelIndex) >= aOutFrames;
}
if (!(mOutRate % mInRate) && !(aOutFrames % mOutRate / mInRate)) {
return InFramesBuffered(aChannelIndex) >= aOutFrames / (mOutRate / mInRate);
}
if (!(mInRate % mOutRate) && !(aOutFrames % mOutRate / mInRate)) {
return InFramesBuffered(aChannelIndex) >= aOutFrames * mInRate / mOutRate;
}
return InFramesBuffered(aChannelIndex) > aOutFrames * mInRate / mOutRate;
}
bool DynamicResampler::CanResample(uint32_t aOutFrames) const {
for (int i = 0; i < mChannels; ++i) {
if (!EnoughInFrames(aOutFrames, i)) {
return false;
}
}
return true;
}
void DynamicResampler::AppendInputSilence(const uint32_t aInFrames) {
MOZ_ASSERT(aInFrames);
MOZ_ASSERT(mChannels);
MOZ_ASSERT(mInternalInBuffer.Length() >= (uint32_t)mChannels);
for (int i = 0; i < mChannels; ++i) {
mInternalInBuffer[i].WriteSilence(aInFrames);
}
}
uint32_t DynamicResampler::InFramesBuffered(int aChannelIndex) const {
MOZ_ASSERT(mChannels);
MOZ_ASSERT(aChannelIndex >= 0);
MOZ_ASSERT(aChannelIndex <= mChannels);
MOZ_ASSERT((uint32_t)aChannelIndex <= mInternalInBuffer.Length());
return mInternalInBuffer[aChannelIndex].AvailableRead();
}
AudioChunkList::AudioChunkList(int aTotalDuration, int aChannels) {
int numOfChunks = aTotalDuration / mChunkCapacity;
if (aTotalDuration % mChunkCapacity) {
++numOfChunks;
}
CreateChunks(numOfChunks, aChannels);
}
void AudioChunkList::CreateChunks(int aNumOfChunks, int aChannels) {
MOZ_ASSERT(!mChunks.Length());
MOZ_ASSERT(aNumOfChunks);
MOZ_ASSERT(aChannels);
mChunks.AppendElements(aNumOfChunks);
for (AudioChunk& chunk : mChunks) {
AutoTArray<nsTArray<float>, STEREO> buffer;
buffer.AppendElements(aChannels);
AutoTArray<const float*, STEREO> bufferPtrs;
bufferPtrs.AppendElements(aChannels);
for (int i = 0; i < aChannels; ++i) {
float* ptr = buffer[i].AppendElements(mChunkCapacity);
bufferPtrs[i] = ptr;
}
chunk.mBuffer = new mozilla::SharedChannelArrayBuffer(&buffer);
chunk.mChannelData.AppendElements(aChannels);
for (int i = 0; i < aChannels; ++i) {
chunk.mChannelData[i] = bufferPtrs[i];
}
}
}
void AudioChunkList::UpdateToMonoOrStereo(int aChannels) {
MOZ_ASSERT(mChunks.Length());
MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_S16 ||
mSampleFormat == AUDIO_FORMAT_FLOAT32);
MOZ_ASSERT(aChannels == 1 || aChannels == 2);
for (AudioChunk& chunk : mChunks) {
MOZ_ASSERT(chunk.ChannelCount() != (uint32_t)aChannels);
MOZ_ASSERT(chunk.ChannelCount() == 1 || chunk.ChannelCount() == 2);
chunk.mChannelData.SetLengthAndRetainStorage(aChannels);
if (mSampleFormat == AUDIO_FORMAT_S16) {
SharedChannelArrayBuffer<short>* channelArray =
static_cast<SharedChannelArrayBuffer<short>*>(chunk.mBuffer.get());
channelArray->mBuffers.SetLengthAndRetainStorage(aChannels);
if (aChannels == 2) {
// This an indirect allocation, unfortunately.
channelArray->mBuffers[1].SetLength(mChunkCapacity);
chunk.mChannelData[1] = channelArray->mBuffers[1].Elements();
}
} else {
SharedChannelArrayBuffer<float>* channelArray =
static_cast<SharedChannelArrayBuffer<float>*>(chunk.mBuffer.get());
channelArray->mBuffers.SetLengthAndRetainStorage(aChannels);
if (aChannels == 2) {
// This an indirect allocation, unfortunately.
channelArray->mBuffers[1].SetLength(mChunkCapacity);
chunk.mChannelData[1] = channelArray->mBuffers[1].Elements();
}
}
}
}
void AudioChunkList::SetSampleFormat(AudioSampleFormat aFormat) {
MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_SILENCE);
MOZ_ASSERT(aFormat == AUDIO_FORMAT_S16 || aFormat == AUDIO_FORMAT_FLOAT32);
mSampleFormat = aFormat;
if (mSampleFormat == AUDIO_FORMAT_S16) {
mChunkCapacity = 2 * mChunkCapacity;
}
}
AudioChunk& AudioChunkList::GetNext() {
AudioChunk& chunk = mChunks[mIndex];
MOZ_ASSERT(!chunk.mChannelData.IsEmpty());
MOZ_ASSERT(chunk.mBuffer);
MOZ_ASSERT(!chunk.mBuffer->IsShared());
MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_S16 ||
mSampleFormat == AUDIO_FORMAT_FLOAT32);
chunk.mDuration = 0;
chunk.mVolume = 1.0f;
chunk.mPrincipalHandle = PRINCIPAL_HANDLE_NONE;
chunk.mBufferFormat = mSampleFormat;
IncrementIndex();
return chunk;
}
void AudioChunkList::Update(int aChannels) {
MOZ_ASSERT(mChunks.Length());
if (mChunks[0].ChannelCount() == (uint32_t)aChannels) {
return;
}
// Special handling between mono and stereo to avoid reallocations.
if (aChannels <= 2 && mChunks[0].ChannelCount() <= 2) {
UpdateToMonoOrStereo(aChannels);
return;
}
int numOfChunks = static_cast<int>(mChunks.Length());
mChunks.ClearAndRetainStorage();
CreateChunks(numOfChunks, aChannels);
}
AudioResampler::AudioResampler(int aInRate, int aOutRate,
uint32_t aPreBufferFrames)
: mResampler(aInRate, aOutRate, aPreBufferFrames),
mOutputChunks(aOutRate / 10, STEREO) {}
void AudioResampler::AppendInput(const AudioSegment& aInSegment) {
MOZ_ASSERT(aInSegment.GetDuration());
for (AudioSegment::ConstChunkIterator iter(aInSegment); !iter.IsEnded();
iter.Next()) {
const AudioChunk& chunk = *iter;
if (!mIsSampleFormatSet) {
// We don't know the format yet and all buffers are empty.
if (chunk.mBufferFormat == AUDIO_FORMAT_SILENCE) {
// Only silence has been received and the format is unkown. Igonre it,
// if Resampler() is called it will return silence too.
continue;
}
// First no silence data, set the format once for lifetime and let it
// continue the rest of the flow. We will not get in here again.
mOutputChunks.SetSampleFormat(chunk.mBufferFormat);
mResampler.SetSampleFormat(chunk.mBufferFormat);
mIsSampleFormatSet = true;
}
MOZ_ASSERT(mIsSampleFormatSet);
if (chunk.IsNull()) {
mResampler.AppendInputSilence(chunk.GetDuration());
continue;
}
// Make sure the channel is up to date. An AudioSegment can contain chunks
// with different channel count.
UpdateChannels(chunk.mChannelData.Length());
if (chunk.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
mResampler.AppendInput(chunk.ChannelData<float>(), chunk.GetDuration());
} else {
mResampler.AppendInput(chunk.ChannelData<int16_t>(), chunk.GetDuration());
}
}
}
AudioSegment AudioResampler::Resample(uint32_t aOutFrames) {
MOZ_ASSERT(aOutFrames);
AudioSegment segment;
// We don't know what to do yet and we only have received silence if any just
// return what they want and leave
if (!mIsSampleFormatSet) {
segment.AppendNullData(aOutFrames);
return segment;
}
// Not enough input frames abort
if (!mResampler.CanResample(aOutFrames)) {
return segment;
}
int totalFrames = aOutFrames;
while (totalFrames) {
MOZ_ASSERT(totalFrames > 0);
AudioChunk& chunk = mOutputChunks.GetNext();
int outFrames = std::min(totalFrames, mOutputChunks.ChunkCapacity());
totalFrames -= outFrames;
for (uint32_t i = 0; i < chunk.ChannelCount(); ++i) {
uint32_t outFramesUsed = outFrames;
if (chunk.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
#ifdef DEBUG
bool rv =
#endif
mResampler.Resample(chunk.ChannelDataForWrite<float>(i),
&outFramesUsed, i);
MOZ_ASSERT(rv);
} else {
#ifdef DEBUG
bool rv =
#endif
mResampler.Resample(chunk.ChannelDataForWrite<int16_t>(i),
&outFramesUsed, i);
MOZ_ASSERT(rv);
}
MOZ_ASSERT(outFramesUsed == (uint32_t)outFrames);
chunk.mDuration = outFrames;
}
// Create a copy in order to consume that copy and not the pre-allocated
// chunk
AudioChunk tmp = chunk;
segment.AppendAndConsumeChunk(&tmp);
}
return segment;
}
void AudioResampler::Update(int aOutRate, int aChannels) {
mResampler.UpdateResampler(aOutRate, aChannels);
mOutputChunks.Update(aChannels);
}
int AudioResampler::InputDuration() const {
if (!mIsSampleFormatSet) {
return 0;
}
MOZ_ASSERT((int)mResampler.InFramesBuffered(0) >= 0);
return (int)mResampler.InFramesBuffered(0);
}
} // namespace mozilla

Просмотреть файл

@ -1,393 +0,0 @@
/* -*- 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_DYNAMIC_RESAMPLER_H_
#define MOZILLA_DYNAMIC_RESAMPLER_H_
#include "AudioRingBuffer.h"
#include "AudioSegment.h"
#include <speex/speex_resampler.h>
namespace mozilla {
const int STEREO = 2;
/**
* DynamicResampler allows updating on the fly the output sample rate and the
* number of channels. In addition to that, it maintains an internal buffer for
* the input data and allows pre-buffering as well. The Resample() method
* strives to provide the requested number of output frames by using the input
* data including any pre-buffering. If this is not possible then it will not
* attempt to resample and it will return failure.
*
* Input data buffering makes use of the AudioRingBuffer. The capacity of the
* buffer is 100ms of float audio and it is pre-allocated at the constructor.
* No extra allocations take place when the input is appended. In addition to
* that, due to special feature of AudioRingBuffer, no extra copies take place
* when the input data is fed to the resampler.
*
* The sample format must be set before using any method. If the provided sample
* format is of type short the pre-allocated capacity of the input buffer
* becomes 200ms of short audio.
*
* The DynamicResampler is not thread-safe, so all the methods appart from the
* constructor must be called on the same thread.
*/
class DynamicResampler final {
public:
/**
* Provide the initial input and output rate and the amount of pre-buffering.
* The channel count will be set to stereo. Memory allocation will take
* place. The input buffer is non-interleaved.
*/
DynamicResampler(int aInRate, int aOutRate, uint32_t aPreBufferFrames = 0);
~DynamicResampler();
/**
* Set the sample format type to float or short.
*/
void SetSampleFormat(AudioSampleFormat aFormat);
int GetOutRate() const { return mOutRate; }
int GetChannels() const { return mChannels; }
/**
* Append `aInFrames` number of frames from `aInBuffer` to the internal input
* buffer. Memory copy/move takes place.
*/
void AppendInput(const nsTArray<const float*>& aInBuffer, uint32_t aInFrames);
void AppendInput(const nsTArray<const int16_t*>& aInBuffer,
uint32_t aInFrames);
/**
* Append `aInFrames` number of frames of silence to the internal input
* buffer. Memory copy/move takes place.
*/
void AppendInputSilence(const uint32_t aInFrames);
/**
* Return the number of frames stored in the internal input buffer.
*/
uint32_t InFramesBuffered(int aChannelIndex) const;
/*
* Resampler as much frame is needed from the internal input buffer to the
* `aOutBuffer` in order to provide all `aOutFrames` and return true. If there
* not enough input frames to provide the requested output frames no
* resampling is attempted and false is returned.
*/
bool Resample(float* aOutBuffer, uint32_t* aOutFrames, int aChannelIndex);
bool Resample(int16_t* aOutBuffer, uint32_t* aOutFrames, int aChannelIndex);
/**
* Update the output rate or/and the channel count. If a value is not updated
* compared to the current one nothing happens. Changing the `aOutRate`
* results in recalculation in the resampler. Changing `aChannels` results in
* the reallocation of the internal input buffer with the exception of
* changes between mono to stereo and vice versa where no reallocation takes
* place. A stereo internal input buffer is always maintained even if the
* sound is mono.
*/
void UpdateResampler(int aOutRate, int aChannels);
/**
* Returns true if the resampler has enough input data to provide to the
* output of the `Resample()` method `aOutFrames` number of frames. This is a
* way to know in advance if the `Resampler` method will return true or false
* given that nothing changes in between.
*/
bool CanResample(uint32_t aOutFrames) const;
private:
template <typename T>
void AppendInputInternal(const nsTArray<const T*>& aInBuffer,
uint32_t aInFrames) {
MOZ_ASSERT(aInBuffer.Length() == (uint32_t)mChannels);
for (int i = 0; i < mChannels; ++i) {
PushInFrames(aInBuffer[i], aInFrames, i);
}
}
void ResampleInternal(const float* aInBuffer, uint32_t* aInFrames,
float* aOutBuffer, uint32_t* aOutFrames,
int aChannelIndex);
void ResampleInternal(const int16_t* aInBuffer, uint32_t* aInFrames,
int16_t* aOutBuffer, uint32_t* aOutFrames,
int aChannelIndex);
template <typename T>
bool ResampleInternal(T* aOutBuffer, uint32_t* aOutFrames,
int aChannelIndex) {
MOZ_ASSERT(mInRate);
MOZ_ASSERT(mOutRate);
MOZ_ASSERT(mChannels);
MOZ_ASSERT(aChannelIndex >= 0);
MOZ_ASSERT(aChannelIndex <= mChannels);
MOZ_ASSERT((uint32_t)aChannelIndex <= mInternalInBuffer.Length());
MOZ_ASSERT(aOutFrames);
MOZ_ASSERT(*aOutFrames);
// Not enough input, don't do anything
if (!EnoughInFrames(*aOutFrames, aChannelIndex)) {
*aOutFrames = 0;
return false;
}
if (mInRate == mOutRate) {
mInternalInBuffer[aChannelIndex].Read(MakeSpan(aOutBuffer, *aOutFrames));
// Workaround to avoid discontinuity when the speex resampler operates
// again. Feed it with the last 20 frames to warm up the internal memory
// of the resampler and then skip memory equals to resampler's input
// latency.
mInputTail[aChannelIndex].StoreTail<T>(aOutBuffer, *aOutFrames);
return true;
}
uint32_t totalOutFramesNeeded = *aOutFrames;
mInternalInBuffer[aChannelIndex].ReadNoCopy(
[this, &aOutBuffer, &totalOutFramesNeeded,
aChannelIndex](const Span<const T>& aInBuffer) -> int {
if (!totalOutFramesNeeded) {
return 0;
}
uint32_t outFramesResampled = totalOutFramesNeeded;
uint32_t inFrames = aInBuffer.Length();
ResampleInternal(aInBuffer.data(), &inFrames, aOutBuffer,
&outFramesResampled, aChannelIndex);
aOutBuffer += outFramesResampled;
totalOutFramesNeeded -= outFramesResampled;
mInputTail[aChannelIndex].StoreTail<T>(aInBuffer);
return inFrames;
});
MOZ_ASSERT(totalOutFramesNeeded == 0);
return true;
}
bool EnoughInFrames(uint32_t aOutFrames, int aChannelIndex) const;
template <typename T>
void PushInFrames(const T* aInBuffer, const uint32_t aInFrames,
int aChannelIndex) {
MOZ_ASSERT(aInBuffer);
MOZ_ASSERT(aInFrames);
MOZ_ASSERT(mChannels);
MOZ_ASSERT(aChannelIndex >= 0);
MOZ_ASSERT(aChannelIndex <= mChannels);
MOZ_ASSERT((uint32_t)aChannelIndex <= mInternalInBuffer.Length());
mInternalInBuffer[aChannelIndex].Write(MakeSpan(aInBuffer, aInFrames));
}
void WarmUpResampler(bool aSkipLatency);
private:
int mChannels = 0;
const int mInRate;
int mOutRate;
AutoTArray<AudioRingBuffer, STEREO> mInternalInBuffer;
SpeexResamplerState* mResampler = nullptr;
AudioSampleFormat mSampleFormat = AUDIO_FORMAT_SILENCE;
const uint32_t mPreBufferFrames;
class TailBuffer {
public:
template <typename T>
T* Buffer() {
return reinterpret_cast<T*>(mBuffer);
}
/* Store the MAXSIZE last elements of the buffer. */
template <typename T>
void StoreTail(const Span<const T>& aInBuffer) {
StoreTail(aInBuffer.data(), aInBuffer.size());
}
template <typename T>
void StoreTail(const T* aInBuffer, uint32_t aInFrames) {
if (aInFrames >= MAXSIZE) {
PodCopy(Buffer<T>(), aInBuffer + aInFrames - MAXSIZE, MAXSIZE);
mSize = MAXSIZE;
} else {
PodCopy(Buffer<T>(), aInBuffer, aInFrames);
mSize = static_cast<int>(aInFrames);
}
}
int Length() { return mSize; }
static const int MAXSIZE = 20;
private:
float mBuffer[MAXSIZE] = {};
int mSize = 0;
};
AutoTArray<TailBuffer, STEREO> mInputTail;
};
/**
* AudioChunkList provides a way to have preallocated audio buffers in
* AudioSegment. The idea is that the amount of AudioChunks is created in
* advance. Each AudioChunk is able to hold a specific amount of audio
* (capacity). The total capacity of AudioChunkList is specified by the number
* of AudioChunks. The important aspect of the AudioChunkList is that
* preallocates everything and reuse the same chunks similar to a ring buffer.
*
* Why the whole AudioChunk is preallocated and not some raw memory buffer? This
* is due to the limitations of MediaTrackGraph. The way that MTG works depends
* on `AudioSegment`s to convey the actual audio data. An AudioSegment consists
* of AudioChunks. The AudioChunk is built in a way, that owns and allocates the
* audio buffers. Thus, since the use of AudioSegment is mandatory if the audio
* data was in a different form, the only way to use it from the audio thread
* would be to create the AudioChunk there. That would result in a copy
* operation (not very important) and most of all an allocation of the audio
* buffer in the audio thread. This happens in many places inside MTG it's a bad
* practice, though, and it has been avoided due to the AudioChunkList.
*
* After construction the sample format must be set, when it is available. It
* can be set in the audio thread. Before setting the sample format is not
* possible to use any method of AudioChunkList.
*
* Every AudioChunk in the AudioChunkList is preallocated with a capacity of 128
* frames of float audio. Nevertheless, the sample format is not available at
* that point. Thus if the sample format is set to short, the capacity of each
* chunk changes to 256 number of frames, and the total duration becomes twice
* big. There are methods to get the chunk capacity and total capacity in frames
* and must always be used.
*
* Two things to note. First, when the channel count changes everything is
* recreated which means reallocations. Second, the total capacity might differs
* from the requested total capacity for two reasons. First, if the sample
* format is set to short and second because the number of chunks in the list
* divides exactly the final total capacity. The corresponding method must
* always be used to query the total capacity.
*/
class AudioChunkList {
public:
/**
* Constructor, the final total duration might be different from the requested
* `aTotalDuration`. Memory allocation takes place.
*/
AudioChunkList(int aTotalDuration, int aChannels);
AudioChunkList(const AudioChunkList&) = delete;
AudioChunkList(AudioChunkList&&) = delete;
~AudioChunkList() = default;
/**
* Set sample format. It must be done before any other method being used.
*/
void SetSampleFormat(AudioSampleFormat aFormat);
/**
* Get the next available AudioChunk. The duration of the chunk will be zero
* and the volume 1.0. However, the buffers will be there ready to be written.
* Please note, that a reference of the preallocated chunk is returned. Thus
* it _must not be consumed_ directly. If the chunk needs to be consumed it
* must be copied to a temporary chunk first. For example:
* ```
* AudioChunk& chunk = audioChunklist.GetNext();
* // Set up the chunk
* AudioChunk tmp = chunk;
* audioSegment.AppendAndConsumeChunk(&tmp);
* ```
* This way no memory allocation or copy, takes place.
*/
AudioChunk& GetNext();
/**
* Get the capacity of each individual AudioChunk in the list.
*/
int ChunkCapacity() const {
MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_S16 ||
mSampleFormat == AUDIO_FORMAT_FLOAT32);
return mChunkCapacity;
}
/**
* Get the total capacity of AudioChunkList.
*/
int TotalCapacity() const {
MOZ_ASSERT(mSampleFormat == AUDIO_FORMAT_S16 ||
mSampleFormat == AUDIO_FORMAT_FLOAT32);
return CheckedInt<int>(mChunkCapacity * mChunks.Length()).value();
}
/**
* Update the channel count of the AudioChunkList. Memory allocation is
* taking place.
*/
void Update(int aChannels);
private:
void IncrementIndex() {
++mIndex;
mIndex = CheckedInt<int>(mIndex % mChunks.Length()).value();
}
void CreateChunks(int aNumOfChunks, int aChannels);
void UpdateToMonoOrStereo(int aChannels);
private:
nsTArray<AudioChunk> mChunks;
int mIndex = 0;
int mChunkCapacity = 128;
AudioSampleFormat mSampleFormat = AUDIO_FORMAT_SILENCE;
};
/**
* Audio Resampler is a resampler able to change the output rate and channels
* count on the fly. The API is simple and it is based in AudioSegment in order
* to be used MTG. All memory allocations, for input and output buffers, happen
* in the constructor and when channel count changes. The memory is recycled in
* order to avoid reallocations. It also supports prebuffering of silence. It
* consists of DynamicResampler and AudioChunkList so please read their
* documentation if you are interested in more details.
*
* The output buffer is preallocated and returned in the form of AudioSegment.
* The intention is to be used directly in a MediaTrack. Since an AudioChunk
* must no be "shared" in order to be written, the AudioSegment returned by
* resampler method must be cleaned up in order to be able for the `AudioChunk`s
* that it consists of to be reused. For `MediaTrack::mSegment` this happens
* every ~50ms (look at MediaTrack::AdvanceTimeVaryingValuesToCurrentTime). Thus
* memory capacity of 100ms has been preallocated for internal input and output
* buffering.
*/
class AudioResampler final {
public:
AudioResampler(int aInRate, int aOutRate, uint32_t aPreBufferFrames = 0);
/**
* Append input data into the resampler internal buffer. Copy/move of the
* memory is taking place. Also, the channel count will change according to
* the channel count of the chunks.
*/
void AppendInput(const AudioSegment& aInSegment);
/*
* Get the duration of internal input buffer in frames.
*/
int InputDuration() const;
/*
* Reguest `aOutFrames` of audio in the output sample rate. The internal
* buffered input os used. If there is no enough input for that amount of
* output and empty AudioSegment is returned
*/
AudioSegment Resample(uint32_t aOutFrames);
/*
* Updates the output rate that will be used by the resampler.
*/
void UpdateOutRate(int aOutRate) {
Update(aOutRate, mResampler.GetChannels());
}
private:
void UpdateChannels(int aChannels) {
Update(mResampler.GetOutRate(), aChannels);
}
void Update(int aOutRate, int aChannels);
private:
DynamicResampler mResampler;
AudioChunkList mOutputChunks;
bool mIsSampleFormatSet = false;
};
} // namespace mozilla
#endif // MOZILLA_DYNAMIC_RESAMPLER_H_

Просмотреть файл

@ -4,9 +4,6 @@
* 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"
@ -16,7 +13,6 @@
#include "mozilla/Unused.h"
#include "mozilla/MathAlgorithms.h"
#include "CubebDeviceEnumerator.h"
#include "MediaTrackGraphImpl.h"
#include "Tracing.h"
#ifdef MOZ_WEBRTC
@ -1199,7 +1195,7 @@ void AudioCallbackDriver::CompleteAudioContextOperations(
s.mOperation == dom::AudioContextOperation::Resume) ||
(aOperation == AsyncCubebOperation::SHUTDOWN &&
s.mOperation != dom::AudioContextOperation::Resume)) {
dom::AudioContextState state;
AudioContextState state;
switch (s.mOperation) {
case dom::AudioContextOperation::Resume:
state = dom::AudioContextState::Running;

Просмотреть файл

@ -10,7 +10,6 @@
#include <mozilla/Sprintf.h>
#include <mozilla/Atomics.h>
#include "audio_thread_priority.h"
#include "nsDebug.h"
namespace mozilla {

Просмотреть файл

@ -1,332 +0,0 @@
/* -*- 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 "AudioDriftCorrection.h"
#include "gmock/gmock.h"
#include "gtest/gtest-printers.h"
#include "gtest/gtest.h"
TEST(TestClockDrift, Basic)
{
ClockDrift c(48000, 48000);
EXPECT_EQ(c.GetCorrection(), 1.0);
// Keep buffered frames to the wanted level in order to not affect that test.
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480, 480 + 48, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.1);
}
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480 + 48, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
c.UpdateClock(0, 0, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 0.90909094);
}
TEST(TestClockDrift, BasicResampler)
{
ClockDrift c(24000, 48000);
for (int i = 0; i < 100; ++i) {
c.UpdateClock(240, 480, 5 * 240);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
// Keep buffered frames to the wanted level in order to not affect that test.
for (int i = 0; i < 100; ++i) {
c.UpdateClock(240, 480 + 48, 5 * 240); // +10%
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
for (int i = 0; i < 100; ++i) {
c.UpdateClock(240 + 24, 480, 5 * 240); // +10%
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.1);
}
for (int i = 0; i < 100; ++i) {
c.UpdateClock(240, 480 - 48, 5 * 240); // -10%
EXPECT_FLOAT_EQ(c.GetCorrection(), 0.90909094);
}
for (int i = 0; i < 100; ++i) {
c.UpdateClock(240 + 12, 480 - 24, 5 * 240); //-5%, -5%
EXPECT_FLOAT_EQ(c.GetCorrection(), 0.90909094);
}
c.UpdateClock(0, 0, 5 * 240);
EXPECT_FLOAT_EQ(c.GetCorrection(), 0.90909094);
}
TEST(TestClockDrift, BufferedInput)
{
ClockDrift c(48000, 48000);
EXPECT_EQ(c.GetCorrection(), 1.0);
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
c.UpdateClock(480, 480, 0); // 0 buffered on 100th iteration
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
for (int i = 0; i < 99; ++i) {
c.UpdateClock(480, 480, 2 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
}
c.UpdateClock(480, 480, 2 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0309278);
for (int i = 0; i < 99; ++i) {
c.UpdateClock(480, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0309278);
}
c.UpdateClock(480, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
for (int i = 0; i < 99; ++i) {
c.UpdateClock(480, 480, 7 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
c.UpdateClock(480, 480, 7 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 0.980392);
}
TEST(TestClockDrift, BufferedInputWithResampling)
{
ClockDrift c(24000, 48000);
EXPECT_EQ(c.GetCorrection(), 1.0);
for (int i = 0; i < 100; ++i) {
c.UpdateClock(240, 480, 5 * 240);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
c.UpdateClock(240, 480, 0); // 0 buffered on 100th iteration
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
for (int i = 0; i < 99; ++i) {
c.UpdateClock(240, 480, 2 * 240);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
}
c.UpdateClock(240, 480, 2 * 240);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0309278);
for (int i = 0; i < 99; ++i) {
c.UpdateClock(240, 480, 5 * 240);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0309278);
}
c.UpdateClock(240, 480, 5 * 240);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
for (int i = 0; i < 99; ++i) {
c.UpdateClock(240, 480, 7 * 240);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
c.UpdateClock(240, 480, 7 * 240);
EXPECT_FLOAT_EQ(c.GetCorrection(), 0.980392);
}
TEST(TestClockDrift, Clamp)
{
ClockDrift c(48000, 48000);
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480, 480 + 2 * 48, 5 * 480); // +20%
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480, 480 - 2 * 48, 5 * 480); // -20%
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.1);
}
c.UpdateClock(0, 0, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 0.9);
}
TEST(TestClockDrift, SmallDiff)
{
ClockDrift c(48000, 48000);
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480 + 4, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480 + 5, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 0.98969072);
}
// Reset to 1.0 again
for (int i = 0; i < 100; ++i) {
c.UpdateClock(480, 480 + 4, 5 * 480); // +0.83%
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
}
c.UpdateClock(0, 0, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0083333);
}
TEST(TestClockDrift, SmallBufferedFrames)
{
ClockDrift c(48000, 48000);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
c.UpdateClock(480, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0);
c.UpdateClock(480, 480, 0);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
for (int i = 0; i < 50; ++i) {
c.UpdateClock(480, 480, 5 * 480);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.0526316);
}
c.UpdateClock(480, 480, 0);
EXPECT_FLOAT_EQ(c.GetCorrection(), 1.1);
}
void printAudioSegment(const AudioSegment& segment) {
for (AudioSegment::ConstChunkIterator iter(segment); !iter.IsEnded();
iter.Next()) {
const AudioChunk& c = *iter;
const float* buffer = c.ChannelData<float>()[0];
for (int i = 0; i < c.GetDuration(); ++i) {
printf("%f\n", buffer[i]);
}
}
}
template <class T>
AudioChunk CreateAudioChunk(uint32_t aFrames, int aChannels,
AudioSampleFormat aSampleFormat);
void testAudioCorrection(int32_t aSourceRate, int32_t aTargetRate) {
const int32_t sampleRateTransmitter = aSourceRate;
const int32_t sampleRateReceiver = aTargetRate;
AudioDriftCorrection ad(sampleRateTransmitter, sampleRateReceiver);
const float amplitude = 0.5;
const float frequency = 10;
const float phase = 0.0;
float time = 0.0;
const float deltaTime = 1.0f / sampleRateTransmitter;
int32_t sourceFrames;
const int32_t targetFrames = sampleRateReceiver / 100;
// Run for some time: 6 * 250 = 1500 iterations
for (int j = 0; j < 6; ++j) {
// apply some drift
if (j % 2 == 0) {
sourceFrames = sampleRateTransmitter / 100 + 10;
} else {
sourceFrames = sampleRateTransmitter / 100 - 10;
}
for (int n = 0; n < 250; ++n) {
// Create the input (sine tone)
AudioChunk chunk =
CreateAudioChunk<float>(sourceFrames, 1, AUDIO_FORMAT_FLOAT32);
float* monoBuffer = chunk.ChannelDataForWrite<float>(0);
for (int i = 0; i < sourceFrames; ++i) {
double value = amplitude * sin(2 * M_PI * frequency * time + phase);
monoBuffer[i] = static_cast<float>(value);
time += deltaTime;
}
AudioSegment inSegment;
inSegment.AppendAndConsumeChunk(&chunk);
// Print the input for debugging
// printAudioSegment(inSegment);
// Get the output of the correction
AudioSegment outSegment = ad.RequestFrames(inSegment, targetFrames);
EXPECT_EQ(outSegment.GetDuration(), targetFrames);
// Print the output for debugging
// printAudioSegment(outSegment);
}
}
}
TEST(TestAudioDriftCorrection, Basic)
{
testAudioCorrection(48000, 48000);
testAudioCorrection(48000, 44100);
testAudioCorrection(44100, 48000);
}
void testMonoToStereoInput(int aSourceRate, int aTargetRate) {
const int32_t sampleRateTransmitter = aSourceRate;
const int32_t sampleRateReceiver = aTargetRate;
AudioDriftCorrection ad(sampleRateTransmitter, sampleRateReceiver);
const float amplitude = 0.5;
const float frequency = 10;
const float phase = 0.0;
float time = 0.0;
const float deltaTime = 1.0f / sampleRateTransmitter;
int32_t sourceFrames;
const int32_t targetFrames = sampleRateReceiver / 100;
// Run for some time: 6 * 250 = 1500 iterations
for (int j = 0; j < 6; ++j) {
// apply some drift
if (j % 2 == 0) {
sourceFrames = sampleRateTransmitter / 100 + 10;
} else {
sourceFrames = sampleRateTransmitter / 100 - 10;
}
for (int n = 0; n < 250; ++n) {
// Create the input (sine tone)
AudioChunk chunk =
CreateAudioChunk<float>(sourceFrames / 2, 1, AUDIO_FORMAT_FLOAT32);
float* monoBuffer = chunk.ChannelDataForWrite<float>(0);
for (int i = 0; i < chunk.GetDuration(); ++i) {
double value = amplitude * sin(2 * M_PI * frequency * time + phase);
monoBuffer[i] = static_cast<float>(value);
time += deltaTime;
}
AudioChunk chunk2 =
CreateAudioChunk<float>(sourceFrames / 2, 2, AUDIO_FORMAT_FLOAT32);
for (int i = 0; i < chunk2.GetDuration(); ++i) {
double value = amplitude * sin(2 * M_PI * frequency * time + phase);
chunk2.ChannelDataForWrite<float>(0)[i] =
chunk2.ChannelDataForWrite<float>(1)[i] = static_cast<float>(value);
time += deltaTime;
}
AudioSegment inSegment;
inSegment.AppendAndConsumeChunk(&chunk);
inSegment.AppendAndConsumeChunk(&chunk2);
// Print the input for debugging
// printAudioSegment(inSegment);
// Get the output of the correction
AudioSegment outSegment = ad.RequestFrames(inSegment, targetFrames);
EXPECT_EQ(outSegment.GetDuration(), targetFrames);
// Print the output for debugging
// printAudioSegment(outSegment);
}
}
}
TEST(TestAudioDriftCorrection, MonoToStereoInput)
{
testMonoToStereoInput(48000, 48000);
testMonoToStereoInput(48000, 44100);
testMonoToStereoInput(44100, 48000);
}

Просмотреть файл

@ -1,978 +0,0 @@
/* -*- 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]);
}
}

Просмотреть файл

@ -6,8 +6,6 @@
#include "gtest/gtest.h"
#include "BufferReader.h"
using namespace mozilla;
TEST(BufferReader, ReaderCursor)
{
// Allocate a buffer and create a BufferReader.

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -20,10 +20,8 @@ UNIFIED_SOURCES += [
'TestAudioBuffers.cpp',
'TestAudioCallbackDriver.cpp',
'TestAudioCompactor.cpp',
'TestAudioDriftCorrection.cpp',
'TestAudioMixer.cpp',
'TestAudioPacketizer.cpp',
'TestAudioRingBuffer.cpp',
'TestAudioSegment.cpp',
'TestAudioTrackEncoder.cpp',
'TestAudioTrackGraph.cpp',
@ -34,7 +32,6 @@ UNIFIED_SOURCES += [
'TestDataMutex.cpp',
'TestDecoderBenchmark.cpp',
'TestDriftCompensation.cpp',
'TestDynamicResampler.cpp',
'TestGMPUtils.cpp',
'TestGroupId.cpp',
'TestIntervalSet.cpp',

Просмотреть файл

@ -110,10 +110,8 @@ EXPORTS += [
'AudioConfig.h',
'AudioConverter.h',
'AudioDeviceInfo.h',
'AudioDriftCorrection.h',
'AudioMixer.h',
'AudioPacketizer.h',
'AudioRingBuffer.h',
'AudioSampleFormat.h',
'AudioSegment.h',
'AudioStream.h',
@ -130,7 +128,6 @@ EXPORTS += [
'DecoderTraits.h',
'DOMMediaStream.h',
'DriftCompensation.h',
'DynamicResampler.h',
'FileBlockCache.h',
'ForwardedInputTrack.h',
'FrameStatistics.h',
@ -237,7 +234,6 @@ UNIFIED_SOURCES += [
'AudioConfig.cpp',
'AudioConverter.cpp',
'AudioDeviceInfo.cpp',
'AudioRingBuffer.cpp',
'AudioSegment.cpp',
'AudioStream.cpp',
'AudioStreamTrack.cpp',
@ -254,7 +250,6 @@ UNIFIED_SOURCES += [
'ChannelMediaResource.cpp',
'CloneableWithRangeMediaResource.cpp',
'DOMMediaStream.cpp',
'DynamicResampler.cpp',
'FileBlockCache.cpp',
'FileMediaResource.cpp',
'ForwardedInputTrack.cpp',

Просмотреть файл

@ -6397,12 +6397,6 @@
value: false
#endif
# ClockDrift desired buffering
- name: media.clockdrift.buffering
type: int32_t
mirror: always
value: 5
# If a resource is known to be smaller than this size (in kilobytes), a
# memory-backed MediaCache may be used; otherwise the (single shared global)
# file-backed MediaCache is used.