summaryrefslogtreecommitdiffstats
path: root/dom/media/gtest/AudioVerifier.h
blob: 2ff8ed92698af32246e447e35c82374aff7ecd3e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/* -*- 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() *
                               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) {
    const bool discontinuity = fabs(fabs(aCurrentSample) - fabs(mPrevious)) >
                               3 * 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 (!discontinuity) {
      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_