• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef ANALYZER_BASE_SINE_ANALYZER_H
18 #define ANALYZER_BASE_SINE_ANALYZER_H
19 
20 #include <algorithm>
21 #include <cctype>
22 #include <iomanip>
23 #include <iostream>
24 
25 #include "InfiniteRecording.h"
26 #include "LatencyAnalyzer.h"
27 
28 /**
29  * Output a steady sine wave and analyze the return signal.
30  *
31  * Use a cosine transform to measure the predicted magnitude and relative phase of the
32  * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
33  */
34 class BaseSineAnalyzer : public LoopbackProcessor {
35 public:
36 
BaseSineAnalyzer()37     BaseSineAnalyzer()
38             : LoopbackProcessor()
39             , mInfiniteRecording(64 * 1024) {}
40 
isOutputEnabled()41     virtual bool isOutputEnabled() { return true; }
42 
setMagnitude(double magnitude)43     void setMagnitude(double magnitude) {
44         mMagnitude = magnitude;
45         mScaledTolerance = mMagnitude * getTolerance();
46     }
47 
48     /**
49      *
50      * @return valid phase or kPhaseInvalid=-999
51      */
getPhaseOffset()52     double getPhaseOffset() {
53         ALOGD("%s(), mPhaseOffset = %f\n", __func__, mPhaseOffset);
54         return mPhaseOffset;
55     }
56 
getMagnitude()57     double getMagnitude() const {
58         return mMagnitude;
59     }
60 
setNoiseAmplitude(double noiseAmplitude)61     void setNoiseAmplitude(double noiseAmplitude) {
62         mNoiseAmplitude = noiseAmplitude;
63     }
64 
getNoiseAmplitude()65     double getNoiseAmplitude() const {
66         return mNoiseAmplitude;
67     }
68 
getTolerance()69     double getTolerance() {
70         return mTolerance;
71     }
72 
setTolerance(double tolerance)73     void setTolerance(double tolerance) {
74         mTolerance = tolerance;
75     }
76 
77     // advance and wrap phase
incrementInputPhase()78     void incrementInputPhase() {
79         mInputPhase += mPhaseIncrement;
80         if (mInputPhase > M_PI) {
81             mInputPhase -= (2.0 * M_PI);
82         }
83     }
84 
85     // advance and wrap phase
incrementOutputPhase()86     void incrementOutputPhase() {
87         mOutputPhase += mPhaseIncrement;
88         if (mOutputPhase > M_PI) {
89             mOutputPhase -= (2.0 * M_PI);
90         }
91     }
92 
93 
94     /**
95      * @param frameData upon return, contains the reference sine wave
96      * @param channelCount
97      */
processOutputFrame(float * frameData,int channelCount)98     result_code processOutputFrame(float *frameData, int channelCount) override {
99         float output = 0.0f;
100         // Output sine wave so we can measure it.
101         if (isOutputEnabled()) {
102             float sinOut = sinf(mOutputPhase);
103             incrementOutputPhase();
104             output = (sinOut * mOutputAmplitude)
105                      + (mWhiteNoise.nextRandomDouble() * getNoiseAmplitude());
106             // ALOGD("sin(%f) = %f, %f\n", mOutputPhase, sinOut,  kPhaseIncrement);
107         }
108         for (int i = 0; i < channelCount; i++) {
109             frameData[i] = (i == getOutputChannel()) ? output : 0.0f;
110         }
111         return RESULT_OK;
112     }
113 
114     /**
115      * Calculate the magnitude of the component of the input signal
116      * that matches the analysis frequency.
117      * Also calculate the phase that we can use to create a
118      * signal that matches that component.
119      * The phase will be between -PI and +PI.
120      */
121     double calculateMagnitudePhase(double *phasePtr = nullptr) {
122         if (mFramesAccumulated == 0) {
123             return 0.0;
124         }
125         double sinMean = mSinAccumulator / mFramesAccumulated;
126         double cosMean = mCosAccumulator / mFramesAccumulated;
127         double magnitude = 2.0 * sqrt((sinMean * sinMean) + (cosMean * cosMean));
128         if (phasePtr != nullptr) {
129             double phase;
130             if (magnitude < kMinValidMagnitude) {
131                 phase = kPhaseInvalid;
132                 ALOGD("%s() mag very low! sinMean = %7.5f, cosMean = %7.5f",
133                       __func__, sinMean, cosMean);
134             } else {
135                 phase = atan2(cosMean, sinMean);
136                 if (phase == 0.0) {
137                     ALOGD("%s() phase zero! sinMean = %7.5f, cosMean = %7.5f",
138                           __func__, sinMean, cosMean);
139                 }
140             }
141             *phasePtr = phase;
142         }
143         return magnitude;
144     }
145 
146     /**
147      * Perform sin/cos analysis on each sample.
148      * Measure magnitude and phase on every period.
149      * Updates mPhaseOffset
150      * @param sample
151      * @param referencePhase
152      * @return true if magnitude and phase updated
153      */
transformSample(float sample)154     bool transformSample(float sample) {
155         // Compare incoming signal with the reference input sine wave.
156         mSinAccumulator += static_cast<double>(sample) * sinf(mInputPhase);
157         mCosAccumulator += static_cast<double>(sample) * cosf(mInputPhase);
158         incrementInputPhase();
159 
160         mFramesAccumulated++;
161         // Must be a multiple of the period or the calculation will not be accurate.
162         if (mFramesAccumulated == mSinePeriod) {
163             const double coefficient = 0.1;
164             double magnitude = calculateMagnitudePhase(&mPhaseOffset);
165 
166             ALOGD("%s(), phaseOffset = %f\n", __func__, mPhaseOffset);
167             if (mPhaseOffset != kPhaseInvalid) {
168                 // One pole averaging filter.
169                 setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient));
170             }
171             resetAccumulator();
172             return true;
173         } else {
174             return false;
175         }
176     }
177 
178     // reset the sine wave detector
resetAccumulator()179     virtual void resetAccumulator() {
180         mFramesAccumulated = 0;
181         mSinAccumulator = 0.0;
182         mCosAccumulator = 0.0;
183     }
184 
reset()185     void reset() override {
186         LoopbackProcessor::reset();
187         resetAccumulator();
188         mMagnitude = 0.0;
189     }
190 
prepareToTest()191     void prepareToTest() override {
192         LoopbackProcessor::prepareToTest();
193         mSinePeriod = getSampleRate() / kTargetGlitchFrequency;
194         mInputPhase = 0.0f;
195         mOutputPhase = 0.0f;
196         mInverseSinePeriod = 1.0 / mSinePeriod;
197         mPhaseIncrement = 2.0 * M_PI * mInverseSinePeriod;
198     }
199 
200 protected:
201     // Try to get a prime period so the waveform plot changes every time.
202     static constexpr int32_t kTargetGlitchFrequency = 48000 / 113;
203 
204     int32_t mSinePeriod = 1; // this will be set before use
205     double  mInverseSinePeriod = 1.0;
206     double  mPhaseIncrement = 0.0;
207     // Use two sine wave phases, input and output.
208     // This is because the number of input and output samples may differ
209     // in a callback and the output frame count may advance ahead of the input, or visa versa.
210     double  mInputPhase = 0.0;
211     double  mOutputPhase = 0.0;
212     double  mOutputAmplitude = 0.75;
213     // This is the phase offset between the mInputPhase sine wave and the recorded
214     // signal at the tuned frequency.
215     // If this jumps around then we are probably just hearing noise.
216     // Noise can cause the magnitude to be high but mPhaseOffset will be pretty random.
217     // If we are tracking a sine wave then mPhaseOffset should be consistent.
218     double  mPhaseOffset = 0.0;
219     // kPhaseInvalid indicates that the phase measurement cannot be used.
220     // We were seeing times when a magnitude of zero was causing atan2(s,c) to
221     // return a phase of zero, which looked valid to Java. This is a way of passing
222     // an error code back to Java as a single value to avoid race conditions.
223     static constexpr double kPhaseInvalid = -999.0;
224     double  mMagnitude = 0.0;
225     static constexpr double kMinValidMagnitude = 2.0 / (1 << 16);
226     int32_t mFramesAccumulated = 0;
227     double  mSinAccumulator = 0.0;
228     double  mCosAccumulator = 0.0;
229     double  mScaledTolerance = 0.0;
230 
231     InfiniteRecording<float> mInfiniteRecording;
232 
233 private:
234     float   mTolerance = 0.10; // scaled from 0.0 to 1.0
235 
236     float mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
237     PseudoRandom  mWhiteNoise;
238 };
239 
240 #endif //ANALYZER_BASE_SINE_ANALYZER_H
241