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