aaudio_loopback: port latency analyzer from OboeTester

Integrate with aaudio_loopback.
Eventually this could be used in the CTS Verifier for latency testing.

Pass std:string back from analyze().
General cleanup.

Test: adb shell aaudio_loopback
Test: check the latency.msec value
Change-Id: Ibf7128d78d47d91e0bf314344ca450d7f70b3ceb
diff --git a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
deleted file mode 100644
index 8eb70b1..0000000
--- a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
+++ /dev/null
@@ -1,1114 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Tools for measuring latency and for detecting glitches.
- * These classes are pure math and can be used with any audio system.
- */
-
-#ifndef AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H
-#define AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H
-
-#include <algorithm>
-#include <assert.h>
-#include <cctype>
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <audio_utils/sndfile.h>
-
-// Tag for machine readable results as property = value pairs
-#define LOOPBACK_RESULT_TAG      "RESULT: "
-
-constexpr int32_t kDefaultSampleRate = 48000;
-constexpr int32_t kMillisPerSecond   = 1000;
-constexpr int32_t kMinLatencyMillis  = 4;    // arbitrary and very low
-constexpr int32_t kMaxLatencyMillis  = 400;  // arbitrary and generous
-constexpr double  kMaxEchoGain       = 10.0; // based on experiments, otherwise too noisy
-constexpr double  kMinimumConfidence = 0.5;
-
-static void printAudioScope(float sample) {
-    const int maxStars = 80; // arbitrary, fits on one line
-    char c = '*';
-    if (sample < -1.0) {
-        sample = -1.0;
-        c = '$';
-    } else if (sample > 1.0) {
-        sample = 1.0;
-        c = '$';
-    }
-    int numSpaces = (int) (((sample + 1.0) * 0.5) * maxStars);
-    for (int i = 0; i < numSpaces; i++) {
-        putchar(' ');
-    }
-    printf("%c\n", c);
-}
-
-/*
-
-FIR filter designed with
-http://t-filter.appspot.com
-
-sampling frequency: 48000 Hz
-
-* 0 Hz - 8000 Hz
-  gain = 1.2
-  desired ripple = 5 dB
-  actual ripple = 5.595266169703693 dB
-
-* 12000 Hz - 20000 Hz
-  gain = 0
-  desired attenuation = -40 dB
-  actual attenuation = -37.58691566571914 dB
-
-*/
-
-#define FILTER_TAP_NUM 11
-
-static const float sFilterTaps8000[FILTER_TAP_NUM] = {
-        -0.05944219353343189f,
-        -0.07303434839503208f,
-        -0.037690487672689066f,
-        0.1870480506596512f,
-        0.3910337357836833f,
-        0.5333672385425637f,
-        0.3910337357836833f,
-        0.1870480506596512f,
-        -0.037690487672689066f,
-        -0.07303434839503208f,
-        -0.05944219353343189f
-};
-
-class LowPassFilter {
-public:
-
-    /*
-     * Filter one input sample.
-     * @return filtered output
-     */
-    float filter(float input) {
-        float output = 0.0f;
-        mX[mCursor] = input;
-        // Index backwards over x.
-        int xIndex = mCursor + FILTER_TAP_NUM;
-        // Write twice so we avoid having to wrap in the middle of the convolution.
-        mX[xIndex] = input;
-        for (int i = 0; i < FILTER_TAP_NUM; i++) {
-            output += sFilterTaps8000[i] * mX[xIndex--];
-        }
-        if (++mCursor >= FILTER_TAP_NUM) {
-            mCursor = 0;
-        }
-        return output;
-    }
-
-    /**
-     * @return true if PASSED
-     */
-    bool test() {
-        // Measure the impulse of the filter at different phases so we exercise
-        // all the wraparound cases in the FIR.
-        for (int offset = 0; offset < (FILTER_TAP_NUM * 2); offset++ ) {
-            // printf("LowPassFilter: cursor = %d\n", mCursor);
-            // Offset by one each time.
-            if (filter(0.0f) != 0.0f) {
-                printf("ERROR: filter should return 0.0 before impulse response\n");
-                return false;
-            }
-            for (int i = 0; i < FILTER_TAP_NUM; i++) {
-                float output = filter((i == 0) ? 1.0f : 0.0f); // impulse
-                if (output != sFilterTaps8000[i]) {
-                    printf("ERROR: filter should return impulse response\n");
-                    return false;
-                }
-            }
-            for (int i = 0; i < FILTER_TAP_NUM; i++) {
-                if (filter(0.0f) != 0.0f) {
-                    printf("ERROR: filter should return 0.0 after impulse response\n");
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-private:
-    float   mX[FILTER_TAP_NUM * 2]{}; // twice as big as needed to avoid wrapping
-    int32_t mCursor = 0;
-};
-
-// A narrow impulse seems to have better immunity against over estimating the
-// latency due to detecting subharmonics by the auto-correlator.
-static const float s_Impulse[] = {
-        0.0f, 0.0f, 0.0f, 0.0f, 0.3f, // silence on each side of the impulse
-        0.99f, 0.0f, -0.99f, // bipolar with one zero crossing in middle
-        -0.3f, 0.0f, 0.0f, 0.0f, 0.0f
-};
-
-constexpr int32_t kImpulseSizeInFrames = (int32_t)(sizeof(s_Impulse) / sizeof(s_Impulse[0]));
-
-class PseudoRandom {
-public:
-    PseudoRandom() {}
-    PseudoRandom(int64_t seed)
-            :    mSeed(seed)
-    {}
-
-    /**
-     * Returns the next random double from -1.0 to 1.0
-     *
-     * @return value from -1.0 to 1.0
-     */
-     double nextRandomDouble() {
-        return nextRandomInteger() * (0.5 / (((int32_t)1) << 30));
-    }
-
-    /** Calculate random 32 bit number using linear-congruential method. */
-    int32_t nextRandomInteger() {
-        // Use values for 64-bit sequence from MMIX by Donald Knuth.
-        mSeed = (mSeed * (int64_t)6364136223846793005) + (int64_t)1442695040888963407;
-        return (int32_t) (mSeed >> 32); // The higher bits have a longer sequence.
-    }
-
-private:
-    int64_t mSeed = 99887766;
-};
-
-
-typedef struct LatencyReport_s {
-    double latencyInFrames;
-    double confidence;
-} LatencyReport;
-
-static double calculateCorrelation(const float *a,
-                                   const float *b,
-                                   int windowSize)
-{
-    double correlation = 0.0;
-    double sumProducts = 0.0;
-    double sumSquares = 0.0;
-
-    // Correlate a against b.
-    for (int i = 0; i < windowSize; i++) {
-        float s1 = a[i];
-        float s2 = b[i];
-        // Use a normalized cross-correlation.
-        sumProducts += s1 * s2;
-        sumSquares += ((s1 * s1) + (s2 * s2));
-    }
-
-    if (sumSquares >= 0.00000001) {
-        correlation = (float) (2.0 * sumProducts / sumSquares);
-    }
-    return correlation;
-}
-
-static int measureLatencyFromEchos(const float *data,
-                                   int32_t numFloats,
-                                   int32_t sampleRate,
-                                   LatencyReport *report) {
-    // Allocate results array
-    const int minReasonableLatencyFrames = sampleRate * kMinLatencyMillis / kMillisPerSecond;
-    const int maxReasonableLatencyFrames = sampleRate * kMaxLatencyMillis / kMillisPerSecond;
-    int32_t maxCorrelationSize = maxReasonableLatencyFrames * 3;
-    int numCorrelations = std::min(numFloats, maxCorrelationSize);
-    float *correlations = new float[numCorrelations]{};
-    float *harmonicSums = new float[numCorrelations]{};
-
-    // Perform sliding auto-correlation.
-    // Skip first frames to avoid huge peak at zero offset.
-    for (int i = minReasonableLatencyFrames; i < numCorrelations; i++) {
-        int32_t remaining = numFloats - i;
-        float correlation = (float) calculateCorrelation(&data[i], data, remaining);
-        correlations[i] = correlation;
-        // printf("correlation[%d] = %f\n", ic, correlation);
-    }
-
-    // Apply a technique similar to Harmonic Product Spectrum Analysis to find echo fundamental.
-    // Add higher harmonics mapped onto lower harmonics. This reinforces the "fundamental" echo.
-    const int numEchoes = 8;
-    for (int partial = 1; partial < numEchoes; partial++) {
-        for (int i = minReasonableLatencyFrames; i < numCorrelations; i++) {
-            harmonicSums[i / partial] += correlations[i] / partial;
-        }
-    }
-
-    // Find highest peak in correlation array.
-    float maxCorrelation = 0.0;
-    int peakIndex = 0;
-    for (int i = 0; i < numCorrelations; i++) {
-        if (harmonicSums[i] > maxCorrelation) {
-            maxCorrelation = harmonicSums[i];
-            peakIndex = i;
-            // printf("maxCorrelation = %f at %d\n", maxCorrelation, peakIndex);
-        }
-    }
-    report->latencyInFrames = peakIndex;
-/*
-    {
-        int32_t topPeak = peakIndex * 7 / 2;
-        for (int i = 0; i < topPeak; i++) {
-            float sample = harmonicSums[i];
-            printf("%4d: %7.5f ", i, sample);
-            printAudioScope(sample);
-        }
-    }
-*/
-
-    // Calculate confidence.
-    if (maxCorrelation < 0.001) {
-        report->confidence = 0.0;
-    } else {
-        // Compare peak to average value around peak.
-        int32_t numSamples = std::min(numCorrelations, peakIndex * 2);
-        if (numSamples <= 0) {
-            report->confidence = 0.0;
-        } else {
-            double sum = 0.0;
-            for (int i = 0; i < numSamples; i++) {
-                sum += harmonicSums[i];
-            }
-            const double average = sum / numSamples;
-            const double ratio = average / maxCorrelation; // will be < 1.0
-            report->confidence = 1.0 - sqrt(ratio);
-        }
-    }
-
-    delete[] correlations;
-    delete[] harmonicSums;
-    return 0;
-}
-
-class AudioRecording
-{
-public:
-    AudioRecording() {
-    }
-    ~AudioRecording() {
-        delete[] mData;
-    }
-
-    void allocate(int maxFrames) {
-        delete[] mData;
-        mData = new float[maxFrames];
-        mMaxFrames = maxFrames;
-    }
-
-    // Write SHORT data from the first channel.
-    int32_t write(int16_t *inputData, int32_t inputChannelCount, int32_t numFrames) {
-        // stop at end of buffer
-        if ((mFrameCounter + numFrames) > mMaxFrames) {
-            numFrames = mMaxFrames - mFrameCounter;
-        }
-        for (int i = 0; i < numFrames; i++) {
-            mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
-        }
-        return numFrames;
-    }
-
-    // Write FLOAT data from the first channel.
-    int32_t write(float *inputData, int32_t inputChannelCount, int32_t numFrames) {
-        // stop at end of buffer
-        if ((mFrameCounter + numFrames) > mMaxFrames) {
-            numFrames = mMaxFrames - mFrameCounter;
-        }
-        for (int i = 0; i < numFrames; i++) {
-            mData[mFrameCounter++] = inputData[i * inputChannelCount];
-        }
-        return numFrames;
-    }
-
-    int32_t size() {
-        return mFrameCounter;
-    }
-
-    float *getData() {
-        return mData;
-    }
-
-    void setSampleRate(int32_t sampleRate) {
-        mSampleRate = sampleRate;
-    }
-
-    int32_t getSampleRate() {
-        return mSampleRate;
-    }
-
-    int save(const char *fileName, bool writeShorts = true) {
-        SNDFILE *sndFile = nullptr;
-        int written = 0;
-        SF_INFO info = {
-                .frames = mFrameCounter,
-                .samplerate = mSampleRate,
-                .channels = 1,
-                .format = SF_FORMAT_WAV | (writeShorts ? SF_FORMAT_PCM_16 : SF_FORMAT_FLOAT)
-        };
-
-        sndFile = sf_open(fileName, SFM_WRITE, &info);
-        if (sndFile == nullptr) {
-            printf("AudioRecording::save(%s) failed to open file\n", fileName);
-            return -errno;
-        }
-
-        written = sf_writef_float(sndFile, mData, mFrameCounter);
-
-        sf_close(sndFile);
-        return written;
-    }
-
-    int load(const char *fileName) {
-        SNDFILE *sndFile = nullptr;
-        SF_INFO info;
-
-        sndFile = sf_open(fileName, SFM_READ, &info);
-        if (sndFile == nullptr) {
-            printf("AudioRecording::load(%s) failed to open file\n", fileName);
-            return -errno;
-        }
-
-        assert(info.channels == 1);
-        assert(info.format == SF_FORMAT_FLOAT);
-
-        setSampleRate(info.samplerate);
-        allocate(info.frames);
-        mFrameCounter = sf_readf_float(sndFile, mData, info.frames);
-
-        sf_close(sndFile);
-        return mFrameCounter;
-    }
-
-    /**
-     * Square the samples so they are all positive and so the peaks are emphasized.
-     */
-    void square() {
-        for (int i = 0; i < mFrameCounter; i++) {
-            const float sample = mData[i];
-            mData[i] = sample * sample;
-        }
-    }
-
-    /**
-     * Low pass filter the recording using a simple FIR filter.
-     * Note that the lowpass filter cutoff tracks the sample rate.
-     * That is OK because the impulse width is a fixed number of samples.
-     */
-    void lowPassFilter() {
-        for (int i = 0; i < mFrameCounter; i++) {
-            mData[i] = mLowPassFilter.filter(mData[i]);
-        }
-    }
-
-    /**
-     * Remove DC offset using a one-pole one-zero IIR filter.
-     */
-    void dcBlocker() {
-        const float R = 0.996; // narrow notch at zero Hz
-        float x1 = 0.0;
-        float y1 = 0.0;
-        for (int i = 0; i < mFrameCounter; i++) {
-            const float x = mData[i];
-            const float y = x - x1 + (R * y1);
-            mData[i] = y;
-            y1 = y;
-            x1 = x;
-        }
-    }
-
-private:
-    float        *mData = nullptr;
-    int32_t       mFrameCounter = 0;
-    int32_t       mMaxFrames = 0;
-    int32_t       mSampleRate = kDefaultSampleRate; // common default
-    LowPassFilter mLowPassFilter;
-};
-
-// ====================================================================================
-class LoopbackProcessor {
-public:
-    virtual ~LoopbackProcessor() = default;
-
-
-    enum process_result {
-        PROCESS_RESULT_OK,
-        PROCESS_RESULT_GLITCH
-    };
-
-    virtual void reset() {}
-
-    virtual process_result process(float *inputData, int inputChannelCount,
-                 float *outputData, int outputChannelCount,
-                 int numFrames) = 0;
-
-
-    virtual void report() = 0;
-
-    virtual void printStatus() {};
-
-    int32_t getResult() {
-        return mResult;
-    }
-
-    void setResult(int32_t result) {
-        mResult = result;
-    }
-
-    virtual bool isDone() {
-        return false;
-    }
-
-    virtual int save(const char *fileName) {
-        (void) fileName;
-        return AAUDIO_ERROR_UNIMPLEMENTED;
-    }
-
-    virtual int load(const char *fileName) {
-        (void) fileName;
-        return AAUDIO_ERROR_UNIMPLEMENTED;
-    }
-
-    virtual void setSampleRate(int32_t sampleRate) {
-        mSampleRate = sampleRate;
-    }
-
-    int32_t getSampleRate() {
-        return mSampleRate;
-    }
-
-    // Measure peak amplitude of buffer.
-    static float measurePeakAmplitude(float *inputData, int inputChannelCount, int numFrames) {
-        float peak = 0.0f;
-        for (int i = 0; i < numFrames; i++) {
-            const float pos = fabs(*inputData);
-            if (pos > peak) {
-                peak = pos;
-            }
-            inputData += inputChannelCount;
-        }
-        return peak;
-    }
-
-
-private:
-    int32_t mSampleRate = kDefaultSampleRate;
-    int32_t mResult = 0;
-};
-
-class PeakDetector {
-public:
-    float process(float input) {
-        float output = mPrevious * mDecay;
-        if (input > output) {
-            output = input;
-        }
-        mPrevious = output;
-        return output;
-    }
-
-private:
-    float  mDecay = 0.99f;
-    float  mPrevious = 0.0f;
-};
-
-// ====================================================================================
-/**
- * Measure latency given a loopback stream data.
- * Uses a state machine to cycle through various stages including:
- *
- */
-class EchoAnalyzer : public LoopbackProcessor {
-public:
-
-    EchoAnalyzer() : LoopbackProcessor() {
-        mAudioRecording.allocate(2 * getSampleRate());
-        mAudioRecording.setSampleRate(getSampleRate());
-    }
-
-    void setSampleRate(int32_t sampleRate) override {
-        LoopbackProcessor::setSampleRate(sampleRate);
-        mAudioRecording.setSampleRate(sampleRate);
-    }
-
-    void reset() override {
-        mDownCounter = getSampleRate() / 2;
-        mLoopCounter = 0;
-        mMeasuredLoopGain = 0.0f;
-        mEchoGain = 1.0f;
-        mState = STATE_INITIAL_SILENCE;
-    }
-
-    virtual bool isDone() {
-        return mState == STATE_DONE || mState == STATE_FAILED;
-    }
-
-    void setGain(float gain) {
-        mEchoGain = gain;
-    }
-
-    float getGain() {
-        return mEchoGain;
-    }
-
-    bool testLowPassFilter() {
-        LowPassFilter filter;
-        return filter.test();
-    }
-
-    void report() override {
-        printf("EchoAnalyzer ---------------\n");
-        if (getResult() != 0) {
-            printf(LOOPBACK_RESULT_TAG "result          = %d\n", getResult());
-            return;
-        }
-
-        // printf("LowPassFilter test %s\n", testLowPassFilter() ? "PASSED" : "FAILED");
-
-        printf(LOOPBACK_RESULT_TAG "measured.gain          = %8f\n", mMeasuredLoopGain);
-        printf(LOOPBACK_RESULT_TAG "echo.gain              = %8f\n", mEchoGain);
-        printf(LOOPBACK_RESULT_TAG "test.state             = %8d\n", mState);
-        printf(LOOPBACK_RESULT_TAG "test.state.name        = %8s\n", convertStateToText(mState));
-
-        if (mState == STATE_WAITING_FOR_SILENCE) {
-            printf("WARNING - Stuck waiting for silence. Input may be too noisy!\n");
-            setResult(ERROR_NOISY);
-        } else if (mMeasuredLoopGain >= 0.9999) {
-            printf("   ERROR - clipping, turn down volume slightly\n");
-            setResult(ERROR_CLIPPING);
-        } else if (mState != STATE_DONE && mState != STATE_GATHERING_ECHOS) {
-            printf("WARNING - Bad state. Check volume on device.\n");
-            setResult(ERROR_INVALID_STATE);
-        } else {
-            // Cleanup the signal to improve the auto-correlation.
-            mAudioRecording.dcBlocker();
-            mAudioRecording.square();
-            mAudioRecording.lowPassFilter();
-
-            printf("Please wait several seconds for auto-correlation to complete.\n");
-            measureLatencyFromEchos(mAudioRecording.getData(),
-                                    mAudioRecording.size(),
-                                    getSampleRate(),
-                                    &mLatencyReport);
-
-            double latencyMillis = kMillisPerSecond * (double) mLatencyReport.latencyInFrames
-                                   / getSampleRate();
-            printf(LOOPBACK_RESULT_TAG "latency.frames         = %8.2f\n",
-                   mLatencyReport.latencyInFrames);
-            printf(LOOPBACK_RESULT_TAG "latency.msec           = %8.2f\n",
-                   latencyMillis);
-            printf(LOOPBACK_RESULT_TAG "latency.confidence     = %8.6f\n",
-                   mLatencyReport.confidence);
-            if (mLatencyReport.confidence < kMinimumConfidence) {
-                printf("   ERROR - confidence too low!\n");
-                setResult(ERROR_CONFIDENCE);
-            }
-        }
-    }
-
-    void printStatus() override {
-        printf("st = %d, echo gain = %f ", mState, mEchoGain);
-    }
-
-    void sendImpulses(float *outputData, int outputChannelCount, int numFrames) {
-        while (numFrames-- > 0) {
-            float sample = s_Impulse[mSampleIndex++];
-            if (mSampleIndex >= kImpulseSizeInFrames) {
-                mSampleIndex = 0;
-            }
-
-            *outputData = sample;
-            outputData += outputChannelCount;
-        }
-    }
-
-    void sendOneImpulse(float *outputData, int outputChannelCount) {
-        mSampleIndex = 0;
-        sendImpulses(outputData, outputChannelCount, kImpulseSizeInFrames);
-    }
-
-    // @return number of frames for a typical block of processing
-    int32_t getBlockFrames() {
-        return getSampleRate() / 8;
-    }
-
-    process_result process(float *inputData, int inputChannelCount,
-                 float *outputData, int outputChannelCount,
-                 int numFrames) override {
-        int channelsValid = std::min(inputChannelCount, outputChannelCount);
-        float peak = 0.0f;
-        int numWritten;
-        int numSamples;
-
-        echo_state nextState = mState;
-
-        switch (mState) {
-            case STATE_INITIAL_SILENCE:
-                // Output silence at the beginning.
-                numSamples = numFrames * outputChannelCount;
-                for (int i = 0; i < numSamples; i++) {
-                    outputData[i] = 0;
-                }
-                mDownCounter -= numFrames;
-                if (mDownCounter <= 0) {
-                    nextState = STATE_MEASURING_GAIN;
-                    //printf("%5d: switch to STATE_MEASURING_GAIN\n", mLoopCounter);
-                    mDownCounter = getBlockFrames() * 2;
-                }
-                break;
-
-            case STATE_MEASURING_GAIN:
-                sendImpulses(outputData, outputChannelCount, numFrames);
-                peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
-                // If we get several in a row then go to next state.
-                if (peak > mPulseThreshold) {
-                    mDownCounter -= numFrames;
-                    if (mDownCounter <= 0) {
-                        //printf("%5d: switch to STATE_WAITING_FOR_SILENCE, measured peak = %f\n",
-                        //       mLoopCounter, peak);
-                        mDownCounter = getBlockFrames();
-                        mMeasuredLoopGain = peak;  // assumes original pulse amplitude is one
-                        mSilenceThreshold = peak * 0.1; // scale silence to measured pulse
-                        // Calculate gain that will give us a nice decaying echo.
-                        mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
-                        if (mEchoGain > kMaxEchoGain) {
-                            printf("ERROR - loop gain too low. Increase the volume.\n");
-                            nextState = STATE_FAILED;
-                        } else {
-                            nextState = STATE_WAITING_FOR_SILENCE;
-                        }
-                    }
-                } else if (numFrames > kImpulseSizeInFrames){ // ignore short callbacks
-                    mDownCounter = getBlockFrames();
-                }
-                break;
-
-            case STATE_WAITING_FOR_SILENCE:
-                // Output silence and wait for the echos to die down.
-                numSamples = numFrames * outputChannelCount;
-                for (int i = 0; i < numSamples; i++) {
-                    outputData[i] = 0;
-                }
-                peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
-                // If we get several in a row then go to next state.
-                if (peak < mSilenceThreshold) {
-                    mDownCounter -= numFrames;
-                    if (mDownCounter <= 0) {
-                        nextState = STATE_SENDING_PULSE;
-                        //printf("%5d: switch to STATE_SENDING_PULSE\n", mLoopCounter);
-                        mDownCounter = getBlockFrames();
-                    }
-                } else {
-                    mDownCounter = getBlockFrames();
-                }
-                break;
-
-            case STATE_SENDING_PULSE:
-                mAudioRecording.write(inputData, inputChannelCount, numFrames);
-                sendOneImpulse(outputData, outputChannelCount);
-                nextState = STATE_GATHERING_ECHOS;
-                //printf("%5d: switch to STATE_GATHERING_ECHOS\n", mLoopCounter);
-                break;
-
-            case STATE_GATHERING_ECHOS:
-                numWritten = mAudioRecording.write(inputData, inputChannelCount, numFrames);
-                peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
-                if (peak > mMeasuredLoopGain) {
-                    mMeasuredLoopGain = peak;  // AGC might be raising gain so adjust it on the fly.
-                    // Recalculate gain that will give us a nice decaying echo.
-                    mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
-                }
-                // Echo input to output.
-                for (int i = 0; i < numFrames; i++) {
-                    int ic;
-                    for (ic = 0; ic < channelsValid; ic++) {
-                        outputData[ic] = inputData[ic] * mEchoGain;
-                    }
-                    for (; ic < outputChannelCount; ic++) {
-                        outputData[ic] = 0;
-                    }
-                    inputData += inputChannelCount;
-                    outputData += outputChannelCount;
-                }
-                if (numWritten  < numFrames) {
-                    nextState = STATE_DONE;
-                }
-                break;
-
-            case STATE_DONE:
-            case STATE_FAILED:
-            default:
-                break;
-        }
-
-        mState = nextState;
-        mLoopCounter++;
-        return PROCESS_RESULT_OK;
-    }
-
-    int save(const char *fileName) override {
-        return mAudioRecording.save(fileName);
-    }
-
-    int load(const char *fileName) override {
-        int result = mAudioRecording.load(fileName);
-        setSampleRate(mAudioRecording.getSampleRate());
-        mState = STATE_DONE;
-        return result;
-    }
-
-private:
-
-    enum error_code {
-        ERROR_OK = 0,
-        ERROR_NOISY = -99,
-        ERROR_CLIPPING,
-        ERROR_CONFIDENCE,
-        ERROR_INVALID_STATE
-    };
-
-    enum echo_state {
-        STATE_INITIAL_SILENCE,
-        STATE_MEASURING_GAIN,
-        STATE_WAITING_FOR_SILENCE,
-        STATE_SENDING_PULSE,
-        STATE_GATHERING_ECHOS,
-        STATE_DONE,
-        STATE_FAILED
-    };
-
-    const char *convertStateToText(echo_state state) {
-        const char *result = "Unknown";
-        switch(state) {
-            case STATE_INITIAL_SILENCE:
-                result = "INIT";
-                break;
-            case STATE_MEASURING_GAIN:
-                result = "GAIN";
-                break;
-            case STATE_WAITING_FOR_SILENCE:
-                result = "SILENCE";
-                break;
-            case STATE_SENDING_PULSE:
-                result = "PULSE";
-                break;
-            case STATE_GATHERING_ECHOS:
-                result = "ECHOS";
-                break;
-            case STATE_DONE:
-                result = "DONE";
-                break;
-            case STATE_FAILED:
-                result = "FAILED";
-                break;
-        }
-        return result;
-    }
-
-
-    int32_t         mDownCounter = 500;
-    int32_t         mLoopCounter = 0;
-    int32_t         mSampleIndex = 0;
-    float           mPulseThreshold = 0.02f;
-    float           mSilenceThreshold = 0.002f;
-    float           mMeasuredLoopGain = 0.0f;
-    float           mDesiredEchoGain = 0.95f;
-    float           mEchoGain = 1.0f;
-    echo_state      mState = STATE_INITIAL_SILENCE;
-
-    AudioRecording  mAudioRecording; // contains only the input after the gain detection burst
-    LatencyReport   mLatencyReport;
-    // PeakDetector    mPeakDetector;
-};
-
-
-// ====================================================================================
-/**
- * Output a steady sinewave and analyze the return signal.
- *
- * Use a cosine transform to measure the predicted magnitude and relative phase of the
- * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
- */
-class SineAnalyzer : public LoopbackProcessor {
-public:
-
-    void report() override {
-        printf("SineAnalyzer ------------------\n");
-        printf(LOOPBACK_RESULT_TAG "peak.amplitude     = %8f\n", mPeakAmplitude);
-        printf(LOOPBACK_RESULT_TAG "sine.magnitude     = %8f\n", mMagnitude);
-        printf(LOOPBACK_RESULT_TAG "peak.noise         = %8f\n", mPeakNoise);
-        printf(LOOPBACK_RESULT_TAG "rms.noise          = %8f\n", mRootMeanSquareNoise);
-        float amplitudeRatio = mMagnitude / mPeakNoise;
-        float signalToNoise = amplitudeRatio * amplitudeRatio;
-        printf(LOOPBACK_RESULT_TAG "signal.to.noise    = %8.2f\n", signalToNoise);
-        float signalToNoiseDB = 10.0 * log(signalToNoise);
-        printf(LOOPBACK_RESULT_TAG "signal.to.noise.db = %8.2f\n", signalToNoiseDB);
-        if (signalToNoiseDB < MIN_SNRATIO_DB) {
-            printf("ERROR - signal to noise ratio is too low! < %d dB. Adjust volume.\n", MIN_SNRATIO_DB);
-            setResult(ERROR_NOISY);
-        }
-        printf(LOOPBACK_RESULT_TAG "frames.accumulated = %8d\n", mFramesAccumulated);
-        printf(LOOPBACK_RESULT_TAG "sine.period        = %8d\n", mSinePeriod);
-        printf(LOOPBACK_RESULT_TAG "test.state         = %8d\n", mState);
-        printf(LOOPBACK_RESULT_TAG "frame.count        = %8d\n", mFrameCounter);
-        // Did we ever get a lock?
-        bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
-        if (!gotLock) {
-            printf("ERROR - failed to lock on reference sine tone\n");
-            setResult(ERROR_NO_LOCK);
-        } else {
-            // Only print if meaningful.
-            printf(LOOPBACK_RESULT_TAG "glitch.count       = %8d\n", mGlitchCount);
-            printf(LOOPBACK_RESULT_TAG "max.glitch         = %8f\n", mMaxGlitchDelta);
-            if (mGlitchCount > 0) {
-                printf("ERROR - number of glitches > 0\n");
-                setResult(ERROR_GLITCHES);
-            }
-        }
-    }
-
-    void printStatus() override {
-        printf("st = %d, #gl = %3d,", mState, mGlitchCount);
-    }
-
-    double calculateMagnitude(double *phasePtr = NULL) {
-        if (mFramesAccumulated == 0) {
-            return 0.0;
-        }
-        double sinMean = mSinAccumulator / mFramesAccumulated;
-        double cosMean = mCosAccumulator / mFramesAccumulated;
-        double magnitude = 2.0 * sqrt( (sinMean * sinMean) + (cosMean * cosMean ));
-        if( phasePtr != NULL )
-        {
-            double phase = M_PI_2 - atan2( sinMean, cosMean );
-            *phasePtr = phase;
-        }
-        return magnitude;
-    }
-
-    /**
-     * @param inputData contains microphone data with sine signal feedback
-     * @param outputData contains the reference sine wave
-     */
-    process_result process(float *inputData, int inputChannelCount,
-                 float *outputData, int outputChannelCount,
-                 int numFrames) override {
-        process_result result = PROCESS_RESULT_OK;
-        mProcessCount++;
-
-        float peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
-        if (peak > mPeakAmplitude) {
-            mPeakAmplitude = peak;
-        }
-
-        for (int i = 0; i < numFrames; i++) {
-            bool sineEnabled = true;
-            float sample = inputData[i * inputChannelCount];
-
-            float sinOut = sinf(mPhase);
-
-            switch (mState) {
-                case STATE_IDLE:
-                    sineEnabled = false;
-                    mDownCounter--;
-                    if (mDownCounter <= 0) {
-                        mState = STATE_MEASURE_NOISE;
-                        mDownCounter = NOISE_FRAME_COUNT;
-                    }
-                    break;
-                case STATE_MEASURE_NOISE:
-                    sineEnabled = false;
-                    mPeakNoise = std::max(abs(sample), mPeakNoise);
-                    mNoiseSumSquared += sample * sample;
-                    mDownCounter--;
-                    if (mDownCounter <= 0) {
-                        mState = STATE_WAITING_FOR_SIGNAL;
-                        mRootMeanSquareNoise = sqrt(mNoiseSumSquared / NOISE_FRAME_COUNT);
-                        mTolerance = std::max(MIN_TOLERANCE, mPeakNoise * 2.0f);
-                        mPhase = 0.0; // prevent spike at start
-                    }
-                    break;
-
-                case STATE_IMMUNE:
-                    mDownCounter--;
-                    if (mDownCounter <= 0) {
-                        mState = STATE_WAITING_FOR_SIGNAL;
-                    }
-                    break;
-
-                case STATE_WAITING_FOR_SIGNAL:
-                    if (peak > mThreshold) {
-                        mState = STATE_WAITING_FOR_LOCK;
-                        //printf("%5d: switch to STATE_WAITING_FOR_LOCK\n", mFrameCounter);
-                        resetAccumulator();
-                    }
-                    break;
-
-                case STATE_WAITING_FOR_LOCK:
-                    mSinAccumulator += sample * sinOut;
-                    mCosAccumulator += sample * cosf(mPhase);
-                    mFramesAccumulated++;
-                    // Must be a multiple of the period or the calculation will not be accurate.
-                    if (mFramesAccumulated == mSinePeriod * PERIODS_NEEDED_FOR_LOCK) {
-                        mPhaseOffset = 0.0;
-                        mMagnitude = calculateMagnitude(&mPhaseOffset);
-                        if (mMagnitude > mThreshold) {
-                            if (fabs(mPreviousPhaseOffset - mPhaseOffset) < 0.001) {
-                                mState = STATE_LOCKED;
-                                //printf("%5d: switch to STATE_LOCKED\n", mFrameCounter);
-                            }
-                            mPreviousPhaseOffset = mPhaseOffset;
-                        }
-                        resetAccumulator();
-                    }
-                    break;
-
-                case STATE_LOCKED: {
-                    // Predict next sine value
-                    float predicted = sinf(mPhase + mPhaseOffset) * mMagnitude;
-                    // printf("    predicted = %f, actual = %f\n", predicted, sample);
-
-                    float diff = predicted - sample;
-                    float absDiff = fabs(diff);
-                    mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
-                    if (absDiff > mTolerance) {
-                        mGlitchCount++;
-                        result = PROCESS_RESULT_GLITCH;
-                        //printf("%5d: Got a glitch # %d, predicted = %f, actual = %f\n",
-                        //       mFrameCounter, mGlitchCount, predicted, sample);
-                        mState = STATE_IMMUNE;
-                        mDownCounter = mSinePeriod * PERIODS_IMMUNE;
-                    }
-
-                    // Track incoming signal and slowly adjust magnitude to account
-                    // for drift in the DRC or AGC.
-                    mSinAccumulator += sample * sinOut;
-                    mCosAccumulator += sample * cosf(mPhase);
-                    mFramesAccumulated++;
-                    // Must be a multiple of the period or the calculation will not be accurate.
-                    if (mFramesAccumulated == mSinePeriod) {
-                        const double coefficient = 0.1;
-                        double phaseOffset = 0.0;
-                        double magnitude = calculateMagnitude(&phaseOffset);
-                        // One pole averaging filter.
-                        mMagnitude = (mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient);
-                        resetAccumulator();
-                    }
-                } break;
-            }
-
-            float output = 0.0f;
-            // Output sine wave so we can measure it.
-            if (sineEnabled) {
-                output = (sinOut * mOutputAmplitude)
-                         + (mWhiteNoise.nextRandomDouble() * mNoiseAmplitude);
-                // printf("%5d: sin(%f) = %f, %f\n", i, mPhase, sinOut,  mPhaseIncrement);
-                // advance and wrap phase
-                mPhase += mPhaseIncrement;
-                if (mPhase > M_PI) {
-                    mPhase -= (2.0 * M_PI);
-                }
-            }
-            outputData[i * outputChannelCount] = output;
-
-
-            mFrameCounter++;
-        }
-        return result;
-    }
-
-    void resetAccumulator() {
-        mFramesAccumulated = 0;
-        mSinAccumulator = 0.0;
-        mCosAccumulator = 0.0;
-    }
-
-    void reset() override {
-        mGlitchCount = 0;
-        mState = STATE_IDLE;
-        mDownCounter = IDLE_FRAME_COUNT;
-        mPhaseIncrement = 2.0 * M_PI / mSinePeriod;
-        printf("phaseInc = %f for period %d\n", mPhaseIncrement, mSinePeriod);
-        resetAccumulator();
-        mProcessCount = 0;
-        mPeakNoise = 0.0f;
-        mNoiseSumSquared = 0.0;
-        mRootMeanSquareNoise = 0.0;
-        mPhase = 0.0f;
-        mMaxGlitchDelta = 0.0;
-    }
-
-private:
-
-    enum error_code {
-        OK,
-        ERROR_NO_LOCK = -80,
-        ERROR_GLITCHES,
-        ERROR_NOISY
-    };
-
-    enum sine_state_t {
-        STATE_IDLE,
-        STATE_MEASURE_NOISE,
-        STATE_IMMUNE,
-        STATE_WAITING_FOR_SIGNAL,
-        STATE_WAITING_FOR_LOCK,
-        STATE_LOCKED
-    };
-
-    enum constants {
-        // Arbitrary durations, assuming 48000 Hz
-        IDLE_FRAME_COUNT = 48 * 100,
-        NOISE_FRAME_COUNT = 48 * 600,
-        PERIODS_NEEDED_FOR_LOCK = 8,
-        PERIODS_IMMUNE = 2,
-        MIN_SNRATIO_DB = 65
-    };
-
-    static constexpr float MIN_TOLERANCE = 0.01;
-
-    int     mSinePeriod = 79;
-    double  mPhaseIncrement = 0.0;
-    double  mPhase = 0.0;
-    double  mPhaseOffset = 0.0;
-    double  mPreviousPhaseOffset = 0.0;
-    double  mMagnitude = 0.0;
-    double  mThreshold = 0.005;
-    double  mTolerance = MIN_TOLERANCE;
-    int32_t mFramesAccumulated = 0;
-    int32_t mProcessCount = 0;
-    double  mSinAccumulator = 0.0;
-    double  mCosAccumulator = 0.0;
-    float   mMaxGlitchDelta = 0.0f;
-    int32_t mGlitchCount = 0;
-    double  mPeakAmplitude = 0.0;
-    int     mDownCounter = IDLE_FRAME_COUNT;
-    int32_t mFrameCounter = 0;
-    float   mOutputAmplitude = 0.75;
-
-    // measure background noise
-    float   mPeakNoise = 0.0f;
-    double  mNoiseSumSquared = 0.0;
-    double  mRootMeanSquareNoise = 0.0;
-
-    PseudoRandom  mWhiteNoise;
-    float   mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
-
-    sine_state_t  mState = STATE_IDLE;
-};
-
-#undef LOOPBACK_RESULT_TAG
-
-#endif /* AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H */
diff --git a/media/libaaudio/examples/loopback/src/analyzer/GlitchAnalyzer.h b/media/libaaudio/examples/loopback/src/analyzer/GlitchAnalyzer.h
new file mode 100644
index 0000000..04435d1
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/GlitchAnalyzer.h
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANALYZER_GLITCH_ANALYZER_H
+#define ANALYZER_GLITCH_ANALYZER_H
+
+#include <algorithm>
+#include <cctype>
+#include <iomanip>
+#include <iostream>
+
+#include "LatencyAnalyzer.h"
+#include "PseudoRandom.h"
+
+/**
+ * Output a steady sine wave and analyze the return signal.
+ *
+ * Use a cosine transform to measure the predicted magnitude and relative phase of the
+ * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
+ */
+class GlitchAnalyzer : public LoopbackProcessor {
+public:
+
+    int32_t getState() const {
+        return mState;
+    }
+
+    double getPeakAmplitude() const {
+        return mPeakFollower.getLevel();
+    }
+
+    double getTolerance() {
+        return mTolerance;
+    }
+
+    void setTolerance(double tolerance) {
+        mTolerance = tolerance;
+        mScaledTolerance = mMagnitude * mTolerance;
+    }
+
+    void setMagnitude(double magnitude) {
+        mMagnitude = magnitude;
+        mScaledTolerance = mMagnitude * mTolerance;
+    }
+
+    int32_t getGlitchCount() const {
+        return mGlitchCount;
+    }
+
+    int32_t getStateFrameCount(int state) const {
+        return mStateFrameCounters[state];
+    }
+
+    double getSignalToNoiseDB() {
+        static const double threshold = 1.0e-14;
+        if (mMeanSquareSignal < threshold || mMeanSquareNoise < threshold) {
+            return 0.0;
+        } else {
+            double signalToNoise = mMeanSquareSignal / mMeanSquareNoise; // power ratio
+            double signalToNoiseDB = 10.0 * log(signalToNoise);
+            if (signalToNoiseDB < MIN_SNR_DB) {
+                ALOGD("ERROR - signal to noise ratio is too low! < %d dB. Adjust volume.",
+                     MIN_SNR_DB);
+                setResult(ERROR_VOLUME_TOO_LOW);
+            }
+            return signalToNoiseDB;
+        }
+    }
+
+    std::string analyze() override {
+        std::stringstream report;
+        report << "GlitchAnalyzer ------------------\n";
+        report << LOOPBACK_RESULT_TAG "peak.amplitude     = " << std::setw(8)
+               << getPeakAmplitude() << "\n";
+        report << LOOPBACK_RESULT_TAG "sine.magnitude     = " << std::setw(8)
+               << mMagnitude << "\n";
+        report << LOOPBACK_RESULT_TAG "rms.noise          = " << std::setw(8)
+               << mMeanSquareNoise << "\n";
+        report << LOOPBACK_RESULT_TAG "signal.to.noise.db = " << std::setw(8)
+               << getSignalToNoiseDB() << "\n";
+        report << LOOPBACK_RESULT_TAG "frames.accumulated = " << std::setw(8)
+               << mFramesAccumulated << "\n";
+        report << LOOPBACK_RESULT_TAG "sine.period        = " << std::setw(8)
+               << mSinePeriod << "\n";
+        report << LOOPBACK_RESULT_TAG "test.state         = " << std::setw(8)
+               << mState << "\n";
+        report << LOOPBACK_RESULT_TAG "frame.count        = " << std::setw(8)
+               << mFrameCounter << "\n";
+        // Did we ever get a lock?
+        bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
+        if (!gotLock) {
+            report << "ERROR - failed to lock on reference sine tone.\n";
+            setResult(ERROR_NO_LOCK);
+        } else {
+            // Only print if meaningful.
+            report << LOOPBACK_RESULT_TAG "glitch.count       = " << std::setw(8)
+                   << mGlitchCount << "\n";
+            report << LOOPBACK_RESULT_TAG "max.glitch         = " << std::setw(8)
+                   << mMaxGlitchDelta << "\n";
+            if (mGlitchCount > 0) {
+                report << "ERROR - number of glitches > 0\n";
+                setResult(ERROR_GLITCHES);
+            }
+        }
+        return report.str();
+    }
+
+    void printStatus() override {
+        ALOGD("st = %d, #gl = %3d,", mState, mGlitchCount);
+    }
+    /**
+     * Calculate the magnitude of the component of the input signal
+     * that matches the analysis frequency.
+     * Also calculate the phase that we can use to create a
+     * signal that matches that component.
+     * The phase will be between -PI and +PI.
+     */
+    double calculateMagnitude(double *phasePtr = nullptr) {
+        if (mFramesAccumulated == 0) {
+            return 0.0;
+        }
+        double sinMean = mSinAccumulator / mFramesAccumulated;
+        double cosMean = mCosAccumulator / mFramesAccumulated;
+        double magnitude = 2.0 * sqrt((sinMean * sinMean) + (cosMean * cosMean));
+        if (phasePtr != nullptr) {
+            double phase = M_PI_2 - atan2(sinMean, cosMean);
+            *phasePtr = phase;
+        }
+        return magnitude;
+    }
+
+    /**
+     * @param frameData contains microphone data with sine signal feedback
+     * @param channelCount
+     */
+    result_code processInputFrame(float *frameData, int /* channelCount */) override {
+        result_code result = RESULT_OK;
+
+        float sample = frameData[0];
+        float peak = mPeakFollower.process(sample);
+
+        // Force a periodic glitch to test the detector!
+        if (mForceGlitchDuration > 0) {
+            if (mForceGlitchCounter == 0) {
+                ALOGE("%s: force a glitch!!", __func__);
+                mForceGlitchCounter = getSampleRate();
+            } else if (mForceGlitchCounter <= mForceGlitchDuration) {
+                // Force an abrupt offset.
+                sample += (sample > 0.0) ? -0.5f : 0.5f;
+            }
+            --mForceGlitchCounter;
+        }
+
+        mStateFrameCounters[mState]++; // count how many frames we are in each state
+
+        switch (mState) {
+            case STATE_IDLE:
+                mDownCounter--;
+                if (mDownCounter <= 0) {
+                    mState = STATE_IMMUNE;
+                    mDownCounter = IMMUNE_FRAME_COUNT;
+                    mInputPhase = 0.0; // prevent spike at start
+                    mOutputPhase = 0.0;
+                }
+                break;
+
+            case STATE_IMMUNE:
+                mDownCounter--;
+                if (mDownCounter <= 0) {
+                    mState = STATE_WAITING_FOR_SIGNAL;
+                }
+                break;
+
+            case STATE_WAITING_FOR_SIGNAL:
+                if (peak > mThreshold) {
+                    mState = STATE_WAITING_FOR_LOCK;
+                    //ALOGD("%5d: switch to STATE_WAITING_FOR_LOCK", mFrameCounter);
+                    resetAccumulator();
+                }
+                break;
+
+            case STATE_WAITING_FOR_LOCK:
+                mSinAccumulator += sample * sinf(mInputPhase);
+                mCosAccumulator += sample * cosf(mInputPhase);
+                mFramesAccumulated++;
+                // Must be a multiple of the period or the calculation will not be accurate.
+                if (mFramesAccumulated == mSinePeriod * PERIODS_NEEDED_FOR_LOCK) {
+                    double phaseOffset = 0.0;
+                    setMagnitude(calculateMagnitude(&phaseOffset));
+//                    ALOGD("%s() mag = %f, offset = %f, prev = %f",
+//                            __func__, mMagnitude, mPhaseOffset, mPreviousPhaseOffset);
+                    if (mMagnitude > mThreshold) {
+                        if (abs(phaseOffset) < kMaxPhaseError) {
+                            mState = STATE_LOCKED;
+//                            ALOGD("%5d: switch to STATE_LOCKED", mFrameCounter);
+                        }
+                        // Adjust mInputPhase to match measured phase
+                        mInputPhase += phaseOffset;
+                    }
+                    resetAccumulator();
+                }
+                incrementInputPhase();
+                break;
+
+            case STATE_LOCKED: {
+                // Predict next sine value
+                double predicted = sinf(mInputPhase) * mMagnitude;
+                double diff = predicted - sample;
+                double absDiff = fabs(diff);
+                mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
+                if (absDiff > mScaledTolerance) {
+                    result = ERROR_GLITCHES;
+                    onGlitchStart();
+//                    LOGI("diff glitch detected, absDiff = %g", absDiff);
+                } else {
+                    mSumSquareSignal += predicted * predicted;
+                    mSumSquareNoise += diff * diff;
+                    // Track incoming signal and slowly adjust magnitude to account
+                    // for drift in the DRC or AGC.
+                    mSinAccumulator += sample * sinf(mInputPhase);
+                    mCosAccumulator += sample * cosf(mInputPhase);
+                    mFramesAccumulated++;
+                    // Must be a multiple of the period or the calculation will not be accurate.
+                    if (mFramesAccumulated == mSinePeriod) {
+                        const double coefficient = 0.1;
+                        double phaseOffset = 0.0;
+                        double magnitude = calculateMagnitude(&phaseOffset);
+                        // One pole averaging filter.
+                        setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient));
+
+                        mMeanSquareNoise = mSumSquareNoise * mInverseSinePeriod;
+                        mMeanSquareSignal = mSumSquareSignal * mInverseSinePeriod;
+                        resetAccumulator();
+
+                        if (abs(phaseOffset) > kMaxPhaseError) {
+                            result = ERROR_GLITCHES;
+                            onGlitchStart();
+                            ALOGD("phase glitch detected, phaseOffset = %g", phaseOffset);
+                        } else if (mMagnitude < mThreshold) {
+                            result = ERROR_GLITCHES;
+                            onGlitchStart();
+                            ALOGD("magnitude glitch detected, mMagnitude = %g", mMagnitude);
+                        }
+                    }
+                }
+                incrementInputPhase();
+            } break;
+
+            case STATE_GLITCHING: {
+                // Predict next sine value
+                mGlitchLength++;
+                double predicted = sinf(mInputPhase) * mMagnitude;
+                double diff = predicted - sample;
+                double absDiff = fabs(diff);
+                mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
+                if (absDiff < mScaledTolerance) { // close enough?
+                    // If we get a full sine period of non-glitch samples in a row then consider the glitch over.
+                    // We don't want to just consider a zero crossing the end of a glitch.
+                    if (mNonGlitchCount++ > mSinePeriod) {
+                        onGlitchEnd();
+                    }
+                } else {
+                    mNonGlitchCount = 0;
+                    if (mGlitchLength > (4 * mSinePeriod)) {
+                        relock();
+                    }
+                }
+                incrementInputPhase();
+            } break;
+
+            case NUM_STATES: // not a real state
+                break;
+        }
+
+        mFrameCounter++;
+
+        return result;
+    }
+
+    // advance and wrap phase
+    void incrementInputPhase() {
+        mInputPhase += mPhaseIncrement;
+        if (mInputPhase > M_PI) {
+            mInputPhase -= (2.0 * M_PI);
+        }
+    }
+
+    // advance and wrap phase
+    void incrementOutputPhase() {
+        mOutputPhase += mPhaseIncrement;
+        if (mOutputPhase > M_PI) {
+            mOutputPhase -= (2.0 * M_PI);
+        }
+    }
+
+    /**
+     * @param frameData upon return, contains the reference sine wave
+     * @param channelCount
+     */
+    result_code processOutputFrame(float *frameData, int channelCount) override {
+        float output = 0.0f;
+        // Output sine wave so we can measure it.
+        if (mState != STATE_IDLE) {
+            float sinOut = sinf(mOutputPhase);
+            incrementOutputPhase();
+            output = (sinOut * mOutputAmplitude)
+                     + (mWhiteNoise.nextRandomDouble() * kNoiseAmplitude);
+            // ALOGD("sin(%f) = %f, %f\n", mOutputPhase, sinOut,  mPhaseIncrement);
+        }
+        frameData[0] = output;
+        for (int i = 1; i < channelCount; i++) {
+            frameData[i] = 0.0f;
+        }
+        return RESULT_OK;
+    }
+
+    void onGlitchStart() {
+        mGlitchCount++;
+//        ALOGD("%5d: STARTED a glitch # %d", mFrameCounter, mGlitchCount);
+        mState = STATE_GLITCHING;
+        mGlitchLength = 1;
+        mNonGlitchCount = 0;
+    }
+
+    void onGlitchEnd() {
+//        ALOGD("%5d: ENDED a glitch # %d, length = %d", mFrameCounter, mGlitchCount, mGlitchLength);
+        mState = STATE_LOCKED;
+        resetAccumulator();
+    }
+
+    // reset the sine wave detector
+    void resetAccumulator() {
+        mFramesAccumulated = 0;
+        mSinAccumulator = 0.0;
+        mCosAccumulator = 0.0;
+        mSumSquareSignal = 0.0;
+        mSumSquareNoise = 0.0;
+    }
+
+    void relock() {
+//        ALOGD("relock: %d because of a very long %d glitch", mFrameCounter, mGlitchLength);
+        mState = STATE_WAITING_FOR_LOCK;
+        resetAccumulator();
+    }
+
+    void reset() override {
+        LoopbackProcessor::reset();
+        mState = STATE_IDLE;
+        mDownCounter = IDLE_FRAME_COUNT;
+        resetAccumulator();
+    }
+
+    void prepareToTest() override {
+        LoopbackProcessor::prepareToTest();
+        mSinePeriod = getSampleRate() / kTargetGlitchFrequency;
+        mOutputPhase = 0.0f;
+        mInverseSinePeriod = 1.0 / mSinePeriod;
+        mPhaseIncrement = 2.0 * M_PI * mInverseSinePeriod;
+        mGlitchCount = 0;
+        mMaxGlitchDelta = 0.0;
+        for (int i = 0; i < NUM_STATES; i++) {
+            mStateFrameCounters[i] = 0;
+        }
+    }
+
+private:
+
+    // These must match the values in GlitchActivity.java
+    enum sine_state_t {
+        STATE_IDLE,               // beginning
+        STATE_IMMUNE,             // ignoring input, waiting fo HW to settle
+        STATE_WAITING_FOR_SIGNAL, // looking for a loud signal
+        STATE_WAITING_FOR_LOCK,   // trying to lock onto the phase of the sine
+        STATE_LOCKED,             // locked on the sine wave, looking for glitches
+        STATE_GLITCHING,           // locked on the sine wave but glitching
+        NUM_STATES
+    };
+
+    enum constants {
+        // Arbitrary durations, assuming 48000 Hz
+        IDLE_FRAME_COUNT = 48 * 100,
+        IMMUNE_FRAME_COUNT = 48 * 100,
+        PERIODS_NEEDED_FOR_LOCK = 8,
+        MIN_SNR_DB = 65
+    };
+
+    static constexpr float kNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
+    static constexpr int kTargetGlitchFrequency = 607;
+    static constexpr double kMaxPhaseError = M_PI * 0.05;
+
+    float   mTolerance = 0.10; // scaled from 0.0 to 1.0
+    double  mThreshold = 0.005;
+    int     mSinePeriod = 1; // this will be set before use
+    double  mInverseSinePeriod = 1.0;
+
+    int32_t mStateFrameCounters[NUM_STATES];
+
+    double  mPhaseIncrement = 0.0;
+    double  mInputPhase = 0.0;
+    double  mOutputPhase = 0.0;
+    double  mMagnitude = 0.0;
+    int32_t mFramesAccumulated = 0;
+    double  mSinAccumulator = 0.0;
+    double  mCosAccumulator = 0.0;
+    double  mMaxGlitchDelta = 0.0;
+    int32_t mGlitchCount = 0;
+    int32_t mNonGlitchCount = 0;
+    int32_t mGlitchLength = 0;
+    // This is used for processing every frame so we cache it here.
+    double  mScaledTolerance = 0.0;
+    int     mDownCounter = IDLE_FRAME_COUNT;
+    int32_t mFrameCounter = 0;
+    double  mOutputAmplitude = 0.75;
+
+    int32_t mForceGlitchDuration = 0; // if > 0 then force a glitch for debugging
+    int32_t mForceGlitchCounter = 4 * 48000; // count down and trigger at zero
+
+    // measure background noise continuously as a deviation from the expected signal
+    double  mSumSquareSignal = 0.0;
+    double  mSumSquareNoise = 0.0;
+    double  mMeanSquareSignal = 0.0;
+    double  mMeanSquareNoise = 0.0;
+
+    PeakDetector  mPeakFollower;
+
+    PseudoRandom  mWhiteNoise;
+
+    sine_state_t  mState = STATE_IDLE;
+};
+
+
+#endif //ANALYZER_GLITCH_ANALYZER_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/LatencyAnalyzer.h b/media/libaaudio/examples/loopback/src/analyzer/LatencyAnalyzer.h
new file mode 100644
index 0000000..e506791
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/LatencyAnalyzer.h
@@ -0,0 +1,606 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Tools for measuring latency and for detecting glitches.
+ * These classes are pure math and can be used with any audio system.
+ */
+
+#ifndef ANALYZER_LATENCY_ANALYZER_H
+#define ANALYZER_LATENCY_ANALYZER_H
+
+#include <algorithm>
+#include <assert.h>
+#include <cctype>
+#include <iomanip>
+#include <iostream>
+#include <math.h>
+#include <memory>
+#include <sstream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <vector>
+
+#include "PeakDetector.h"
+#include "PseudoRandom.h"
+#include "RandomPulseGenerator.h"
+
+// This is used when the code is in Oboe.
+#ifndef ALOGD
+#define ALOGD printf
+#define ALOGE printf
+#define ALOGW printf
+#endif
+
+#define LOOPBACK_RESULT_TAG  "RESULT: "
+
+static constexpr int32_t kDefaultSampleRate = 48000;
+static constexpr int32_t kMillisPerSecond   = 1000;
+static constexpr int32_t kMaxLatencyMillis  = 700;  // arbitrary and generous
+static constexpr double  kMinimumConfidence = 0.2;
+
+struct LatencyReport {
+    int32_t latencyInFrames = 0.0;
+    double confidence = 0.0;
+
+    void reset() {
+        latencyInFrames = 0;
+        confidence = 0.0;
+    }
+};
+
+// Calculate a normalized cross correlation.
+static double calculateNormalizedCorrelation(const float *a,
+                                             const float *b,
+                                             int windowSize) {
+    double correlation = 0.0;
+    double sumProducts = 0.0;
+    double sumSquares = 0.0;
+
+    // Correlate a against b.
+    for (int i = 0; i < windowSize; i++) {
+        float s1 = a[i];
+        float s2 = b[i];
+        // Use a normalized cross-correlation.
+        sumProducts += s1 * s2;
+        sumSquares += ((s1 * s1) + (s2 * s2));
+    }
+
+    if (sumSquares >= 1.0e-9) {
+        correlation = 2.0 * sumProducts / sumSquares;
+    }
+    return correlation;
+}
+
+static double calculateRootMeanSquare(float *data, int32_t numSamples) {
+    double sum = 0.0;
+    for (int32_t i = 0; i < numSamples; i++) {
+        float sample = data[i];
+        sum += sample * sample;
+    }
+    return sqrt(sum / numSamples);
+}
+
+/**
+ * Monophonic recording with processing.
+ */
+class AudioRecording
+{
+public:
+
+    void allocate(int maxFrames) {
+        mData = std::make_unique<float[]>(maxFrames);
+        mMaxFrames = maxFrames;
+    }
+
+    // Write SHORT data from the first channel.
+    int32_t write(int16_t *inputData, int32_t inputChannelCount, int32_t numFrames) {
+        // stop at end of buffer
+        if ((mFrameCounter + numFrames) > mMaxFrames) {
+            numFrames = mMaxFrames - mFrameCounter;
+        }
+        for (int i = 0; i < numFrames; i++) {
+            mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
+        }
+        return numFrames;
+    }
+
+    // Write FLOAT data from the first channel.
+    int32_t write(float *inputData, int32_t inputChannelCount, int32_t numFrames) {
+        // stop at end of buffer
+        if ((mFrameCounter + numFrames) > mMaxFrames) {
+            numFrames = mMaxFrames - mFrameCounter;
+        }
+        for (int i = 0; i < numFrames; i++) {
+            mData[mFrameCounter++] = inputData[i * inputChannelCount];
+        }
+        return numFrames;
+    }
+
+    // Write FLOAT data from the first channel.
+    int32_t write(float sample) {
+        // stop at end of buffer
+        if (mFrameCounter < mMaxFrames) {
+            mData[mFrameCounter++] = sample;
+            return 1;
+        }
+        return 0;
+    }
+
+    void clear() {
+        mFrameCounter = 0;
+    }
+    int32_t size() const {
+        return mFrameCounter;
+    }
+
+    bool isFull() const {
+        return mFrameCounter >= mMaxFrames;
+    }
+
+    float *getData() const {
+        return mData.get();
+    }
+
+    void setSampleRate(int32_t sampleRate) {
+        mSampleRate = sampleRate;
+    }
+
+    int32_t getSampleRate() const {
+        return mSampleRate;
+    }
+
+    /**
+     * Square the samples so they are all positive and so the peaks are emphasized.
+     */
+    void square() {
+        float *x = mData.get();
+        for (int i = 0; i < mFrameCounter; i++) {
+            x[i] *= x[i];
+        }
+    }
+
+    /**
+     * Amplify a signal so that the peak matches the specified target.
+     *
+     * @param target final max value
+     * @return gain applied to signal
+     */
+    float normalize(float target) {
+        float maxValue = 1.0e-9f;
+        for (int i = 0; i < mFrameCounter; i++) {
+            maxValue = std::max(maxValue, abs(mData[i]));
+        }
+        float gain = target / maxValue;
+        for (int i = 0; i < mFrameCounter; i++) {
+            mData[i] *= gain;
+        }
+        return gain;
+    }
+
+private:
+    std::unique_ptr<float[]> mData;
+    int32_t       mFrameCounter = 0;
+    int32_t       mMaxFrames = 0;
+    int32_t       mSampleRate = kDefaultSampleRate; // common default
+};
+
+static int measureLatencyFromPulse(AudioRecording &recorded,
+                                   AudioRecording &pulse,
+                                   LatencyReport *report) {
+
+    report->latencyInFrames = 0;
+    report->confidence = 0.0;
+
+    int numCorrelations = recorded.size() - pulse.size();
+    if (numCorrelations < 10) {
+        ALOGE("%s() recording too small = %d frames\n", __func__, recorded.size());
+        return -1;
+    }
+    std::unique_ptr<float[]> correlations= std::make_unique<float[]>(numCorrelations);
+
+    // Correlate pulse against the recorded data.
+    for (int i = 0; i < numCorrelations; i++) {
+        float correlation = (float) calculateNormalizedCorrelation(&recorded.getData()[i],
+                                                                   &pulse.getData()[0],
+                                                                   pulse.size());
+        correlations[i] = correlation;
+    }
+
+    // Find highest peak in correlation array.
+    float peakCorrelation = 0.0;
+    int peakIndex = -1;
+    for (int i = 0; i < numCorrelations; i++) {
+        float value = abs(correlations[i]);
+        if (value > peakCorrelation) {
+            peakCorrelation = value;
+            peakIndex = i;
+        }
+    }
+    if (peakIndex < 0) {
+        ALOGE("%s() no signal for correlation\n", __func__);
+        return -2;
+    }
+
+    report->latencyInFrames = peakIndex;
+    report->confidence = peakCorrelation;
+
+    return 0;
+}
+
+// ====================================================================================
+class LoopbackProcessor {
+public:
+    virtual ~LoopbackProcessor() = default;
+
+    enum result_code {
+        RESULT_OK = 0,
+        ERROR_NOISY = -99,
+        ERROR_VOLUME_TOO_LOW,
+        ERROR_VOLUME_TOO_HIGH,
+        ERROR_CONFIDENCE,
+        ERROR_INVALID_STATE,
+        ERROR_GLITCHES,
+        ERROR_NO_LOCK
+    };
+
+    virtual void prepareToTest() {
+        reset();
+    }
+
+    virtual void reset() {
+        mResult = 0;
+        mResetCount++;
+    }
+
+    virtual result_code processInputFrame(float *frameData, int channelCount) = 0;
+    virtual result_code processOutputFrame(float *frameData, int channelCount) = 0;
+
+    void process(float *inputData, int inputChannelCount, int numInputFrames,
+                 float *outputData, int outputChannelCount, int numOutputFrames) {
+        int numBoth = std::min(numInputFrames, numOutputFrames);
+        // Process one frame at a time.
+        for (int i = 0; i < numBoth; i++) {
+            processInputFrame(inputData, inputChannelCount);
+            inputData += inputChannelCount;
+            processOutputFrame(outputData, outputChannelCount);
+            outputData += outputChannelCount;
+        }
+        // If there is more input than output.
+        for (int i = numBoth; i < numInputFrames; i++) {
+            processInputFrame(inputData, inputChannelCount);
+            inputData += inputChannelCount;
+        }
+        // If there is more output than input.
+        for (int i = numBoth; i < numOutputFrames; i++) {
+            processOutputFrame(outputData, outputChannelCount);
+            outputData += outputChannelCount;
+        }
+    }
+
+    virtual std::string analyze() = 0;
+
+    virtual void printStatus() {};
+
+    int32_t getResult() {
+        return mResult;
+    }
+
+    void setResult(int32_t result) {
+        mResult = result;
+    }
+
+    virtual bool isDone() {
+        return false;
+    }
+
+    virtual int save(const char *fileName) {
+        (void) fileName;
+        return -1;
+    }
+
+    virtual int load(const char *fileName) {
+        (void) fileName;
+        return -1;
+    }
+
+    virtual void setSampleRate(int32_t sampleRate) {
+        mSampleRate = sampleRate;
+    }
+
+    int32_t getSampleRate() const {
+        return mSampleRate;
+    }
+
+    int32_t getResetCount() const {
+        return mResetCount;
+    }
+
+    /** Called when not enough input frames could be read after synchronization.
+     */
+    virtual void onInsufficientRead() {
+        reset();
+    }
+
+protected:
+    int32_t   mResetCount = 0;
+
+private:
+    int32_t mSampleRate = kDefaultSampleRate;
+    int32_t mResult = 0;
+};
+
+class LatencyAnalyzer : public LoopbackProcessor {
+public:
+
+    LatencyAnalyzer() : LoopbackProcessor() {}
+    virtual ~LatencyAnalyzer() = default;
+
+    virtual int32_t getProgress() const = 0;
+
+    virtual int getState() = 0;
+
+    // @return latency in frames
+    virtual int32_t getMeasuredLatency() = 0;
+
+    virtual double getMeasuredConfidence() = 0;
+
+    virtual double getBackgroundRMS() = 0;
+
+    virtual double getSignalRMS() = 0;
+
+};
+
+// ====================================================================================
+/**
+ * Measure latency given a loopback stream data.
+ * Use an encoded bit train as the sound source because it
+ * has an unambiguous correlation value.
+ * Uses a state machine to cycle through various stages.
+ *
+ */
+class PulseLatencyAnalyzer : public LatencyAnalyzer {
+public:
+
+    PulseLatencyAnalyzer() : LatencyAnalyzer() {
+        int32_t maxLatencyFrames = getSampleRate() * kMaxLatencyMillis / kMillisPerSecond;
+        int32_t numPulseBits = getSampleRate() * kPulseLengthMillis
+                / (kFramesPerEncodedBit * kMillisPerSecond);
+        int32_t  pulseLength = numPulseBits * kFramesPerEncodedBit;
+        mFramesToRecord = pulseLength + maxLatencyFrames;
+        mAudioRecording.allocate(mFramesToRecord);
+        mAudioRecording.setSampleRate(getSampleRate());
+        generateRandomPulse(pulseLength);
+    }
+
+    void generateRandomPulse(int32_t pulseLength) {
+        mPulse.allocate(pulseLength);
+        RandomPulseGenerator pulser(kFramesPerEncodedBit);
+        for (int i = 0; i < pulseLength; i++) {
+            mPulse.write(pulser.nextFloat());
+        }
+    }
+
+    int getState() override {
+        return mState;
+    }
+
+    void setSampleRate(int32_t sampleRate) override {
+        LoopbackProcessor::setSampleRate(sampleRate);
+        mAudioRecording.setSampleRate(sampleRate);
+    }
+
+    void reset() override {
+        LoopbackProcessor::reset();
+        mDownCounter = getSampleRate() / 2;
+        mLoopCounter = 0;
+
+        mPulseCursor = 0;
+        mBackgroundSumSquare = 0.0f;
+        mBackgroundSumCount = 0;
+        mBackgroundRMS = 0.0f;
+        mSignalRMS = 0.0f;
+
+        mState = STATE_MEASURE_BACKGROUND;
+        mAudioRecording.clear();
+        mLatencyReport.reset();
+    }
+
+    bool hasEnoughData() {
+        return mAudioRecording.isFull();
+    }
+
+    bool isDone() override {
+        return mState == STATE_DONE;
+    }
+
+    int32_t getProgress() const override {
+        return mAudioRecording.size();
+    }
+
+    std::string analyze() override {
+        std::stringstream report;
+        report << "PulseLatencyAnalyzer ---------------\n";
+        report << LOOPBACK_RESULT_TAG "test.state             = "
+                << std::setw(8) << mState << "\n";
+        report << LOOPBACK_RESULT_TAG "test.state.name        = "
+                << convertStateToText(mState) << "\n";
+        report << LOOPBACK_RESULT_TAG "background.rms         = "
+                << std::setw(8) << mBackgroundRMS << "\n";
+
+        int32_t newResult = RESULT_OK;
+        if (mState != STATE_GOT_DATA) {
+            report << "WARNING - Bad state. Check volume on device.\n";
+            // setResult(ERROR_INVALID_STATE);
+        } else {
+            float gain = mAudioRecording.normalize(1.0f);
+            measureLatencyFromPulse(mAudioRecording,
+                                    mPulse,
+                                    &mLatencyReport);
+
+            if (mLatencyReport.confidence < kMinimumConfidence) {
+                report << "   ERROR - confidence too low!";
+                newResult = ERROR_CONFIDENCE;
+            } else {
+                mSignalRMS = calculateRootMeanSquare(
+                        &mAudioRecording.getData()[mLatencyReport.latencyInFrames], mPulse.size())
+                                / gain;
+            }
+            double latencyMillis = kMillisPerSecond * (double) mLatencyReport.latencyInFrames
+                                   / getSampleRate();
+            report << LOOPBACK_RESULT_TAG "latency.frames         = " << std::setw(8)
+                   << mLatencyReport.latencyInFrames << "\n";
+            report << LOOPBACK_RESULT_TAG "latency.msec           = " << std::setw(8)
+                   << latencyMillis << "\n";
+            report << LOOPBACK_RESULT_TAG "latency.confidence     = " << std::setw(8)
+                   << mLatencyReport.confidence << "\n";
+        }
+        mState = STATE_DONE;
+        if (getResult() == RESULT_OK) {
+            setResult(newResult);
+        }
+
+        return report.str();
+    }
+
+    int32_t getMeasuredLatency() override {
+        return mLatencyReport.latencyInFrames;
+    }
+
+    double getMeasuredConfidence() override {
+        return mLatencyReport.confidence;
+    }
+
+    double getBackgroundRMS() override {
+        return mBackgroundRMS;
+    }
+
+    double getSignalRMS() override {
+        return mSignalRMS;
+    }
+
+    void printStatus() override {
+        ALOGD("st = %d", mState);
+    }
+
+    result_code processInputFrame(float *frameData, int channelCount) override {
+        echo_state nextState = mState;
+        mLoopCounter++;
+
+        switch (mState) {
+            case STATE_MEASURE_BACKGROUND:
+                // Measure background RMS on channel 0
+                mBackgroundSumSquare += frameData[0] * frameData[0];
+                mBackgroundSumCount++;
+                mDownCounter--;
+                if (mDownCounter <= 0) {
+                    mBackgroundRMS = sqrtf(mBackgroundSumSquare / mBackgroundSumCount);
+                    nextState = STATE_IN_PULSE;
+                    mPulseCursor = 0;
+                }
+                break;
+
+            case STATE_IN_PULSE:
+                // Record input until the mAudioRecording is full.
+                mAudioRecording.write(frameData, channelCount, 1);
+                if (hasEnoughData()) {
+                    nextState = STATE_GOT_DATA;
+                }
+                break;
+
+            case STATE_GOT_DATA:
+            case STATE_DONE:
+            default:
+                break;
+        }
+
+        mState = nextState;
+        return RESULT_OK;
+    }
+
+    result_code processOutputFrame(float *frameData, int channelCount) override {
+        switch (mState) {
+            case STATE_IN_PULSE:
+                if (mPulseCursor < mPulse.size()) {
+                    float pulseSample = mPulse.getData()[mPulseCursor++];
+                    for (int i = 0; i < channelCount; i++) {
+                        frameData[i] = pulseSample;
+                    }
+                } else {
+                    for (int i = 0; i < channelCount; i++) {
+                        frameData[i] = 0;
+                    }
+                }
+                break;
+
+            case STATE_MEASURE_BACKGROUND:
+            case STATE_GOT_DATA:
+            case STATE_DONE:
+            default:
+                for (int i = 0; i < channelCount; i++) {
+                    frameData[i] = 0.0f; // silence
+                }
+                break;
+        }
+
+        return RESULT_OK;
+    }
+
+private:
+
+    enum echo_state {
+        STATE_MEASURE_BACKGROUND,
+        STATE_IN_PULSE,
+        STATE_GOT_DATA, // must match RoundTripLatencyActivity.java
+        STATE_DONE,
+    };
+
+    const char *convertStateToText(echo_state state) {
+        switch (state) {
+            case STATE_MEASURE_BACKGROUND:
+                return "INIT";
+            case STATE_IN_PULSE:
+                return "PULSE";
+            case STATE_GOT_DATA:
+                return "GOT_DATA";
+            case STATE_DONE:
+                return "DONE";
+        }
+        return "UNKNOWN";
+    }
+
+    int32_t         mDownCounter = 500;
+    int32_t         mLoopCounter = 0;
+    echo_state      mState = STATE_MEASURE_BACKGROUND;
+
+    static constexpr int32_t kFramesPerEncodedBit = 8; // multiple of 2
+    static constexpr int32_t kPulseLengthMillis = 500;
+
+    AudioRecording     mPulse;
+    int32_t            mPulseCursor = 0;
+
+    double             mBackgroundSumSquare = 0.0;
+    int32_t            mBackgroundSumCount = 0;
+    double             mBackgroundRMS = 0.0;
+    double             mSignalRMS = 0.0;
+    int32_t            mFramesToRecord = 0;
+
+    AudioRecording     mAudioRecording; // contains only the input after starting the pulse
+    LatencyReport      mLatencyReport;
+};
+
+#endif // ANALYZER_LATENCY_ANALYZER_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/ManchesterEncoder.h b/media/libaaudio/examples/loopback/src/analyzer/ManchesterEncoder.h
new file mode 100644
index 0000000..0a4bd5b
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/ManchesterEncoder.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANALYZER_MANCHESTER_ENCODER_H
+#define ANALYZER_MANCHESTER_ENCODER_H
+
+#include <cstdint>
+
+/**
+ * Encode bytes using Manchester Coding scheme.
+ *
+ * Manchester Code is self clocking.
+ * There is a transition in the middle of every bit.
+ * Zero is high then low.
+ * One is low then high.
+ *
+ * This avoids having long DC sections that would droop when
+ * passed though analog circuits with AC coupling.
+ *
+ * IEEE 802.3 compatible.
+ */
+
+class ManchesterEncoder {
+public:
+    ManchesterEncoder(int samplesPerPulse)
+            : mSamplesPerPulse(samplesPerPulse)
+            , mSamplesPerPulseHalf(samplesPerPulse / 2)
+            , mCursor(samplesPerPulse) {
+    }
+
+    virtual ~ManchesterEncoder() = default;
+
+    /**
+     * This will be called when the next byte is needed.
+     * @return
+     */
+    virtual uint8_t onNextByte() = 0;
+
+    /**
+     * Generate the next floating point sample.
+     * @return
+     */
+    virtual float nextFloat() {
+        advanceSample();
+        if (mCurrentBit) {
+            return (mCursor < mSamplesPerPulseHalf) ? -1.0f : 1.0f; // one
+        } else {
+            return (mCursor < mSamplesPerPulseHalf) ? 1.0f : -1.0f; // zero
+        }
+    }
+
+protected:
+    /**
+     * This will be called when a new bit is ready to be encoded.
+     * It can be used to prepare the encoded samples.
+     * @param current
+     */
+    virtual void onNextBit(bool /* current */) {};
+
+    void advanceSample() {
+        // Are we ready for a new bit?
+        if (++mCursor >= mSamplesPerPulse) {
+            mCursor = 0;
+            if (mBitsLeft == 0) {
+                mCurrentByte = onNextByte();
+                mBitsLeft = 8;
+            }
+            --mBitsLeft;
+            mCurrentBit = (mCurrentByte >> mBitsLeft) & 1;
+            onNextBit(mCurrentBit);
+        }
+    }
+
+    bool getCurrentBit() {
+        return mCurrentBit;
+    }
+
+    const int mSamplesPerPulse;
+    const int mSamplesPerPulseHalf;
+    int       mCursor;
+    int       mBitsLeft = 0;
+    uint8_t   mCurrentByte = 0;
+    bool      mCurrentBit = false;
+};
+#endif //ANALYZER_MANCHESTER_ENCODER_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/PeakDetector.h b/media/libaaudio/examples/loopback/src/analyzer/PeakDetector.h
new file mode 100644
index 0000000..4b3b4e7
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/PeakDetector.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANALYZER_PEAK_DETECTOR_H
+#define ANALYZER_PEAK_DETECTOR_H
+
+#include <math.h>
+
+/**
+ * Measure a peak envelope by rising with the peaks,
+ * and decaying exponentially after each peak.
+ * The absolute value of the input signal is used.
+ */
+class PeakDetector {
+public:
+
+    void reset() {
+        mLevel = 0.0;
+    }
+
+    double process(double input) {
+        mLevel *= mDecay; // exponential decay
+        input = fabs(input);
+        // never fall below the input signal
+        if (input > mLevel) {
+            mLevel = input;
+        }
+        return mLevel;
+    }
+
+    double getLevel() const {
+        return mLevel;
+    }
+
+    double getDecay() const {
+        return mDecay;
+    }
+
+    /**
+     * Multiply the level by this amount on every iteration.
+     * This provides an exponential decay curve.
+     * A value just under 1.0 is best, for example, 0.99;
+     * @param decay scale level for each input
+     */
+    void setDecay(double decay) {
+        mDecay = decay;
+    }
+
+private:
+    static constexpr double kDefaultDecay = 0.99f;
+
+    double mLevel = 0.0;
+    double mDecay = kDefaultDecay;
+};
+#endif //ANALYZER_PEAK_DETECTOR_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/PseudoRandom.h b/media/libaaudio/examples/loopback/src/analyzer/PseudoRandom.h
new file mode 100644
index 0000000..1c4938c
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/PseudoRandom.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANALYZER_PSEUDORANDOM_H
+#define ANALYZER_PSEUDORANDOM_H
+
+#include <cctype>
+
+class PseudoRandom {
+public:
+    PseudoRandom(int64_t seed = 99887766)
+            :    mSeed(seed)
+    {}
+
+    /**
+     * Returns the next random double from -1.0 to 1.0
+     *
+     * @return value from -1.0 to 1.0
+     */
+    double nextRandomDouble() {
+        return nextRandomInteger() * (0.5 / (((int32_t)1) << 30));
+    }
+
+    /** Calculate random 32 bit number using linear-congruential method
+     * with known real-time performance.
+     */
+    int32_t nextRandomInteger() {
+#if __has_builtin(__builtin_mul_overflow) && __has_builtin(__builtin_add_overflow)
+        int64_t prod;
+        // Use values for 64-bit sequence from MMIX by Donald Knuth.
+        __builtin_mul_overflow(mSeed, (int64_t)6364136223846793005, &prod);
+        __builtin_add_overflow(prod, (int64_t)1442695040888963407, &mSeed);
+#else
+        mSeed = (mSeed * (int64_t)6364136223846793005) + (int64_t)1442695040888963407;
+#endif
+        return (int32_t) (mSeed >> 32); // The higher bits have a longer sequence.
+    }
+
+private:
+    int64_t mSeed;
+};
+
+#endif //ANALYZER_PSEUDORANDOM_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/RandomPulseGenerator.h b/media/libaaudio/examples/loopback/src/analyzer/RandomPulseGenerator.h
new file mode 100644
index 0000000..030050b
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/RandomPulseGenerator.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANALYZER_RANDOM_PULSE_GENERATOR_H
+#define ANALYZER_RANDOM_PULSE_GENERATOR_H
+
+#include <stdlib.h>
+#include "RoundedManchesterEncoder.h"
+
+/**
+ * Encode random ones and zeros using Manchester Code per IEEE 802.3.
+ */
+class RandomPulseGenerator : public RoundedManchesterEncoder {
+public:
+    RandomPulseGenerator(int samplesPerPulse)
+    : RoundedManchesterEncoder(samplesPerPulse) {
+    }
+
+    virtual ~RandomPulseGenerator() = default;
+
+    /**
+     * This will be called when the next byte is needed.
+     * @return random byte
+     */
+    uint8_t onNextByte() override {
+        return static_cast<uint8_t>(rand());
+    }
+};
+
+#endif //ANALYZER_RANDOM_PULSE_GENERATOR_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/RoundedManchesterEncoder.h b/media/libaaudio/examples/loopback/src/analyzer/RoundedManchesterEncoder.h
new file mode 100644
index 0000000..f2eba84
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/RoundedManchesterEncoder.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANALYZER_ROUNDED_MANCHESTER_ENCODER_H
+#define ANALYZER_ROUNDED_MANCHESTER_ENCODER_H
+
+#include <math.h>
+#include <memory.h>
+#include <stdlib.h>
+#include "ManchesterEncoder.h"
+
+/**
+ * Encode bytes using Manchester Code.
+ * Round the edges using a half cosine to reduce ringing caused by a hard edge.
+ */
+
+class RoundedManchesterEncoder : public ManchesterEncoder {
+public:
+    RoundedManchesterEncoder(int samplesPerPulse)
+            : ManchesterEncoder(samplesPerPulse) {
+        int rampSize = samplesPerPulse / 4;
+        mZeroAfterZero = std::make_unique<float[]>(samplesPerPulse);
+        mZeroAfterOne = std::make_unique<float[]>(samplesPerPulse);
+
+        int sampleIndex = 0;
+        for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
+            float phase = (rampIndex + 1) * M_PI / rampSize;
+            float sample = -cosf(phase);
+            mZeroAfterZero[sampleIndex] = sample;
+            mZeroAfterOne[sampleIndex] = 1.0f;
+            sampleIndex++;
+        }
+        for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
+            mZeroAfterZero[sampleIndex] = 1.0f;
+            mZeroAfterOne[sampleIndex] = 1.0f;
+            sampleIndex++;
+        }
+        for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
+            float phase = (rampIndex + 1) * M_PI / rampSize;
+            float sample = cosf(phase);
+            mZeroAfterZero[sampleIndex] = sample;
+            mZeroAfterOne[sampleIndex] = sample;
+            sampleIndex++;
+        }
+        for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
+            mZeroAfterZero[sampleIndex] = -1.0f;
+            mZeroAfterOne[sampleIndex] = -1.0f;
+            sampleIndex++;
+        }
+    }
+
+    void onNextBit(bool current) override {
+        // Do we need to use the rounded edge?
+        mCurrentSamples = (current ^ mPreviousBit)
+                          ? mZeroAfterOne.get()
+                          : mZeroAfterZero.get();
+        mPreviousBit = current;
+    }
+
+    float nextFloat() override {
+        advanceSample();
+        float output = mCurrentSamples[mCursor];
+        if (getCurrentBit()) output = -output;
+        return output;
+    }
+
+private:
+
+    bool mPreviousBit = false;
+    float *mCurrentSamples = nullptr;
+    std::unique_ptr<float[]> mZeroAfterZero;
+    std::unique_ptr<float[]> mZeroAfterOne;
+};
+
+#endif //ANALYZER_ROUNDED_MANCHESTER_ENCODER_H
diff --git a/media/libaaudio/examples/loopback/src/loopback.cpp b/media/libaaudio/examples/loopback/src/loopback.cpp
index 49d921f..0d2ec70 100644
--- a/media/libaaudio/examples/loopback/src/loopback.cpp
+++ b/media/libaaudio/examples/loopback/src/loopback.cpp
@@ -20,6 +20,8 @@
 #include <assert.h>
 #include <cctype>
 #include <errno.h>
