Merge "GenericSource: reset timed text/subtitle after seek" into oc-dr1-dev
diff --git a/media/libaaudio/examples/loopback/jni/Android.mk b/media/libaaudio/examples/loopback/jni/Android.mk
index dc933e3..d78f286 100644
--- a/media/libaaudio/examples/loopback/jni/Android.mk
+++ b/media/libaaudio/examples/loopback/jni/Android.mk
@@ -4,7 +4,8 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_C_INCLUDES := \
     $(call include-path-for, audio-utils) \
-    frameworks/av/media/libaaudio/include
+    frameworks/av/media/libaaudio/include \
+    frameworks/av/media/libaaudio/examples/utils
 
 # NDK recommends using this kind of relative path instead of an absolute path.
 LOCAL_SRC_FILES:= ../src/loopback.cpp
diff --git a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
new file mode 100644
index 0000000..21cf341
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
@@ -0,0 +1,794 @@
+/*
+ * 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>
+
+// Tag for machine readable results as property = value pairs
+#define LOOPBACK_RESULT_TAG      "RESULT: "
+#define LOOPBACK_SAMPLE_RATE     48000
+
+#define MILLIS_PER_SECOND        1000
+
+#define MAX_ZEROTH_PARTIAL_BINS  40
+
+static const float s_Impulse[] = {
+        0.0f, 0.0f, 0.0f, 0.0f, 0.2f, // silence on each side of the impulse
+        0.5f, 0.9999f, 0.0f, -0.9999, -0.5f, // bipolar
+        -0.2f, 0.0f, 0.0f, 0.0f, 0.0f
+};
+
+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;
+};
+
+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 calculateCorrelations(const float *haystack, int haystackSize,
+                                 const float *needle, int needleSize,
+                                 float *results, int resultSize)
+{
+    int maxCorrelations = haystackSize - needleSize;
+    int numCorrelations = std::min(maxCorrelations, resultSize);
+
+    for (int ic = 0; ic < numCorrelations; ic++) {
+        double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
+        results[ic] = correlation;
+    }
+
+    return numCorrelations;
+}
+
+/*==========================================================================================*/
+/**
+ * Scan until we get a correlation of a single scan that goes over the tolerance level,
+ * peaks then drops back down.
+ */
+static double findFirstMatch(const float *haystack, int haystackSize,
+                             const float *needle, int needleSize, double threshold  )
+{
+    int ic;
+    // How many correlations can we calculate?
+    int numCorrelations = haystackSize - needleSize;
+    double maxCorrelation = 0.0;
+    int peakIndex = -1;
+    double location = -1.0;
+    const double backThresholdScaler = 0.5;
+
+    for (ic = 0; ic < numCorrelations; ic++) {
+        double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
+
+        if( (correlation > maxCorrelation) ) {
+            maxCorrelation = correlation;
+            peakIndex = ic;
+        }
+
+        //printf("PaQa_FindFirstMatch: ic = %4d, correlation = %8f, maxSum = %8f\n",
+        //    ic, correlation, maxSum );
+        // Are we past what we were looking for?
+        if((maxCorrelation > threshold) && (correlation < backThresholdScaler * maxCorrelation)) {
+            location = peakIndex;
+            break;
+        }
+    }
+
+    return location;
+}
+
+typedef struct LatencyReport_s {
+    double latencyInFrames;
+    double confidence;
+} LatencyReport;
+
+// Apply a technique similar to Harmonic Product Spectrum Analysis to find echo fundamental.
+// Using first echo instead of the original impulse for a better match.
+static int measureLatencyFromEchos(const float *haystack, int haystackSize,
+                            const float *needle, int needleSize,
+                            LatencyReport *report) {
+    const double threshold = 0.1;
+
+    // Find first peak
+    int first = (int) (findFirstMatch(haystack,
+                                      haystackSize,
+                                      needle,
+                                      needleSize,
+                                      threshold) + 0.5);
+
+    // Use first echo as the needle for the other echos because
+    // it will be more similar.
+    needle = &haystack[first];
+    int again = (int) (findFirstMatch(haystack,
+                                      haystackSize,
+                                      needle,
+                                      needleSize,
+                                      threshold) + 0.5);
+
+    printf("first = %d, again at %d\n", first, again);
+    first = again;
+
+    // Allocate results array
+    int remaining = haystackSize - first;
+    const int maxReasonableLatencyFrames = 48000 * 2; // arbitrary but generous value
+    int numCorrelations = std::min(remaining, maxReasonableLatencyFrames);
+    float *correlations = new float[numCorrelations];
+    float *harmonicSums = new float[numCorrelations](); // set to zero
+
+    // Generate correlation for every position.
+    numCorrelations = calculateCorrelations(&haystack[first], remaining,
+                                            needle, needleSize,
+                                            correlations, numCorrelations);
+
+    // Add higher harmonics mapped onto lower harmonics.
+    // This reinforces the "fundamental" echo.
+    const int numEchoes = 10;
+    for (int partial = 1; partial < numEchoes; partial++) {
+        for (int i = 0; i < numCorrelations; i++) {
+            harmonicSums[i / partial] += correlations[i] / partial;
+        }
+    }
+
+    // Find highest peak in correlation array.
+    float maxCorrelation = 0.0;
+    float sumOfPeaks = 0.0;
+    int peakIndex = 0;
+    const int skip = MAX_ZEROTH_PARTIAL_BINS; // skip low bins
+    for (int i = skip; i < numCorrelations; i++) {
+        if (harmonicSums[i] > maxCorrelation) {
+            maxCorrelation = harmonicSums[i];
+            sumOfPeaks += maxCorrelation;
+            peakIndex = i;
+            printf("maxCorrelation = %f at %d\n", maxCorrelation, peakIndex);
+        }
+    }
+
+    report->latencyInFrames = peakIndex;
+    if (sumOfPeaks < 0.0001) {
+        report->confidence = 0.0;
+    } else {
+        report->confidence = maxCorrelation / sumOfPeaks;
+    }
+
+    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.
+    int write(int16_t *inputData, int inputChannelCount, int 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.
+    int write(float *inputData, int inputChannelCount, int 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;
+    }
+
+    int size() {
+        return mFrameCounter;
+    }
+
+    float *getData() {
+        return mData;
+    }
+
+    int save(const char *fileName, bool writeShorts = true) {
+        int written = 0;
+        const int chunkSize = 64;
+        FILE *fid = fopen(fileName, "wb");
+        if (fid == NULL) {
+            return -errno;
+        }
+
+        if (writeShorts) {
+            int16_t buffer[chunkSize];
+            int32_t framesLeft = mFrameCounter;
+            int32_t cursor = 0;
+            while (framesLeft) {
+                int32_t framesToWrite = framesLeft < chunkSize ? framesLeft : chunkSize;
+                for (int i = 0; i < framesToWrite; i++) {
+                    buffer[i] = (int16_t) (mData[cursor++] * 32767);
+                }
+                written += fwrite(buffer, sizeof(int16_t), framesToWrite, fid);
+                framesLeft -= framesToWrite;
+            }
+        } else {
+            written = (int) fwrite(mData, sizeof(float), mFrameCounter, fid);
+        }
+        fclose(fid);
+        return written;
+    }
+
+private:
+    float  *mData = nullptr;
+    int32_t mFrameCounter = 0;
+    int32_t mMaxFrames = 0;
+};
+
+// ====================================================================================
+class LoopbackProcessor {
+public:
+    virtual ~LoopbackProcessor() = default;
+
+
+    virtual void reset() {}
+
+    virtual void process(float *inputData, int inputChannelCount,
+                 float *outputData, int outputChannelCount,
+                 int numFrames) = 0;
+
+
+    virtual void report() = 0;
+
+    virtual void printStatus() {};
+
+    virtual bool isDone() {
+        return false;
+    }
+
+    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++) {
+            float pos = fabs(*inputData);
+            if (pos > peak) {
+                peak = pos;
+            }
+            inputData += inputChannelCount;
+        }
+        return peak;
+    }
+
+
+private:
+    int32_t mSampleRate = LOOPBACK_SAMPLE_RATE;
+};
+
+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;
+};
+
+
+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);
+}
+
+// ====================================================================================
+/**
+ * Measure latency given a loopback stream data.
+ * Uses a state machine to cycle through various stages including:
+ *
+ */
+class EchoAnalyzer : public LoopbackProcessor {
+public:
+
+    EchoAnalyzer() : LoopbackProcessor() {
+        audioRecorder.allocate(2 * LOOPBACK_SAMPLE_RATE);
+    }
+
+    void reset() override {
+        mDownCounter = 200;
+        mLoopCounter = 0;
+        mMeasuredLoopGain = 0.0f;
+        mEchoGain = 1.0f;
+        mState = STATE_INITIAL_SILENCE;
+    }
+
+    virtual bool isDone() {
+        return mState == STATE_DONE;
+    }
+
+    void setGain(float gain) {
+        mEchoGain = gain;
+    }
+
+    float getGain() {
+        return mEchoGain;
+    }
+
+    void report() override {
+
+        printf("EchoAnalyzer ---------------\n");
+        printf(LOOPBACK_RESULT_TAG "measured.gain          = %f\n", mMeasuredLoopGain);
+        printf(LOOPBACK_RESULT_TAG "echo.gain              = %f\n", mEchoGain);
+        printf(LOOPBACK_RESULT_TAG "frame.count            = %d\n", mFrameCounter);
+        printf(LOOPBACK_RESULT_TAG "test.state             = %d\n", mState);
+        if (mMeasuredLoopGain >= 0.9999) {
+            printf("   ERROR - clipping, turn down volume slightly\n");
+        } else {
+            const float *needle = s_Impulse;
+            int needleSize = (int) (sizeof(s_Impulse) / sizeof(float));
+            float *haystack = audioRecorder.getData();
+            int haystackSize = audioRecorder.size();
+            int result = measureLatencyFromEchos(haystack, haystackSize,
+                                                 needle, needleSize,
+                                                 &latencyReport);
+            if (latencyReport.confidence < 0.01) {
+                printf("   ERROR - confidence too low = %f\n", latencyReport.confidence);
+            } else {
+                double latencyMillis = 1000.0 * latencyReport.latencyInFrames / getSampleRate();
+                printf(LOOPBACK_RESULT_TAG "latency.frames        = %8.2f\n", latencyReport.latencyInFrames);
+                printf(LOOPBACK_RESULT_TAG "latency.msec          = %8.2f\n", latencyMillis);
+                printf(LOOPBACK_RESULT_TAG "latency.confidence    = %8.6f\n", latencyReport.confidence);
+            }
+        }
+
+        {
+#define ECHO_FILENAME "/data/oboe_echo.raw"
+            int written = audioRecorder.save(ECHO_FILENAME);
+            printf("Echo wrote %d mono samples to %s on Android device\n", written, ECHO_FILENAME);
+        }
+    }
+
+    void printStatus() override {
+        printf("state = %d, echo gain = %f ", mState, mEchoGain);
+    }
+
+    static void sendImpulse(float *outputData, int outputChannelCount) {
+        for (float sample : s_Impulse) {
+            *outputData = sample;
+            outputData += outputChannelCount;
+        }
+    }
+
+    void 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_t 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;
+                }
+                if (mDownCounter-- <= 0) {
+                    nextState = STATE_MEASURING_GAIN;
+                    //printf("%5d: switch to STATE_MEASURING_GAIN\n", mLoopCounter);
+                    mDownCounter = 8;
+                }
+                break;
+
+            case STATE_MEASURING_GAIN:
+                sendImpulse(outputData, outputChannelCount);
+                peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
+                // If we get several in a row then go to next state.
+                if (peak > mPulseThreshold) {
+                    if (mDownCounter-- <= 0) {
+                        nextState = STATE_WAITING_FOR_SILENCE;
+                        //printf("%5d: switch to STATE_WAITING_FOR_SILENCE, measured peak = %f\n",
+                        //       mLoopCounter, peak);
+                        mDownCounter = 8;
+                        mMeasuredLoopGain = peak;  // assumes original pulse amplitude is one
+                        // Calculate gain that will give us a nice decaying echo.
+                        mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
+                    }
+                } else {
+                    mDownCounter = 8;
+                }
+                break;
+
+            case STATE_WAITING_FOR_SILENCE:
+                // Output silence.
+                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) {
+                    if (mDownCounter-- <= 0) {
+                        nextState = STATE_SENDING_PULSE;
+                        //printf("%5d: switch to STATE_SENDING_PULSE\n", mLoopCounter);
+                        mDownCounter = 8;
+                    }
+                } else {
+                    mDownCounter = 8;
+                }
+                break;
+
+            case STATE_SENDING_PULSE:
+                audioRecorder.write(inputData, inputChannelCount, numFrames);
+                sendImpulse(outputData, outputChannelCount);
+                nextState = STATE_GATHERING_ECHOS;
+                //printf("%5d: switch to STATE_GATHERING_ECHOS\n", mLoopCounter);
+                break;
+
+            case STATE_GATHERING_ECHOS:
+                numWritten = audioRecorder.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;
+                    //printf("%5d: switch to STATE_DONE\n", mLoopCounter);
+                }
+                break;
+
+            case STATE_DONE:
+            default:
+                break;
+        }
+
+        mState = nextState;
+        mLoopCounter++;
+    }
+
+private:
+
+    enum echo_state_t {
+        STATE_INITIAL_SILENCE,
+        STATE_MEASURING_GAIN,
+        STATE_WAITING_FOR_SILENCE,
+        STATE_SENDING_PULSE,
+        STATE_GATHERING_ECHOS,
+        STATE_DONE
+    };
+
+    int           mDownCounter = 500;
+    int           mLoopCounter = 0;
+    int           mLoopStart = 1000;
+    float         mPulseThreshold = 0.02f;
+    float         mSilenceThreshold = 0.002f;
+    float         mMeasuredLoopGain = 0.0f;
+    float         mDesiredEchoGain = 0.95f;
+    float         mEchoGain = 1.0f;
+    echo_state_t  mState = STATE_INITIAL_SILENCE;
+    int32_t       mFrameCounter = 0;
+
+    AudioRecording     audioRecorder;
+    LatencyReport      latencyReport;
+    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     = %7.5f\n", mPeakAmplitude);
+        printf(LOOPBACK_RESULT_TAG "sine.magnitude     = %7.5f\n", mMagnitude);
+        printf(LOOPBACK_RESULT_TAG "phase.offset       = %7.5f\n", mPhaseOffset);
+        printf(LOOPBACK_RESULT_TAG "ref.phase          = %7.5f\n", mPhase);
+        printf(LOOPBACK_RESULT_TAG "frames.accumulated = %6d\n", mFramesAccumulated);
+        printf(LOOPBACK_RESULT_TAG "sine.period        = %6d\n", mPeriod);
+        printf(LOOPBACK_RESULT_TAG "test.state         = %6d\n", mState);
+        printf(LOOPBACK_RESULT_TAG "frame.count        = %6d\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");
+        } else {
+            // Only print if meaningful.
+            printf(LOOPBACK_RESULT_TAG "glitch.count       = %6d\n", mGlitchCount);
+        }
+    }
+
+    void printStatus() override {
+        printf("  state = %d, glitches = %d,", 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
+     */
+    void process(float *inputData, int inputChannelCount,
+                 float *outputData, int outputChannelCount,
+                 int numFrames) override {
+        float sample;
+        float peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
+        if (peak > mPeakAmplitude) {
+            mPeakAmplitude = peak;
+        }
+
+        for (int i = 0; i < numFrames; i++) {
+            float sample = inputData[i * inputChannelCount];
+
+            float sinOut = sinf(mPhase);
+
+            switch (mState) {
+                case STATE_IMMUNE:
+                case STATE_WAITING_FOR_SIGNAL:
+                    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 == mPeriod * 4) {
+                        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;
+                    if (fabs(diff) > mTolerance) {
+                        mGlitchCount++;
+                        //printf("%5d: Got a glitch # %d, predicted = %f, actual = %f\n",
+                        //       mFrameCounter, mGlitchCount, predicted, sample);
+                        mState = STATE_IMMUNE;
+                        //printf("%5d: switch to STATE_IMMUNE\n", mFrameCounter);
+                        mDownCounter = mPeriod;  // Set duration of IMMUNE state.
+                    }
+                } break;
+            }
+
+            // Output sine wave so we can measure it.
+            outputData[i * outputChannelCount] = (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);
+            }
+
+            mFrameCounter++;
+        }
+
+        // Do these once per buffer.
+        switch (mState) {
+            case STATE_IMMUNE:
+                mDownCounter -= numFrames;
+                if (mDownCounter <= 0) {
+                    mState = STATE_WAITING_FOR_SIGNAL;
+                    //printf("%5d: switch to STATE_WAITING_FOR_SIGNAL\n", mFrameCounter);
+                }
+                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:
+            case STATE_LOCKED:
+                break;
+        }
+
+    }
+
+    void resetAccumulator() {
+        mFramesAccumulated = 0;
+        mSinAccumulator = 0.0;
+        mCosAccumulator = 0.0;
+    }
+
+    void reset() override {
+        mGlitchCount = 0;
+        mState = STATE_IMMUNE;
+        mPhaseIncrement = 2.0 * M_PI / mPeriod;
+        printf("phaseInc = %f for period %d\n", mPhaseIncrement, mPeriod);
+        resetAccumulator();
+    }
+
+private:
+
+    enum sine_state_t {
+        STATE_IMMUNE,
+        STATE_WAITING_FOR_SIGNAL,
+        STATE_WAITING_FOR_LOCK,
+        STATE_LOCKED
+    };
+
+    int     mPeriod = 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 = 0.01;
+    int32_t mFramesAccumulated = 0;
+    double  mSinAccumulator = 0.0;
+    double  mCosAccumulator = 0.0;
+    int32_t mGlitchCount = 0;
+    double  mPeakAmplitude = 0.0;
+    int     mDownCounter = 4000;
+    int32_t mFrameCounter = 0;
+    float   mOutputAmplitude = 0.75;
+
+    int32_t mZeroCrossings = 0;
+
+    PseudoRandom  mWhiteNoise;
+    float   mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
+
+    sine_state_t  mState = STATE_IMMUNE;
+};
+
+
+#undef LOOPBACK_SAMPLE_RATE
+#undef LOOPBACK_RESULT_TAG
+
+#endif /* AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H */
diff --git a/media/libaaudio/examples/loopback/src/loopback.cpp b/media/libaaudio/examples/loopback/src/loopback.cpp
index 57d45cd..144c941 100644
--- a/media/libaaudio/examples/loopback/src/loopback.cpp
+++ b/media/libaaudio/examples/loopback/src/loopback.cpp
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-// Play an impulse and then record it.
-// Measure the round trip latency.
+// Audio loopback tests to measure the round trip latency and glitches.
 
 #include <algorithm>
 #include <assert.h>
