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