xref: /aosp_15_r20/external/oboe/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h (revision 05767d913155b055644481607e6fa1e35e2fe72c)
1 /*
2  * Copyright (C) 2017 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_GLITCH_ANALYZER_H
18 #define ANALYZER_GLITCH_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 #include "BaseSineAnalyzer.h"
28 #include "PseudoRandom.h"
29 
30 /**
31  * Output a steady sine wave and analyze the return signal.
32  *
33  * Use a cosine transform to measure the predicted magnitude and relative phase of the
34  * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
35  */
36 class GlitchAnalyzer : public BaseSineAnalyzer {
37 public:
38 
GlitchAnalyzer()39     GlitchAnalyzer() : BaseSineAnalyzer() {}
40 
getState()41     int32_t getState() const {
42         return mState;
43     }
44 
getPeakAmplitude()45     double getPeakAmplitude() const {
46         return mPeakFollower.getLevel();
47     }
48 
getSineAmplitude()49     double getSineAmplitude() const {
50         return mMagnitude;
51     }
52 
getSinePeriod()53     int getSinePeriod() const {
54         return mSinePeriod;
55     }
56 
getGlitchCount()57     int32_t getGlitchCount() const {
58         return mGlitchCount;
59     }
60 
getGlitchLength()61     int32_t getGlitchLength() const {
62         return mGlitchLength;
63     }
64 
getStateFrameCount(int state)65     int32_t getStateFrameCount(int state) const {
66         return mStateFrameCounters[state];
67     }
68 
getSignalToNoiseDB()69     double getSignalToNoiseDB() {
70         static const double threshold = 1.0e-14;
71         if (mState != STATE_LOCKED
72                 || mMeanSquareSignal < threshold
73                 || mMeanSquareNoise < threshold) {
74             return -999.0; // error indicator
75         } else {
76             double signalToNoise = mMeanSquareSignal / mMeanSquareNoise; // power ratio
77             double signalToNoiseDB = 10.0 * log(signalToNoise);
78             if (signalToNoiseDB < static_cast<float>(MIN_SNR_DB)) {
79                 setResult(ERROR_VOLUME_TOO_LOW);
80             }
81             return signalToNoiseDB;
82         }
83     }
84 
analyze()85     std::string analyze() override {
86         std::stringstream report;
87         report << "GlitchAnalyzer ------------------\n";
88         report << LOOPBACK_RESULT_TAG "peak.amplitude     = " << std::setw(8)
89                << getPeakAmplitude() << "\n";
90         report << LOOPBACK_RESULT_TAG "sine.magnitude     = " << std::setw(8)
91                << getSineAmplitude() << "\n";
92         report << LOOPBACK_RESULT_TAG "rms.noise          = " << std::setw(8)
93                << mMeanSquareNoise << "\n";
94         report << LOOPBACK_RESULT_TAG "signal.to.noise.db = " << std::setw(8)
95                << getSignalToNoiseDB() << "\n";
96         report << LOOPBACK_RESULT_TAG "frames.accumulated = " << std::setw(8)
97                << mFramesAccumulated << "\n";
98         report << LOOPBACK_RESULT_TAG "sine.period        = " << std::setw(8)
99                << mSinePeriod << "\n";
100         report << LOOPBACK_RESULT_TAG "test.state         = " << std::setw(8)
101                << mState << "\n";
102         report << LOOPBACK_RESULT_TAG "frame.count        = " << std::setw(8)
103                << mFrameCounter << "\n";
104         // Did we ever get a lock?
105         bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
106         if (!gotLock) {
107             report << "ERROR - failed to lock on reference sine tone.\n";
108             setResult(ERROR_NO_LOCK);
109         } else {
110             // Only print if meaningful.
111             report << LOOPBACK_RESULT_TAG "glitch.count       = " << std::setw(8)
112                    << mGlitchCount << "\n";
113             report << LOOPBACK_RESULT_TAG "max.glitch         = " << std::setw(8)
114                    << mMaxGlitchDelta << "\n";
115             if (mGlitchCount > 0) {
116                 report << "ERROR - number of glitches > 0\n";
117                 setResult(ERROR_GLITCHES);
118             }
119         }
120         return report.str();
121     }
122 
printStatus()123     void printStatus() override {
124         ALOGD("st = %d, #gl = %3d,", mState, mGlitchCount);
125     }
126 
127     /**
128      * @param frameData contains microphone data with sine signal feedback
129      * @param channelCount
130      */
processInputFrame(const float * frameData,int)131     result_code processInputFrame(const float *frameData, int /* channelCount */) override {
132         result_code result = RESULT_OK;
133 
134         float sample = frameData[getInputChannel()];
135 
136         // Force a periodic glitch to test the detector!
137         if (mForceGlitchDurationFrames > 0) {
138             if (mForceGlitchCounter == 0) {
139                 ALOGE("%s: finish a glitch!!", __func__);
140                 mForceGlitchCounter = kForceGlitchPeriod;
141             } else if (mForceGlitchCounter <= mForceGlitchDurationFrames) {
142                 // Force an abrupt offset.
143                 sample += (sample > 0.0) ? -kForceGlitchOffset : kForceGlitchOffset;
144             }
145             --mForceGlitchCounter;
146         }
147 
148         float peak = mPeakFollower.process(sample);
149         mInfiniteRecording.write(sample);
150 
151         mStateFrameCounters[mState]++; // count how many frames we are in each state
152 
153         switch (mState) {
154             case STATE_IDLE:
155                 mDownCounter--;
156                 if (mDownCounter <= 0) {
157                     mState = STATE_IMMUNE;
158                     mDownCounter = IMMUNE_FRAME_COUNT;
159                     mInputPhase = 0.0; // prevent spike at start
160                     mOutputPhase = 0.0;
161                     resetAccumulator();
162                 }
163                 break;
164 
165             case STATE_IMMUNE:
166                 mDownCounter--;
167                 if (mDownCounter <= 0) {
168                     mState = STATE_WAITING_FOR_SIGNAL;
169                 }
170                 break;
171 
172             case STATE_WAITING_FOR_SIGNAL:
173                 if (peak > mThreshold) {
174                     mState = STATE_WAITING_FOR_LOCK;
175                     //ALOGD("%5d: switch to STATE_WAITING_FOR_LOCK", mFrameCounter);
176                     resetAccumulator();
177                 }
178                 break;
179 
180             case STATE_WAITING_FOR_LOCK:
181                 mSinAccumulator += static_cast<double>(sample) * sinf(mInputPhase);
182                 mCosAccumulator += static_cast<double>(sample) * cosf(mInputPhase);
183                 mFramesAccumulated++;
184                 // Must be a multiple of the period or the calculation will not be accurate.
185                 if (mFramesAccumulated == mSinePeriod * PERIODS_NEEDED_FOR_LOCK) {
186                     double magnitude = calculateMagnitudePhase(&mPhaseOffset);
187                     if (mPhaseOffset != kPhaseInvalid) {
188                         setMagnitude(magnitude);
189                         ALOGD("%s() mag = %f, mPhaseOffset = %f",
190                               __func__, magnitude, mPhaseOffset);
191                         if (mMagnitude > mThreshold) {
192                             if (fabs(mPhaseOffset) < kMaxPhaseError) {
193                                 mState = STATE_LOCKED;
194                                 mConsecutiveBadFrames = 0;
195 //                            ALOGD("%5d: switch to STATE_LOCKED", mFrameCounter);
196                             }
197                             // Adjust mInputPhase to match measured phase
198                             mInputPhase += mPhaseOffset;
199                         }
200                     }
201                     resetAccumulator();
202                 }
203                 incrementInputPhase();
204                 break;
205 
206             case STATE_LOCKED: {
207                 // Predict next sine value
208                 double predicted = sinf(mInputPhase) * mMagnitude;
209                 double diff = predicted - sample;
210                 double absDiff = fabs(diff);
211                 mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
212                 if (absDiff > mScaledTolerance) { // bad frame
213                     mConsecutiveBadFrames++;
214                     mConsecutiveGoodFrames = 0;
215                     LOGI("diff glitch frame #%d detected, absDiff = %g > %g",
216                          mConsecutiveBadFrames, absDiff, mScaledTolerance);
217                     if (mConsecutiveBadFrames > 0) {
218                         result = ERROR_GLITCHES;
219                         onGlitchStart();
220                     }
221                     resetAccumulator();
222                 } else { // good frame
223                     mConsecutiveBadFrames = 0;
224                     mConsecutiveGoodFrames++;
225 
226                     mSumSquareSignal += predicted * predicted;
227                     mSumSquareNoise += diff * diff;
228 
229                     // Track incoming signal and slowly adjust magnitude to account
230                     // for drift in the DRC or AGC.
231                     // Must be a multiple of the period or the calculation will not be accurate.
232                     if (transformSample(sample, mInputPhase)) {
233                         // Adjust phase to account for sample rate drift.
234                         mInputPhase += mPhaseOffset;
235 
236                         mMeanSquareNoise = mSumSquareNoise * mInverseSinePeriod;
237                         mMeanSquareSignal = mSumSquareSignal * mInverseSinePeriod;
238                         mSumSquareNoise = 0.0;
239                         mSumSquareSignal = 0.0;
240 
241                         if (fabs(mPhaseOffset) > kMaxPhaseError) {
242                             result = ERROR_GLITCHES;
243                             onGlitchStart();
244                             ALOGD("phase glitch detected, phaseOffset = %g", mPhaseOffset);
245                         } else if (mMagnitude < mThreshold) {
246                             result = ERROR_GLITCHES;
247                             onGlitchStart();
248                             ALOGD("magnitude glitch detected, mMagnitude = %g", mMagnitude);
249                         }
250                     }
251                 }
252                 incrementInputPhase();
253             } break;
254 
255             case STATE_GLITCHING: {
256                 // Predict next sine value
257                 double predicted = sinf(mInputPhase) * mMagnitude;
258                 double diff = predicted - sample;
259                 double absDiff = fabs(diff);
260                 mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
261                 if (absDiff > mScaledTolerance) { // bad frame
262                     mConsecutiveBadFrames++;
263                     mConsecutiveGoodFrames = 0;
264                     mGlitchLength++;
265                     if (mGlitchLength > maxMeasurableGlitchLength()) {
266                         onGlitchTerminated();
267                     }
268                 } else { // good frame
269                     mConsecutiveBadFrames = 0;
270                     mConsecutiveGoodFrames++;
271                     // If we get a full sine period of good samples in a row then consider the glitch over.
272                     // We don't want to just consider a zero crossing the end of a glitch.
273                     if (mConsecutiveGoodFrames > mSinePeriod) {
274                         onGlitchEnd();
275                     }
276                 }
277                 incrementInputPhase();
278             } break;
279 
280             case NUM_STATES: // not a real state
281                 break;
282         }
283 
284         mFrameCounter++;
285 
286         return result;
287     }
288 
maxMeasurableGlitchLength()289     int maxMeasurableGlitchLength() const { return 2 * mSinePeriod; }
290 
291     // advance and wrap phase
incrementInputPhase()292     void incrementInputPhase() {
293         mInputPhase += mPhaseIncrement;
294         if (mInputPhase > M_PI) {
295             mInputPhase -= (2.0 * M_PI);
296         }
297     }
298 
isOutputEnabled()299     bool isOutputEnabled() override { return mState != STATE_IDLE; }
300 
onGlitchStart()301     void onGlitchStart() {
302         mState = STATE_GLITCHING;
303         mGlitchLength = 1;
304         mLastGlitchPosition = mInfiniteRecording.getTotalWritten();
305         ALOGD("%5d: STARTED a glitch # %d, pos = %5d",
306               mFrameCounter, mGlitchCount, (int)mLastGlitchPosition);
307         ALOGD("glitch mSinePeriod = %d", mSinePeriod);
308     }
309 
310     /**
311      * Give up waiting for a glitch to end and try to resync.
312      */
onGlitchTerminated()313     void onGlitchTerminated() {
314         mGlitchCount++;
315         ALOGD("%5d: TERMINATED a glitch # %d, length = %d", mFrameCounter, mGlitchCount, mGlitchLength);
316         // We don't know how long the glitch really is so set the length to -1.
317         mGlitchLength = -1;
318         mState = STATE_WAITING_FOR_LOCK;
319         resetAccumulator();
320     }
321 
onGlitchEnd()322     void onGlitchEnd() {
323         mGlitchCount++;
324         ALOGD("%5d: ENDED a glitch # %d, length = %d", mFrameCounter, mGlitchCount, mGlitchLength);
325         mState = STATE_LOCKED;
326         resetAccumulator();
327     }
328 
329     // reset the sine wave detector
resetAccumulator()330     void resetAccumulator() override {
331         BaseSineAnalyzer::resetAccumulator();
332     }
333 
reset()334     void reset() override {
335         BaseSineAnalyzer::reset();
336         mState = STATE_IDLE;
337         mDownCounter = IDLE_FRAME_COUNT;
338     }
339 
prepareToTest()340     void prepareToTest() override {
341         BaseSineAnalyzer::prepareToTest();
342         mGlitchCount = 0;
343         mGlitchLength = 0;
344         mMaxGlitchDelta = 0.0;
345         for (int i = 0; i < NUM_STATES; i++) {
346             mStateFrameCounters[i] = 0;
347         }
348     }
349 
getLastGlitch(float * buffer,int32_t length)350     int32_t getLastGlitch(float *buffer, int32_t length) {
351         const int margin = mSinePeriod;
352         int32_t numSamples = mInfiniteRecording.readFrom(buffer,
353                                                          mLastGlitchPosition - margin,
354                                                          length);
355         ALOGD("%s: glitch at %d, edge = %7.4f, %7.4f, %7.4f",
356               __func__, (int)mLastGlitchPosition,
357             buffer[margin - 1], buffer[margin], buffer[margin+1]);
358         return numSamples;
359     }
360 
getRecentSamples(float * buffer,int32_t length)361     int32_t getRecentSamples(float *buffer, int32_t length) {
362         int firstSample = mInfiniteRecording.getTotalWritten() - length;
363         int32_t numSamples = mInfiniteRecording.readFrom(buffer,
364                                                          firstSample,
365                                                          length);
366         return numSamples;
367     }
368 
setForcedGlitchDuration(int frames)369     void setForcedGlitchDuration(int frames) {
370         mForceGlitchDurationFrames = frames;
371     }
372 
373 private:
374 
375     // These must match the values in GlitchActivity.java
376     enum sine_state_t {
377         STATE_IDLE,               // beginning
378         STATE_IMMUNE,             // ignoring input, waiting for HW to settle
379         STATE_WAITING_FOR_SIGNAL, // looking for a loud signal
380         STATE_WAITING_FOR_LOCK,   // trying to lock onto the phase of the sine
381         STATE_LOCKED,             // locked on the sine wave, looking for glitches
382         STATE_GLITCHING,          // locked on the sine wave but glitching
383         NUM_STATES
384     };
385 
386     enum constants {
387         // Arbitrary durations, assuming 48000 Hz
388         IDLE_FRAME_COUNT = 48 * 100,
389         IMMUNE_FRAME_COUNT = 48 * 100,
390         PERIODS_NEEDED_FOR_LOCK = 8,
391         MIN_SNR_DB = 65
392     };
393 
394     static constexpr double kMaxPhaseError = M_PI * 0.05;
395 
396     double  mThreshold = 0.005;
397 
398     int32_t mStateFrameCounters[NUM_STATES];
399     sine_state_t  mState = STATE_IDLE;
400     int64_t       mLastGlitchPosition;
401 
402     double  mInputPhase = 0.0;
403     double  mMaxGlitchDelta = 0.0;
404     int32_t mGlitchCount = 0;
405     int32_t mConsecutiveBadFrames = 0;
406     int32_t mConsecutiveGoodFrames = 0;
407     int32_t mGlitchLength = 0;
408     int     mDownCounter = IDLE_FRAME_COUNT;
409     int32_t mFrameCounter = 0;
410 
411     int32_t mForceGlitchDurationFrames = 0; // if > 0 then force a glitch for debugging
412     static constexpr int32_t kForceGlitchPeriod = 2 * 48000; // How often we glitch
413     static constexpr float   kForceGlitchOffset = 0.20f;
414     int32_t mForceGlitchCounter = kForceGlitchPeriod; // count down and trigger at zero
415 
416     // measure background noise continuously as a deviation from the expected signal
417     double  mSumSquareSignal = 0.0;
418     double  mSumSquareNoise = 0.0;
419     double  mMeanSquareSignal = 0.0;
420     double  mMeanSquareNoise = 0.0;
421 
422     PeakDetector  mPeakFollower;
423 };
424 
425 
426 #endif //ANALYZER_GLITCH_ANALYZER_H
427