+#include <iomanip>
+#include <iostream>
 #include <math.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -33,7 +35,9 @@
 #include "AAudioSimplePlayer.h"
 #include "AAudioSimpleRecorder.h"
 #include "AAudioExampleUtils.h"
-#include "LoopbackAnalyzer.h"
+
+#include "analyzer/GlitchAnalyzer.h"
+#include "analyzer/LatencyAnalyzer.h"
 #include "../../utils/AAudioExampleUtils.h"
 
 // V0.4.00 = rectify and low-pass filter the echos, auto-correlate entire echo
@@ -41,7 +45,8 @@
 //           fix -n option to set output buffer for -tm
 //           plot first glitch
 // V0.4.02 = allow -n0 for minimal buffer size
-#define APP_VERSION             "0.4.02"
+// V0.5.00 = use latency analyzer from OboeTester, uses random noise for latency
+#define APP_VERSION             "0.5.00"
 
 // Tag for machine readable results as property = value pairs
 #define RESULT_TAG              "RESULT: "
@@ -57,6 +62,20 @@
 constexpr int kDefaultHangTimeMillis = 50;
 constexpr int kMaxGlitchEventsToSave = 32;
 
+static void printAudioScope(float sample) {
+    const int maxStars = 80; // arbitrary, fits on one line
+    char c = '*';
+    if (sample < -1.0) {
+        sample = -1.0;
+        c = '$';
+    } else if (sample > 1.0) {
+        sample = 1.0;
+        c = '$';
+    }
+    int numSpaces = (int) (((sample + 1.0) * 0.5) * maxStars);
+    printf("%*c%c\n", numSpaces, ' ', c);
+}
+
 struct LoopbackData {
     AAudioStream      *inputStream = nullptr;
     AAudioStream      *outputStream = nullptr;
@@ -83,8 +102,8 @@
     aaudio_result_t    inputError = AAUDIO_OK;
     aaudio_result_t    outputError = AAUDIO_OK;
 
-    SineAnalyzer       sineAnalyzer;
-    EchoAnalyzer       echoAnalyzer;
+    GlitchAnalyzer     sineAnalyzer;
+    PulseLatencyAnalyzer echoAnalyzer;
     AudioRecording     audioRecording;
     LoopbackProcessor *loopbackProcessor;
 
@@ -254,17 +273,18 @@
             }
 
             // Analyze the data.
