Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 https://mozilla.org/MPL/2.0/. */
#ifndef DOM_MEDIA_GTEST_AUDIOVERIFIER_H_
#define DOM_MEDIA_GTEST_AUDIOVERIFIER_H_
#include "AudioGenerator.h"
namespace mozilla {
template <typename Sample>
class AudioVerifier {
public:
explicit AudioVerifier(uint32_t aRate, uint32_t aFrequency)
: mRate(aRate), mFrequency(aFrequency) {}
// Only the mono channel is taken into account.
void AppendData(const AudioSegment& segment) {
for (AudioSegment::ConstChunkIterator iter(segment); !iter.IsEnded();
iter.Next()) {
const AudioChunk& c = *iter;
if (c.IsNull()) {
for (int i = 0; i < c.GetDuration(); ++i) {
CheckSample(0);
}
} else {
const Sample* buffer = c.ChannelData<Sample>()[0];
for (int i = 0; i < c.GetDuration(); ++i) {
CheckSample(buffer[i]);
}
}
}
}
void AppendDataInterleaved(const Sample* aBuffer, uint32_t aFrames,
uint32_t aChannels) {
for (uint32_t i = 0; i < aFrames * aChannels; i += aChannels) {
CheckSample(aBuffer[i]);
}
}
float EstimatedFreq() const {
if (mTotalFramesSoFar == PreSilenceSamples()) {
return 0;
}
if (mSumPeriodInSamples == 0) {
return 0;
}
if (mZeroCrossCount <= 1) {
return 0;
}
return mRate /
(static_cast<float>(mSumPeriodInSamples) / (mZeroCrossCount - 1));
}
// Returns the maximum difference in value between two adjacent samples along
// the sine curve.
Sample MaxMagnitudeDifference() {
return static_cast<Sample>(AudioGenerator<Sample>::Amplitude() * 2 *
sin(2 * M_PI * mFrequency / mRate));
}
bool PreSilenceEnded() const {
return mTotalFramesSoFar > mPreSilenceSamples;
}
uint64_t PreSilenceSamples() const { return mPreSilenceSamples; }
uint32_t CountDiscontinuities() const { return mDiscontinuitiesCount; }
private:
void CheckSample(Sample aCurrentSample) {
++mTotalFramesSoFar;
// Avoid pre-silence
if (!CountPreSilence(aCurrentSample)) {
CountZeroCrossing(aCurrentSample);
CountDiscontinuities(aCurrentSample);
}
mPrevious = aCurrentSample;
}
bool CountPreSilence(Sample aCurrentSample) {
if (IsZero(aCurrentSample) && mPreSilenceSamples == mTotalFramesSoFar - 1) {
++mPreSilenceSamples;
return true;
}
if (IsZero(mPrevious) && aCurrentSample > 0 &&
aCurrentSample < 2 * MaxMagnitudeDifference() &&
mPreSilenceSamples == mTotalFramesSoFar - 1) {
// Previous zero considered the first sample of the waveform.
--mPreSilenceSamples;
}
return false;
}
// Positive to negative direction
void CountZeroCrossing(Sample aCurrentSample) {
if (mPrevious > 0 && aCurrentSample <= 0) {
if (mZeroCrossCount++) {
MOZ_RELEASE_ASSERT(mZeroCrossCount > 1);
mSumPeriodInSamples += mTotalFramesSoFar - mLastZeroCrossPosition;
}
mLastZeroCrossPosition = mTotalFramesSoFar;
}
}
void CountDiscontinuities(Sample aCurrentSample) {
// The factor of 2 tolerates up to 1 skipped frame.
const bool haveDiscontinuity =
fabs(aCurrentSample - mPrevious) > 2 * MaxMagnitudeDifference();
if (mCurrentDiscontinuityFrameCount > 0) {
if (++mCurrentDiscontinuityFrameCount == 5) {
// Allow a grace-period of 5 samples for any given discontinuity.
// For instance the speex resampler can smooth out a sudden drop to 0
// over several samples.
mCurrentDiscontinuityFrameCount = 0;
}
return;
}
MOZ_RELEASE_ASSERT(mCurrentDiscontinuityFrameCount == 0);
if (!haveDiscontinuity) {
return;
}
// Encountered a new discontinuity.
++mCurrentDiscontinuityFrameCount;
++mDiscontinuitiesCount;
}
bool IsZero(float aValue) { return fabs(aValue) < 1e-8; }
bool IsZero(short aValue) { return aValue == 0; }
private:
const uint32_t mRate;
const uint32_t mFrequency;
uint32_t mZeroCrossCount = 0;
uint64_t mLastZeroCrossPosition = 0;
uint64_t mSumPeriodInSamples = 0;
uint64_t mTotalFramesSoFar = 0;
uint64_t mPreSilenceSamples = 0;
uint32_t mCurrentDiscontinuityFrameCount = 0;
uint32_t mDiscontinuitiesCount = 0;
// This is needed to connect the previous buffers.
Sample mPrevious = {};
};
} // namespace mozilla
#endif // DOM_MEDIA_GTEST_AUDIOVERIFIER_H_