/* -*- 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_