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 ANALYZER_LATENCY_ANALYZER_H
23 #define ANALYZER_LATENCY_ANALYZER_H
24
25 #include <algorithm>
26 #include <assert.h>
27 #include <cctype>
28 #include <iomanip>
29 #include <iostream>
30 #include <math.h>
31 #include <memory>
32 #include <sstream>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <vector>
37
38 #include "PeakDetector.h"
39 #include "PseudoRandom.h"
40 #include "RandomPulseGenerator.h"
41
42 // This is used when the code is in not in Android.
43 #ifndef ALOGD
44 #define ALOGD LOGD
45 #define ALOGE LOGE
46 #define ALOGW LOGW
47 #endif
48
49 #define LOOPBACK_RESULT_TAG "RESULT: "
50
51 // Enable or disable the optimized latency calculation.
52 #define USE_FAST_LATENCY_CALCULATION 1
53
54 static constexpr int32_t kDefaultSampleRate = 48000;
55 static constexpr int32_t kMillisPerSecond = 1000; // by definition
56 static constexpr int32_t kMaxLatencyMillis = 1000; // arbitrary and generous
57
58 struct LatencyReport {
59 int32_t latencyInFrames = 0.0;
60 double correlation = 0.0;
61
resetLatencyReport62 void reset() {
63 latencyInFrames = 0;
64 correlation = 0.0;
65 }
66 };
67
68 /**
69 * Calculate a normalized cross correlation.
70 * @return value between -1.0 and 1.0
71 */
72
calculateNormalizedCorrelation(const float * a,const float * b,int windowSize,int stride)73 static float calculateNormalizedCorrelation(const float *a,
74 const float *b,
75 int windowSize,
76 int stride) {
77 float correlation = 0.0;
78 float sumProducts = 0.0;
79 float sumSquares = 0.0;
80
81 // Correlate a against b.
82 for (int i = 0; i < windowSize; i += stride) {
83 float s1 = a[i];
84 float s2 = b[i];
85 // Use a normalized cross-correlation.
86 sumProducts += s1 * s2;
87 sumSquares += ((s1 * s1) + (s2 * s2));
88 }
89
90 if (sumSquares >= 1.0e-9) {
91 correlation = 2.0 * sumProducts / sumSquares;
92 }
93 return correlation;
94 }
95
calculateRootMeanSquare(float * data,int32_t numSamples)96 static double calculateRootMeanSquare(float *data, int32_t numSamples) {
97 double sum = 0.0;
98 for (int32_t i = 0; i < numSamples; i++) {
99 double sample = data[i];
100 sum += sample * sample;
101 }
102 return sqrt(sum / numSamples);
103 }
104
105 /**
106 * Monophonic recording with processing.
107 * Samples are stored as floats internally.
108 */
109 class AudioRecording
110 {
111 public:
112
allocate(int maxFrames)113 void allocate(int maxFrames) {
114 mData = std::make_unique<float[]>(maxFrames);
115 mMaxFrames = maxFrames;
116 mFrameCounter = 0;
117 }
118
119 // Write SHORT data from the first channel.
write(const int16_t * inputData,int32_t inputChannelCount,int32_t numFrames)120 int32_t write(const int16_t *inputData, int32_t inputChannelCount, int32_t numFrames) {
121 // stop at end of buffer
122 if ((mFrameCounter + numFrames) > mMaxFrames) {
123 numFrames = mMaxFrames - mFrameCounter;
124 }
125 for (int i = 0; i < numFrames; i++) {
126 mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
127 }
128 return numFrames;
129 }
130
131 // Write FLOAT data from the first channel.
write(const float * inputData,int32_t inputChannelCount,int32_t numFrames)132 int32_t write(const float *inputData, int32_t inputChannelCount, int32_t numFrames) {
133 // stop at end of buffer
134 if ((mFrameCounter + numFrames) > mMaxFrames) {
135 numFrames = mMaxFrames - mFrameCounter;
136 }
137 for (int i = 0; i < numFrames; i++) {
138 mData[mFrameCounter++] = inputData[i * inputChannelCount];
139 }
140 return numFrames;
141 }
142
143 // Write single FLOAT value.
write(float sample)144 int32_t write(float sample) {
145 // stop at end of buffer
146 if (mFrameCounter < mMaxFrames) {
147 mData[mFrameCounter++] = sample;
148 return 1;
149 }
150 return 0;
151 }
152
clear()153 void clear() {
154 mFrameCounter = 0;
155 }
156
size()157 int32_t size() const {
158 return mFrameCounter;
159 }
160
isFull()161 bool isFull() const {
162 return mFrameCounter >= mMaxFrames;
163 }
164
getData()165 float *getData() const {
166 return mData.get();
167 }
168
setSampleRate(int32_t sampleRate)169 void setSampleRate(int32_t sampleRate) {
170 mSampleRate = sampleRate;
171 }
172
getSampleRate()173 int32_t getSampleRate() const {
174 return mSampleRate;
175 }
176
177 /**
178 * Square the samples so they are all positive and so the peaks are emphasized.
179 */
square()180 void square() {
181 float *x = mData.get();
182 for (int i = 0; i < mFrameCounter; i++) {
183 x[i] *= x[i];
184 }
185 }
186
187 // Envelope follower that rides over the peak values.
detectPeaks(float decay)188 void detectPeaks(float decay) {
189 float level = 0.0f;
190 float *x = mData.get();
191 for (int i = 0; i < mFrameCounter; i++) {
192 level *= decay; // exponential decay
193 float input = fabs(x[i]);
194 // never fall below the input signal
195 if (input > level) {
196 level = input;
197 }
198 x[i] = level; // write result back into the array
199 }
200 }
201
202 /**
203 * Amplify a signal so that the peak matches the specified target.
204 *
205 * @param target final max value
206 * @return gain applied to signal
207 */
normalize(float target)208 float normalize(float target) {
209 float maxValue = 1.0e-9f;
210 for (int i = 0; i < mFrameCounter; i++) {
211 maxValue = std::max(maxValue, fabsf(mData[i]));
212 }
213 float gain = target / maxValue;
214 for (int i = 0; i < mFrameCounter; i++) {
215 mData[i] *= gain;
216 }
217 return gain;
218 }
219
220 private:
221 std::unique_ptr<float[]> mData;
222 int32_t mFrameCounter = 0;
223 int32_t mMaxFrames = 0;
224 int32_t mSampleRate = kDefaultSampleRate; // common default
225 };
226
227 /**
228 * Find latency using cross correlation in window of the recorded audio.
229 * The stride is used to skip over samples and reduce the CPU load.
230 */
measureLatencyFromPulsePartial(AudioRecording & recorded,int32_t recordedOffset,int32_t recordedWindowSize,AudioRecording & pulse,LatencyReport * report,int32_t stride)231 static int measureLatencyFromPulsePartial(AudioRecording &recorded,
232 int32_t recordedOffset,
233 int32_t recordedWindowSize,
234 AudioRecording &pulse,
235 LatencyReport *report,
236 int32_t stride) {
237 report->reset();
238
239 if (recordedOffset + recordedWindowSize + pulse.size() > recorded.size()) {
240 ALOGE("%s() tried to correlate past end of recording, recordedOffset = %d frames\n",
241 __func__, recordedOffset);
242 return -3;
243 }
244
245 int32_t numCorrelations = recordedWindowSize / stride;
246 if (numCorrelations < 10) {
247 ALOGE("%s() recording too small = %d frames, numCorrelations = %d\n",
248 __func__, recorded.size(), numCorrelations);
249 return -1;
250 }
251 std::unique_ptr<float[]> correlations= std::make_unique<float[]>(numCorrelations);
252
253 // Correlate pulse against the recorded data.
254 for (int32_t i = 0; i < numCorrelations; i++) {
255 const int32_t index = (i * stride) + recordedOffset;
256 float correlation = calculateNormalizedCorrelation(&recorded.getData()[index],
257 &pulse.getData()[0],
258 pulse.size(),
259 stride);
260 correlations[i] = correlation;
261 }
262 // Find highest peak in correlation array.
263 float peakCorrelation = 0.0;
264 int32_t peakIndex = -1;
265 for (int32_t i = 0; i < numCorrelations; i++) {
266 float value = fabsf(correlations[i]);
267 if (value > peakCorrelation) {
268 peakCorrelation = value;
269 peakIndex = i;
270 }
271 }
272 if (peakIndex < 0) {
273 ALOGE("%s() no signal for correlation\n", __func__);
274 return -2;
275 }
276 #if 0
277 // Dump correlation data for charting.
278 else {
279 const int32_t margin = 50;
280 int32_t startIndex = std::max(0, peakIndex - margin);
281 int32_t endIndex = std::min(numCorrelations - 1, peakIndex + margin);
282 for (int32_t index = startIndex; index < endIndex; index++) {
283 ALOGD("Correlation, %d, %f", index, correlations[index]);
284 }
285 }
286 #endif
287
288 report->latencyInFrames = recordedOffset + (peakIndex * stride);
289 report->correlation = peakCorrelation;
290
291 return 0;
292 }
293
294 #if USE_FAST_LATENCY_CALCULATION
measureLatencyFromPulse(AudioRecording & recorded,AudioRecording & pulse,LatencyReport * report)295 static int measureLatencyFromPulse(AudioRecording &recorded,
296 AudioRecording &pulse,
297 LatencyReport *report) {
298 const int32_t coarseStride = 16;
299 const int32_t fineWindowSize = coarseStride * 8;
300 const int32_t fineStride = 1;
301 LatencyReport courseReport;
302 courseReport.reset();
303 // Do a rough search, skipping over most of the samples.
304 int result = measureLatencyFromPulsePartial(recorded,
305 0, // recordedOffset,
306 recorded.size() - pulse.size(),
307 pulse,
308 &courseReport,
309 coarseStride);
310 if (result != 0) {
311 return result;
312 }
313 // Now do a fine resolution search near the coarse latency result.
314 int32_t recordedOffset = std::max(0, courseReport.latencyInFrames - (fineWindowSize / 2));
315 result = measureLatencyFromPulsePartial(recorded,
316 recordedOffset,
317 fineWindowSize,
318 pulse,
319 report,
320 fineStride );
321 return result;
322 }
323 #else
324 // TODO - When we are confident of the new code we can remove this old code.
measureLatencyFromPulse(AudioRecording & recorded,AudioRecording & pulse,LatencyReport * report)325 static int measureLatencyFromPulse(AudioRecording &recorded,
326 AudioRecording &pulse,
327 LatencyReport *report) {
328 return measureLatencyFromPulsePartial(recorded,
329 0,
330 recorded.size() - pulse.size(),
331 pulse,
332 report,
333 1 );
334 }
335 #endif
336
337 // ====================================================================================
338 class LoopbackProcessor {
339 public:
340 virtual ~LoopbackProcessor() = default;
341
342 enum result_code {
343 RESULT_OK = 0,
344 ERROR_NOISY = -99,
345 ERROR_VOLUME_TOO_LOW,
346 ERROR_VOLUME_TOO_HIGH,
347 ERROR_CONFIDENCE,
348 ERROR_INVALID_STATE,
349 ERROR_GLITCHES,
350 ERROR_NO_LOCK
351 };
352
prepareToTest()353 virtual void prepareToTest() {
354 reset();
355 }
356
reset()357 virtual void reset() {
358 mResult = 0;
359 mResetCount++;
360 }
361
362 virtual result_code processInputFrame(const float *frameData, int channelCount) = 0;
363 virtual result_code processOutputFrame(float *frameData, int channelCount) = 0;
364
process(const float * inputData,int inputChannelCount,int numInputFrames,float * outputData,int outputChannelCount,int numOutputFrames)365 void process(const float *inputData, int inputChannelCount, int numInputFrames,
366 float *outputData, int outputChannelCount, int numOutputFrames) {
367 int numBoth = std::min(numInputFrames, numOutputFrames);
368 // Process one frame at a time.
369 for (int i = 0; i < numBoth; i++) {
370 processInputFrame(inputData, inputChannelCount);
371 inputData += inputChannelCount;
372 processOutputFrame(outputData, outputChannelCount);
373 outputData += outputChannelCount;
374 }
375 // If there is more input than output.
376 for (int i = numBoth; i < numInputFrames; i++) {
377 processInputFrame(inputData, inputChannelCount);
378 inputData += inputChannelCount;
379 }
380 // If there is more output than input.
381 for (int i = numBoth; i < numOutputFrames; i++) {
382 processOutputFrame(outputData, outputChannelCount);
383 outputData += outputChannelCount;
384 }
385 }
386
387 virtual std::string analyze() = 0;
388
printStatus()389 virtual void printStatus() {};
390
getResult()391 int32_t getResult() {
392 return mResult;
393 }
394
setResult(int32_t result)395 void setResult(int32_t result) {
396 mResult = result;
397 }
398
isDone()399 virtual bool isDone() {
400 return false;
401 }
402
save(const char * fileName)403 virtual int save(const char *fileName) {
404 (void) fileName;
405 return -1;
406 }
407
load(const char * fileName)408 virtual int load(const char *fileName) {
409 (void) fileName;
410 return -1;
411 }
412
setSampleRate(int32_t sampleRate)413 virtual void setSampleRate(int32_t sampleRate) {
414 mSampleRate = sampleRate;
415 }
416
getSampleRate()417 int32_t getSampleRate() const {
418 return mSampleRate;
419 }
420
getResetCount()421 int32_t getResetCount() const {
422 return mResetCount;
423 }
424
425 /** Called when not enough input frames could be read after synchronization.
426 */
onInsufficientRead()427 virtual void onInsufficientRead() {
428 reset();
429 }
430
431 protected:
432 int32_t mResetCount = 0;
433
434 private:
435 int32_t mSampleRate = kDefaultSampleRate;
436 int32_t mResult = 0;
437 };
438
439 class LatencyAnalyzer : public LoopbackProcessor {
440 public:
441
LatencyAnalyzer()442 LatencyAnalyzer() : LoopbackProcessor() {}
443 virtual ~LatencyAnalyzer() = default;
444
445 /**
446 * Call this after the constructor because it calls other virtual methods.
447 */
448 virtual void setup() = 0;
449
450 virtual int32_t getProgress() const = 0;
451
452 virtual int getState() const = 0;
453
454 // @return latency in frames
455 virtual int32_t getMeasuredLatency() const = 0;
456
457 /**
458 * This is an overall confidence in the latency result based on correlation, SNR, etc.
459 * @return probability value between 0.0 and 1.0
460 */
getMeasuredConfidence()461 double getMeasuredConfidence() const {
462 // Limit the ratio and prevent divide-by-zero.
463 double noiseSignalRatio = getSignalRMS() <= getBackgroundRMS()
464 ? 1.0 : getBackgroundRMS() / getSignalRMS();
465 // Prevent high background noise and low signals from generating false matches.
466 double adjustedConfidence = getMeasuredCorrelation() - noiseSignalRatio;
467 return std::max(0.0, adjustedConfidence);
468 }
469
470 /**
471 * Cross correlation value for the noise pulse against
472 * the corresponding position in the normalized recording.
473 *
474 * @return value between -1.0 and 1.0
475 */
476 virtual double getMeasuredCorrelation() const = 0;
477
478 virtual double getBackgroundRMS() const = 0;
479
480 virtual double getSignalRMS() const = 0;
481
482 virtual bool hasEnoughData() const = 0;
483 };
484
485 // ====================================================================================
486 /**
487 * Measure latency given a loopback stream data.
488 * Use an encoded bit train as the sound source because it
489 * has an unambiguous correlation value.
490 * Uses a state machine to cycle through various stages.
491 *
492 */
493 class PulseLatencyAnalyzer : public LatencyAnalyzer {
494 public:
495
setup()496 void setup() override {
497 int32_t pulseLength = calculatePulseLength();
498 int32_t maxLatencyFrames = getSampleRate() * kMaxLatencyMillis / kMillisPerSecond;
499 mFramesToRecord = pulseLength + maxLatencyFrames;
500 mAudioRecording.allocate(mFramesToRecord);
501 mAudioRecording.setSampleRate(getSampleRate());
502 }
503
getState()504 int getState() const override {
505 return mState;
506 }
507
setSampleRate(int32_t sampleRate)508 void setSampleRate(int32_t sampleRate) override {
509 LoopbackProcessor::setSampleRate(sampleRate);
510 mAudioRecording.setSampleRate(sampleRate);
511 }
512
reset()513 void reset() override {
514 LoopbackProcessor::reset();
515 mState = STATE_MEASURE_BACKGROUND;
516 mDownCounter = (int32_t) (getSampleRate() * kBackgroundMeasurementLengthSeconds);
517 mLoopCounter = 0;
518
519 mPulseCursor = 0;
520 mBackgroundSumSquare = 0.0f;
521 mBackgroundSumCount = 0;
522 mBackgroundRMS = 0.0f;
523 mSignalRMS = 0.0f;
524
525 generatePulseRecording(calculatePulseLength());
526 mAudioRecording.clear();
527 mLatencyReport.reset();
528 }
529
hasEnoughData()530 bool hasEnoughData() const override {
531 return mAudioRecording.isFull();
532 }
533
isDone()534 bool isDone() override {
535 return mState == STATE_DONE;
536 }
537
getProgress()538 int32_t getProgress() const override {
539 return mAudioRecording.size();
540 }
541
analyze()542 std::string analyze() override {
543 std::stringstream report;
544 report << "PulseLatencyAnalyzer ---------------\n";
545 report << LOOPBACK_RESULT_TAG "test.state = "
546 << std::setw(8) << mState << "\n";
547 report << LOOPBACK_RESULT_TAG "test.state.name = "
548 << convertStateToText(mState) << "\n";
549 report << LOOPBACK_RESULT_TAG "background.rms = "
550 << std::setw(8) << mBackgroundRMS << "\n";
551
552 int32_t newResult = RESULT_OK;
553 if (mState != STATE_GOT_DATA) {
554 report << "WARNING - Bad state. Check volume on device.\n";
555 // setResult(ERROR_INVALID_STATE);
556 } else {
557 float gain = mAudioRecording.normalize(1.0f);
558 measureLatency();
559
560 // Calculate signalRMS even if it is bogus.
561 // Also it may be used in the confidence calculation below.
562 mSignalRMS = calculateRootMeanSquare(
563 &mAudioRecording.getData()[mLatencyReport.latencyInFrames], mPulse.size())
564 / gain;
565 if (getMeasuredConfidence() < getMinimumConfidence()) {
566 report << " ERROR - confidence too low!";
567 newResult = ERROR_CONFIDENCE;
568 }
569
570 double latencyMillis = kMillisPerSecond * (double) mLatencyReport.latencyInFrames
571 / getSampleRate();
572 report << LOOPBACK_RESULT_TAG "latency.frames = " << std::setw(8)
573 << mLatencyReport.latencyInFrames << "\n";
574 report << LOOPBACK_RESULT_TAG "latency.msec = " << std::setw(8)
575 << latencyMillis << "\n";
576 report << LOOPBACK_RESULT_TAG "latency.confidence = " << std::setw(8)
577 << getMeasuredConfidence() << "\n";
578 report << LOOPBACK_RESULT_TAG "latency.correlation = " << std::setw(8)
579 << getMeasuredCorrelation() << "\n";
580 }
581 mState = STATE_DONE;
582 if (getResult() == RESULT_OK) {
583 setResult(newResult);
584 }
585
586 return report.str();
587 }
588
getMeasuredLatency()589 int32_t getMeasuredLatency() const override {
590 return mLatencyReport.latencyInFrames;
591 }
592
getMeasuredCorrelation()593 double getMeasuredCorrelation() const override {
594 return mLatencyReport.correlation;
595 }
596
getBackgroundRMS()597 double getBackgroundRMS() const override {
598 return mBackgroundRMS;
599 }
600
getSignalRMS()601 double getSignalRMS() const override {
602 return mSignalRMS;
603 }
604
isRecordingComplete()605 bool isRecordingComplete() {
606 return mState == STATE_GOT_DATA;
607 }
608
printStatus()609 void printStatus() override {
610 ALOGD("latency: st = %d = %s", mState, convertStateToText(mState));
611 }
612
processInputFrame(const float * frameData,int)613 result_code processInputFrame(const float *frameData, int /* channelCount */) override {
614 echo_state nextState = mState;
615 mLoopCounter++;
616 float input = frameData[0];
617
618 switch (mState) {
619 case STATE_MEASURE_BACKGROUND:
620 // Measure background RMS on channel 0
621 mBackgroundSumSquare += static_cast<double>(input) * input;
622 mBackgroundSumCount++;
623 mDownCounter--;
624 if (mDownCounter <= 0) {
625 mBackgroundRMS = sqrtf(mBackgroundSumSquare / mBackgroundSumCount);
626 nextState = STATE_IN_PULSE;
627 mPulseCursor = 0;
628 }
629 break;
630
631 case STATE_IN_PULSE:
632 // Record input until the mAudioRecording is full.
633 mAudioRecording.write(input);
634 if (hasEnoughData()) {
635 nextState = STATE_GOT_DATA;
636 }
637 break;
638
639 case STATE_GOT_DATA:
640 case STATE_DONE:
641 default:
642 break;
643 }
644
645 mState = nextState;
646 return RESULT_OK;
647 }
648
processOutputFrame(float * frameData,int channelCount)649 result_code processOutputFrame(float *frameData, int channelCount) override {
650 switch (mState) {
651 case STATE_IN_PULSE:
652 if (mPulseCursor < mPulse.size()) {
653 float pulseSample = mPulse.getData()[mPulseCursor++];
654 for (int i = 0; i < channelCount; i++) {
655 frameData[i] = pulseSample;
656 }
657 } else {
658 for (int i = 0; i < channelCount; i++) {
659 frameData[i] = 0;
660 }
661 }
662 break;
663
664 case STATE_MEASURE_BACKGROUND:
665 case STATE_GOT_DATA:
666 case STATE_DONE:
667 default:
668 for (int i = 0; i < channelCount; i++) {
669 frameData[i] = 0.0f; // silence
670 }
671 break;
672 }
673
674 return RESULT_OK;
675 }
676
677 protected:
678
679 virtual int32_t calculatePulseLength() const = 0;
680
681 virtual void generatePulseRecording(int32_t pulseLength) = 0;
682
683 virtual void measureLatency() = 0;
684
getMinimumConfidence()685 virtual double getMinimumConfidence() const {
686 return 0.5;
687 }
688
689 AudioRecording mPulse;
690 AudioRecording mAudioRecording; // contains only the input after starting the pulse
691 LatencyReport mLatencyReport;
692
693 static constexpr int32_t kPulseLengthMillis = 500;
694 float mPulseAmplitude = 0.5f;
695 double mBackgroundRMS = 0.0;
696 double mSignalRMS = 0.0;
697
698 private:
699
700 enum echo_state {
701 STATE_MEASURE_BACKGROUND,
702 STATE_IN_PULSE,
703 STATE_GOT_DATA, // must match RoundTripLatencyActivity.java
704 STATE_DONE,
705 };
706
convertStateToText(echo_state state)707 const char *convertStateToText(echo_state state) {
708 switch (state) {
709 case STATE_MEASURE_BACKGROUND:
710 return "INIT";
711 case STATE_IN_PULSE:
712 return "PULSE";
713 case STATE_GOT_DATA:
714 return "GOT_DATA";
715 case STATE_DONE:
716 return "DONE";
717 }
718 return "UNKNOWN";
719 }
720
721 int32_t mDownCounter = 500;
722 int32_t mLoopCounter = 0;
723 echo_state mState = STATE_MEASURE_BACKGROUND;
724
725 static constexpr double kBackgroundMeasurementLengthSeconds = 0.5;
726
727 int32_t mPulseCursor = 0;
728
729 double mBackgroundSumSquare = 0.0;
730 int32_t mBackgroundSumCount = 0;
731 int32_t mFramesToRecord = 0;
732
733 };
734
735 /**
736 * This algorithm uses a series of random bits encoded using the
737 * Manchester encoder. It works well for wired loopback but not very well for
738 * through the air loopback.
739 */
740 class EncodedRandomLatencyAnalyzer : public PulseLatencyAnalyzer {
741
742 protected:
743
calculatePulseLength()744 int32_t calculatePulseLength() const override {
745 // Calculate integer number of bits.
746 int32_t numPulseBits = getSampleRate() * kPulseLengthMillis
747 / (kFramesPerEncodedBit * kMillisPerSecond);
748 return numPulseBits * kFramesPerEncodedBit;
749 }
750
generatePulseRecording(int32_t pulseLength)751 void generatePulseRecording(int32_t pulseLength) override {
752 mPulse.allocate(pulseLength);
753 RandomPulseGenerator pulser(kFramesPerEncodedBit);
754 for (int i = 0; i < pulseLength; i++) {
755 mPulse.write(pulser.nextFloat() * mPulseAmplitude);
756 }
757 }
758
getMinimumConfidence()759 double getMinimumConfidence() const override {
760 return 0.2;
761 }
762
measureLatency()763 void measureLatency() override {
764 measureLatencyFromPulse(mAudioRecording,
765 mPulse,
766 &mLatencyReport);
767 }
768
769 private:
770 static constexpr int32_t kFramesPerEncodedBit = 8; // multiple of 2
771 };
772
773 /**
774 * This algorithm uses White Noise sent in a short burst pattern.
775 * The original signal and the recorded signal are then run through
776 * an envelope follower to convert the fine detail into more of
777 * a rectangular block before the correlation phase.
778 */
779 class WhiteNoiseLatencyAnalyzer : public PulseLatencyAnalyzer {
780
781 protected:
782
calculatePulseLength()783 int32_t calculatePulseLength() const override {
784 return getSampleRate() * kPulseLengthMillis / kMillisPerSecond;
785 }
786
generatePulseRecording(int32_t pulseLength)787 void generatePulseRecording(int32_t pulseLength) override {
788 mPulse.allocate(pulseLength);
789 // Turn the noise on and off to sharpen the correlation peak.
790 // Use more zeros than ones so that the correlation will be less than 0.5 even when there
791 // is a strong background noise.
792 int8_t pattern[] = {1, 0, 0,
793 1, 1, 0, 0, 0,
794 1, 1, 1, 0, 0, 0, 0,
795 1, 1, 1, 1, 0, 0, 0, 0, 0
796 };
797 PseudoRandom random;
798 const int32_t numSections = sizeof(pattern);
799 const int32_t framesPerSection = pulseLength / numSections;
800 for (int section = 0; section < numSections; section++) {
801 if (pattern[section]) {
802 for (int i = 0; i < framesPerSection; i++) {
803 mPulse.write((float) (random.nextRandomDouble() * mPulseAmplitude));
804 }
805 } else {
806 for (int i = 0; i < framesPerSection; i++) {
807 mPulse.write(0.0f);
808 }
809 }
810 }
811 // Write any remaining frames.
812 int32_t framesWritten = framesPerSection * numSections;
813 for (int i = framesWritten; i < pulseLength; i++) {
814 mPulse.write(0.0f);
815 }
816 }
817
measureLatency()818 void measureLatency() override {
819 // Smooth out the noise so we see rectangular blocks.
820 // This improves immunity against phase cancellation and distortion.
821 static constexpr float decay = 0.99f; // just under 1.0, lower numbers decay faster
822 mAudioRecording.detectPeaks(decay);
823 mPulse.detectPeaks(decay);
824 measureLatencyFromPulse(mAudioRecording,
825 mPulse,
826 &mLatencyReport);
827 }
828
829 };
830
831 #endif // ANALYZER_LATENCY_ANALYZER_H
832