@@ -28,460 +27,38 @@
 #include <aaudio/AAudio.h>
 #include <aaudio/AAudioTesting.h>
 
+#include "AAudioSimplePlayer.h"
+#include "AAudioSimpleRecorder.h"
+#include "AAudioExampleUtils.h"
+#include "LoopbackAnalyzer.h"
+
 // Tag for machine readable results as property = value pairs
 #define RESULT_TAG              "RESULT: "
 #define SAMPLE_RATE             48000
 #define NUM_SECONDS             5
 #define NUM_INPUT_CHANNELS      1
 #define FILENAME                "/data/oboe_input.raw"
-
-#define NANOS_PER_MICROSECOND ((int64_t)1000)
-#define NANOS_PER_MILLISECOND (NANOS_PER_MICROSECOND * 1000)
-#define MILLIS_PER_SECOND     1000
-#define NANOS_PER_SECOND      (NANOS_PER_MILLISECOND * MILLIS_PER_SECOND)
-
-#define MAX_ZEROTH_PARTIAL_BINS   40
-
-static const float s_Impulse[] = {
-        0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // silence on each side of the impulse
-        0.5f, 0.9f, 0.0f, -0.9f, -0.5f, // bipolar
-        0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
-
-
-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 calculateCorrelations(const float *haystack, int haystackSize,
-                                 const float *needle, int needleSize,
-                                 float *results, int resultSize)
-{
-    int ic;
-    int maxCorrelations = haystackSize - needleSize;
-    int numCorrelations = std::min(maxCorrelations, resultSize);
-
-    for (ic = 0; ic < numCorrelations; ic++) {
-        double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
-        results[ic] = correlation;
-    }
-
-    return numCorrelations;
-}
-
-/*==========================================================================================*/
-/**
- * Scan until we get a correlation of a single scan that goes over the tolerance level,
- * peaks then drops back down.
- */
-static double findFirstMatch(const float *haystack, int haystackSize,
-                             const float *needle, int needleSize, double threshold  )
-{
-    int ic;
-    // How many correlations can we calculate?
-    int numCorrelations = haystackSize - needleSize;
-    double maxCorrelation = 0.0;
-    int peakIndex = -1;
-    double location = -1.0;
-
-    for (ic = 0; ic < numCorrelations; ic++) {
-        double correlation = calculateCorrelation(&haystack[ic], needle, needleSize);
-
-        if( (correlation > maxCorrelation) ) {
-            maxCorrelation = correlation;
-            peakIndex = ic;
-        }
-
-        //printf("PaQa_FindFirstMatch: ic = %4d, correlation = %8f, maxSum = %8f\n",
-        //    ic, correlation, maxSum );
-        // Are we past what we were looking for?
-        if((maxCorrelation > threshold) && (correlation < 0.5 * maxCorrelation)) {
-            location = peakIndex;
-            break;
-        }
-    }
-
-    return location;
-}
-
-typedef struct LatencyReport_s {
-    double latencyInFrames;
-    double confidence;
-} LatencyReport;
-
-// Apply a technique similar to Harmonic Product Spectrum Analysis to find echo fundamental.
-// Using first echo instead of the original impulse for a better match.
-int measureLatencyFromEchos(const float *haystack, int haystackSize,
-                          const float *needle, int needleSize,
-                          LatencyReport *report) {
-    double threshold = 0.1;
-
-    // Find first peak
-    int first = (int) (findFirstMatch(haystack,
-                                      haystackSize,
-                                      needle,
-                                      needleSize,
-                                      threshold) + 0.5);
-
-    // Use first echo as the needle for the other echos because
-    // it will be more similar.
-    needle = &haystack[first];
-    int again = (int) (findFirstMatch(haystack,
-                                      haystackSize,
-                                      needle,
-                                      needleSize,
-                                      threshold) + 0.5);
-
-    printf("first = %d, again at %d\n", first, again);
-    first = again;
-
-    // Allocate results array
-    int remaining = haystackSize - first;
-    int generous = 48000 * 2;
-    int numCorrelations = std::min(remaining, generous);
-    float *correlations = new float[numCorrelations];
-    float *harmonicSums = new float[numCorrelations](); // cleared to zero
-
-    // Generate correlation for every position.
-    numCorrelations = calculateCorrelations(&haystack[first], remaining,
-                                            needle, needleSize,
-                                            correlations, numCorrelations);
-
-    // Add higher harmonics mapped onto lower harmonics.
-    // This reinforces the "fundamental" echo.
-    const int numEchoes = 10;
-    for (int partial = 1; partial < numEchoes; partial++) {
-        for (int i = 0; i < numCorrelations; i++) {
-            harmonicSums[i / partial] += correlations[i] / partial;
-        }
-    }
-
-    // Find highest peak in correlation array.
-    float maxCorrelation = 0.0;
-    float sumOfPeaks = 0.0;
-    int peakIndex = 0;
-    const int skip = MAX_ZEROTH_PARTIAL_BINS; // skip low bins
-    for (int i = skip; i < numCorrelations; i++) {
-        if (harmonicSums[i] > maxCorrelation) {
-            maxCorrelation = harmonicSums[i];
-            sumOfPeaks += maxCorrelation;
-            peakIndex = i;
-            printf("maxCorrelation = %f at %d\n", maxCorrelation, peakIndex);
-        }
-    }
-
-    report->latencyInFrames = peakIndex;
-    if (sumOfPeaks < 0.0001) {
-        report->confidence = 0.0;
-    } else {
-        report->confidence = maxCorrelation / sumOfPeaks;
-    }
-
-    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.
-    int write(int16_t *inputData, int inputChannelCount, int 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.
-    int write(float *inputData, int inputChannelCount, int 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;
-    }
-
-    int size() {
-        return mFrameCounter;
-    }
-
-    float *getData() {
-        return mData;
-    }
-
-    int save(const char *fileName, bool writeShorts = true) {
-        int written = 0;
-        const int chunkSize = 64;
-        FILE *fid = fopen(fileName, "wb");
-        if (fid == NULL) {
-            return -errno;
-        }
-
-        if (writeShorts) {
-            int16_t buffer[chunkSize];
-            int32_t framesLeft = mFrameCounter;
-            int32_t cursor = 0;
-            while (framesLeft) {
-                int32_t framesToWrite = framesLeft < chunkSize ? framesLeft : chunkSize;
-                for (int i = 0; i < framesToWrite; i++) {
-                    buffer[i] = (int16_t) (mData[cursor++] * 32767);
-                }
-                written += fwrite(buffer, sizeof(int16_t), framesToWrite, fid);
-                framesLeft -= framesToWrite;
-            }
-        } else {
-            written = fwrite(mData, sizeof(float), mFrameCounter, fid);
-        }
-        fclose(fid);
-        return written;
-    }
-
-private:
-    float  *mData = nullptr;
-    int32_t mFrameCounter = 0;
-    int32_t mMaxFrames = 0;
-};
-
-// ====================================================================================
-class LoopbackProcessor {
-public:
-    virtual ~LoopbackProcessor() = default;
-
-    virtual void process(float *inputData, int inputChannelCount,
-                 float *outputData, int outputChannelCount,
-                 int numFrames) = 0;
-
-
-    virtual void report() = 0;
-
-    void setSampleRate(int32_t sampleRate) {
-        mSampleRate = sampleRate;
-    }
-
-    int32_t getSampleRate() {
-        return mSampleRate;
-    }
-
-private:
-    int32_t mSampleRate = SAMPLE_RATE;
-};
-
-
-// ====================================================================================
-class EchoAnalyzer : public LoopbackProcessor {
-public:
-
-    EchoAnalyzer() : LoopbackProcessor() {
-        audioRecorder.allocate(NUM_SECONDS * SAMPLE_RATE);
-    }
-
-    void setGain(float gain) {
-        mGain = gain;
-    }
-
-    float getGain() {
-        return mGain;
-    }
-
-    void report() override {
-
-        const float *needle = s_Impulse;
-        int needleSize = (int)(sizeof(s_Impulse) / sizeof(float));
-        float *haystack = audioRecorder.getData();
-        int haystackSize = audioRecorder.size();
-        int result = measureLatencyFromEchos(haystack, haystackSize,
-                                              needle, needleSize,
-                                              &latencyReport);
-        if (latencyReport.confidence < 0.01) {
-            printf(" ERROR - confidence too low = %f\n", latencyReport.confidence);
-        } else {
-            double latencyMillis = 1000.0 * latencyReport.latencyInFrames / getSampleRate();
-            printf(RESULT_TAG "latency.frames     = %8.2f\n", latencyReport.latencyInFrames);
-            printf(RESULT_TAG "latency.msec       = %8.2f\n", latencyMillis);
-            printf(RESULT_TAG "latency.confidence = %8.6f\n", latencyReport.confidence);
-        }
-    }
-
-    void process(float *inputData, int inputChannelCount,
-                 float *outputData, int outputChannelCount,
-                 int numFrames) override {
-        int channelsValid = std::min(inputChannelCount, outputChannelCount);
-
-        audioRecorder.write(inputData, inputChannelCount, numFrames);
-
-        if (mLoopCounter < mLoopStart) {
-            // Output silence at the beginning.
-            for (int i = 0; i < numFrames; i++) {
-                int ic;
-                for (ic = 0; ic < outputChannelCount; ic++) {
-                    outputData[ic] = 0;
-                }
-                inputData += inputChannelCount;
-                outputData += outputChannelCount;
-            }
-        } else if (mLoopCounter == mLoopStart) {
-            // Send a bipolar impulse that we can easily detect.
-            for (float sample : s_Impulse) {
-                *outputData = sample;
-                outputData += outputChannelCount;
-            }
-        } else {
-            // Echo input to output.
-            for (int i = 0; i < numFrames; i++) {
-                int ic;
-                for (ic = 0; ic < channelsValid; ic++) {
-                    outputData[ic] = inputData[ic] * mGain;
-                }
-                for (; ic < outputChannelCount; ic++) {
-                    outputData[ic] = 0;
-                }
-                inputData += inputChannelCount;
-                outputData += outputChannelCount;
-            }
-        }
-
-        mLoopCounter++;
-    }
-
-private:
-    int   mLoopCounter = 0;
-    int   mLoopStart = 1000;
-    float mGain = 1.0f;
-
-    AudioRecording     audioRecorder;
-    LatencyReport      latencyReport;
-};
-
-
-// ====================================================================================
-class SineAnalyzer : public LoopbackProcessor {
-public:
-
-    void report() override {
-        double magnitude = calculateMagnitude();
-        printf("sine magnitude = %7.5f\n", magnitude);
-        printf("sine frames    = %7d\n", mFrameCounter);
-        printf("sine frequency = %7.1f Hz\n", mFrequency);
-    }
-
-    double calculateMagnitude(double *phasePtr = NULL) {
-        if (mFrameCounter == 0) {
-            return 0.0;
-        }
-        double sinMean = mSinAccumulator / mFrameCounter;
-        double cosMean = mCosAccumulator / mFrameCounter;
-        double magnitude = 2.0 * sqrt( (sinMean * sinMean) + (cosMean * cosMean ));
-        if( phasePtr != NULL )
-        {
-            double phase = atan2( sinMean, cosMean );
-            *phasePtr = phase;
-        }
-        return magnitude;
-    }
-
-    void process(float *inputData, int inputChannelCount,
-                 float *outputData, int outputChannelCount,
-                 int numFrames) override {
-        double phaseIncrement = 2.0 * M_PI * mFrequency / getSampleRate();
-
-        for (int i = 0; i < numFrames; i++) {
-            // Multiply input by sine/cosine
-            float sample = inputData[i * inputChannelCount];
-            float sinOut = sinf(mPhase);
-            mSinAccumulator += sample * sinOut;
-            mCosAccumulator += sample * cosf(mPhase);
-            // Advance and wrap phase
-            mPhase += phaseIncrement;
-            if (mPhase > (2.0 * M_PI)) {
-                mPhase -= (2.0 * M_PI);
-            }
-
-            // Output sine wave so we can measure it.
-            outputData[i * outputChannelCount] = sinOut;
-        }
-        mFrameCounter += numFrames;
-
-        double magnitude = calculateMagnitude();
-        if (mWaiting) {
-            if (magnitude < 0.001) {
-                // discard silence
-                mFrameCounter = 0;
-                mSinAccumulator = 0.0;
-                mCosAccumulator = 0.0;
-            } else {
-                mWaiting = false;
-            }
-        }
-    };
-
-    void setFrequency(int32_t frequency) {
-        mFrequency = frequency;
-    }
-
-    int32_t getFrequency() {
-        return mFrequency;
-    }
-
-private:
-    double  mFrequency = 300.0;
-    double  mPhase = 0.0;
-    int32_t mFrameCounter = 0;
-    double  mSinAccumulator = 0.0;
-    double  mCosAccumulator = 0.0;
-    bool    mWaiting = true;
-};
+#define APP_VERSION             "0.1.22"
 
-// TODO make this a class that manages its own buffer allocation
 struct LoopbackData {
     AAudioStream      *inputStream = nullptr;
     int32_t            inputFramesMaximum = 0;
     int16_t           *inputData = nullptr;
+    int16_t            peakShort = 0;
     float             *conversionBuffer = nullptr;
     int32_t            actualInputChannelCount = 0;
     int32_t            actualOutputChannelCount = 0;
     int32_t            inputBuffersToDiscard = 10;
+    int32_t            minNumFrames = INT32_MAX;
+    int32_t            maxNumFrames = 0;
+    bool               isDone = false;
 
-    aaudio_result_t    inputError;
+    aaudio_result_t    inputError = AAUDIO_OK;
+    aaudio_result_t    outputError = AAUDIO_OK;
+
     SineAnalyzer       sineAnalyzer;
     EchoAnalyzer       echoAnalyzer;
+    AudioRecording     audioRecorder;
     LoopbackProcessor *loopbackProcessor;
 };
 
@@ -517,6 +94,13 @@
         return AAUDIO_CALLBACK_RESULT_STOP;
     }
 
+    if (numFrames > myData->maxNumFrames) {
+        myData->maxNumFrames = numFrames;
+    }
+    if (numFrames < myData->minNumFrames) {
+        myData->minNumFrames = numFrames;
+    }
+
     if (myData->inputBuffersToDiscard > 0) {
         // Drain the input.
         do {
@@ -524,6 +108,7 @@
                                        numFrames, 0);
             if (framesRead < 0) {
                 myData->inputError = framesRead;
+                printf("ERROR in read = %d", framesRead);
                 result = AAUDIO_CALLBACK_RESULT_STOP;
             } else if (framesRead > 0) {
                 myData->inputBuffersToDiscard--;
@@ -534,9 +119,14 @@
                                        numFrames, 0);
         if (framesRead < 0) {
             myData->inputError = framesRead;
+            printf("ERROR in read = %d", framesRead);
             result = AAUDIO_CALLBACK_RESULT_STOP;
         } else if (framesRead > 0) {
 
+            myData->audioRecorder.write(myData->inputData,
+                                        myData->actualInputChannelCount,
+                                        numFrames);
+
             int32_t numSamples = framesRead * myData->actualInputChannelCount;
             convertPcm16ToFloat(myData->inputData, myData->conversionBuffer, numSamples);
 
@@ -545,12 +135,25 @@
                                               outputData,
                                               myData->actualOutputChannelCount,
                                               framesRead);
+            myData->isDone = myData->loopbackProcessor->isDone();
+            if (myData->isDone) {
+                result = AAUDIO_CALLBACK_RESULT_STOP;
+            }
         }
     }
 
     return result;
 }
 
