• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 /**
18  * Tools for measuring latency and for detecting glitches.
19  * These classes are pure math and can be used with any audio system.
20  */
21 
22 #ifndef AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H
23 #define AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H
24 
25 #include <algorithm>
26 #include <assert.h>
27 #include <cctype>
28 #include <math.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 
33 #include <audio_utils/sndfile.h>
34 
35 // Tag for machine readable results as property = value pairs
36 #define LOOPBACK_RESULT_TAG      "RESULT: "
37 #define LOOPBACK_SAMPLE_RATE     48000
38 
39 #define MILLIS_PER_SECOND        1000
40 
41 #define MAX_ZEROTH_PARTIAL_BINS  40
42 constexpr double MAX_ECHO_GAIN = 10.0; // based on experiments, otherwise autocorrelation too noisy
43 
44 // A narrow impulse seems to have better immunity against over estimating the
45 // latency due to detecting subharmonics by the auto-correlator.
46 static const float s_Impulse[] = {
47         0.0f, 0.0f, 0.0f, 0.0f, 0.3f, // silence on each side of the impulse
48         0.99f, 0.0f, -0.99f, // bipolar with one zero crossing in middle
49         -0.3f, 0.0f, 0.0f, 0.0f, 0.0f
50 };
51 
52 constexpr int32_t kImpulseSizeInFrames = (int32_t)(sizeof(s_Impulse) / sizeof(s_Impulse[0]));
53 
54 class PseudoRandom {
55 public:
PseudoRandom()56     PseudoRandom() {}
PseudoRandom(int64_t seed)57     PseudoRandom(int64_t seed)
58             :    mSeed(seed)
59     {}
60 
61     /**
62      * Returns the next random double from -1.0 to 1.0
63      *
64      * @return value from -1.0 to 1.0
65      */
nextRandomDouble()66      double nextRandomDouble() {
67         return nextRandomInteger() * (0.5 / (((int32_t)1) << 30));
68     }
69 
70     /** Calculate random 32 bit number using linear-congruential method. */
nextRandomInteger()71     int32_t nextRandomInteger() {
72         // Use values for 64-bit sequence from MMIX by Donald Knuth.
73         mSeed = (mSeed * (int64_t)6364136223846793005) + (int64_t)1442695040888963407;
74         return (int32_t) (mSeed >> 32); // The higher bits have a longer sequence.
75     }
76 
77 private:
78     int64_t mSeed = 99887766;
79 };
80 
calculateCorrelation(const float * a,const float * b,int windowSize)81 static double calculateCorrelation(const float *a,
82                                    const float *b,
83                                    int windowSize)
84 {
85     double correlation = 0.0;
86     double sumProducts = 0.0;
87     double sumSquares = 0.0;
88 
89     // Correlate a against b.
90     for (int i = 0; i < windowSize; i++) {
91         float s1 = a[i];
92         float s2 = b[i];
93         // Use a normalized cross-correlation.
94         sumProducts += s1 * s2;
95         sumSquares += ((s1 * s1) + (s2 * s2));
96     }
97 
98     if (sumSquares >= 0.00000001) {
99         correlation = (float) (2.0 * sumProducts / sumSquares);
100     }
101     return correlation;
102 }
103 
calculateCorrelations(const float * haystack,int haystackSize,const float * needle,int needleSize,float * results,int resultSize)104 static int calculateCorrelations(const float *haystack, int haystackSize,
105                                  const float *needle, int needleSize,
106                                  float *results, int resultSize)
107 {
108     int maxCorrelations = haystackSize - needleSize;
109     int numCorrelations = std::min(maxCorrelations, resultSize);
110 
111     for (int ic = 0; ic < numCorrelations; ic++) {
112         double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
113         results[ic] = correlation;
114     }
115 
116     return numCorrelations;
117 }
118 
119 /*==========================================================================================*/
120 /**
121  * Scan until we get a correlation of a single scan that goes over the tolerance level,
122  * peaks then drops back down.
123  */
findFirstMatch(const float * haystack,int haystackSize,const float * needle,int needleSize,double threshold)124 static double findFirstMatch(const float *haystack, int haystackSize,
125                              const float *needle, int needleSize, double threshold  )
126 {
127     int ic;
128     // How many correlations can we calculate?
129     int numCorrelations = haystackSize - needleSize;
130     double maxCorrelation = 0.0;
131     int peakIndex = -1;
132     double location = -1.0;
133     const double backThresholdScaler = 0.5;
134 
135     for (ic = 0; ic < numCorrelations; ic++) {
136         double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
137 
138         if( (correlation > maxCorrelation) ) {
139             maxCorrelation = correlation;
140             peakIndex = ic;
141         }
142 
143         //printf("PaQa_FindFirstMatch: ic = %4d, correlation = %8f, maxSum = %8f\n",
144         //    ic, correlation, maxSum );
145         // Are we past what we were looking for?
146         if((maxCorrelation > threshold) && (correlation < backThresholdScaler * maxCorrelation)) {
147             location = peakIndex;
148             break;
149         }
150     }
151 
152     return location;
153 }
154 
155 typedef struct LatencyReport_s {
156     double latencyInFrames;
157     double confidence;
158 } LatencyReport;
159 
160 // Apply a technique similar to Harmonic Product Spectrum Analysis to find echo fundamental.
161 // Using first echo instead of the original impulse for a better match.
measureLatencyFromEchos(const float * haystack,int haystackSize,const float * needle,int needleSize,LatencyReport * report)162 static int measureLatencyFromEchos(const float *haystack, int haystackSize,
163                             const float *needle, int needleSize,
164                             LatencyReport *report) {
165     const double threshold = 0.1;
166     printf("measureLatencyFromEchos: haystackSize = %d, needleSize = %d\n",
167            haystackSize, needleSize);
168 
169     // Find first peak
170     int first = (int) (findFirstMatch(haystack,
171                                       haystackSize,
172                                       needle,
173                                       needleSize,
174                                       threshold) + 0.5);
175 
176     // Use first echo as the needle for the other echos because
177     // it will be more similar.
178     needle = &haystack[first];
179     int again = (int) (findFirstMatch(haystack,
180                                       haystackSize,
181                                       needle,
182                                       needleSize,
183                                       threshold) + 0.5);
184 
185     printf("measureLatencyFromEchos: first = %d, again at %d\n", first, again);
186     first = again;
187 
188     // Allocate results array
189     int remaining = haystackSize - first;
190     const int maxReasonableLatencyFrames = 48000 * 2; // arbitrary but generous value
191     int numCorrelations = std::min(remaining, maxReasonableLatencyFrames);
192     float *correlations = new float[numCorrelations];
193     float *harmonicSums = new float[numCorrelations](); // set to zero
194 
195     // Generate correlation for every position.
196     numCorrelations = calculateCorrelations(&haystack[first], remaining,
197                                             needle, needleSize,
198                                             correlations, numCorrelations);
199 
200     // Add higher harmonics mapped onto lower harmonics.
201     // This reinforces the "fundamental" echo.
202     const int numEchoes = 10;
203     for (int partial = 1; partial < numEchoes; partial++) {
204         for (int i = 0; i < numCorrelations; i++) {
205             harmonicSums[i / partial] += correlations[i] / partial;
206         }
207     }
208 
209     // Find highest peak in correlation array.
210     float maxCorrelation = 0.0;
211     float sumOfPeaks = 0.0;
212     int peakIndex = 0;
213     const int skip = MAX_ZEROTH_PARTIAL_BINS; // skip low bins
214     for (int i = skip; i < numCorrelations; i++) {
215         if (harmonicSums[i] > maxCorrelation) {
216             maxCorrelation = harmonicSums[i];
217             sumOfPeaks += maxCorrelation;
218             peakIndex = i;
219             printf("maxCorrelation = %f at %d\n", maxCorrelation, peakIndex);
220         }
221     }
222 
223     report->latencyInFrames = peakIndex;
224     if (sumOfPeaks < 0.0001) {
225         report->confidence = 0.0;
226     } else {
227         report->confidence = maxCorrelation / sumOfPeaks;
228     }
229 
230     delete[] correlations;
231     delete[] harmonicSums;
232     return 0;
233 }
234 
235 class AudioRecording
236 {
237 public:
AudioRecording()238     AudioRecording() {
239     }
~AudioRecording()240     ~AudioRecording() {
241         delete[] mData;
242     }
243 
allocate(int maxFrames)244     void allocate(int maxFrames) {
245         delete[] mData;
246         mData = new float[maxFrames];
247         mMaxFrames = maxFrames;
248     }
249 
250     // Write SHORT data from the first channel.
write(int16_t * inputData,int inputChannelCount,int numFrames)251     int write(int16_t *inputData, int inputChannelCount, int numFrames) {
252         // stop at end of buffer
253         if ((mFrameCounter + numFrames) > mMaxFrames) {
254             numFrames = mMaxFrames - mFrameCounter;
255         }
256         for (int i = 0; i < numFrames; i++) {
257             mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
258         }
259         return numFrames;
260     }
261 
262     // Write FLOAT data from the first channel.
write(float * inputData,int inputChannelCount,int numFrames)263     int write(float *inputData, int inputChannelCount, int numFrames) {
264         // stop at end of buffer
265         if ((mFrameCounter + numFrames) > mMaxFrames) {
266             numFrames = mMaxFrames - mFrameCounter;
267         }
268         for (int i = 0; i < numFrames; i++) {
269             mData[mFrameCounter++] = inputData[i * inputChannelCount];
270         }
271         return numFrames;
272     }
273 
size()274     int size() {
275         return mFrameCounter;
276     }
277 
getData()278     float *getData() {
279         return mData;
280     }
281 
setSampleRate(int32_t sampleRate)282     void setSampleRate(int32_t sampleRate) {
283         mSampleRate = sampleRate;
284     }
285 
getSampleRate()286     int32_t getSampleRate() {
287         return mSampleRate;
288     }
289 
290     int save(const char *fileName, bool writeShorts = true) {
291         SNDFILE *sndFile = nullptr;
292         int written = 0;
293         SF_INFO info = {
294                 .frames = mFrameCounter,
295                 .samplerate = mSampleRate,
296                 .channels = 1,
297                 .format = SF_FORMAT_WAV | (writeShorts ? SF_FORMAT_PCM_16 : SF_FORMAT_FLOAT)
298         };
299 
300         sndFile = sf_open(fileName, SFM_WRITE, &info);
301         if (sndFile == nullptr) {
302             printf("AudioRecording::save(%s) failed to open file\n", fileName);
303             return -errno;
304         }
305 
306         written = sf_writef_float(sndFile, mData, mFrameCounter);
307 
308         sf_close(sndFile);
309         return written;
310     }
311 
load(const char * fileName)312     int load(const char *fileName) {
313         SNDFILE *sndFile = nullptr;
314         SF_INFO info;
315 
316         sndFile = sf_open(fileName, SFM_READ, &info);
317         if (sndFile == nullptr) {
318             printf("AudioRecording::load(%s) failed to open file\n", fileName);
319             return -errno;
320         }
321 
322         assert(info.channels == 1);
323 
324         allocate(info.frames);
325         mFrameCounter = sf_readf_float(sndFile, mData, info.frames);
326 
327         sf_close(sndFile);
328         return mFrameCounter;
329     }
330 
331 private:
332     float  *mData = nullptr;
333     int32_t mFrameCounter = 0;
334     int32_t mMaxFrames = 0;
335     int32_t mSampleRate = 48000; // common default
336 };
337 
338 // ====================================================================================
339 class LoopbackProcessor {
340 public:
341     virtual ~LoopbackProcessor() = default;
342 
343 
reset()344     virtual void reset() {}
345 
346     virtual void process(float *inputData, int inputChannelCount,
347                  float *outputData, int outputChannelCount,
348                  int numFrames) = 0;
349 
350 
351     virtual void report() = 0;
352 
printStatus()353     virtual void printStatus() {};
354 
getResult()355     virtual int getResult() {
356         return -1;
357     }
358 
isDone()359     virtual bool isDone() {
360         return false;
361     }
362 
save(const char * fileName)363     virtual int save(const char *fileName) {
364         (void) fileName;
365         return AAUDIO_ERROR_UNIMPLEMENTED;
366     }
367 
load(const char * fileName)368     virtual int load(const char *fileName) {
369         (void) fileName;
370         return AAUDIO_ERROR_UNIMPLEMENTED;
371     }
372 
setSampleRate(int32_t sampleRate)373     virtual void setSampleRate(int32_t sampleRate) {
374         mSampleRate = sampleRate;
375     }
376 
getSampleRate()377     int32_t getSampleRate() {
378         return mSampleRate;
379     }
380 
381     // Measure peak amplitude of buffer.
measurePeakAmplitude(float * inputData,int inputChannelCount,int numFrames)382     static float measurePeakAmplitude(float *inputData, int inputChannelCount, int numFrames) {
383         float peak = 0.0f;
384         for (int i = 0; i < numFrames; i++) {
385             float pos = fabs(*inputData);
386             if (pos > peak) {
387                 peak = pos;
388             }
389             inputData += inputChannelCount;
390         }
391         return peak;
392     }
393 
394 
395 private:
396     int32_t mSampleRate = LOOPBACK_SAMPLE_RATE;
397 };
398 
399 class PeakDetector {
400 public:
process(float input)401     float process(float input) {
402         float output = mPrevious * mDecay;
403         if (input > output) {
404             output = input;
405         }
406         mPrevious = output;
407         return output;
408     }
409 
410 private:
411     float  mDecay = 0.99f;
412     float  mPrevious = 0.0f;
413 };
414 
415 
printAudioScope(float sample)416 static void printAudioScope(float sample) {
417     const int maxStars = 80; // arbitrary, fits on one line
418     char c = '*';
419     if (sample < -1.0) {
420         sample = -1.0;
421         c = '$';
422     } else if (sample > 1.0) {
423         sample = 1.0;
424         c = '$';
425     }
426     int numSpaces = (int) (((sample + 1.0) * 0.5) * maxStars);
427     for (int i = 0; i < numSpaces; i++) {
428         putchar(' ');
429     }
430     printf("%c\n", c);
431 }
432 
433 // ====================================================================================
434 /**
435  * Measure latency given a loopback stream data.
436  * Uses a state machine to cycle through various stages including:
437  *
438  */
439 class EchoAnalyzer : public LoopbackProcessor {
440 public:
441 
EchoAnalyzer()442     EchoAnalyzer() : LoopbackProcessor() {
443         mAudioRecording.allocate(2 * getSampleRate());
444         mAudioRecording.setSampleRate(getSampleRate());
445     }
446 
setSampleRate(int32_t sampleRate)447     void setSampleRate(int32_t sampleRate) override {
448         LoopbackProcessor::setSampleRate(sampleRate);
449         mAudioRecording.setSampleRate(sampleRate);
450     }
451 
reset()452     void reset() override {
453         mDownCounter = 200;
454         mLoopCounter = 0;
455         mMeasuredLoopGain = 0.0f;
456         mEchoGain = 1.0f;
457         mState = STATE_INITIAL_SILENCE;
458     }
459 
getResult()460     virtual int getResult() {
461         return mState == STATE_DONE ? 0 : -1;
462     }
463 
isDone()464     virtual bool isDone() {
465         return mState == STATE_DONE || mState == STATE_FAILED;
466     }
467 
setGain(float gain)468     void setGain(float gain) {
469         mEchoGain = gain;
470     }
471 
getGain()472     float getGain() {
473         return mEchoGain;
474     }
475 
report()476     void report() override {
477 
478         printf("EchoAnalyzer ---------------\n");
479         printf(LOOPBACK_RESULT_TAG "measured.gain          = %f\n", mMeasuredLoopGain);
480         printf(LOOPBACK_RESULT_TAG "echo.gain              = %f\n", mEchoGain);
481         printf(LOOPBACK_RESULT_TAG "test.state             = %d\n", mState);
482         if (mMeasuredLoopGain >= 0.9999) {
483             printf("   ERROR - clipping, turn down volume slightly\n");
484         } else {
485             const float *needle = s_Impulse;
486             int needleSize = (int) (sizeof(s_Impulse) / sizeof(float));
487             float *haystack = mAudioRecording.getData();
488             int haystackSize = mAudioRecording.size();
489             measureLatencyFromEchos(haystack, haystackSize, needle, needleSize, &mLatencyReport);
490             if (mLatencyReport.confidence < 0.01) {
491                 printf("   ERROR - confidence too low = %f\n", mLatencyReport.confidence);
492             } else {
493                 double latencyMillis = 1000.0 * mLatencyReport.latencyInFrames / getSampleRate();
494                 printf(LOOPBACK_RESULT_TAG "latency.frames        = %8.2f\n", mLatencyReport.latencyInFrames);
495                 printf(LOOPBACK_RESULT_TAG "latency.msec          = %8.2f\n", latencyMillis);
496                 printf(LOOPBACK_RESULT_TAG "latency.confidence    = %8.6f\n", mLatencyReport.confidence);
497             }
498         }
499     }
500 
printStatus()501     void printStatus() override {
502         printf("st = %d, echo gain = %f ", mState, mEchoGain);
503     }
504 
sendImpulses(float * outputData,int outputChannelCount,int numFrames)505     void sendImpulses(float *outputData, int outputChannelCount, int numFrames) {
506         while (numFrames-- > 0) {
507             float sample = s_Impulse[mSampleIndex++];
508             if (mSampleIndex >= kImpulseSizeInFrames) {
509                 mSampleIndex = 0;
510             }
511 
512             *outputData = sample;
513             outputData += outputChannelCount;
514         }
515     }
516 
sendOneImpulse(float * outputData,int outputChannelCount)517     void sendOneImpulse(float *outputData, int outputChannelCount) {
518         mSampleIndex = 0;
519         sendImpulses(outputData, outputChannelCount, kImpulseSizeInFrames);
520     }
521 
process(float * inputData,int inputChannelCount,float * outputData,int outputChannelCount,int numFrames)522     void process(float *inputData, int inputChannelCount,
523                  float *outputData, int outputChannelCount,
524                  int numFrames) override {
525         int channelsValid = std::min(inputChannelCount, outputChannelCount);
526         float peak = 0.0f;
527         int numWritten;
528         int numSamples;
529 
530         echo_state_t nextState = mState;
531 
532         switch (mState) {
533             case STATE_INITIAL_SILENCE:
534                 // Output silence at the beginning.
535                 numSamples = numFrames * outputChannelCount;
536                 for (int i = 0; i < numSamples; i++) {
537                     outputData[i] = 0;
538                 }
539                 if (mDownCounter-- <= 0) {
540                     nextState = STATE_MEASURING_GAIN;
541                     //printf("%5d: switch to STATE_MEASURING_GAIN\n", mLoopCounter);
542                     mDownCounter = 8;
543                 }
544                 break;
545 
546             case STATE_MEASURING_GAIN:
547                 sendImpulses(outputData, outputChannelCount, numFrames);
548                 peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
549                 // If we get several in a row then go to next state.
550                 if (peak > mPulseThreshold) {
551                     if (mDownCounter-- <= 0) {
552                         //printf("%5d: switch to STATE_WAITING_FOR_SILENCE, measured peak = %f\n",
553                         //       mLoopCounter, peak);
554                         mDownCounter = 8;
555                         mMeasuredLoopGain = peak;  // assumes original pulse amplitude is one
556                         // Calculate gain that will give us a nice decaying echo.
557                         mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
558                         if (mEchoGain > MAX_ECHO_GAIN) {
559                             printf("ERROR - loop gain too low. Increase the volume.\n");
560                             nextState = STATE_FAILED;
561                         } else {
562                             nextState = STATE_WAITING_FOR_SILENCE;
563                         }
564                     }
565                 } else if (numFrames > kImpulseSizeInFrames){ // ignore short callbacks
566                     mDownCounter = 8;
567                 }
568                 break;
569 
570             case STATE_WAITING_FOR_SILENCE:
571                 // Output silence and wait for the echos to die down.
572                 numSamples = numFrames * outputChannelCount;
573                 for (int i = 0; i < numSamples; i++) {
574                     outputData[i] = 0;
575                 }
576                 peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
577                 // If we get several in a row then go to next state.
578                 if (peak < mSilenceThreshold) {
579                     if (mDownCounter-- <= 0) {
580                         nextState = STATE_SENDING_PULSE;
581                         //printf("%5d: switch to STATE_SENDING_PULSE\n", mLoopCounter);
582                         mDownCounter = 8;
583                     }
584                 } else {
585                     mDownCounter = 8;
586                 }
587                 break;
588 
589             case STATE_SENDING_PULSE:
590                 mAudioRecording.write(inputData, inputChannelCount, numFrames);
591                 sendOneImpulse(outputData, outputChannelCount);
592                 nextState = STATE_GATHERING_ECHOS;
593                 //printf("%5d: switch to STATE_GATHERING_ECHOS\n", mLoopCounter);
594                 break;
595 
596             case STATE_GATHERING_ECHOS:
597                 numWritten = mAudioRecording.write(inputData, inputChannelCount, numFrames);
598                 peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
599                 if (peak > mMeasuredLoopGain) {
600                     mMeasuredLoopGain = peak;  // AGC might be raising gain so adjust it on the fly.
601                     // Recalculate gain that will give us a nice decaying echo.
602                     mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
603                 }
604                 // Echo input to output.
605                 for (int i = 0; i < numFrames; i++) {
606                     int ic;
607                     for (ic = 0; ic < channelsValid; ic++) {
608                         outputData[ic] = inputData[ic] * mEchoGain;
609                     }
610                     for (; ic < outputChannelCount; ic++) {
611                         outputData[ic] = 0;
612                     }
613                     inputData += inputChannelCount;
614                     outputData += outputChannelCount;
615                 }
616                 if (numWritten  < numFrames) {
617                     nextState = STATE_DONE;
618                     //printf("%5d: switch to STATE_DONE\n", mLoopCounter);
619                 }
620                 break;
621 
622             case STATE_DONE:
623             default:
624                 break;
625         }
626 
627         mState = nextState;
628         mLoopCounter++;
629     }
630 
save(const char * fileName)631     int save(const char *fileName) override {
632         return mAudioRecording.save(fileName);
633     }
634 
load(const char * fileName)635     int load(const char *fileName) override {
636         return mAudioRecording.load(fileName);
637     }
638 
639 private:
640 
641     enum echo_state_t {
642         STATE_INITIAL_SILENCE,
643         STATE_MEASURING_GAIN,
644         STATE_WAITING_FOR_SILENCE,
645         STATE_SENDING_PULSE,
646         STATE_GATHERING_ECHOS,
647         STATE_DONE,
648         STATE_FAILED
649     };
650 
651     int32_t         mDownCounter = 500;
652     int32_t         mLoopCounter = 0;
653     int32_t         mSampleIndex = 0;
654     float           mPulseThreshold = 0.02f;
655     float           mSilenceThreshold = 0.002f;
656     float           mMeasuredLoopGain = 0.0f;
657     float           mDesiredEchoGain = 0.95f;
658     float           mEchoGain = 1.0f;
659     echo_state_t    mState = STATE_INITIAL_SILENCE;
660 
661     AudioRecording  mAudioRecording; // contains only the input after the gain detection burst
662     LatencyReport   mLatencyReport;
663     // PeakDetector    mPeakDetector;
664 };
665 
666 
667 // ====================================================================================
668 /**
669  * Output a steady sinewave and analyze the return signal.
670  *
671  * Use a cosine transform to measure the predicted magnitude and relative phase of the
672  * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
673  */
674 class SineAnalyzer : public LoopbackProcessor {
675 public:
676 
getResult()677     virtual int getResult() {
678         return mState == STATE_LOCKED ? 0 : -1;
679     }
680 
report()681     void report() override {
682         printf("SineAnalyzer ------------------\n");
683         printf(LOOPBACK_RESULT_TAG "peak.amplitude     = %7.5f\n", mPeakAmplitude);
684         printf(LOOPBACK_RESULT_TAG "sine.magnitude     = %7.5f\n", mMagnitude);
685         printf(LOOPBACK_RESULT_TAG "phase.offset       = %7.5f\n", mPhaseOffset);
686         printf(LOOPBACK_RESULT_TAG "ref.phase          = %7.5f\n", mPhase);
687         printf(LOOPBACK_RESULT_TAG "frames.accumulated = %6d\n", mFramesAccumulated);
688         printf(LOOPBACK_RESULT_TAG "sine.period        = %6d\n", mSinePeriod);
689         printf(LOOPBACK_RESULT_TAG "test.state         = %6d\n", mState);
690         printf(LOOPBACK_RESULT_TAG "frame.count        = %6d\n", mFrameCounter);
691         // Did we ever get a lock?
692         bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
693         if (!gotLock) {
694             printf("ERROR - failed to lock on reference sine tone\n");
695         } else {
696             // Only print if meaningful.
697             printf(LOOPBACK_RESULT_TAG "glitch.count       = %6d\n", mGlitchCount);
698         }
699     }
700 
printStatus()701     void printStatus() override {
702         printf("st = %d, #gl = %3d,", mState, mGlitchCount);
703     }
704 
705     double calculateMagnitude(double *phasePtr = NULL) {
706         if (mFramesAccumulated == 0) {
707             return 0.0;
708         }
709         double sinMean = mSinAccumulator / mFramesAccumulated;
710         double cosMean = mCosAccumulator / mFramesAccumulated;
711         double magnitude = 2.0 * sqrt( (sinMean * sinMean) + (cosMean * cosMean ));
712         if( phasePtr != NULL )
713         {
714             double phase = M_PI_2 - atan2( sinMean, cosMean );
715             *phasePtr = phase;
716         }
717         return magnitude;
718     }
719 
720     /**
721      * @param inputData contains microphone data with sine signal feedback
722      * @param outputData contains the reference sine wave
723      */
process(float * inputData,int inputChannelCount,float * outputData,int outputChannelCount,int numFrames)724     void process(float *inputData, int inputChannelCount,
725                  float *outputData, int outputChannelCount,
726                  int numFrames) override {
727         mProcessCount++;
728 
729         float peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
730         if (peak > mPeakAmplitude) {
731             mPeakAmplitude = peak;
732         }
733 
734         for (int i = 0; i < numFrames; i++) {
735             float sample = inputData[i * inputChannelCount];
736 
737             float sinOut = sinf(mPhase);
738 
739             switch (mState) {
740                 case STATE_IDLE:
741                 case STATE_IMMUNE:
742                 case STATE_WAITING_FOR_SIGNAL:
743                     break;
744                 case STATE_WAITING_FOR_LOCK:
745                     mSinAccumulator += sample * sinOut;
746                     mCosAccumulator += sample * cosf(mPhase);
747                     mFramesAccumulated++;
748                     // Must be a multiple of the period or the calculation will not be accurate.
749                     if (mFramesAccumulated == mSinePeriod * PERIODS_NEEDED_FOR_LOCK) {
750                         mPhaseOffset = 0.0;
751                         mMagnitude = calculateMagnitude(&mPhaseOffset);
752                         if (mMagnitude > mThreshold) {
753                             if (fabs(mPreviousPhaseOffset - mPhaseOffset) < 0.001) {
754                                 mState = STATE_LOCKED;
755                                 //printf("%5d: switch to STATE_LOCKED\n", mFrameCounter);
756                             }
757                             mPreviousPhaseOffset = mPhaseOffset;
758                         }
759                         resetAccumulator();
760                     }
761                     break;
762 
763                 case STATE_LOCKED: {
764                     // Predict next sine value
765                     float predicted = sinf(mPhase + mPhaseOffset) * mMagnitude;
766                     // printf("    predicted = %f, actual = %f\n", predicted, sample);
767 
768                     float diff = predicted - sample;
769                     if (fabs(diff) > mTolerance) {
770                         mGlitchCount++;
771                         //printf("%5d: Got a glitch # %d, predicted = %f, actual = %f\n",
772                         //       mFrameCounter, mGlitchCount, predicted, sample);
773                         mState = STATE_IMMUNE;
774                         //printf("%5d: switch to STATE_IMMUNE\n", mFrameCounter);
775                         mDownCounter = mSinePeriod;  // Set duration of IMMUNE state.
776                     }
777 
778                     // Track incoming signal and slowly adjust magnitude to account
779                     // for drift in the DRC or AGC.
780                     mSinAccumulator += sample * sinOut;
781                     mCosAccumulator += sample * cosf(mPhase);
782                     mFramesAccumulated++;
783                     // Must be a multiple of the period or the calculation will not be accurate.
784                     if (mFramesAccumulated == mSinePeriod) {
785                         const double coefficient = 0.1;
786                         double phaseOffset = 0.0;
787                         double magnitude = calculateMagnitude(&phaseOffset);
788                         // One pole averaging filter.
789                         mMagnitude = (mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient);
790                         resetAccumulator();
791                     }
792                 } break;
793             }
794 
795             // Output sine wave so we can measure it.
796             outputData[i * outputChannelCount] = (sinOut * mOutputAmplitude)
797                     + (mWhiteNoise.nextRandomDouble() * mNoiseAmplitude);
798             // printf("%5d: sin(%f) = %f, %f\n", i, mPhase, sinOut,  mPhaseIncrement);
799 
800             // advance and wrap phase
801             mPhase += mPhaseIncrement;
802             if (mPhase > M_PI) {
803                 mPhase -= (2.0 * M_PI);
804             }
805 
806             mFrameCounter++;
807         }
808 
809         // Do these once per buffer.
810         switch (mState) {
811             case STATE_IDLE:
812                 mState = STATE_IMMUNE; // so we can tell when
813                 break;
814             case STATE_IMMUNE:
815                 mDownCounter -= numFrames;
816                 if (mDownCounter <= 0) {
817                     mState = STATE_WAITING_FOR_SIGNAL;
818                     //printf("%5d: switch to STATE_WAITING_FOR_SIGNAL\n", mFrameCounter);
819                 }
820                 break;
821             case STATE_WAITING_FOR_SIGNAL:
822                 if (peak > mThreshold) {
823                     mState = STATE_WAITING_FOR_LOCK;
824                     //printf("%5d: switch to STATE_WAITING_FOR_LOCK\n", mFrameCounter);
825                     resetAccumulator();
826                 }
827                 break;
828             case STATE_WAITING_FOR_LOCK:
829             case STATE_LOCKED:
830                 break;
831         }
832 
833     }
834 
resetAccumulator()835     void resetAccumulator() {
836         mFramesAccumulated = 0;
837         mSinAccumulator = 0.0;
838         mCosAccumulator = 0.0;
839     }
840 
reset()841     void reset() override {
842         mGlitchCount = 0;
843         mState = STATE_IMMUNE;
844         mDownCounter = IMMUNE_FRAME_COUNT;
845         mPhaseIncrement = 2.0 * M_PI / mSinePeriod;
846         printf("phaseInc = %f for period %d\n", mPhaseIncrement, mSinePeriod);
847         resetAccumulator();
848         mProcessCount = 0;
849     }
850 
851 private:
852 
853     enum sine_state_t {
854         STATE_IDLE,
855         STATE_IMMUNE,
856         STATE_WAITING_FOR_SIGNAL,
857         STATE_WAITING_FOR_LOCK,
858         STATE_LOCKED
859     };
860 
861     enum constants {
862         IMMUNE_FRAME_COUNT = 48 * 500,
863         PERIODS_NEEDED_FOR_LOCK = 8
864     };
865 
866     int     mSinePeriod = 79;
867     double  mPhaseIncrement = 0.0;
868     double  mPhase = 0.0;
869     double  mPhaseOffset = 0.0;
870     double  mPreviousPhaseOffset = 0.0;
871     double  mMagnitude = 0.0;
872     double  mThreshold = 0.005;
873     double  mTolerance = 0.01;
874     int32_t mFramesAccumulated = 0;
875     int32_t mProcessCount = 0;
876     double  mSinAccumulator = 0.0;
877     double  mCosAccumulator = 0.0;
878     int32_t mGlitchCount = 0;
879     double  mPeakAmplitude = 0.0;
880     int     mDownCounter = IMMUNE_FRAME_COUNT;
881     int32_t mFrameCounter = 0;
882     float   mOutputAmplitude = 0.75;
883 
884     PseudoRandom  mWhiteNoise;
885     float   mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
886 
887     sine_state_t  mState = STATE_IDLE;
888 };
889 
890 
891 #undef LOOPBACK_SAMPLE_RATE
892 #undef LOOPBACK_RESULT_TAG
893 
894 #endif /* AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H */
895