-            LoopbackProcessor::process_result procResult = myData->loopbackProcessor->process(myData->inputFloatData,
+            myData->loopbackProcessor->process(myData->inputFloatData,
                                                myData->actualInputChannelCount,
+                                               numFrames,
                                                outputData,
                                                myData->actualOutputChannelCount,
                                                numFrames);
-
-            if (procResult == LoopbackProcessor::PROCESS_RESULT_GLITCH) {
-                if (myData->numGlitchEvents < kMaxGlitchEventsToSave) {
-                    myData->glitchFrames[myData->numGlitchEvents++] = myData->audioRecording.size();
-                }
-            }
+//
+//            if (procResult == LoopbackProcessor::PROCESS_RESULT_GLITCH) {
+//                if (myData->numGlitchEvents < kMaxGlitchEventsToSave) {
+//                    myData->glitchFrames[myData->numGlitchEvents++] = myData->audioRecording.size();
+//                }
+//            }
 
             // Save for later.
             myData->audioRecording.write(myData->inputFloatData,
@@ -283,8 +303,8 @@
 }
 
 static void MyErrorCallbackProc(
-        AAudioStream *stream __unused,
-        void *userData __unused,
+        AAudioStream * /* stream */,
+        void * userData,
         aaudio_result_t error) {
     printf("Error Callback, error: %d\n",(int)error);
     LoopbackData *myData = (LoopbackData *) userData;
@@ -305,8 +325,8 @@
     printf("          l for _LATENCY\n");
     printf("          p for _POWER_SAVING\n");
     printf("      -t{test}          select test mode\n");
-    printf("          m for sine magnitude\n");
-    printf("          e for echo latency (default)\n");
+    printf("          g for Glitch detection\n");
+    printf("          l for round trip Latency (default)\n");
     printf("          f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
     printf("      -X  use EXCLUSIVE mode for input\n");
     printf("Example:  aaudio_loopback -n2 -pl -Pl -x\n");
@@ -333,20 +353,22 @@
 }
 
 enum {
-    TEST_SINE_MAGNITUDE = 0,
-    TEST_ECHO_LATENCY,
+    TEST_GLITCHES = 0,
+    TEST_LATENCY,
     TEST_FILE_LATENCY,
 };
 
 static int parseTestMode(char c) {
-    int testMode = TEST_ECHO_LATENCY;
+    int testMode = TEST_LATENCY;
     c = tolower(c);
     switch (c) {
-        case 'm':
-            testMode = TEST_SINE_MAGNITUDE;
+        case 'm': // deprecated
+        case 'g':
+            testMode = TEST_GLITCHES;
             break;
-        case 'e':
-            testMode = TEST_ECHO_LATENCY;
+        case 'e': // deprecated
+        case 'l':
+            testMode = TEST_LATENCY;
             break;
         case 'f':
             testMode = TEST_FILE_LATENCY;
@@ -408,9 +430,10 @@
     int32_t               actualSampleRate           = 0;
     int                   written                    = 0;
 
-    int                   testMode                   = TEST_ECHO_LATENCY;
+    int                   testMode                   = TEST_LATENCY;
     double                gain                       = 1.0;
     int                   hangTimeMillis             = 0;
+    std::string           report;
 
     // Make printf print immediately so that debug info is not stuck
     // in a buffer if we hang or crash.
@@ -488,22 +511,21 @@
     int32_t requestedOutputBursts = argParser.getNumberOfBursts();
 
     switch(testMode) {
-        case TEST_SINE_MAGNITUDE:
+        case TEST_GLITCHES:
             loopbackData.loopbackProcessor = &loopbackData.sineAnalyzer;
             break;
-        case TEST_ECHO_LATENCY:
-            loopbackData.echoAnalyzer.setGain(gain);
+        case TEST_LATENCY:
+            // TODO loopbackData.echoAnalyzer.setGain(gain);
             loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
             break;
         case TEST_FILE_LATENCY: {
-            loopbackData.echoAnalyzer.setGain(gain);
-
+            // TODO loopbackData.echoAnalyzer.setGain(gain);
             loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
             int read = loopbackData.loopbackProcessor->load(FILENAME_ECHOS);
             printf("main() read %d mono samples from %s on Android device, rate = %d\n",
                    read, FILENAME_ECHOS,
                    loopbackData.loopbackProcessor->getSampleRate());
-            loopbackData.loopbackProcessor->report();
+            std::cout << loopbackData.loopbackProcessor->analyze();
             goto report_result;
         }
             break;
@@ -557,7 +579,7 @@
         int32_t actualCapacity = AAudioStream_getBufferCapacityInFrames(inputStream);
         (void) AAudioStream_setBufferSizeInFrames(inputStream, actualCapacity);
 
-        if (testMode == TEST_SINE_MAGNITUDE
+        if (testMode == TEST_GLITCHES
                 && requestedOutputBursts == AAUDIO_UNSPECIFIED) {
             result = AAudioStream_setBufferSizeInFrames(outputStream, actualCapacity);
             if (result < 0) {
@@ -594,10 +616,10 @@
     loopbackData.inputFloatData = new float[loopbackData.inputFramesMaximum *
                                               loopbackData.actualInputChannelCount]{};
 
-    loopbackData.loopbackProcessor->reset();
-
     loopbackData.hangTimeMillis = hangTimeMillis;
 
+    loopbackData.loopbackProcessor->prepareToTest();
+
     // Start OUTPUT first so INPUT does not overflow.
     result = player.start();
     if (result != AAUDIO_OK) {
@@ -669,7 +691,8 @@
 
     printf("input error = %d = %s\n",
            loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
-
+/*
+    // TODO Restore this code some day if we want to save files.
     written = loopbackData.loopbackProcessor->save(FILENAME_ECHOS);
     if (written > 0) {
         printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
@@ -681,9 +704,9 @@
         printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
                written, FILENAME_ALL);
     }
-
+*/
     if (loopbackData.inputError == AAUDIO_OK) {
-        if (testMode == TEST_SINE_MAGNITUDE) {
+        if (testMode == TEST_GLITCHES) {
             if (loopbackData.numGlitchEvents > 0) {
                 // Graph around the first glitch if there is one.
                 const int32_t start = loopbackData.glitchFrames[0] - 8;
@@ -697,7 +720,8 @@
             }
         }
 
-        loopbackData.loopbackProcessor->report();
+        std::cout << "Please wait several seconds for analysis to complete.\n";
+        std::cout << loopbackData.loopbackProcessor->analyze();
     }
 
     {