+static void MyErrorCallbackProc(
+        AAudioStream *stream __unused,
+        void *userData __unused,
+        aaudio_result_t error)
+{
+    printf("Error Callback, error: %d\n",(int)error);
+    LoopbackData *myData = (LoopbackData *) userData;
+    myData->outputError = error;
+}
 
 static void usage() {
     printf("loopback: -n{numBursts} -p{outPerf} -P{inPerf} -t{test} -g{gain} -f{freq}\n");
@@ -571,7 +174,7 @@
 }
 
 static aaudio_performance_mode_t parsePerformanceMode(char c) {
-    aaudio_performance_mode_t mode = AAUDIO_PERFORMANCE_MODE_NONE;
+    aaudio_performance_mode_t mode = AAUDIO_ERROR_ILLEGAL_ARGUMENT;
     c = tolower(c);
     switch (c) {
         case 'n':
@@ -612,86 +215,117 @@
     return testMode;
 }
 
+void printAudioGraph(AudioRecording &recording, int numSamples) {
+    int32_t start = recording.size() / 2;
+    int32_t end = start + numSamples;
+    if (end >= recording.size()) {
+        end = recording.size() - 1;
+    }
+    float *data = recording.getData();
+    // Normalize data so we can see it better.
+    float maxSample = 0.01;
+    for (int32_t i = start; i < end; i++) {
+        float samplePos = fabs(data[i]);
+        if (samplePos > maxSample) {
+            maxSample = samplePos;
+        }
+    }
+    float gain = 0.98f / maxSample;
+    for (int32_t i = start; i < end; i++) {
+        float sample = data[i];
+        printf("%5.3f ", sample); // actual value
+        sample *= gain;
+        printAudioScope(sample);
+    }
+}
+
+
 // ====================================================================================
 // TODO break up this large main() function into smaller functions
 int main(int argc, const char **argv)
 {
-    aaudio_result_t result = AAUDIO_OK;
-    LoopbackData loopbackData;
-    AAudioStream *outputStream = nullptr;
 
-    int requestedInputChannelCount = NUM_INPUT_CHANNELS;
-    const int requestedOutputChannelCount = AAUDIO_UNSPECIFIED;
-    const int requestedSampleRate = SAMPLE_RATE;
-    int actualSampleRate = 0;
+    AAudioArgsParser     argParser;
+    AAudioSimplePlayer   player;
+    AAudioSimpleRecorder recorder;
+    LoopbackData         loopbackData;
+    AAudioStream        *outputStream = nullptr;
+
+    aaudio_result_t      result = AAUDIO_OK;
+    aaudio_sharing_mode_t requestedInputSharingMode     = AAUDIO_SHARING_MODE_SHARED;
+    int                   requestedInputChannelCount = NUM_INPUT_CHANNELS;
+    const int             requestedOutputChannelCount = AAUDIO_UNSPECIFIED;
+    int                   actualSampleRate = 0;
     const aaudio_format_t requestedInputFormat = AAUDIO_FORMAT_PCM_I16;
     const aaudio_format_t requestedOutputFormat = AAUDIO_FORMAT_PCM_FLOAT;
-    aaudio_format_t actualInputFormat;
-    aaudio_format_t actualOutputFormat;
+    aaudio_format_t       actualInputFormat;
+    aaudio_format_t       actualOutputFormat;
+    aaudio_performance_mode_t outputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
+    aaudio_performance_mode_t inputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
+
     int testMode = TEST_ECHO_LATENCY;
-    double frequency = 1000.0;
     double gain = 1.0;
 
-    const aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
-    //const aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_SHARED;
-    aaudio_sharing_mode_t       actualSharingMode;
-
-    AAudioStreamBuilder  *builder = nullptr;
     aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
     int32_t framesPerBurst = 0;
     float *outputData = NULL;
     double deviation;
     double latency;
-    aaudio_performance_mode_t outputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
-    aaudio_performance_mode_t inputPerformanceLevel = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
-
     int32_t burstsPerBuffer = 1; // single buffered
 
+    // Make printf print immediately so that debug info is not stuck
+    // in a buffer if we hang or crash.
+    setvbuf(stdout, NULL, _IONBF, (size_t) 0);
+
+    printf("%s - Audio loopback using AAudio V" APP_VERSION "\n", argv[0]);
+
     for (int i = 1; i < argc; i++) {
         const char *arg = argv[i];
-        if (arg[0] == '-') {
-            char option = arg[1];
-            switch (option) {
-                case 'c':
-                    requestedInputChannelCount = atoi(&arg[2]);
-                    break;
-                case 'f':
-                    frequency = atof(&arg[2]);
-                    break;
-                case 'g':
-                    gain = atof(&arg[2]);
-                    break;
-                case 'm':
-                    AAudio_setMMapPolicy(AAUDIO_POLICY_AUTO);
-                    break;
-                case 'n':
-                    burstsPerBuffer = atoi(&arg[2]);
-                    break;
-                case 'p':
-                    outputPerformanceLevel = parsePerformanceMode(arg[2]);
-                    break;
-                case 'P':
-                    inputPerformanceLevel = parsePerformanceMode(arg[2]);
-                    break;
-                case 't':
-                    testMode = parseTestMode(arg[2]);
-                    break;
-                default:
-                    usage();
-                    exit(0);
-                    break;
+        if (argParser.parseArg(arg)) {
+            // Handle options that are not handled by the ArgParser
+            if (arg[0] == '-') {
+                char option = arg[1];
+                switch (option) {
+                    case 'C':
+                        requestedInputChannelCount = atoi(&arg[2]);
+                        break;
+                    case 'g':
+                        gain = atof(&arg[2]);
+                        break;
+                    case 'P':
+                        inputPerformanceLevel = parsePerformanceMode(arg[2]);
+                        break;
+                    case 'X':
+                        requestedInputSharingMode = AAUDIO_SHARING_MODE_EXCLUSIVE;
+                        break;
+                    case 't':
+                        testMode = parseTestMode(arg[2]);
+                        break;
+                    default:
+                        usage();
+                        exit(EXIT_FAILURE);
+                        break;
+                }
+            } else {
+                usage();
+                exit(EXIT_FAILURE);
+                break;
             }
-        } else {
-            usage();
-            exit(0);
-            break;
         }
+
     }
 
+    if (inputPerformanceLevel < 0) {
+        printf("illegal inputPerformanceLevel = %d\n", inputPerformanceLevel);
+        exit(EXIT_FAILURE);
+    }
+
+    int32_t requestedDuration = argParser.getDurationSeconds();
+    int32_t recordingDuration = std::min(60, requestedDuration);
+    loopbackData.audioRecorder.allocate(recordingDuration * SAMPLE_RATE);
 
     switch(testMode) {
         case TEST_SINE_MAGNITUDE:
-            loopbackData.sineAnalyzer.setFrequency(frequency);
             loopbackData.loopbackProcessor = &loopbackData.sineAnalyzer;
             break;
         case TEST_ECHO_LATENCY:
@@ -703,106 +337,44 @@
             break;
     }
 
-    // Make printf print immediately so that debug info is not stuck
-    // in a buffer if we hang or crash.
-    setvbuf(stdout, NULL, _IONBF, (size_t) 0);
-
-    printf("%s - Audio loopback using AAudio\n", argv[0]);
-
-    // Use an AAudioStreamBuilder to contain requested parameters.
-    result = AAudio_createStreamBuilder(&builder);
-    if (result < 0) {
-        goto finish;
-    }
-
-    // Request common stream properties.
-    AAudioStreamBuilder_setSampleRate(builder, requestedSampleRate);
-    AAudioStreamBuilder_setFormat(builder, requestedInputFormat);
-    AAudioStreamBuilder_setSharingMode(builder, requestedSharingMode);
-
-    // Open the input stream.
-    AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_INPUT);
-    AAudioStreamBuilder_setPerformanceMode(builder, inputPerformanceLevel);
-    AAudioStreamBuilder_setChannelCount(builder, requestedInputChannelCount);
-
-    result = AAudioStreamBuilder_openStream(builder, &loopbackData.inputStream);
-    printf("AAudioStreamBuilder_openStream(input) returned %d = %s\n",
-           result, AAudio_convertResultToText(result));
-    if (result < 0) {
-        goto finish;
-    }
-
-    // Create an output stream using the Builder.
-    AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT);
-    AAudioStreamBuilder_setFormat(builder, requestedOutputFormat);
-    AAudioStreamBuilder_setPerformanceMode(builder, outputPerformanceLevel);
-    AAudioStreamBuilder_setChannelCount(builder, requestedOutputChannelCount);
-    AAudioStreamBuilder_setDataCallback(builder, MyDataCallbackProc, &loopbackData);
-
-    result = AAudioStreamBuilder_openStream(builder, &outputStream);
-    printf("AAudioStreamBuilder_openStream(output) returned %d = %s\n",
-           result, AAudio_convertResultToText(result));
+    printf("OUTPUT stream ----------------------------------------\n");
+    argParser.setFormat(requestedOutputFormat);
+    result = player.open(argParser, MyDataCallbackProc, MyErrorCallbackProc, &loopbackData);
     if (result != AAUDIO_OK) {
+        fprintf(stderr, "ERROR -  player.open() returned %d\n", result);
         goto finish;
     }
+    outputStream = player.getStream();
+    argParser.compareWithStream(outputStream);
 
-    printf("Stream INPUT ---------------------\n");
-    loopbackData.actualInputChannelCount = AAudioStream_getChannelCount(loopbackData.inputStream);
-    printf("    channelCount: requested = %d, actual = %d\n", requestedInputChannelCount,
-           loopbackData.actualInputChannelCount);
-    printf("    framesPerBurst = %d\n", AAudioStream_getFramesPerBurst(loopbackData.inputStream));
-    printf("    bufferSize     = %d\n",
-           AAudioStream_getBufferSizeInFrames(loopbackData.inputStream));
-    printf("    bufferCapacity = %d\n",
-           AAudioStream_getBufferCapacityInFrames(loopbackData.inputStream));
+    actualOutputFormat = AAudioStream_getFormat(outputStream);
+    assert(actualOutputFormat == AAUDIO_FORMAT_PCM_FLOAT);
 
-    actualSharingMode = AAudioStream_getSharingMode(loopbackData.inputStream);
-    printf("    sharingMode: requested = %d, actual = %d\n",
-           requestedSharingMode, actualSharingMode);
-
-    actualInputFormat = AAudioStream_getFormat(loopbackData.inputStream);
-    printf("    dataFormat: requested = %d, actual = %d\n",
-           requestedInputFormat, actualInputFormat);
-    assert(actualInputFormat == AAUDIO_FORMAT_PCM_I16);
-
-    printf("    is MMAP used?         = %s\n", AAudioStream_isMMapUsed(loopbackData.inputStream)
-                                               ? "yes" : "no");
-
-
-    printf("Stream OUTPUT ---------------------\n");
-    // Check to see what kind of stream we actually got.
-    actualSampleRate = AAudioStream_getSampleRate(outputStream);
-    printf("    sampleRate: requested = %d, actual = %d\n", requestedSampleRate, actualSampleRate);
-    loopbackData.echoAnalyzer.setSampleRate(actualSampleRate);
-
-    loopbackData.actualOutputChannelCount = AAudioStream_getChannelCount(outputStream);
-    printf("    channelCount: requested = %d, actual = %d\n", requestedOutputChannelCount,
-           loopbackData.actualOutputChannelCount);
-
-    actualSharingMode = AAudioStream_getSharingMode(outputStream);
-    printf("    sharingMode: requested = %d, actual = %d\n",
-           requestedSharingMode, actualSharingMode);
+    printf("INPUT stream ----------------------------------------\n");
+    // Use different parameters for the input.
+    argParser.setNumberOfBursts(AAUDIO_UNSPECIFIED);
+    argParser.setFormat(requestedInputFormat);
+    argParser.setPerformanceMode(inputPerformanceLevel);
+    argParser.setChannelCount(requestedInputChannelCount);
+    argParser.setSharingMode(requestedInputSharingMode);
+    result = recorder.open(argParser);
+    if (result != AAUDIO_OK) {
+        fprintf(stderr, "ERROR -  recorder.open() returned %d\n", result);
+        goto finish;
+    }
+    loopbackData.inputStream = recorder.getStream();
+    argParser.compareWithStream(loopbackData.inputStream);
 
     // This is the number of frames that are read in one chunk by a DMA controller
     // or a DSP or a mixer.
     framesPerBurst = AAudioStream_getFramesPerBurst(outputStream);
-    printf("    framesPerBurst = %d\n", framesPerBurst);
 
-    result = AAudioStream_setBufferSizeInFrames(outputStream, burstsPerBuffer * framesPerBurst);
-    if (result < 0) { // may be positive buffer size
-        fprintf(stderr, "ERROR - AAudioStream_setBufferSize() returned %d\n", result);
-        goto finish;
-    }
-    printf("    bufferSize     = %d\n", AAudioStream_getBufferSizeInFrames(outputStream));
-    printf("    bufferCapacity = %d\n", AAudioStream_getBufferCapacityInFrames(outputStream));
+    actualInputFormat = AAudioStream_getFormat(outputStream);
+    assert(actualInputFormat == AAUDIO_FORMAT_PCM_I16);
 
-    actualOutputFormat = AAudioStream_getFormat(outputStream);
-    printf("    dataFormat: requested = %d, actual = %d\n",
-           requestedOutputFormat, actualOutputFormat);
-    assert(actualOutputFormat == AAUDIO_FORMAT_PCM_FLOAT);
 
-    printf("    is MMAP used?         = %s\n", AAudioStream_isMMapUsed(outputStream)
-                                               ? "yes" : "no");
+    loopbackData.actualInputChannelCount = recorder.getChannelCount();
+    loopbackData.actualOutputChannelCount = player.getChannelCount();
 
     // Allocate a buffer for the audio data.
     loopbackData.inputFramesMaximum = 32 * framesPerBurst;
@@ -813,49 +385,75 @@
     loopbackData.conversionBuffer = new float[loopbackData.inputFramesMaximum *
                                               loopbackData.actualInputChannelCount];
 
+    loopbackData.loopbackProcessor->reset();
 
-    // Start output first so input stream runs low.
-    result = AAudioStream_requestStart(outputStream);
+    result = recorder.start();
     if (result != AAUDIO_OK) {
-        fprintf(stderr, "ERROR - AAudioStream_requestStart(output) returned %d = %s\n",
-                result, AAudio_convertResultToText(result));
+        printf("ERROR - AAudioStream_requestStart(input) returned %d = %s\n",
+               result, AAudio_convertResultToText(result));
         goto finish;
     }
 
-    result = AAudioStream_requestStart(loopbackData.inputStream);
+    result = player.start();
     if (result != AAUDIO_OK) {
-        fprintf(stderr, "ERROR - AAudioStream_requestStart(input) returned %d = %s\n",
-                result, AAudio_convertResultToText(result));
+        printf("ERROR - AAudioStream_requestStart(output) returned %d = %s\n",
+               result, AAudio_convertResultToText(result));
         goto finish;
     }
 
     printf("------- sleep while the callback runs --------------\n");
     fflush(stdout);
-    sleep(NUM_SECONDS);
-
+    for (int i = requestedDuration; i > 0 ; i--) {
+        if (loopbackData.inputError != AAUDIO_OK) {
+            printf("  ERROR on input stream\n");
+            break;
+        } else if (loopbackData.outputError != AAUDIO_OK) {
+                printf("  ERROR on output stream\n");
+                break;
+        } else if (loopbackData.isDone) {
+                printf("  test says it is done!\n");
+                break;
+        } else {
+            sleep(1);
+            printf("%4d: ", i);
+            loopbackData.loopbackProcessor->printStatus();
+            int64_t framesWritten = AAudioStream_getFramesWritten(loopbackData.inputStream);
+            int64_t framesRead = AAudioStream_getFramesRead(loopbackData.inputStream);
+            printf(" input written = %lld, read %lld, xruns = %d\n",
+                   (long long) framesWritten,
+                   (long long) framesRead,
+                   AAudioStream_getXRunCount(outputStream)
+            );
+        }
+    }
 
     printf("input error = %d = %s\n",
                 loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
 
     printf("AAudioStream_getXRunCount %d\n", AAudioStream_getXRunCount(outputStream));
-    printf("framesRead    = %d\n", (int) AAudioStream_getFramesRead(outputStream));
-    printf("framesWritten = %d\n", (int) AAudioStream_getFramesWritten(outputStream));
+    printf("framesRead    = %8d\n", (int) AAudioStream_getFramesRead(outputStream));
+    printf("framesWritten = %8d\n", (int) AAudioStream_getFramesWritten(outputStream));
+    printf("min numFrames = %8d\n", (int) loopbackData.minNumFrames);
+    printf("max numFrames = %8d\n", (int) loopbackData.maxNumFrames);
 
-    loopbackData.loopbackProcessor->report();
+    if (loopbackData.inputError == AAUDIO_OK) {
+        if (testMode == TEST_SINE_MAGNITUDE) {
+            printAudioGraph(loopbackData.audioRecorder, 200);
+        }
+        loopbackData.loopbackProcessor->report();
+    }
 
-//    {
-//        int written = loopbackData.audioRecorder.save(FILENAME);
-//        printf("wrote %d mono samples to %s on Android device\n", written, FILENAME);
-//    }
-
+    {
+        int written = loopbackData.audioRecorder.save(FILENAME);
+        printf("main() wrote %d mono samples to %s on Android device\n", written, FILENAME);
+    }
 
 finish:
-    AAudioStream_close(outputStream);
-    AAudioStream_close(loopbackData.inputStream);
+    player.close();
+    recorder.close();
     delete[] loopbackData.conversionBuffer;
     delete[] loopbackData.inputData;
     delete[] outputData;
-    AAudioStreamBuilder_delete(builder);
 
     printf(RESULT_TAG "error = %d = %s\n", result, AAudio_convertResultToText(result));
     if ((result != AAUDIO_OK)) {
diff --git a/media/libaaudio/examples/loopback/src/loopback.sh b/media/libaaudio/examples/loopback/src/loopback.sh
new file mode 100644
index 0000000..bc63125
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/loopback.sh
@@ -0,0 +1,14 @@
+#!/system/bin/sh
+# Run a loopback test in the background after a delay.
+# To run the script enter:
+#    adb shell "nohup sh /data/loopback.sh &"
+
+SLEEP_TIME=10
+TEST_COMMAND="aaudio_loopback -pl -Pl -C1 -n2 -m2 -tm -d5"
+
+echo "Plug in USB Mir and Fun Plug."
+echo "Test will start in ${SLEEP_TIME} seconds: ${TEST_COMMAND}"
+sleep ${SLEEP_TIME}
+date > /data/loopreport.txt
+${TEST_COMMAND} >> /data/loopreport.txt
+date >> /data/loopreport.txt
diff --git a/media/libaaudio/examples/utils/AAudioArgsParser.h b/media/libaaudio/examples/utils/AAudioArgsParser.h
index 54217a5..46bc99e 100644
--- a/media/libaaudio/examples/utils/AAudioArgsParser.h
+++ b/media/libaaudio/examples/utils/AAudioArgsParser.h
@@ -121,7 +121,7 @@
     aaudio_sharing_mode_t      mSharingMode     = AAUDIO_SHARING_MODE_SHARED;
     aaudio_performance_mode_t  mPerformanceMode = AAUDIO_PERFORMANCE_MODE_NONE;
 
-    int32_t                    mNumberOfBursts = AAUDIO_UNSPECIFIED;
+    int32_t                    mNumberOfBursts  = AAUDIO_UNSPECIFIED;
 };
 
 class AAudioArgsParser : public AAudioParameters {
@@ -151,9 +151,13 @@
                 case 'd':
                     mDurationSeconds = atoi(&arg[2]);
                     break;
-                case 'm':
-                    AAudio_setMMapPolicy(AAUDIO_POLICY_AUTO);
-                    break;
+                case 'm': {
+                    aaudio_policy_t policy = AAUDIO_POLICY_AUTO;
+                    if (strlen(arg) > 2) {
+                        policy = atoi(&arg[2]);
+                    }
+                    AAudio_setMMapPolicy(policy);
+                } break;
                 case 'n':
                     setNumberOfBursts(atoi(&arg[2]));
                     break;
@@ -198,7 +202,11 @@
         printf("      -b{bufferCapacity} frames\n");
         printf("      -c{channels} for example 2 for stereo\n");
         printf("      -d{duration} in seconds, default is %d\n", DEFAULT_DURATION_SECONDS);
-        printf("      -m enable MMAP\n");
+        printf("      -m{0|1|2|3} set MMAP policy\n");
+        printf("          0 = _UNSPECIFIED, default\n");
+        printf("          1 = _NEVER\n");
+        printf("          2 = _AUTO, also if -m is used with no number\n");
+        printf("          3 = _ALWAYS\n");
         printf("      -n{numberOfBursts} for setBufferSize\n");
         printf("      -p{performanceMode} set output AAUDIO_PERFORMANCE_MODE*, default NONE\n");
         printf("          n for _NONE\n");
diff --git a/media/libaaudio/examples/utils/AAudioSimplePlayer.h b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
index 19f8aff..cc0cb34 100644
--- a/media/libaaudio/examples/utils/AAudioSimplePlayer.h
+++ b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
@@ -30,6 +30,11 @@
 #define SHARING_MODE  AAUDIO_SHARING_MODE_SHARED
 #define PERFORMANCE_MODE AAUDIO_PERFORMANCE_MODE_NONE
 
+// Arbitrary period for glitches, once per second at 48000 Hz.
+#define FORCED_UNDERRUN_PERIOD_FRAMES    48000
+// How long to sleep in a callback to cause an intentional glitch. For testing.
+#define FORCED_UNDERRUN_SLEEP_MICROS     (10 * 1000)
+
 /**
  * Simple wrapper for AAudio that opens an output stream either in callback or blocking write mode.
  */
@@ -219,8 +224,13 @@
 typedef struct SineThreadedData_s {
     SineGenerator  sineOsc1;
     SineGenerator  sineOsc2;
+    int64_t        framesTotal = 0;
+    int64_t        nextFrameToGlitch = FORCED_UNDERRUN_PERIOD_FRAMES;
+    int32_t        minNumFrames = INT32_MAX;
+    int32_t        maxNumFrames = 0;
     int            scheduler;
-    bool           schedulerChecked;
+    bool           schedulerChecked = false;
+    bool           forceUnderruns = false;
 } SineThreadedData_t;
 
 // Callback function that fills the audio output buffer.
@@ -233,16 +243,33 @@
 
     // should not happen but just in case...
     if (userData == nullptr) {
-        fprintf(stderr, "ERROR - SimplePlayerDataCallbackProc needs userData\n");
+        printf("ERROR - SimplePlayerDataCallbackProc needs userData\n");
         return AAUDIO_CALLBACK_RESULT_STOP;
     }
     SineThreadedData_t *sineData = (SineThreadedData_t *) userData;
 
+    sineData->framesTotal += numFrames;
+
+    if (sineData->forceUnderruns) {
+        if (sineData->framesTotal > sineData->nextFrameToGlitch) {
+            usleep(FORCED_UNDERRUN_SLEEP_MICROS);
+            printf("Simulate glitch at %lld\n", (long long) sineData->framesTotal);
+            sineData->nextFrameToGlitch += FORCED_UNDERRUN_PERIOD_FRAMES;
+        }
+    }
+
     if (!sineData->schedulerChecked) {
         sineData->scheduler = sched_getscheduler(gettid());
         sineData->schedulerChecked = true;
     }
 
+    if (numFrames > sineData->maxNumFrames) {
+        sineData->maxNumFrames = numFrames;
+    }
+    if (numFrames < sineData->minNumFrames) {
+        sineData->minNumFrames = numFrames;
+    }
+
     int32_t samplesPerFrame = AAudioStream_getChannelCount(stream);
     // This code only plays on the first one or two channels.
     // TODO Support arbitrary number of channels.
diff --git a/media/libaaudio/examples/utils/AAudioSimpleRecorder.h b/media/libaaudio/examples/utils/AAudioSimpleRecorder.h
index cb12fda..5ecac04 100644
--- a/media/libaaudio/examples/utils/AAudioSimpleRecorder.h
+++ b/media/libaaudio/examples/utils/AAudioSimpleRecorder.h
@@ -73,12 +73,20 @@
     /**
      * Only call this after open() has been called.
      */
-    int32_t getSamplesPerFrame() {
+    int32_t getChannelCount() {
         if (mStream == nullptr) {
             return AAUDIO_ERROR_INVALID_STATE;
         }
         return AAudioStream_getChannelCount(mStream);
     }
+
+    /**
+     * @deprecated use getChannelCount()
+     */
+    int32_t getSamplesPerFrame() {
+        return getChannelCount();
+    }
+
     /**
      * Only call this after open() has been called.
      */
diff --git a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
index 2211b72..b5602e9 100644
--- a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
+++ b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
@@ -26,9 +26,7 @@
 #include <aaudio/AAudio.h>
 #include "AAudioExampleUtils.h"
 #include "AAudioSimplePlayer.h"
-
-// Application data that gets passed to the callback.
-#define MAX_FRAME_COUNT_RECORDS    256
+#include "../../utils/AAudioSimplePlayer.h"
 
 int main(int argc, const char **argv)
 {
@@ -42,9 +40,10 @@
     // in a buffer if we hang or crash.
     setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
 
-    printf("%s - Play a sine sweep using an AAudio callback V0.1.1\n", argv[0]);
+    printf("%s - Play a sine sweep using an AAudio callback V0.1.2\n", argv[0]);
 
     myData.schedulerChecked = false;
+    myData.forceUnderruns = false; // set true to test AAudioStream_getXRunCount()
 
     if (argParser.parseArgs(argc, argv)) {
         return EXIT_FAILURE;
@@ -99,7 +98,10 @@
             printf("Stream state is %d %s!\n", state, AAudio_convertStreamStateToText(state));
             break;
         }
-        printf("framesWritten = %d\n", (int) AAudioStream_getFramesWritten(player.getStream()));
+        printf("framesWritten = %d, underruns = %d\n",
+               (int) AAudioStream_getFramesWritten(player.getStream()),
+               (int) AAudioStream_getXRunCount(player.getStream())
+        );
     }
     printf("Woke up now.\n");
 
@@ -120,6 +122,9 @@
                SCHED_FIFO);
     }
 
+    printf("min numFrames = %8d\n", (int) myData.minNumFrames);
+    printf("max numFrames = %8d\n", (int) myData.maxNumFrames);
+
     printf("SUCCESS\n");
     return EXIT_SUCCESS;
 error:
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 77f0cab..942a8fc 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -2318,6 +2318,33 @@
             }
         }
     } else {
+        // VolumeShapers are not affected when a track moves between players for
+        // gapless playback (setNextMediaPlayer).
+        // We forward VolumeShaper operations that do not change configuration
+        // to the new player so that unducking may occur as expected.
+        // Unducking is an idempotent operation, same if applied back-to-back.
+        if (configuration->getType() == VolumeShaper::Configuration::TYPE_ID
+                && mNextOutput != nullptr) {
+            ALOGV("applyVolumeShaper: Attempting to forward missed operation: %s %s",
+                    configuration->toString().c_str(), operation->toString().c_str());
+            Mutex::Autolock nextLock(mNextOutput->mLock);
+
+            // recycled track should be forwarded from this AudioSink by switchToNextOutput
+            sp<AudioTrack> track = mNextOutput->mRecycledTrack;
+            if (track != nullptr) {
+                ALOGD("Forward VolumeShaper operation to recycled track %p", track.get());
+                (void)track->applyVolumeShaper(configuration, operation);
+            } else {
+                // There is a small chance that the unduck occurs after the next
+                // player has already started, but before it is registered to receive
+                // the unduck command.
+                track = mNextOutput->mTrack;
+                if (track != nullptr) {
+                    ALOGD("Forward VolumeShaper operation to track %p", track.get());
+                    (void)track->applyVolumeShaper(configuration, operation);
+                }
+            }
+        }
         status = mVolumeHandler->applyVolumeShaper(configuration, operation);
     }
     return status;
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 92399f1..93d4f57 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -1860,10 +1860,12 @@
             || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) {
         if (mMeta->findData(kKeyESDS, &type, &data, &size)) {
             ESDS esds(data, size);
-            if (esds.getCodecSpecificInfo(&data, &size) != OK) {
-                data = NULL;
-                size = 0;
+            if (esds.getCodecSpecificInfo(&data, &size) == OK &&
+                    data != NULL &&
+                    copyCodecSpecificData((uint8_t*)data, size) == OK) {
+                mGotAllCodecSpecificData = true;
             }
+            return;
         }
     }
     if (data != NULL && copyCodecSpecificData((uint8_t *)data, size) == OK) {
diff --git a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
index f89688c..cff4a33 100644
--- a/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
+++ b/media/libstagefright/codecs/flac/dec/SoftFlacDecoder.cpp
@@ -68,7 +68,7 @@
     def.eDir = OMX_DirInput;
     def.nBufferCountMin = kNumInputBuffers;
     def.nBufferCountActual = def.nBufferCountMin;
-    def.nBufferSize = 8192;
+    def.nBufferSize = 32768;
     def.bEnabled = OMX_TRUE;
     def.bPopulated = OMX_FALSE;
     def.eDomain = OMX_PortDomainAudio;
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 1bd840e..65eccb6 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -7855,17 +7855,34 @@
 {
     AudioParameter param = AudioParameter(keyValuePair);
     int value;
+    bool sendToHal = true;
     if (param.getInt(String8(AudioParameter::keyRouting), value) == NO_ERROR) {
+        audio_devices_t device = (audio_devices_t)value;
         // forward device change to effects that have requested to be
         // aware of attached audio device.
-        if (value != AUDIO_DEVICE_NONE) {
-            mOutDevice = value;
+        if (device != AUDIO_DEVICE_NONE) {
             for (size_t i = 0; i < mEffectChains.size(); i++) {
-                mEffectChains[i]->setDevice_l(mOutDevice);
+                mEffectChains[i]->setDevice_l(device);
             }
         }
+        if (audio_is_output_devices(device)) {
+            mOutDevice = device;
+            if (!isOutput()) {
+                sendToHal = false;
+            }
+        } else {
+            mInDevice = device;
+            if (device != AUDIO_DEVICE_NONE) {
+                mPrevInDevice = value;
+            }
+            // TODO: implement and call checkBtNrec_l();
+        }
     }
-    status = mHalStream->setParameters(keyValuePair);
+    if (sendToHal) {
+        status = mHalStream->setParameters(keyValuePair);
+    } else {
+        status = NO_ERROR;
+    }
 
     return false;
 }
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 55c364f..1a7db26 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -933,7 +933,6 @@
     if (stream == AUDIO_STREAM_TTS) {
         flags = AUDIO_OUTPUT_FLAG_TTS;
     } else if (stream == AUDIO_STREAM_VOICE_CALL &&
-               getPhoneState() == AUDIO_MODE_IN_COMMUNICATION &&
                audio_is_linear_pcm(format)) {
         flags = (audio_output_flags_t)(AUDIO_OUTPUT_FLAG_VOIP_RX |
                                        AUDIO_OUTPUT_FLAG_DIRECT);
@@ -1647,7 +1646,6 @@
             halInputSource = AUDIO_SOURCE_VOICE_RECOGNITION;
         }
     } else if (inputSource == AUDIO_SOURCE_VOICE_COMMUNICATION &&
-               getPhoneState() == AUDIO_MODE_IN_COMMUNICATION &&
                audio_is_linear_pcm(format)) {
         flags = (audio_input_flags_t)(flags | AUDIO_INPUT_FLAG_VOIP_TX);
     }
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.h b/services/camera/libcameraservice/api2/CameraDeviceClient.h
index 2bf73a0..e8fc080 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.h
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.h
@@ -85,7 +85,7 @@
 
     virtual binder::Status endConfigure(int operatingMode) override;
 
-    // Returns -EBUSY if device is not idle
+    // Returns -EBUSY if device is not idle or in error state
     virtual binder::Status deleteStream(int streamId) override;
 
     virtual binder::Status createStream(
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 0fc3740..69b1d7d 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -240,6 +240,7 @@
 
     status_t res = OK;
     std::vector<wp<Camera3StreamInterface>> streams;
+    nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
     {
         Mutex::Autolock l(mLock);
         if (mStatus == STATUS_UNINITIALIZED) return res;
@@ -251,7 +252,6 @@
                 SET_ERR_L("Can't stop streaming");
                 // Continue to close device even in case of error
             } else {
-                nsecs_t maxExpectedDuration = getExpectedInFlightDurationLocked();
                 res = waitUntilStateThenRelock(/*active*/ false, maxExpectedDuration);
                 if (res != OK) {
                     SET_ERR_L("Timeout waiting for HAL to drain (% " PRIi64 " ns)",
@@ -311,10 +311,10 @@
 
     {
         Mutex::Autolock l(mLock);
-        mExpectedInflightDuration = 0;
         mInterface->clear();
         mOutputStreams.clear();
         mInputStream.clear();
+        mDeletedStreams.clear();
         mBufferManager.clear();
         internalUpdateStatusLocked(STATUS_UNINITIALIZED);
     }
@@ -1141,6 +1141,7 @@
         uint32_t width, uint32_t height, int format, int *id) {
     ATRACE_CALL();
     Mutex::Autolock il(mInterfaceLock);
+    nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
     Mutex::Autolock l(mLock);
     ALOGV("Camera %s: Creating new input stream %d: %d x %d, format %d",
             mId.string(), mNextStreamId, width, height, format);
@@ -1161,7 +1162,7 @@
             break;
         case STATUS_ACTIVE:
             ALOGV("%s: Stopping activity to reconfigure streams", __FUNCTION__);
-            res = internalPauseAndWaitLocked();
+            res = internalPauseAndWaitLocked(maxExpectedDuration);
             if (res != OK) {
                 SET_ERR_L("Can't pause captures to reconfigure streams!");
                 return res;
@@ -1228,6 +1229,7 @@
         int streamSetId, bool isShared, uint32_t consumerUsage) {
     ATRACE_CALL();
     Mutex::Autolock il(mInterfaceLock);
+    nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
     Mutex::Autolock l(mLock);
     ALOGV("Camera %s: Creating new stream %d: %d x %d, format %d, dataspace %d rotation %d"
             " consumer usage 0x%x, isShared %d", mId.string(), mNextStreamId, width, height, format,
@@ -1249,7 +1251,7 @@
             break;
         case STATUS_ACTIVE:
             ALOGV("%s: Stopping activity to reconfigure streams", __FUNCTION__);
-            res = internalPauseAndWaitLocked();
+            res = internalPauseAndWaitLocked(maxExpectedDuration);
             if (res != OK) {
                 SET_ERR_L("Can't pause captures to reconfigure streams!");
                 return res;
@@ -1428,6 +1430,12 @@
         return -EBUSY;
     }
 
+    if (mStatus == STATUS_ERROR) {
+        ALOGW("%s: Camera %s: deleteStream not allowed in ERROR state",
+                __FUNCTION__, mId.string());
+        return -EBUSY;
+    }
+
     sp<Camera3StreamInterface> deletedStream;
     ssize_t outputStreamIdx = mOutputStreams.indexOfKey(id);
     if (mInputStream != NULL && id == mInputStream->getId()) {
@@ -1494,59 +1502,66 @@
     }
 
     Mutex::Autolock il(mInterfaceLock);
-    Mutex::Autolock l(mLock);
 
-    switch (mStatus) {
-        case STATUS_ERROR:
-            CLOGE("Device has encountered a serious error");
-            return INVALID_OPERATION;
-        case STATUS_UNINITIALIZED:
-            CLOGE("Device is not initialized!");
-            return INVALID_OPERATION;
-        case STATUS_UNCONFIGURED:
-        case STATUS_CONFIGURED:
-        case STATUS_ACTIVE:
-            // OK
-            break;
-        default:
-            SET_ERR_L("Unexpected status: %d", mStatus);
-            return INVALID_OPERATION;
-    }
+    {
+        Mutex::Autolock l(mLock);
+        switch (mStatus) {
+            case STATUS_ERROR:
+                CLOGE("Device has encountered a serious error");
+                return INVALID_OPERATION;
+            case STATUS_UNINITIALIZED:
+                CLOGE("Device is not initialized!");
+                return INVALID_OPERATION;
+            case STATUS_UNCONFIGURED:
+            case STATUS_CONFIGURED:
+            case STATUS_ACTIVE:
+                // OK
+                break;
+            default:
+                SET_ERR_L("Unexpected status: %d", mStatus);
+                return INVALID_OPERATION;
+        }
 
-    if (!mRequestTemplateCache[templateId].isEmpty()) {
-        *request = mRequestTemplateCache[templateId];
-        return OK;
+        if (!mRequestTemplateCache[templateId].isEmpty()) {
+            *request = mRequestTemplateCache[templateId];
+            return OK;
+        }
     }
 
     camera_metadata_t *rawRequest;
     status_t res = mInterface->constructDefaultRequestSettings(
             (camera3_request_template_t) templateId, &rawRequest);
-    if (res == BAD_VALUE) {
-        ALOGI("%s: template %d is not supported on this camera device",
-              __FUNCTION__, templateId);
-        return res;
-    } else if (res != OK) {
-        CLOGE("Unable to construct request template %d: %s (%d)",
-                templateId, strerror(-res), res);
-        return res;
+
+    {
+        Mutex::Autolock l(mLock);
+        if (res == BAD_VALUE) {
+            ALOGI("%s: template %d is not supported on this camera device",
+                  __FUNCTION__, templateId);
+            return res;
+        } else if (res != OK) {
+            CLOGE("Unable to construct request template %d: %s (%d)",
+                    templateId, strerror(-res), res);
+            return res;
+        }
+
+        set_camera_metadata_vendor_id(rawRequest, mVendorTagId);
+        mRequestTemplateCache[templateId].acquire(rawRequest);
+
+        *request = mRequestTemplateCache[templateId];
     }
-
-    set_camera_metadata_vendor_id(rawRequest, mVendorTagId);
-    mRequestTemplateCache[templateId].acquire(rawRequest);
-
-    *request = mRequestTemplateCache[templateId];
     return OK;
 }
 
 status_t Camera3Device::waitUntilDrained() {
     ATRACE_CALL();
     Mutex::Autolock il(mInterfaceLock);
+    nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
     Mutex::Autolock l(mLock);
 
-    return waitUntilDrainedLocked();
+    return waitUntilDrainedLocked(maxExpectedDuration);
 }
 
-status_t Camera3Device::waitUntilDrainedLocked() {
+status_t Camera3Device::waitUntilDrainedLocked(nsecs_t maxExpectedDuration) {
     switch (mStatus) {
         case STATUS_UNINITIALIZED:
         case STATUS_UNCONFIGURED:
@@ -1562,9 +1577,6 @@
             SET_ERR_L("Unexpected status: %d",mStatus);
             return INVALID_OPERATION;
     }
-
-    nsecs_t maxExpectedDuration = getExpectedInFlightDurationLocked();
-
     ALOGV("%s: Camera %s: Waiting until idle (%" PRIi64 "ns)", __FUNCTION__, mId.string(),
             maxExpectedDuration);
     status_t res = waitUntilStateThenRelock(/*active*/ false, maxExpectedDuration);
@@ -1583,11 +1595,10 @@
 }
 
 // Pause to reconfigure
-status_t Camera3Device::internalPauseAndWaitLocked() {
+status_t Camera3Device::internalPauseAndWaitLocked(nsecs_t maxExpectedDuration) {
     mRequestThread->setPaused(true);
     mPauseStateNotify = true;
 
-    nsecs_t maxExpectedDuration = getExpectedInFlightDurationLocked();
     ALOGV("%s: Camera %s: Internal wait until idle (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
           maxExpectedDuration);
     status_t res = waitUntilStateThenRelock(/*active*/ false, maxExpectedDuration);
@@ -2339,7 +2350,9 @@
 
     mErrorCause = errorCause;
 
-    mRequestThread->setPaused(true);
+    if (mRequestThread != nullptr) {
+        mRequestThread->setPaused(true);
+    }
     internalUpdateStatusLocked(STATUS_ERROR);
 
     // Notify upstream about a device error
@@ -2377,9 +2390,7 @@
         }
     }
 
-    Mutex::Autolock ml(mLock);
     mExpectedInflightDuration += maxExpectedDuration;
-
     return OK;
 }
 
@@ -2411,7 +2422,6 @@
             mStatusTracker->markComponentIdle(mInFlightStatusId, Fence::NO_FENCE);
         }
     }
-    Mutex::Autolock l(mLock);
     mExpectedInflightDuration -= duration;
 }
 
@@ -2493,6 +2503,7 @@
                 request.pendingOutputBuffers.size(), 0);
         }
         mInFlightMap.clear();
+        mExpectedInflightDuration = 0;
     }
 
     // Then return all inflight buffers not returned by HAL
@@ -2516,16 +2527,60 @@
         streamBuffer.status = CAMERA3_BUFFER_STATUS_ERROR;
         streamBuffer.acquire_fence = -1;
         streamBuffer.release_fence = -1;
+
+        // First check if the buffer belongs to deleted stream
+        bool streamDeleted = false;
+        for (auto& stream : mDeletedStreams) {
+            if (streamId == stream->getId()) {
+                streamDeleted = true;
+                // Return buffer to deleted stream
+                camera3_stream* halStream = stream->asHalStream();
+                streamBuffer.stream = halStream;
+                switch (halStream->stream_type) {
+                    case CAMERA3_STREAM_OUTPUT:
+                        res = stream->returnBuffer(streamBuffer, /*timestamp*/ 0);
+                        if (res != OK) {
+                            ALOGE("%s: Can't return output buffer for frame %d to"
+                                  " stream %d: %s (%d)",  __FUNCTION__,
+                                  frameNumber, streamId, strerror(-res), res);
+                        }
+                        break;
+                    case CAMERA3_STREAM_INPUT:
+                        res = stream->returnInputBuffer(streamBuffer);
+                        if (res != OK) {
+                            ALOGE("%s: Can't return input buffer for frame %d to"
+                                  " stream %d: %s (%d)",  __FUNCTION__,
+                                  frameNumber, streamId, strerror(-res), res);
+                        }
+                        break;
+                    default: // Bi-direcitonal stream is deprecated
+                        ALOGE("%s: stream %d has unknown stream type %d",
+                                __FUNCTION__, streamId, halStream->stream_type);
+                        break;
+                }
+                break;
+            }
+        }
+        if (streamDeleted) {
+            continue;
+        }
+
+        // Then check against configured streams
         if (streamId == inputStreamId) {
             streamBuffer.stream = mInputStream->asHalStream();
             res = mInputStream->returnInputBuffer(streamBuffer);
             if (res != OK) {
                 ALOGE("%s: Can't return input buffer for frame %d to"
-                      "  its stream:%s (%d)",  __FUNCTION__,
-                      frameNumber, strerror(-res), res);
+                      " stream %d: %s (%d)",  __FUNCTION__,
+                      frameNumber, streamId, strerror(-res), res);
             }
         } else {
-            streamBuffer.stream = mOutputStreams.valueFor(streamId)->asHalStream();
+            ssize_t idx = mOutputStreams.indexOfKey(streamId);
+            if (idx == NAME_NOT_FOUND) {
+                ALOGE("%s: Output stream id %d not found!", __FUNCTION__, streamId);
+                continue;
+            }
+            streamBuffer.stream = mOutputStreams.valueAt(idx)->asHalStream();
             returnOutputBuffers(&streamBuffer, /*size*/1, /*timestamp*/ 0);
         }
     }
@@ -4243,7 +4298,8 @@
     return false;
 }
 
-nsecs_t Camera3Device::getExpectedInFlightDurationLocked() {
+nsecs_t Camera3Device::getExpectedInFlightDuration() {
+    Mutex::Autolock al(mInFlightLock);
     return mExpectedInflightDuration > kMinInflightDuration ?
             mExpectedInflightDuration : kMinInflightDuration;
 }
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index 686a28b..d700e03 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -486,7 +486,7 @@
      * CameraDeviceBase interface we shouldn't need to.
      * Must be called with mLock and mInterfaceLock both held.
      */
-    status_t internalPauseAndWaitLocked();
+    status_t internalPauseAndWaitLocked(nsecs_t maxExpectedDuration);
 
     /**
      * Resume work after internalPauseAndWaitLocked()
@@ -512,7 +512,7 @@
      *
      * Need to be called with mLock and mInterfaceLock held.
      */
-    status_t waitUntilDrainedLocked();
+    status_t waitUntilDrainedLocked(nsecs_t maxExpectedDuration);
 
     /**
      * Do common work for setting up a streaming or single capture request.
@@ -915,11 +915,14 @@
     // Map from frame number to the in-flight request state
     typedef KeyedVector<uint32_t, InFlightRequest> InFlightMap;
 
-    nsecs_t                mExpectedInflightDuration = 0;
-    Mutex                  mInFlightLock; // Protects mInFlightMap
+
+    Mutex                  mInFlightLock; // Protects mInFlightMap and
+                                          // mExpectedInflightDuration
     InFlightMap            mInFlightMap;
+    nsecs_t                mExpectedInflightDuration = 0;
     int                    mInFlightStatusId;
 
+
     status_t registerInFlight(uint32_t frameNumber,
             int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
             bool callback, nsecs_t maxExpectedDuration);
@@ -928,7 +931,7 @@
      * Returns the maximum expected time it'll take for all currently in-flight
      * requests to complete, based on their settings
      */
-    nsecs_t getExpectedInFlightDurationLocked();
+    nsecs_t getExpectedInFlightDuration();
 
     /**
      * Tracking for idle detection
diff --git a/services/mediacodec/seccomp_policy/mediacodec-arm.policy b/services/mediacodec/seccomp_policy/mediacodec-arm.policy
index 52658d1..cbd7fb9 100644
--- a/services/mediacodec/seccomp_policy/mediacodec-arm.policy
+++ b/services/mediacodec/seccomp_policy/mediacodec-arm.policy
@@ -12,6 +12,7 @@
 dup: 1
 ppoll: 1
 mmap2: 1
+getrandom: 1
 
 # mremap: Ensure |flags| are (MREMAP_MAYMOVE | MREMAP_FIXED) TODO: Once minijail
 # parser support for '<' is in this needs to be modified to also prevent
diff --git a/services/oboeservice/AAudioMixer.cpp b/services/oboeservice/AAudioMixer.cpp
index 8222734..952aa82 100644
--- a/services/oboeservice/AAudioMixer.cpp
+++ b/services/oboeservice/AAudioMixer.cpp
@@ -88,11 +88,11 @@
         }
         partIndex++;
     }
-    fifo->getFifoControllerBase()->advanceReadIndex(mFramesPerBurst - framesLeft);
-    if (framesLeft > 0) {
-        //ALOGW("AAudioMixer::mix() UNDERFLOW by %d / %d frames ----- UNDERFLOW !!!!!!!!!!",
-        //      framesLeft, mFramesPerBurst);
-    }
+    // Always advance by one burst even if we do not have the data.
+    // Otherwise the stream timing will drift whenever there is an underflow.
+    // This actual underflow can then be detected by the client for XRun counting.
+    fifo->getFifoControllerBase()->advanceReadIndex(mFramesPerBurst);
+
 #if AAUDIO_MIXER_ATRACE_ENABLED
     ATRACE_END();
 #endif /* AAUDIO_MIXER_ATRACE_ENABLED */