Merge "Camera: Don't advertize zoom in case max digital zoom is 1" into oc-dev am: 0ba7cd8501
am: 01612a5621

Change-Id: I6893eac824dcfc277a4a047240281a78a7d765ab
diff --git a/media/libaaudio/examples/input_monitor/Android.mk b/media/libaaudio/examples/input_monitor/Android.mk
new file mode 100644
index 0000000..b56328b
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/Android.mk
@@ -0,0 +1,6 @@
+# include $(call all-subdir-makefiles)
+
+# Just include static/ for now.
+LOCAL_PATH := $(call my-dir)
+#include $(LOCAL_PATH)/jni/Android.mk
+include $(LOCAL_PATH)/static/Android.mk
diff --git a/media/libaaudio/examples/input_monitor/README.md b/media/libaaudio/examples/input_monitor/README.md
new file mode 100644
index 0000000..3e54ef0
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/README.md
@@ -0,0 +1 @@
+Monitor input level and print value.
diff --git a/media/libaaudio/examples/input_monitor/jni/Android.mk b/media/libaaudio/examples/input_monitor/jni/Android.mk
new file mode 100644
index 0000000..51a5a85
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/jni/Android.mk
@@ -0,0 +1,35 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/liboboe/include
+
+LOCAL_SRC_FILES:= frameworks/av/media/liboboe/src/write_sine.cpp
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia libtinyalsa \
+        libbinder libcutils libutils
+LOCAL_STATIC_LIBRARIES := libsndfile
+LOCAL_MODULE := write_sine_ndk
+LOCAL_SHARED_LIBRARIES += liboboe_prebuilt
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/liboboe/include
+
+LOCAL_SRC_FILES:= frameworks/av/media/liboboe/src/write_sine_threaded.cpp
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia libtinyalsa \
+        libbinder libcutils libutils
+LOCAL_STATIC_LIBRARIES := libsndfile
+LOCAL_MODULE := write_sine_threaded_ndk
+LOCAL_SHARED_LIBRARIES += liboboe_prebuilt
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := liboboe_prebuilt
+LOCAL_SRC_FILES := liboboe.so
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
+include $(PREBUILT_SHARED_LIBRARY)
diff --git a/media/libaaudio/examples/input_monitor/jni/Application.mk b/media/libaaudio/examples/input_monitor/jni/Application.mk
new file mode 100644
index 0000000..e74475c
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/jni/Application.mk
@@ -0,0 +1,3 @@
+# TODO remove then when we support other architectures
+APP_ABI := arm64-v8a
+APP_CPPFLAGS += -std=c++11
diff --git a/media/libaaudio/examples/input_monitor/src/input_monitor.cpp b/media/libaaudio/examples/input_monitor/src/input_monitor.cpp
new file mode 100644
index 0000000..545496f
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/src/input_monitor.cpp
@@ -0,0 +1,194 @@
+/*
+ * 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.
+ */
+
+// Record input using AAudio and display the peak amplitudes.
+
+#include <new>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <aaudio/AAudioDefinitions.h>
+#include <aaudio/AAudio.h>
+
+#define SAMPLE_RATE        48000
+#define NUM_SECONDS        10
+#define NANOS_PER_MICROSECOND ((int64_t)1000)
+#define NANOS_PER_MILLISECOND (NANOS_PER_MICROSECOND * 1000)
+#define NANOS_PER_SECOND   (NANOS_PER_MILLISECOND * 1000)
+
+#define DECAY_FACTOR       0.999
+#define MIN_FRAMES_TO_READ 48  /* arbitrary, 1 msec at 48000 Hz */
+
+static const char *getSharingModeText(aaudio_sharing_mode_t mode) {
+    const char *modeText = "unknown";
+    switch (mode) {
+    case AAUDIO_SHARING_MODE_EXCLUSIVE:
+        modeText = "EXCLUSIVE";
+        break;
+    case AAUDIO_SHARING_MODE_SHARED:
+        modeText = "SHARED";
+        break;
+    default:
+        break;
+    }
+    return modeText;
+}
+
+int main(int argc, char **argv)
+{
+    (void)argc; // unused
+
+    aaudio_result_t result;
+
+    int actualSamplesPerFrame;
+    int actualSampleRate;
+    const aaudio_audio_format_t requestedDataFormat = AAUDIO_FORMAT_PCM_I16;
+    aaudio_audio_format_t actualDataFormat;
+
+    const aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_SHARED;
+    aaudio_sharing_mode_t actualSharingMode;
+
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    AAudioStream *aaudioStream = nullptr;
+    aaudio_stream_state_t state;
+    int32_t framesPerBurst = 0;
+    int32_t framesPerRead = 0;
+    int32_t framesToRecord = 0;
+    int32_t framesLeft = 0;
+    int32_t xRunCount = 0;
+    int16_t *data = nullptr;
+    float peakLevel = 0.0;
+    int loopCounter = 0;
+
+    // Make printf print immediately so that debug info is not stuck
+    // in a buffer if we hang or crash.
+    setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
+
+    printf("%s - Monitor input level using AAudio\n", argv[0]);
+
+    // Use an AAudioStreamBuilder to contain requested parameters.
+    result = AAudio_createStreamBuilder(&aaudioBuilder);
+    if (result != AAUDIO_OK) {
+        goto finish;
+    }
+
+    // Request stream properties.
+    AAudioStreamBuilder_setDirection(aaudioBuilder, AAUDIO_DIRECTION_INPUT);
+    AAudioStreamBuilder_setFormat(aaudioBuilder, requestedDataFormat);
+    AAudioStreamBuilder_setSharingMode(aaudioBuilder, requestedSharingMode);
+
+    // Create an AAudioStream using the Builder.
+    result = AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream);
+    if (result != AAUDIO_OK) {
+        goto finish;
+    }
+
+    actualSamplesPerFrame = AAudioStream_getSamplesPerFrame(aaudioStream);
+    printf("SamplesPerFrame = %d\n", actualSamplesPerFrame);
+    actualSampleRate = AAudioStream_getSampleRate(aaudioStream);
+    printf("SamplesPerFrame = %d\n", actualSampleRate);
+
+    actualSharingMode = AAudioStream_getSharingMode(aaudioStream);
+    printf("SharingMode: requested = %s, actual = %s\n",
+            getSharingModeText(requestedSharingMode),
+            getSharingModeText(actualSharingMode));
+
+    // This is the number of frames that are written in one chunk by a DMA controller
+    // or a DSP.
+    framesPerBurst = AAudioStream_getFramesPerBurst(aaudioStream);
+    printf("DataFormat: framesPerBurst = %d\n",framesPerBurst);
+
+    // Some DMA might use very short bursts of 16 frames. We don't need to read such small
+    // buffers. But it helps to use a multiple of the burst size for predictable scheduling.
+    framesPerRead = framesPerBurst;
+    while (framesPerRead < MIN_FRAMES_TO_READ) {
+        framesPerRead *= 2;
+    }
+    printf("DataFormat: framesPerRead = %d\n",framesPerRead);
+
+    actualDataFormat = AAudioStream_getFormat(aaudioStream);
+    printf("DataFormat: requested = %d, actual = %d\n", requestedDataFormat, actualDataFormat);
+    // TODO handle other data formats
+    assert(actualDataFormat == AAUDIO_FORMAT_PCM_I16);
+
+    // Allocate a buffer for the audio data.
+    data = new(std::nothrow) int16_t[framesPerRead * actualSamplesPerFrame];
+    if (data == nullptr) {
+        fprintf(stderr, "ERROR - could not allocate data buffer\n");
+        result = AAUDIO_ERROR_NO_MEMORY;
+        goto finish;
+    }
+
+    // Start the stream.
+    printf("call AAudioStream_requestStart()\n");
+    result = AAudioStream_requestStart(aaudioStream);
+    if (result != AAUDIO_OK) {
+        fprintf(stderr, "ERROR - AAudioStream_requestStart() returned %d\n", result);
+        goto finish;
+    }
+
+    state = AAudioStream_getState(aaudioStream);
+    printf("after start, state = %s\n", AAudio_convertStreamStateToText(state));
+
+    // Play for a while.
+    framesToRecord = actualSampleRate * NUM_SECONDS;
+    framesLeft = framesToRecord;
+    while (framesLeft > 0) {
+        // Read audio data from the stream.
+        int64_t timeoutNanos = 100 * NANOS_PER_MILLISECOND;
+        int minFrames = (framesToRecord < framesPerRead) ? framesToRecord : framesPerRead;
+        int actual = AAudioStream_read(aaudioStream, data, minFrames, timeoutNanos);
+        if (actual < 0) {
+            fprintf(stderr, "ERROR - AAudioStream_read() returned %zd\n", actual);
+            goto finish;
+        } else if (actual == 0) {
+            fprintf(stderr, "WARNING - AAudioStream_read() returned %zd\n", actual);
+            goto finish;
+        }
+        framesLeft -= actual;
+
+        // Peak follower.
+        for (int frameIndex = 0; frameIndex < actual; frameIndex++) {
+            float sample = data[frameIndex * actualSamplesPerFrame] * (1.0/32768);
+            peakLevel *= DECAY_FACTOR;
+            if (sample > peakLevel) {
+                peakLevel = sample;
+            }
+        }
+
+        // Display level as stars, eg. "******".
+        if ((loopCounter++ % 10) == 0) {
+            printf("%5.3f ", peakLevel);
+            int numStars = (int)(peakLevel * 50);
+            for (int i = 0; i < numStars; i++) {
+                printf("*");
+            }
+            printf("\n");
+        }
+    }
+
+    xRunCount = AAudioStream_getXRunCount(aaudioStream);
+    printf("AAudioStream_getXRunCount %d\n", xRunCount);
+
+finish:
+    delete[] data;
+    AAudioStream_close(aaudioStream);
+    AAudioStreamBuilder_delete(aaudioBuilder);
+    printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
+    return (result != AAUDIO_OK) ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
diff --git a/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp b/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp
new file mode 100644
index 0000000..8d40d94
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+
+// Record input using AAudio and display the peak amplitudes.
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <time.h>
+#include <aaudio/AAudioDefinitions.h>
+#include <aaudio/AAudio.h>
+
+#define NUM_SECONDS           10
+#define NANOS_PER_MICROSECOND ((int64_t)1000)
+#define NANOS_PER_MILLISECOND (NANOS_PER_MICROSECOND * 1000)
+#define NANOS_PER_SECOND      (NANOS_PER_MILLISECOND * 1000)
+
+//#define SHARING_MODE  AAUDIO_SHARING_MODE_EXCLUSIVE
+#define SHARING_MODE  AAUDIO_SHARING_MODE_SHARED
+
+/**
+ * Simple wrapper for AAudio that opens a default stream and then calls
+ * a callback function to fill the output buffers.
+ */
+class SimpleAAudioPlayer {
+public:
+    SimpleAAudioPlayer() {}
+    ~SimpleAAudioPlayer() {
+        close();
+    };
+
+    /**
+     * Call this before calling open().
+     * @param requestedSharingMode
+     */
+    void setSharingMode(aaudio_sharing_mode_t requestedSharingMode) {
+        mRequestedSharingMode = requestedSharingMode;
+    }
+
+    /**
+     * Also known as "sample rate"
+     * Only call this after open() has been called.
+     */
+    int32_t getFramesPerSecond() {
+        if (mStream == nullptr) {
+            return AAUDIO_ERROR_INVALID_STATE;
+        }
+        return AAudioStream_getSampleRate(mStream);;
+    }
+
+    /**
+     * Only call this after open() has been called.
+     */
+    int32_t getSamplesPerFrame() {
+        if (mStream == nullptr) {
+            return AAUDIO_ERROR_INVALID_STATE;
+        }
+        return AAudioStream_getSamplesPerFrame(mStream);;
+    }
+
+    /**
+     * Open a stream
+     */
+    aaudio_result_t open(AAudioStream_dataCallback proc, void *userContext) {
+        aaudio_result_t result = AAUDIO_OK;
+
+        // Use an AAudioStreamBuilder to contain requested parameters.
+        result = AAudio_createStreamBuilder(&mBuilder);
+        if (result != AAUDIO_OK) return result;
+
+        AAudioStreamBuilder_setDirection(mBuilder, AAUDIO_DIRECTION_INPUT);
+        AAudioStreamBuilder_setSharingMode(mBuilder, mRequestedSharingMode);
+        AAudioStreamBuilder_setDataCallback(mBuilder, proc, userContext);
+        AAudioStreamBuilder_setFormat(mBuilder, AAUDIO_FORMAT_PCM_I16);
+
+        // Open an AAudioStream using the Builder.
+        result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
+        if (result != AAUDIO_OK) {
+            fprintf(stderr, "ERROR - AAudioStreamBuilder_openStream() returned %d %s\n",
+                    result, AAudio_convertResultToText(result));
+            goto finish1;
+        }
+
+        printf("AAudioStream_getFramesPerBurst() = %d\n",
+               AAudioStream_getFramesPerBurst(mStream));
+        printf("AAudioStream_getBufferSizeInFrames() = %d\n",
+               AAudioStream_getBufferSizeInFrames(mStream));
+        printf("AAudioStream_getBufferCapacityInFrames() = %d\n",
+               AAudioStream_getBufferCapacityInFrames(mStream));
+        return result;
+
+     finish1:
+        AAudioStreamBuilder_delete(mBuilder);
+        mBuilder = nullptr;
+        return result;
+    }
+
+    aaudio_result_t close() {
+        if (mStream != nullptr) {
+            printf("call AAudioStream_close(%p)\n", mStream);  fflush(stdout);
+            AAudioStream_close(mStream);
+            mStream = nullptr;
+            AAudioStreamBuilder_delete(mBuilder);
+            mBuilder = nullptr;
+        }
+        return AAUDIO_OK;
+    }
+
+    // Write zero data to fill up the buffer and prevent underruns.
+    // Assume format is PCM_I16. TODO use floats.
+    aaudio_result_t prime() {
+        int32_t samplesPerFrame = AAudioStream_getSamplesPerFrame(mStream);
+        const int numFrames = 32; // arbitrary
+        int16_t zeros[numFrames * samplesPerFrame];
+        memset(zeros, 0, sizeof(zeros));
+        aaudio_result_t result = numFrames;
+        while (result == numFrames) {
+            result = AAudioStream_write(mStream, zeros, numFrames, 0);
+        }
+        return result;
+    }
+
+    // Start the stream. AAudio will start calling your callback function.
+     aaudio_result_t start() {
+        aaudio_result_t result = AAudioStream_requestStart(mStream);
+        if (result != AAUDIO_OK) {
+            fprintf(stderr, "ERROR - AAudioStream_requestStart() returned %d %s\n",
+                    result, AAudio_convertResultToText(result));
+        }
+        return result;
+    }
+
+    // Stop the stream. AAudio will stop calling your callback function.
+    aaudio_result_t stop() {
+        aaudio_result_t result = AAudioStream_requestStop(mStream);
+        if (result != AAUDIO_OK) {
+            fprintf(stderr, "ERROR - AAudioStream_requestStop() returned %d %s\n",
+                    result, AAudio_convertResultToText(result));
+        }
+        int32_t xRunCount = AAudioStream_getXRunCount(mStream);
+        printf("AAudioStream_getXRunCount %d\n", xRunCount);
+        return result;
+    }
+
+private:
+    AAudioStreamBuilder    *mBuilder = nullptr;
+    AAudioStream           *mStream = nullptr;
+    aaudio_sharing_mode_t   mRequestedSharingMode = SHARING_MODE;
+};
+
+// Application data that gets passed to the callback.
+typedef struct PeakTrackerData {
+    float peakLevel;
+} PeakTrackerData_t;
+
+#define DECAY_FACTOR   0.999
+
+// Callback function that fills the audio output buffer.
+aaudio_data_callback_result_t MyDataCallbackProc(
+        AAudioStream *stream,
+        void *userData,
+        void *audioData,
+        int32_t numFrames
+        ) {
+
+    PeakTrackerData_t *data = (PeakTrackerData_t *) userData;
+    // printf("MyCallbackProc(): frameCount = %d\n", numFrames);
+    int32_t samplesPerFrame = AAudioStream_getSamplesPerFrame(stream);
+    float sample;
+    // This code assume mono or stereo.
+    switch (AAudioStream_getFormat(stream)) {
+        case AAUDIO_FORMAT_PCM_I16: {
+            int16_t *audioBuffer = (int16_t *) audioData;
+            // Peak follower
+            for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+                sample = audioBuffer[frameIndex * samplesPerFrame] * (1.0/32768);
+                data->peakLevel *= DECAY_FACTOR;
+                if (sample > data->peakLevel) {
+                    data->peakLevel = sample;
+                }
+            }
+        }
+        break;
+        case AAUDIO_FORMAT_PCM_FLOAT: {
+            float *audioBuffer = (float *) audioData;
+            // Peak follower
+            for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+                sample = audioBuffer[frameIndex * samplesPerFrame];
+                data->peakLevel *= DECAY_FACTOR;
+                if (sample > data->peakLevel) {
+                    data->peakLevel = sample;
+                }
+            }
+        }
+        break;
+        default:
+            return AAUDIO_CALLBACK_RESULT_STOP;
+    }
+
+    return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+void displayPeakLevel(float peakLevel) {
+    printf("%5.3f ", peakLevel);
+    const int maxStars = 50; // arbitrary, fits on one line
+    int numStars = (int) (peakLevel * maxStars);
+    for (int i = 0; i < numStars; i++) {
+        printf("*");
+    }
+    printf("\n");
+}
+
+int main(int argc, char **argv)
+{
+    (void)argc; // unused
+    SimpleAAudioPlayer player;
+    PeakTrackerData_t myData = {0.0};
+    aaudio_result_t result;
+    const int displayRateHz = 20; // arbitrary
+    const int loopsNeeded = NUM_SECONDS * displayRateHz;
+
+    // Make printf print immediately so that debug info is not stuck
+    // in a buffer if we hang or crash.
+    setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
+    printf("%s - Display audio input using an AAudio callback\n", argv[0]);
+
+    player.setSharingMode(SHARING_MODE);
+
+    result = player.open(MyDataCallbackProc, &myData);
+    if (result != AAUDIO_OK) {
+        fprintf(stderr, "ERROR -  player.open() returned %d\n", result);
+        goto error;
+    }
+    printf("player.getFramesPerSecond() = %d\n", player.getFramesPerSecond());
+    printf("player.getSamplesPerFrame() = %d\n", player.getSamplesPerFrame());
+
+    result = player.start();
+    if (result != AAUDIO_OK) {
+        fprintf(stderr, "ERROR -  player.start() returned %d\n", result);
+        goto error;
+    }
+
+    printf("Sleep for %d seconds while audio plays in a callback thread.\n", NUM_SECONDS);
+   for (int i = 0; i < loopsNeeded; i++)
+    {
+        const struct timespec request = { .tv_sec = 0,
+                .tv_nsec = NANOS_PER_SECOND / displayRateHz };
+        (void) clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, &request, NULL /*remain*/);
+        displayPeakLevel(myData.peakLevel);
+    }
+    printf("Woke up now.\n");
+
+    result = player.stop();
+    if (result != AAUDIO_OK) {
+        goto error;
+    }
+    result = player.close();
+    if (result != AAUDIO_OK) {
+        goto error;
+    }
+
+    printf("SUCCESS\n");
+    return EXIT_SUCCESS;
+error:
+    player.close();
+    printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
+    return EXIT_FAILURE;
+}
+
diff --git a/media/libaaudio/examples/input_monitor/static/Android.mk b/media/libaaudio/examples/input_monitor/static/Android.mk
new file mode 100644
index 0000000..e83f179
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/static/Android.mk
@@ -0,0 +1,35 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := examples
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/libaaudio/include
+
+# TODO reorganize folders to avoid using ../
+LOCAL_SRC_FILES:= ../src/input_monitor.cpp
+
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia \
+                          libbinder libcutils libutils \
+                          libaudioclient liblog libtinyalsa
+LOCAL_STATIC_LIBRARIES := libaaudio
+
+LOCAL_MODULE := input_monitor
+include $(BUILD_EXECUTABLE)
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/libaaudio/include
+
+LOCAL_SRC_FILES:= ../src/input_monitor_callback.cpp
+
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia \
+                          libbinder libcutils libutils \
+                          libaudioclient liblog
+LOCAL_STATIC_LIBRARIES := libaaudio
+
+LOCAL_MODULE := input_monitor_callback
+include $(BUILD_EXECUTABLE)
diff --git a/media/libaaudio/examples/input_monitor/static/README.md b/media/libaaudio/examples/input_monitor/static/README.md
new file mode 100644
index 0000000..6e26d7b
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/static/README.md
@@ -0,0 +1,2 @@
+Makefile for building simple command line examples.
+They link with AAudio as a static library.
diff --git a/media/libaaudio/examples/write_sine/src/SineGenerator.h b/media/libaaudio/examples/write_sine/src/SineGenerator.h
index ade7527..64b772d 100644
--- a/media/libaaudio/examples/write_sine/src/SineGenerator.h
+++ b/media/libaaudio/examples/write_sine/src/SineGenerator.h
@@ -79,7 +79,7 @@
         }
     }
 
-    double mAmplitude = 0.01;
+    double mAmplitude = 0.05;  // unitless scaler
     double mPhase = 0.0;
     double mPhaseIncrement = 440 * M_PI * 2 / 48000;
     double mFrameRate = 48000;
diff --git a/media/libaaudio/examples/write_sine/src/write_sine.cpp b/media/libaaudio/examples/write_sine/src/write_sine.cpp
index 80b6252..d8e5ec1 100644
--- a/media/libaaudio/examples/write_sine/src/write_sine.cpp
+++ b/media/libaaudio/examples/write_sine/src/write_sine.cpp
@@ -19,7 +19,6 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>
-#include <aaudio/AAudioDefinitions.h>
 #include <aaudio/AAudio.h>
 #include "SineGenerator.h"
 
@@ -44,6 +43,7 @@
     return modeText;
 }
 
+// TODO move to a common utility library
 static int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC) {
     struct timespec time;
     int result = clock_gettime(clockId, &time);
@@ -74,6 +74,8 @@
     AAudioStream *aaudioStream = nullptr;
     aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
     int32_t framesPerBurst = 0;
+    int32_t framesPerWrite = 0;
+    int32_t bufferCapacity = 0;
     int32_t framesToPlay = 0;
     int32_t framesLeft = 0;
     int32_t xRunCount = 0;
@@ -100,7 +102,6 @@
     AAudioStreamBuilder_setFormat(aaudioBuilder, requestedDataFormat);
     AAudioStreamBuilder_setSharingMode(aaudioBuilder, requestedSharingMode);
 
-
     // Create an AAudioStream using the Builder.
     result = AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream);
     if (result != AAUDIO_OK) {
@@ -129,21 +130,25 @@
     // 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(aaudioStream);
-    printf("DataFormat: original framesPerBurst = %d\n",framesPerBurst);
+    printf("DataFormat: framesPerBurst = %d\n",framesPerBurst);
+    bufferCapacity = AAudioStream_getBufferCapacityInFrames(aaudioStream);
+    printf("DataFormat: bufferCapacity = %d, remainder = %d\n",
+           bufferCapacity, bufferCapacity % framesPerBurst);
 
     // Some DMA might use very short bursts of 16 frames. We don't need to write such small
     // buffers. But it helps to use a multiple of the burst size for predictable scheduling.
-    while (framesPerBurst < 48) {
-        framesPerBurst *= 2;
+    framesPerWrite = framesPerBurst;
+    while (framesPerWrite < 48) {
+        framesPerWrite *= 2;
     }
-    printf("DataFormat: final framesPerBurst = %d\n",framesPerBurst);
+    printf("DataFormat: framesPerWrite = %d\n",framesPerWrite);
 
     actualDataFormat = AAudioStream_getFormat(aaudioStream);
     printf("DataFormat: requested = %d, actual = %d\n", requestedDataFormat, actualDataFormat);
     // TODO handle other data formats
 
     // Allocate a buffer for the audio data.
-    data = new int16_t[framesPerBurst * actualSamplesPerFrame];
+    data = new int16_t[framesPerWrite * actualSamplesPerFrame];
     if (data == nullptr) {
         fprintf(stderr, "ERROR - could not allocate data buffer\n");
         result = AAUDIO_ERROR_NO_MEMORY;
@@ -166,14 +171,14 @@
     framesLeft = framesToPlay;
     while (framesLeft > 0) {
         // Render sine waves to left and right channels.
-        sineOsc1.render(&data[0], actualSamplesPerFrame, framesPerBurst);
+        sineOsc1.render(&data[0], actualSamplesPerFrame, framesPerWrite);
         if (actualSamplesPerFrame > 1) {
-            sineOsc2.render(&data[1], actualSamplesPerFrame, framesPerBurst);
+            sineOsc2.render(&data[1], actualSamplesPerFrame, framesPerWrite);
         }
 
         // Write audio data to the stream.
         int64_t timeoutNanos = 100 * NANOS_PER_MILLISECOND;
-        int minFrames = (framesToPlay < framesPerBurst) ? framesToPlay : framesPerBurst;
+        int minFrames = (framesToPlay < framesPerWrite) ? framesToPlay : framesPerWrite;
         int actual = AAudioStream_write(aaudioStream, data, minFrames, timeoutNanos);
         if (actual < 0) {
             fprintf(stderr, "ERROR - AAudioStream_write() returned %zd\n", actual);
diff --git a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
new file mode 100644
index 0000000..9414236
--- /dev/null
+++ b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
@@ -0,0 +1,320 @@
+/*
+ * 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.
+ */
+
+// Play sine waves using an AAudio callback.
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <stdio.h>
+#include <math.h>
+#include <time.h>
+#include <aaudio/AAudio.h>
+#include "SineGenerator.h"
+
+#define NUM_SECONDS              5
+
+//#define SHARING_MODE  AAUDIO_SHARING_MODE_EXCLUSIVE
+#define SHARING_MODE  AAUDIO_SHARING_MODE_SHARED
+
+#define  CALLBACK_SIZE_FRAMES    128
+
+// TODO refactor common code into a single SimpleAAudio class
+/**
+ * Simple wrapper for AAudio that opens a default stream and then calls
+ * a callback function to fill the output buffers.
+ */
+class SimpleAAudioPlayer {
+public:
+    SimpleAAudioPlayer() {}
+    ~SimpleAAudioPlayer() {
+        close();
+    };
+
+    /**
+     * Call this before calling open().
+     * @param requestedSharingMode
+     */
+    void setSharingMode(aaudio_sharing_mode_t requestedSharingMode) {
+        mRequestedSharingMode = requestedSharingMode;
+    }
+
+    /**
+     * Also known as "sample rate"
+     * Only call this after open() has been called.
+     */
+    int32_t getFramesPerSecond() {
+        if (mStream == nullptr) {
+            return AAUDIO_ERROR_INVALID_STATE;
+        }
+        return AAudioStream_getSampleRate(mStream);;
+    }
+
+    /**
+     * Only call this after open() has been called.
+     */
+    int32_t getSamplesPerFrame() {
+        if (mStream == nullptr) {
+            return AAUDIO_ERROR_INVALID_STATE;
+        }
+        return AAudioStream_getSamplesPerFrame(mStream);;
+    }
+
+    /**
+     * Open a stream
+     */
+    aaudio_result_t open(AAudioStream_dataCallback dataProc, void *userContext) {
+        aaudio_result_t result = AAUDIO_OK;
+
+        // Use an AAudioStreamBuilder to contain requested parameters.
+        result = AAudio_createStreamBuilder(&mBuilder);
+        if (result != AAUDIO_OK) return result;
+
+        AAudioStreamBuilder_setSharingMode(mBuilder, mRequestedSharingMode);
+        AAudioStreamBuilder_setDataCallback(mBuilder, dataProc, userContext);
+        AAudioStreamBuilder_setFormat(mBuilder, AAUDIO_FORMAT_PCM_FLOAT);
+        AAudioStreamBuilder_setFramesPerDataCallback(mBuilder, CALLBACK_SIZE_FRAMES);
+ //       AAudioStreamBuilder_setBufferCapacityInFrames(mBuilder, CALLBACK_SIZE_FRAMES * 4);
+
+        // Open an AAudioStream using the Builder.
+        result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
+        if (result != AAUDIO_OK) goto finish1;
+
+        printf("AAudioStream_getFramesPerBurst() = %d\n",
+               AAudioStream_getFramesPerBurst(mStream));
+        printf("AAudioStream_getBufferSizeInFrames() = %d\n",
+               AAudioStream_getBufferSizeInFrames(mStream));
+        printf("AAudioStream_getBufferCapacityInFrames() = %d\n",
+               AAudioStream_getBufferCapacityInFrames(mStream));
+        return result;
+
+     finish1:
+        AAudioStreamBuilder_delete(mBuilder);
+        mBuilder = nullptr;
+        return result;
+    }
+
+    aaudio_result_t close() {
+        if (mStream != nullptr) {
+            printf("call AAudioStream_close(%p)\n", mStream);  fflush(stdout);
+            AAudioStream_close(mStream);
+            mStream = nullptr;
+            AAudioStreamBuilder_delete(mBuilder);
+            mBuilder = nullptr;
+        }
+        return AAUDIO_OK;
+    }
+
+    // Write zero data to fill up the buffer and prevent underruns.
+    aaudio_result_t prime() {
+        int32_t samplesPerFrame = AAudioStream_getSamplesPerFrame(mStream);
+        const int numFrames = 32;
+        float zeros[numFrames * samplesPerFrame];
+        memset(zeros, 0, sizeof(zeros));
+        aaudio_result_t result = numFrames;
+        while (result == numFrames) {
+            result = AAudioStream_write(mStream, zeros, numFrames, 0);
+        }
+        return result;
+    }
+
+    // Start the stream. AAudio will start calling your callback function.
+     aaudio_result_t start() {
+        aaudio_result_t result = AAudioStream_requestStart(mStream);
+        if (result != AAUDIO_OK) {
+            fprintf(stderr, "ERROR - AAudioStream_requestStart() returned %d %s\n",
+                    result, AAudio_convertResultToText(result));
+        }
+        return result;
+    }
+
+    // Stop the stream. AAudio will stop calling your callback function.
+    aaudio_result_t stop() {
+        aaudio_result_t result = AAudioStream_requestStop(mStream);
+        if (result != AAUDIO_OK) {
+            fprintf(stderr, "ERROR - AAudioStream_requestStop() returned %d %s\n",
+                    result, AAudio_convertResultToText(result));
+        }
+        int32_t xRunCount = AAudioStream_getXRunCount(mStream);
+        printf("AAudioStream_getXRunCount %d\n", xRunCount);
+        return result;
+    }
+
+    AAudioStream *getStream() const {
+        return mStream;
+    }
+
+private:
+    AAudioStreamBuilder    *mBuilder = nullptr;
+    AAudioStream           *mStream = nullptr;
+    aaudio_sharing_mode_t   mRequestedSharingMode = SHARING_MODE;
+};
+
+// Application data that gets passed to the callback.
+#define MAX_FRAME_COUNT_RECORDS    256
+typedef struct SineThreadedData_s {
+    SineGenerator  sineOsc1;
+    SineGenerator  sineOsc2;
+    // Remove these variables used for testing.
+    int32_t        numFrameCounts;
+    int32_t        frameCounts[MAX_FRAME_COUNT_RECORDS];
+    int            scheduler;
+    bool           schedulerChecked;
+} SineThreadedData_t;
+
+// Callback function that fills the audio output buffer.
+aaudio_data_callback_result_t MyDataCallbackProc(
+        AAudioStream *stream,
+        void *userData,
+        void *audioData,
+        int32_t numFrames
+        ) {
+
+    SineThreadedData_t *sineData = (SineThreadedData_t *) userData;
+
+    if (sineData->numFrameCounts < MAX_FRAME_COUNT_RECORDS) {
+        sineData->frameCounts[sineData->numFrameCounts++] = numFrames;
+    }
+
+    if (!sineData->schedulerChecked) {
+        sineData->scheduler = sched_getscheduler(gettid());
+        sineData->schedulerChecked = true;
+    }
+
+    int32_t samplesPerFrame = AAudioStream_getSamplesPerFrame(stream);
+    // This code only plays on the first one or two channels.
+    // TODO Support arbitrary number of channels.
+    switch (AAudioStream_getFormat(stream)) {
+        case AAUDIO_FORMAT_PCM_I16: {
+            int16_t *audioBuffer = (int16_t *) audioData;
+            // Render sine waves as shorts to first channel.
+            sineData->sineOsc1.render(&audioBuffer[0], samplesPerFrame, numFrames);
+            // Render sine waves to second channel if there is one.
+            if (samplesPerFrame > 1) {
+                sineData->sineOsc2.render(&audioBuffer[1], samplesPerFrame, numFrames);
+            }
+        }
+        break;
+        case AAUDIO_FORMAT_PCM_FLOAT: {
+            float *audioBuffer = (float *) audioData;
+            // Render sine waves as floats to first channel.
+            sineData->sineOsc1.render(&audioBuffer[0], samplesPerFrame, numFrames);
+            // Render sine waves to second channel if there is one.
+            if (samplesPerFrame > 1) {
+                sineData->sineOsc2.render(&audioBuffer[1], samplesPerFrame, numFrames);
+            }
+        }
+        break;
+        default:
+            return AAUDIO_CALLBACK_RESULT_STOP;
+    }
+
+    return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+int main(int argc, char **argv)
+{
+    (void)argc; // unused
+    SimpleAAudioPlayer player;
+    SineThreadedData_t myData;
+    aaudio_result_t result;
+
+    // Make printf print immediately so that debug info is not stuck
+    // 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\n", argv[0]);
+
+    player.setSharingMode(SHARING_MODE);
+
+    myData.numFrameCounts = 0;
+    myData.schedulerChecked = false;
+
+    result = player.open(MyDataCallbackProc, &myData);
+    if (result != AAUDIO_OK) {
+        fprintf(stderr, "ERROR -  player.open() returned %d\n", result);
+        goto error;
+    }
+    printf("player.getFramesPerSecond() = %d\n", player.getFramesPerSecond());
+    printf("player.getSamplesPerFrame() = %d\n", player.getSamplesPerFrame());
+    myData.sineOsc1.setup(440.0, 48000);
+    myData.sineOsc1.setSweep(300.0, 600.0, 5.0);
+    myData.sineOsc2.setup(660.0, 48000);
+    myData.sineOsc2.setSweep(350.0, 900.0, 7.0);
+
+#if 0
+    result = player.prime(); // FIXME crashes AudioTrack.cpp
+    if (result != AAUDIO_OK) {
+        fprintf(stderr, "ERROR - player.prime() returned %d\n", result);
+        goto error;
+    }
+#endif
+
+    result = player.start();
+    if (result != AAUDIO_OK) {
+        fprintf(stderr, "ERROR - player.start() returned %d\n", result);
+        goto error;
+    }
+
+    printf("Sleep for %d seconds while audio plays in a callback thread.\n", NUM_SECONDS);
+    for (int second = 0; second < NUM_SECONDS; second++)
+    {
+        const struct timespec request = { .tv_sec = 1, .tv_nsec = 0 };
+        (void) clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, &request, NULL /*remain*/);
+
+        aaudio_stream_state_t state;
+        result = AAudioStream_waitForStateChange(player.getStream(),
+                                                 AAUDIO_STREAM_STATE_CLOSED,
+                                                 &state,
+                                                 0);
+        if (result != AAUDIO_OK) {
+            fprintf(stderr, "ERROR - AAudioStream_waitForStateChange() returned %d\n", result);
+            goto error;
+        }
+        if (state != AAUDIO_STREAM_STATE_STARTING && state != AAUDIO_STREAM_STATE_STARTED) {
+            printf("Stream state is %d %s!\n", state, AAudio_convertStreamStateToText(state));
+            break;
+        }
+    }
+    printf("Woke up now.\n");
+
+    result = player.stop();
+    if (result != AAUDIO_OK) {
+        goto error;
+    }
+    result = player.close();
+    if (result != AAUDIO_OK) {
+        goto error;
+    }
+
+    // Report data gathered in the callback.
+    for (int i = 0; i < myData.numFrameCounts; i++) {
+        printf("numFrames[%4d] = %4d\n", i, myData.frameCounts[i]);
+    }
+    if (myData.schedulerChecked) {
+        printf("scheduler = 0x%08x, SCHED_FIFO = 0x%08X\n",
+               myData.scheduler,
+               SCHED_FIFO);
+    }
+
+    printf("SUCCESS\n");
+    return EXIT_SUCCESS;
+error:
+    player.close();
+    printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
+    return EXIT_FAILURE;
+}
+
diff --git a/media/libaaudio/examples/write_sine/src/write_sine_threaded.cpp b/media/libaaudio/examples/write_sine/src/write_sine_threaded.cpp
index 40e5016..8065c48 100644
--- a/media/libaaudio/examples/write_sine/src/write_sine_threaded.cpp
+++ b/media/libaaudio/examples/write_sine/src/write_sine_threaded.cpp
@@ -22,7 +22,6 @@
 #include <stdio.h>
 #include <math.h>
 #include <time.h>
-#include <aaudio/AAudioDefinitions.h>
 #include <aaudio/AAudio.h>
 #include "SineGenerator.h"
 
@@ -49,7 +48,7 @@
 class SimpleAAudioPlayer {
 public:
     SimpleAAudioPlayer() {}
-    virtual ~SimpleAAudioPlayer() {
+    ~SimpleAAudioPlayer() {
         close();
     };
 
@@ -83,7 +82,7 @@
 
         // Open an AAudioStream using the Builder.
         result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
-        if (result != AAUDIO_OK) goto finish1;
+        if (result != AAUDIO_OK) goto error;
 
         // Check to see what kind of stream we actually got.
         mFramesPerSecond = AAudioStream_getSampleRate(mStream);
@@ -126,7 +125,7 @@
         }
         return result;
 
-     finish1:
+    error:
         AAudioStreamBuilder_delete(mBuilder);
         mBuilder = nullptr;
         return result;
diff --git a/media/libaaudio/examples/write_sine/static/Android.mk b/media/libaaudio/examples/write_sine/static/Android.mk
index 139b70a..aeccb4a 100644
--- a/media/libaaudio/examples/write_sine/static/Android.mk
+++ b/media/libaaudio/examples/write_sine/static/Android.mk
@@ -17,6 +17,8 @@
 LOCAL_MODULE := write_sine
 include $(BUILD_EXECUTABLE)
 
+
+
 include $(CLEAR_VARS)
 LOCAL_MODULE_TAGS := tests
 LOCAL_C_INCLUDES := \
@@ -27,8 +29,26 @@
 
 LOCAL_SHARED_LIBRARIES := libaudioutils libmedia \
                           libbinder libcutils libutils \
-                          libaudioclient liblog libtinyalsa
+                          libaudioclient liblog
 LOCAL_STATIC_LIBRARIES := libaaudio
 
 LOCAL_MODULE := write_sine_threaded
 include $(BUILD_EXECUTABLE)
+
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/libaaudio/include
+
+LOCAL_SRC_FILES:= ../src/write_sine_callback.cpp
+
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia \
+                          libbinder libcutils libutils \
+                          libaudioclient liblog
+LOCAL_STATIC_LIBRARIES := libaaudio
+
+LOCAL_MODULE := write_sine_callback
+include $(BUILD_EXECUTABLE)
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index 551dcc9..25ad5f8 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -89,7 +89,8 @@
  * Request an audio device identified device using an ID.
  * On Android, for example, the ID could be obtained from the Java AudioManager.
  *
- * By default, the primary device will be used.
+ * The default, if you do not call this function, is AAUDIO_DEVICE_UNSPECIFIED,
+ * in which case the primary device will be used.
  *
  * @param builder reference provided by AAudio_createStreamBuilder()
  * @param deviceId device identifier or AAUDIO_DEVICE_UNSPECIFIED
@@ -98,52 +99,71 @@
                                                      int32_t deviceId);
 
 /**
- * Request a sample rate in Hz.
+ * Request a sample rate in Hertz.
+ *
  * The stream may be opened with a different sample rate.
  * So the application should query for the actual rate after the stream is opened.
  *
  * Technically, this should be called the "frame rate" or "frames per second",
  * because it refers to the number of complete frames transferred per second.
- * But it is traditionally called "sample rate". Se we use that term.
+ * But it is traditionally called "sample rate". So we use that term.
  *
- * Default is AAUDIO_UNSPECIFIED.
-
+ * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param sampleRate frames per second. Common rates include 44100 and 48000 Hz.
  */
 AAUDIO_API void AAudioStreamBuilder_setSampleRate(AAudioStreamBuilder* builder,
                                                        int32_t sampleRate);
 
 /**
  * Request a number of samples per frame.
+ *
  * The stream may be opened with a different value.
  * So the application should query for the actual value after the stream is opened.
  *
- * Default is AAUDIO_UNSPECIFIED.
+ * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
  *
  * Note, this quantity is sometimes referred to as "channel count".
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param samplesPerFrame Number of samples in one frame, ie. numChannels.
  */
 AAUDIO_API void AAudioStreamBuilder_setSamplesPerFrame(AAudioStreamBuilder* builder,
                                                    int32_t samplesPerFrame);
 
 /**
  * Request a sample data format, for example AAUDIO_FORMAT_PCM_I16.
- * The application should query for the actual format after the stream is opened.
+ *
+ * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
+ *
+ * The stream may be opened with a different value.
+ * So the application should query for the actual value after the stream is opened.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param format Most common formats are AAUDIO_FORMAT_PCM_FLOAT and AAUDIO_FORMAT_PCM_I16.
  */
 AAUDIO_API void AAudioStreamBuilder_setFormat(AAudioStreamBuilder* builder,
                                                    aaudio_audio_format_t format);
 
 /**
  * Request a mode for sharing the device.
+ *
+ * The default, if you do not call this function, is AAUDIO_SHARING_MODE_SHARED.
+ *
  * The requested sharing mode may not be available.
- * So the application should query for the actual mode after the stream is opened.
+ * The application can query for the actual mode after the stream is opened.
  *
  * @param builder reference provided by AAudio_createStreamBuilder()
- * @param sharingMode AAUDIO_SHARING_MODE_LEGACY or AAUDIO_SHARING_MODE_EXCLUSIVE
+ * @param sharingMode AAUDIO_SHARING_MODE_SHARED or AAUDIO_SHARING_MODE_EXCLUSIVE
  */
 AAUDIO_API void AAudioStreamBuilder_setSharingMode(AAudioStreamBuilder* builder,
                                                         aaudio_sharing_mode_t sharingMode);
 
 /**
- * Request the direction for a stream. The default is AAUDIO_DIRECTION_OUTPUT.
+ * Request the direction for a stream.
+ *
+ * The default, if you do not call this function, is AAUDIO_DIRECTION_OUTPUT.
  *
  * @param builder reference provided by AAudio_createStreamBuilder()
  * @param direction AAUDIO_DIRECTION_OUTPUT or AAUDIO_DIRECTION_INPUT
@@ -152,16 +172,162 @@
                                                             aaudio_direction_t direction);
 
 /**
- * Set the requested maximum buffer capacity in frames.
+ * Set the requested buffer capacity in frames.
  * The final AAudioStream capacity may differ, but will probably be at least this big.
  *
- * Default is AAUDIO_UNSPECIFIED.
+ * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
  *
  * @param builder reference provided by AAudio_createStreamBuilder()
- * @param frames the desired buffer capacity in frames or AAUDIO_UNSPECIFIED
+ * @param numFrames the desired buffer capacity in frames or AAUDIO_UNSPECIFIED
  */
 AAUDIO_API void AAudioStreamBuilder_setBufferCapacityInFrames(AAudioStreamBuilder* builder,
-                                                                 int32_t frames);
+                                                                 int32_t numFrames);
+/**
+ * Return one of these values from the data callback function.
+ */
+enum {
+
+    /**
+     * Continue calling the callback.
+     */
+    AAUDIO_CALLBACK_RESULT_CONTINUE = 0,
+
+    /**
+     * Stop calling the callback.
+     *
+     * The application will still need to call AAudioStream_requestPause()
+     * or AAudioStream_requestStop().
+     */
+    AAUDIO_CALLBACK_RESULT_STOP,
+
+};
+typedef int32_t aaudio_data_callback_result_t;
+
+/**
+ * Prototype for the data function that is passed to AAudioStreamBuilder_setDataCallback().
+ *
+ * For an output stream, this function should render and write numFrames of data
+ * in the streams current data format to the audioData buffer.
+ *
+ * For an input stream, this function should read and process numFrames of data
+ * from the audioData buffer.
+ *
+ * Note that this callback function should be considered a "real-time" function.
+ * It must not do anything that could cause an unbounded delay because that can cause the
+ * audio to glitch or pop.
+ *
+ * These are things the function should NOT do:
+ * <ul>
+ * <li>allocate memory using, for example, malloc() or new</li>
+ * <li>any file operations such as opening, closing, reading or writing</li>
+ * <li>any network operations such as streaming</li>
+ * <li>use any mutexes or other synchronization primitives</li>
+ * <li>sleep</li>
+ * </ul>
+ *
+ * If you need to move data, eg. MIDI commands, in or out of the callback function then
+ * we recommend the use of non-blocking techniques such as an atomic FIFO.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @param userData the same address that was passed to AAudioStreamBuilder_setCallback()
+ * @param audioData a pointer to the audio data
+ * @param numFrames the number of frames to be processed
+ * @return AAUDIO_CALLBACK_RESULT_*
+ */
+typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
+        AAudioStream *stream,
+        void *userData,
+        void *audioData,
+        int32_t numFrames);
+
+/**
+ * Request that AAudio call this functions when the stream is running.
+ *
+ * Note that when using this callback, the audio data will be passed in or out
+ * of the function as an argument.
+ * So you cannot call AAudioStream_write() or AAudioStream_read() on the same stream
+ * that has an active data callback.
+ *
+ * The callback function will start being called after AAudioStream_requestStart() is called.
+ * It will stop being called after AAudioStream_requestPause() or
+ * AAudioStream_requestStop() is called.
+ *
+ * This callback function will be called on a real-time thread owned by AAudio. See
+ * {@link aaudio_data_callback_proc_t} for more information.
+ *
+ * Note that the AAudio callbacks will never be called simultaneously from multiple threads.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param callback pointer to a function that will process audio data.
+ * @param userData pointer to an application data structure that will be passed
+ *          to the callback functions.
+ */
+AAUDIO_API void AAudioStreamBuilder_setDataCallback(AAudioStreamBuilder* builder,
+                                                 AAudioStream_dataCallback callback,
+                                                 void *userData);
+
+/**
+ * Set the requested data callback buffer size in frames.
+ * See {@link AAudioStream_dataCallback}.
+ *
+ * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
+ *
+ * For the lowest possible latency, do not call this function. AAudio will then
+ * call the dataProc callback function with whatever size is optimal.
+ * That size may vary from one callback to another.
+ *
+ * Only use this function if the application requires a specific number of frames for processing.
+ * The application might, for example, be using an FFT that requires
+ * a specific power-of-two sized buffer.
+ *
+ * AAudio may need to add additional buffering in order to adapt between the internal
+ * buffer size and the requested buffer size.
+ *
+ * If you do call this function then the requested size should be less than
+ * half the buffer capacity, to allow double buffering.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param numFrames the desired buffer size in frames or AAUDIO_UNSPECIFIED
+ */
+AAUDIO_API void AAudioStreamBuilder_setFramesPerDataCallback(AAudioStreamBuilder* builder,
+                                                             int32_t numFrames);
+
+/**
+ * Prototype for the callback function that is passed to
+ * AAudioStreamBuilder_setErrorCallback().
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @param userData the same address that was passed to AAudioStreamBuilder_setErrorCallback()
+ * @param error an AAUDIO_ERROR_* value.
+ */
+typedef void (*AAudioStream_errorCallback)(
+        AAudioStream *stream,
+        void *userData,
+        aaudio_result_t error);
+
+/**
+ * Request that AAudio call this functions if any error occurs on a callback thread.
+ *
+ * It will be called, for example, if a headset or a USB device is unplugged causing the stream's
+ * device to be unavailable.
+ * In response, this function could signal or launch another thread to reopen a
+ * stream on another device. Do not reopen the stream in this callback.
+ *
+ * This will not be called because of actions by the application, such as stopping
+ * or closing a stream.
+ *
+ * Another possible cause of error would be a timeout or an unanticipated internal error.
+ *
+ * Note that the AAudio callbacks will never be called simultaneously from multiple threads.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param callback pointer to a function that will be called if an error occurs.
+ * @param userData pointer to an application data structure that will be passed
+ *          to the callback functions.
+ */
+AAUDIO_API void AAudioStreamBuilder_setErrorCallback(AAudioStreamBuilder* builder,
+                                                AAudioStream_errorCallback callback,
+                                                void *userData);
 
 /**
  * Open a stream based on the options in the StreamBuilder.
@@ -333,9 +499,14 @@
 // High priority audio threads
 // ============================================================
 
+/**
+ * @deprecated Use AudioStreamBuilder_setCallback()
+ */
 typedef void *(*aaudio_audio_thread_proc_t)(void *);
 
 /**
+ * @deprecated Use AudioStreamBuilder_setCallback()
+ *
  * Create a thread associated with a stream. The thread has special properties for
  * low latency audio performance. This thread can be used to implement a callback API.
  *
@@ -360,6 +531,8 @@
                                      void *arg);
 
 /**
+ * @deprecated Use AudioStreamBuilder_setCallback()
+ *
  * Wait until the thread exits or an error occurs.
  *
  * @param stream A stream created using AAudioStreamBuilder_openStream().
@@ -388,11 +561,11 @@
  * Call AAudioStream_getBufferSizeInFrames() to see what the actual final size is.
  *
  * @param stream reference provided by AAudioStreamBuilder_openStream()
- * @param requestedFrames requested number of frames that can be filled without blocking
+ * @param numFrames requested number of frames that can be filled without blocking
  * @return actual buffer size in frames or a negative error
  */
 AAUDIO_API aaudio_result_t AAudioStream_setBufferSizeInFrames(AAudioStream* stream,
-                                                      int32_t requestedFrames);
+                                                      int32_t numFrames);
 
 /**
  * Query the maximum number of frames that can be filled without blocking.
@@ -421,11 +594,32 @@
  * Query maximum buffer capacity in frames.
  *
  * @param stream reference provided by AAudioStreamBuilder_openStream()
- * @return  the buffer capacity in frames
+ * @return  buffer capacity in frames
  */
 AAUDIO_API int32_t AAudioStream_getBufferCapacityInFrames(AAudioStream* stream);
 
 /**
+ * Query the size of the buffer that will be passed to the dataProc callback
+ * in the numFrames parameter.
+ *
+ * This call can be used if the application needs to know the value of numFrames before
+ * the stream is started. This is not normally necessary.
+ *
+ * If a specific size was requested by calling AAudioStreamBuilder_setCallbackSizeInFrames()
+ * then this will be the same size.
+ *
+ * If AAudioStreamBuilder_setCallbackSizeInFrames() was not called then this will
+ * return the size chosen by AAudio, or AAUDIO_UNSPECIFIED.
+ *
+ * AAUDIO_UNSPECIFIED indicates that the callback buffer size for this stream
+ * may vary from one dataProc callback to the next.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @return callback buffer size in frames or AAUDIO_UNSPECIFIED
+ */
+AAUDIO_API int32_t AAudioStream_getFramesPerDataCallback(AAudioStream* stream);
+
+/**
  * An XRun is an Underrun or an Overrun.
  * During playing, an underrun will occur if the stream is not written in time
  * and the system runs out of valid data.
diff --git a/media/libaaudio/libaaudio.map.txt b/media/libaaudio/libaaudio.map.txt
index a9e9109..f22fdfe 100644
--- a/media/libaaudio/libaaudio.map.txt
+++ b/media/libaaudio/libaaudio.map.txt
@@ -4,6 +4,9 @@
     AAudio_convertStreamStateToText;
     AAudio_createStreamBuilder;
     AAudioStreamBuilder_setDeviceId;
+    AAudioStreamBuilder_setDataCallback;
+    AAudioStreamBuilder_setErrorCallback;
+    AAudioStreamBuilder_setFramesPerDataCallback;
     AAudioStreamBuilder_setSampleRate;
     AAudioStreamBuilder_setSamplesPerFrame;
     AAudioStreamBuilder_setFormat;
@@ -25,6 +28,7 @@
     AAudioStream_joinThread;
     AAudioStream_setBufferSizeInFrames;
     AAudioStream_getBufferSizeInFrames;
+    AAudioStream_getFramesPerDataCallback;
     AAudioStream_getFramesPerBurst;
     AAudioStream_getBufferCapacityInFrames;
     AAudioStream_getXRunCount;
diff --git a/media/libaaudio/src/Android.mk b/media/libaaudio/src/Android.mk
index a016b49..1ee73bf 100644
--- a/media/libaaudio/src/Android.mk
+++ b/media/libaaudio/src/Android.mk
@@ -30,10 +30,14 @@
     core/AudioStream.cpp \
     core/AudioStreamBuilder.cpp \
     core/AAudioAudio.cpp \
+    legacy/AudioStreamLegacy.cpp \
     legacy/AudioStreamRecord.cpp \
     legacy/AudioStreamTrack.cpp \
     utility/HandleTracker.cpp \
     utility/AAudioUtilities.cpp \
+    utility/FixedBlockAdapter.cpp \
+    utility/FixedBlockReader.cpp \
+    utility/FixedBlockWriter.cpp \
     fifo/FifoBuffer.cpp \
     fifo/FifoControllerBase.cpp \
     client/AudioEndpoint.cpp \
@@ -79,10 +83,14 @@
 LOCAL_SRC_FILES = core/AudioStream.cpp \
     core/AudioStreamBuilder.cpp \
     core/AAudioAudio.cpp \
+    legacy/AudioStreamLegacy.cpp \
     legacy/AudioStreamRecord.cpp \
     legacy/AudioStreamTrack.cpp \
     utility/HandleTracker.cpp \
     utility/AAudioUtilities.cpp \
+    utility/FixedBlockAdapter.cpp \
+    utility/FixedBlockReader.cpp \
+    utility/FixedBlockWriter.cpp \
     fifo/FifoBuffer.cpp \
     fifo/FifoControllerBase.cpp \
     client/AudioEndpoint.cpp \
diff --git a/media/libaaudio/src/client/AudioEndpoint.cpp b/media/libaaudio/src/client/AudioEndpoint.cpp
index 47c4774..90c619c 100644
--- a/media/libaaudio/src/client/AudioEndpoint.cpp
+++ b/media/libaaudio/src/client/AudioEndpoint.cpp
@@ -19,7 +19,7 @@
 #include <utils/Log.h>
 
 #include <cassert>
-#include <aaudio/AAudioDefinitions.h>
+#include <aaudio/AAudio.h>
 
 #include "AudioEndpointParcelable.h"
 #include "AudioEndpoint.h"
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 54f4870..1f9ce4f 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -18,23 +18,19 @@
 //#define LOG_NDEBUG 0
 #include <utils/Log.h>
 
-#include <stdint.h>
 #include <assert.h>
 
 #include <binder/IServiceManager.h>
 #include <utils/Mutex.h>
 
 #include <aaudio/AAudio.h>
+#include <utils/String16.h>
 
-#include "AudioClock.h"
-#include "AudioEndpointParcelable.h"
-#include "binding/AAudioStreamRequest.h"
-#include "binding/AAudioStreamConfiguration.h"
-#include "binding/IAAudioService.h"
+#include "utility/AudioClock.h"
+#include "AudioStreamInternal.h"
 #include "binding/AAudioServiceMessage.h"
 
 #include "core/AudioStreamBuilder.h"
-#include "AudioStreamInternal.h"
 
 #define LOG_TIMESTAMPS   0
 
@@ -51,6 +47,11 @@
 
 #define AAUDIO_SERVICE_NAME   "AAudioService"
 
+#define MIN_TIMEOUT_NANOS        (1000 * AAUDIO_NANOS_PER_MILLISECOND)
+
+// Wait at least this many times longer than the operation should take.
+#define MIN_TIMEOUT_OPERATIONS    4
+
 // Helper function to get access to the "AAudioService" service.
 // This code was modeled after frameworks/av/media/libaudioclient/AudioSystem.cpp
 static const sp<IAAudioService> getAAudioService() {
@@ -151,6 +152,29 @@
         mClockModel.setSampleRate(getSampleRate());
         mClockModel.setFramesPerBurst(mFramesPerBurst);
 
+        if (getDataCallbackProc()) {
+            mCallbackFrames = builder.getFramesPerDataCallback();
+            if (mCallbackFrames > getBufferCapacity() / 2) {
+                ALOGE("AudioStreamInternal.open(): framesPerCallback too large");
+                service->closeStream(mServiceStreamHandle);
+                return AAUDIO_ERROR_OUT_OF_RANGE;
+
+            } else if (mCallbackFrames < 0) {
+                ALOGE("AudioStreamInternal.open(): framesPerCallback negative");
+                service->closeStream(mServiceStreamHandle);
+                return AAUDIO_ERROR_OUT_OF_RANGE;
+
+            }
+            if (mCallbackFrames == AAUDIO_UNSPECIFIED) {
+                mCallbackFrames = mFramesPerBurst;
+            }
+
+            int32_t bytesPerFrame = getSamplesPerFrame()
+                                    * AAudioConvert_formatToSizeInBytes(getFormat());
+            int32_t callbackBufferSize = mCallbackFrames * bytesPerFrame;
+            mCallbackBuffer = new uint8_t[callbackBufferSize];
+        }
+
         setState(AAUDIO_STREAM_STATE_OPEN);
     }
     return result;
@@ -164,12 +188,69 @@
         const sp<IAAudioService>& aaudioService = getAAudioService();
         if (aaudioService == 0) return AAUDIO_ERROR_NO_SERVICE;
         aaudioService->closeStream(serviceStreamHandle);
+        delete[] mCallbackBuffer;
         return AAUDIO_OK;
     } else {
         return AAUDIO_ERROR_INVALID_HANDLE;
     }
 }
 
+// Render audio in the application callback and then write the data to the stream.
+void *AudioStreamInternal::callbackLoop() {
+    aaudio_result_t result = AAUDIO_OK;
+    aaudio_data_callback_result_t callbackResult = AAUDIO_CALLBACK_RESULT_CONTINUE;
+    int32_t framesWritten = 0;
+    AAudioStream_dataCallback appCallback = getDataCallbackProc();
+    if (appCallback == nullptr) return NULL;
+
+    while (mCallbackEnabled.load() && isPlaying() && (result >= 0)) { // result might be a frame count
+        // Call application using the AAudio callback interface.
+        callbackResult = (*appCallback)(
+                (AAudioStream *) this,
+                getDataCallbackUserData(),
+                mCallbackBuffer,
+                mCallbackFrames);
+
+        if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) {
+            // Write audio data to stream
+            int64_t timeoutNanos = calculateReasonableTimeout(mCallbackFrames);
+            result = write(mCallbackBuffer, mCallbackFrames, timeoutNanos);
+            if (result == AAUDIO_ERROR_DISCONNECTED) {
+                if (getErrorCallbackProc() != nullptr) {
+                    ALOGD("AudioStreamAAudio(): callbackLoop() stream disconnected");
+                    (*getErrorCallbackProc())(
+                            (AAudioStream *) this,
+                            getErrorCallbackUserData(),
+                            AAUDIO_OK);
+                }
+                break;
+            } else if (result != mCallbackFrames) {
+                ALOGE("AudioStreamAAudio(): callbackLoop() wrote %d / %d",
+                      framesWritten, mCallbackFrames);
+                break;
+            }
+        } else if (callbackResult == AAUDIO_CALLBACK_RESULT_STOP) {
+            ALOGD("AudioStreamAAudio(): callback returned AAUDIO_CALLBACK_RESULT_STOP");
+            break;
+        }
+    }
+
+    ALOGD("AudioStreamAAudio(): callbackLoop() exiting, result = %d, isPlaying() = %d",
+          result, (int) isPlaying());
+    return NULL; // TODO review
+}
+
+static void *aaudio_callback_thread_proc(void *context)
+{
+    AudioStreamInternal *stream = (AudioStreamInternal *)context;
+    //LOGD("AudioStreamAAudio(): oboe_callback_thread, stream = %p", stream);
+    if (stream != NULL) {
+        return stream->callbackLoop();
+    } else {
+        return NULL;
+    }
+}
+
 aaudio_result_t AudioStreamInternal::requestStart()
 {
     int64_t startTime;
@@ -178,35 +259,81 @@
         return AAUDIO_ERROR_INVALID_STATE;
     }
     const sp<IAAudioService>& aaudioService = getAAudioService();
-    if (aaudioService == 0) return AAUDIO_ERROR_NO_SERVICE;
+    if (aaudioService == 0) {
+        return AAUDIO_ERROR_NO_SERVICE;
+    }
     startTime = AudioClock::getNanoseconds();
     mClockModel.start(startTime);
     processTimestamp(0, startTime);
     setState(AAUDIO_STREAM_STATE_STARTING);
-    return aaudioService->startStream(mServiceStreamHandle);
+    aaudio_result_t result = aaudioService->startStream(mServiceStreamHandle);
+
+    if (result == AAUDIO_OK && getDataCallbackProc() != nullptr) {
+        // Launch the callback loop thread.
+        int64_t periodNanos = mCallbackFrames
+                              * AAUDIO_NANOS_PER_SECOND
+                              / getSampleRate();
+        mCallbackEnabled.store(true);
+        result = createThread(periodNanos, aaudio_callback_thread_proc, this);
+    }
+    return result;
 }
 
-aaudio_result_t AudioStreamInternal::requestPause()
+int64_t AudioStreamInternal::calculateReasonableTimeout(int32_t framesPerOperation) {
+
+    // Wait for at least a second or some number of callbacks to join the thread.
+    int64_t timeoutNanoseconds = (MIN_TIMEOUT_OPERATIONS * framesPerOperation * AAUDIO_NANOS_PER_SECOND)
+                         / getSampleRate();
+    if (timeoutNanoseconds < MIN_TIMEOUT_NANOS) { // arbitrary number of seconds
+        timeoutNanoseconds = MIN_TIMEOUT_NANOS;
+    }
+    return timeoutNanoseconds;
+}
+
+aaudio_result_t AudioStreamInternal::stopCallback()
+{
+    if (isDataCallbackActive()) {
+        mCallbackEnabled.store(false);
+        return joinThread(NULL, calculateReasonableTimeout(mCallbackFrames));
+    } else {
+        return AAUDIO_OK;
+    }
+}
+
+aaudio_result_t AudioStreamInternal::requestPauseInternal()
 {
     ALOGD("AudioStreamInternal(): pause()");
     if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
         return AAUDIO_ERROR_INVALID_STATE;
     }
     const sp<IAAudioService>& aaudioService = getAAudioService();
-    if (aaudioService == 0) return AAUDIO_ERROR_NO_SERVICE;
+    if (aaudioService == 0) {
+        return AAUDIO_ERROR_NO_SERVICE;
+    }
     mClockModel.stop(AudioClock::getNanoseconds());
     setState(AAUDIO_STREAM_STATE_PAUSING);
     return aaudioService->pauseStream(mServiceStreamHandle);
 }
 
+aaudio_result_t AudioStreamInternal::requestPause()
+{
+    aaudio_result_t result = stopCallback();
+    if (result != AAUDIO_OK) {
+        return result;
+    }
+    return requestPauseInternal();
+}
+
 aaudio_result_t AudioStreamInternal::requestFlush() {
     ALOGD("AudioStreamInternal(): flush()");
     if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
         return AAUDIO_ERROR_INVALID_STATE;
     }
     const sp<IAAudioService>& aaudioService = getAAudioService();
-    if (aaudioService == 0) return AAUDIO_ERROR_NO_SERVICE;
-setState(AAUDIO_STREAM_STATE_FLUSHING);
+    if (aaudioService == 0) {
+        return AAUDIO_ERROR_NO_SERVICE;
+    }
+    setState(AAUDIO_STREAM_STATE_FLUSHING);
     return aaudioService->flushStream(mServiceStreamHandle);
 }
 
@@ -260,18 +387,20 @@
     return aaudioService->unregisterAudioThread(mServiceStreamHandle, gettid());
 }
 
-// TODO use aaudio_clockid_t all the way down to AudioClock
 aaudio_result_t AudioStreamInternal::getTimestamp(clockid_t clockId,
                            int64_t *framePosition,
                            int64_t *timeNanoseconds) {
-// TODO implement using real HAL
+    // TODO implement using real HAL
     int64_t time = AudioClock::getNanoseconds();
     *framePosition = mClockModel.convertTimeToPosition(time);
     *timeNanoseconds = time + (10 * AAUDIO_NANOS_PER_MILLISECOND); // Fake hardware delay
     return AAUDIO_OK;
 }
 
-aaudio_result_t AudioStreamInternal::updateState() {
+aaudio_result_t AudioStreamInternal::updateStateWhileWaiting() {
+    if (isDataCallbackActive()) {
+        return AAUDIO_OK; // state is getting updated by the callback thread read/write call
+    }
     return processCommands();
 }
 
@@ -485,43 +614,6 @@
     return framesWritten;
 }
 
-aaudio_result_t AudioStreamInternal::waitForStateChange(aaudio_stream_state_t currentState,
-                                                      aaudio_stream_state_t *nextState,
-                                                      int64_t timeoutNanoseconds)
-
-{
-    aaudio_result_t result = processCommands();
-//    ALOGD("AudioStreamInternal::waitForStateChange() - processCommands() returned %d", result);
-    if (result != AAUDIO_OK) {
-        return result;
-    }
-    // TODO replace this polling with a timed sleep on a futex on the message queue
-    int32_t durationNanos = 5 * AAUDIO_NANOS_PER_MILLISECOND;
-    aaudio_stream_state_t state = getState();
-//    ALOGD("AudioStreamInternal::waitForStateChange() - state = %d", state);
-    while (state == currentState && timeoutNanoseconds > 0) {
-        // TODO use futex from service message queue
-        if (durationNanos > timeoutNanoseconds) {
-            durationNanos = timeoutNanoseconds;
-        }
-        AudioClock::sleepForNanos(durationNanos);
-        timeoutNanoseconds -= durationNanos;
-
-        result = processCommands();
-        if (result != AAUDIO_OK) {
-            return result;
-        }
-
-        state = getState();
-//        ALOGD("AudioStreamInternal::waitForStateChange() - state = %d", state);
-    }
-    if (nextState != nullptr) {
-        *nextState = state;
-    }
-    return (state == currentState) ? AAUDIO_ERROR_TIMEOUT : AAUDIO_OK;
-}
-
-
 void AudioStreamInternal::processTimestamp(uint64_t position, int64_t time) {
     mClockModel.processTimestamp( position, time);
 }
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 6f3a7ac..9a15a9b 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -53,7 +53,7 @@
                                        int64_t *timeNanoseconds) override;
 
 
-    virtual aaudio_result_t updateState() override;
+    virtual aaudio_result_t updateStateWhileWaiting() override;
     // =========== End ABSTRACT methods ===========================
 
     virtual aaudio_result_t open(const AudioStreamBuilder &builder) override;
@@ -64,10 +64,6 @@
                              int32_t numFrames,
                              int64_t timeoutNanoseconds) override;
 
-    virtual aaudio_result_t waitForStateChange(aaudio_stream_state_t currentState,
-                                          aaudio_stream_state_t *nextState,
-                                          int64_t timeoutNanoseconds) override;
-
     virtual aaudio_result_t setBufferSize(int32_t requestedFrames) override;
 
     virtual int32_t getBufferSize() const override;
@@ -86,10 +82,17 @@
 
     virtual aaudio_result_t unregisterThread() override;
 
+    // Called internally from 'C'
+    void *callbackLoop();
+
 protected:
 
     aaudio_result_t processCommands();
 
+    aaudio_result_t requestPauseInternal();
+
+    aaudio_result_t stopCallback();
+
 /**
  * Low level write that will not block. It will just write as much as it can.
  *
@@ -108,17 +111,22 @@
 
     aaudio_result_t onTimestampFromServer(AAudioServiceMessage *message);
 
+    // Calculate timeout for an operation involving framesPerOperation.
+    int64_t calculateReasonableTimeout(int32_t framesPerOperation);
+
 private:
     IsochronousClockModel    mClockModel;
     AudioEndpoint            mAudioEndpoint;
     aaudio_handle_t          mServiceStreamHandle;
     EndpointDescriptor       mEndpointDescriptor;
+    uint8_t                 *mCallbackBuffer = nullptr;
+    int32_t                  mCallbackFrames = 0;
+
     // Offset from underlying frame position.
     int64_t                  mFramesOffsetFromService = 0;
     int64_t                  mLastFramesRead = 0;
     int32_t                  mFramesPerBurst;
     int32_t                  mXRunCount = 0;
-
     void processTimestamp(uint64_t position, int64_t time);
 };
 
diff --git a/media/libaaudio/src/client/IsochronousClockModel.cpp b/media/libaaudio/src/client/IsochronousClockModel.cpp
index 4c8aabc..c278c8b 100644
--- a/media/libaaudio/src/client/IsochronousClockModel.cpp
+++ b/media/libaaudio/src/client/IsochronousClockModel.cpp
@@ -19,7 +19,6 @@
 #include <utils/Log.h>
 
 #include <stdint.h>
-#include <aaudio/AAudioDefinitions.h>
 
 #include "utility/AudioClock.h"
 #include "IsochronousClockModel.h"
diff --git a/media/libaaudio/src/client/IsochronousClockModel.h b/media/libaaudio/src/client/IsochronousClockModel.h
index 524c286..205c341 100644
--- a/media/libaaudio/src/client/IsochronousClockModel.h
+++ b/media/libaaudio/src/client/IsochronousClockModel.h
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef AAUDIO_ISOCHRONOUSCLOCKMODEL_H
-#define AAUDIO_ISOCHRONOUSCLOCKMODEL_H
+#ifndef AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
+#define AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
 
 #include <stdint.h>
-#include <aaudio/AAudio.h>
 
 namespace aaudio {
 
@@ -107,4 +106,4 @@
 
 } /* namespace aaudio */
 
-#endif //AAUDIO_ISOCHRONOUSCLOCKMODEL_H
+#endif //AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
diff --git a/media/libaaudio/src/core/AAudioAudio.cpp b/media/libaaudio/src/core/AAudioAudio.cpp
index 52bad70..bc2f281 100644
--- a/media/libaaudio/src/core/AAudioAudio.cpp
+++ b/media/libaaudio/src/core/AAudioAudio.cpp
@@ -114,53 +114,79 @@
 AAUDIO_API void AAudioStreamBuilder_setDeviceId(AAudioStreamBuilder* builder,
                                                      int32_t deviceId)
 {
-    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
     streamBuilder->setDeviceId(deviceId);
 }
 
 AAUDIO_API void AAudioStreamBuilder_setSampleRate(AAudioStreamBuilder* builder,
                                               int32_t sampleRate)
 {
-    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
     streamBuilder->setSampleRate(sampleRate);
 }
 
 AAUDIO_API void AAudioStreamBuilder_setSamplesPerFrame(AAudioStreamBuilder* builder,
                                                    int32_t samplesPerFrame)
 {
-    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
     streamBuilder->setSamplesPerFrame(samplesPerFrame);
 }
 
 AAUDIO_API void AAudioStreamBuilder_setDirection(AAudioStreamBuilder* builder,
                                              aaudio_direction_t direction)
 {
-    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
     streamBuilder->setDirection(direction);
 }
 
-
 AAUDIO_API void AAudioStreamBuilder_setFormat(AAudioStreamBuilder* builder,
                                                    aaudio_audio_format_t format)
 {
-    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
     streamBuilder->setFormat(format);
 }
 
 AAUDIO_API void AAudioStreamBuilder_setSharingMode(AAudioStreamBuilder* builder,
                                                         aaudio_sharing_mode_t sharingMode)
 {
-    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
     streamBuilder->setSharingMode(sharingMode);
 }
 
 AAUDIO_API void AAudioStreamBuilder_setBufferCapacityInFrames(AAudioStreamBuilder* builder,
                                                         int32_t frames)
 {
-    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
     streamBuilder->setBufferCapacity(frames);
 }
 
+AAUDIO_API void AAudioStreamBuilder_setDataCallback(AAudioStreamBuilder* builder,
+                                                    AAudioStream_dataCallback callback,
+                                                    void *userData)
+{
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+    ALOGD("AAudioStreamBuilder_setCallback(): userData = %p", userData);
+    streamBuilder->setDataCallbackProc(callback);
+    streamBuilder->setDataCallbackUserData(userData);
+}
+AAUDIO_API void AAudioStreamBuilder_setErrorCallback(AAudioStreamBuilder* builder,
+                                                 AAudioStream_errorCallback callback,
+                                                 void *userData)
+{
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+    ALOGD("AAudioStreamBuilder_setCallback(): userData = %p", userData);
+    streamBuilder->setErrorCallbackProc(callback);
+    streamBuilder->setErrorCallbackUserData(userData);
+}
+
+AAUDIO_API void AAudioStreamBuilder_setFramesPerDataCallback(AAudioStreamBuilder* builder,
+                                                int32_t frames)
+{
+    AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+    ALOGD("%s: frames = %d", __func__, frames);
+    streamBuilder->setFramesPerDataCallback(frames);
+}
+
 static aaudio_result_t  AAudioInternal_openStream(AudioStreamBuilder *streamBuilder,
                                               AAudioStream** streamPtr)
 {
@@ -276,6 +302,13 @@
     if (buffer == nullptr) {
         return AAUDIO_ERROR_NULL;
     }
+
+    // Don't allow writes when playing with a callback.
+    if (audioStream->getDataCallbackProc() != nullptr && audioStream->isPlaying()) {
+        ALOGE("Cannot write to a callback stream when running.");
+        return AAUDIO_ERROR_INVALID_STATE;
+    }
+
     if (numFrames < 0) {
         return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
     } else if (numFrames == 0) {
@@ -297,6 +330,9 @@
                                      aaudio_audio_thread_proc_t threadProc, void *arg)
 {
     AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
+    if (audioStream->getDataCallbackProc() != nullptr) {
+        return AAUDIO_ERROR_INCOMPATIBLE;
+    }
     return audioStream->createThread(periodNanoseconds, threadProc, arg);
 }
 
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index b054d94..68579fd 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -28,7 +28,9 @@
 
 using namespace aaudio;
 
-AudioStream::AudioStream() {
+AudioStream::AudioStream()
+        : mCallbackEnabled(false)
+{
     // mThread is a pthread_t of unknown size so we need memset.
     memset(&mThread, 0, sizeof(mThread));
     setPeriodNanoseconds(0);
@@ -36,13 +38,30 @@
 
 aaudio_result_t AudioStream::open(const AudioStreamBuilder& builder)
 {
-    // TODO validate parameters.
+
     // Copy parameters from the Builder because the Builder may be deleted after this call.
     mSamplesPerFrame = builder.getSamplesPerFrame();
     mSampleRate = builder.getSampleRate();
     mDeviceId = builder.getDeviceId();
     mFormat = builder.getFormat();
-    mSharingMode = builder.getSharingMode();
+    mDirection = builder.getDirection();
+
+    // callbacks
+    mFramesPerDataCallback = builder.getFramesPerDataCallback();
+    mDataCallbackProc = builder.getDataCallbackProc();
+    mErrorCallbackProc = builder.getErrorCallbackProc();
+    mDataCallbackUserData = builder.getDataCallbackUserData();
+
+    // TODO validate more parameters.
+    if (mErrorCallbackProc != nullptr && mDataCallbackProc == nullptr) {
+        ALOGE("AudioStream::open(): disconnect callback cannot be used without a data callback.");
+        return AAUDIO_ERROR_UNEXPECTED_VALUE;
+    }
+    if (mDirection != AAUDIO_DIRECTION_INPUT && mDirection != AAUDIO_DIRECTION_OUTPUT) {
+        ALOGE("AudioStream::open(): illegal direction %d", mDirection);
+        return AAUDIO_ERROR_UNEXPECTED_VALUE;
+    }
+
     return AAUDIO_OK;
 }
 
@@ -75,8 +94,13 @@
                                                 aaudio_stream_state_t *nextState,
                                                 int64_t timeoutNanoseconds)
 {
+    aaudio_result_t result = updateStateWhileWaiting();
+    if (result != AAUDIO_OK) {
+        return result;
+    }
+
     // TODO replace this when similar functionality added to AudioTrack.cpp
-    int64_t durationNanos = 20 * AAUDIO_NANOS_PER_MILLISECOND;
+    int64_t durationNanos = 20 * AAUDIO_NANOS_PER_MILLISECOND; // arbitrary
     aaudio_stream_state_t state = getState();
     while (state == currentState && timeoutNanoseconds > 0) {
         if (durationNanos > timeoutNanoseconds) {
@@ -85,7 +109,7 @@
         AudioClock::sleepForNanos(durationNanos);
         timeoutNanoseconds -= durationNanos;
 
-        aaudio_result_t result = updateState();
+        aaudio_result_t result = updateStateWhileWaiting();
         if (result != AAUDIO_OK) {
             return result;
         }
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index 6ac8554..1485d20 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -18,8 +18,8 @@
 #define AAUDIO_AUDIOSTREAM_H
 
 #include <atomic>
+#include <mutex>
 #include <stdint.h>
-#include <aaudio/AAudioDefinitions.h>
 #include <aaudio/AAudio.h>
 
 #include "AAudioUtilities.h"
@@ -55,14 +55,18 @@
                                        int64_t *timeNanoseconds) = 0;
 
 
-    virtual aaudio_result_t updateState() = 0;
+    /**
+     * Update state while in the middle of waitForStateChange()
+     * @return
+     */
+    virtual aaudio_result_t updateStateWhileWaiting() = 0;
 
 
     // =========== End ABSTRACT methods ===========================
 
     virtual aaudio_result_t waitForStateChange(aaudio_stream_state_t currentState,
-                                          aaudio_stream_state_t *nextState,
-                                          int64_t timeoutNanoseconds);
+                                               aaudio_stream_state_t *nextState,
+                                               int64_t timeoutNanoseconds);
 
     /**
      * Open the stream using the parameters in the builder.
@@ -152,10 +156,16 @@
         return mDirection;
     }
 
+    /**
+     * This is only valid after setSamplesPerFrame() and setFormat() have been called.
+     */
     int32_t getBytesPerFrame() const {
         return mSamplesPerFrame * getBytesPerSample();
     }
 
+    /**
+     * This is only valid after setFormat() has been called.
+     */
     int32_t getBytesPerSample() const {
         return AAudioConvert_formatToSizeInBytes(mFormat);
     }
@@ -168,6 +178,27 @@
         return mFramesRead.get();
     }
 
+    AAudioStream_dataCallback getDataCallbackProc() const {
+        return mDataCallbackProc;
+    }
+    AAudioStream_errorCallback getErrorCallbackProc() const {
+        return mErrorCallbackProc;
+    }
+
+    void *getDataCallbackUserData() const {
+        return mDataCallbackUserData;
+    }
+    void *getErrorCallbackUserData() const {
+        return mErrorCallbackUserData;
+    }
+
+    int32_t getFramesPerDataCallback() const {
+        return mFramesPerDataCallback;
+    }
+
+    bool isDataCallbackActive() {
+        return (mDataCallbackProc != nullptr) && isPlaying();
+    }
 
     // ============== I/O ===========================
     // A Stream will only implement read() or write() depending on its direction.
@@ -235,6 +266,9 @@
         mState = state;
     }
 
+    std::mutex           mStreamMutex;
+
+    std::atomic<bool>    mCallbackEnabled;
 
 
 protected:
@@ -259,6 +293,15 @@
     aaudio_direction_t     mDirection = AAUDIO_DIRECTION_OUTPUT;
     aaudio_stream_state_t  mState = AAUDIO_STREAM_STATE_UNINITIALIZED;
 
+    // callback ----------------------------------
+
+    AAudioStream_dataCallback   mDataCallbackProc = nullptr;  // external callback functions
+    void                       *mDataCallbackUserData = nullptr;
+    int32_t                     mFramesPerDataCallback = AAUDIO_UNSPECIFIED; // frames
+
+    AAudioStream_errorCallback  mErrorCallbackProc = nullptr;
+    void                       *mErrorCallbackUserData = nullptr;
+
     // background thread ----------------------------------
     bool                   mHasThread = false;
     pthread_t              mThread; // initialized in constructor
diff --git a/media/libaaudio/src/core/AudioStreamBuilder.cpp b/media/libaaudio/src/core/AudioStreamBuilder.cpp
index 5a54e62..858ae80 100644
--- a/media/libaaudio/src/core/AudioStreamBuilder.cpp
+++ b/media/libaaudio/src/core/AudioStreamBuilder.cpp
@@ -44,6 +44,7 @@
 aaudio_result_t AudioStreamBuilder::build(AudioStream** streamPtr) {
     AudioStream* audioStream = nullptr;
     const aaudio_sharing_mode_t sharingMode = getSharingMode();
+    ALOGE("AudioStreamBuilder.build() sharingMode = %d", sharingMode);
     switch (getDirection()) {
     case AAUDIO_DIRECTION_INPUT:
         switch (sharingMode) {
diff --git a/media/libaaudio/src/core/AudioStreamBuilder.h b/media/libaaudio/src/core/AudioStreamBuilder.h
index 7b5f35c..93ca7f5 100644
--- a/media/libaaudio/src/core/AudioStreamBuilder.h
+++ b/media/libaaudio/src/core/AudioStreamBuilder.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef AAUDIO_AUDIOSTREAMBUILDER_H
-#define AAUDIO_AUDIOSTREAMBUILDER_H
+#ifndef AAUDIO_AUDIO_STREAM_BUILDER_H
+#define AAUDIO_AUDIO_STREAM_BUILDER_H
 
 #include <stdint.h>
 
@@ -101,6 +101,52 @@
         return this;
     }
 
+    AAudioStream_dataCallback getDataCallbackProc() const {
+        return mDataCallbackProc;
+    }
+
+    AudioStreamBuilder* setDataCallbackProc(AAudioStream_dataCallback proc) {
+        mDataCallbackProc = proc;
+        return this;
+    }
+
+
+    void *getDataCallbackUserData() const {
+        return mDataCallbackUserData;
+    }
+
+    AudioStreamBuilder* setDataCallbackUserData(void *userData) {
+        mDataCallbackUserData = userData;
+        return this;
+    }
+
+    AAudioStream_errorCallback getErrorCallbackProc() const {
+        return mErrorCallbackProc;
+    }
+
+    AudioStreamBuilder* setErrorCallbackProc(AAudioStream_errorCallback proc) {
+        mErrorCallbackProc = proc;
+        return this;
+    }
+
+    AudioStreamBuilder* setErrorCallbackUserData(void *userData) {
+        mErrorCallbackUserData = userData;
+        return this;
+    }
+
+    void *getErrorCallbackUserData() const {
+        return mErrorCallbackUserData;
+    }
+
+    int32_t getFramesPerDataCallback() const {
+        return mFramesPerDataCallback;
+    }
+
+    AudioStreamBuilder* setFramesPerDataCallback(int32_t sizeInFrames) {
+        mFramesPerDataCallback = sizeInFrames;
+        return this;
+    }
+
     aaudio_result_t build(AudioStream **streamPtr);
 
 private:
@@ -111,8 +157,15 @@
     aaudio_audio_format_t  mFormat = AAUDIO_FORMAT_UNSPECIFIED;
     aaudio_direction_t     mDirection = AAUDIO_DIRECTION_OUTPUT;
     int32_t                mBufferCapacity = AAUDIO_UNSPECIFIED;
+
+    AAudioStream_dataCallback  mDataCallbackProc = nullptr;  // external callback functions
+    void                      *mDataCallbackUserData = nullptr;
+    int32_t                    mFramesPerDataCallback = AAUDIO_UNSPECIFIED; // frames
+
+    AAudioStream_errorCallback mErrorCallbackProc = nullptr;
+    void                      *mErrorCallbackUserData = nullptr;
 };
 
 } /* namespace aaudio */
 
-#endif /* AAUDIO_AUDIOSTREAMBUILDER_H */
+#endif //AAUDIO_AUDIO_STREAM_BUILDER_H
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
new file mode 100644
index 0000000..baa24c9
--- /dev/null
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "AudioStreamLegacy"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <utils/String16.h>
+#include <media/AudioTrack.h>
+#include <aaudio/AAudio.h>
+
+#include "core/AudioStream.h"
+#include "legacy/AudioStreamLegacy.h"
+
+using namespace android;
+using namespace aaudio;
+
+AudioStreamLegacy::AudioStreamLegacy()
+        : AudioStream() {
+}
+
+AudioStreamLegacy::~AudioStreamLegacy() {
+}
+
+// Called from AudioTrack.cpp or AudioRecord.cpp
+static void AudioStreamLegacy_callback(int event, void* userData, void *info) {
+    AudioStreamLegacy *streamLegacy = (AudioStreamLegacy *) userData;
+    streamLegacy->processCallback(event, info);
+}
+
+aaudio_legacy_callback_t AudioStreamLegacy::getLegacyCallback() {
+    return AudioStreamLegacy_callback;
+}
+
+// Implement FixedBlockProcessor
+int32_t AudioStreamLegacy::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
+    int32_t frameCount = numBytes / getBytesPerFrame();
+    // Call using the AAudio callback interface.
+    AAudioStream_dataCallback appCallback = getDataCallbackProc();
+    return (*appCallback)(
+            (AAudioStream *) this,
+            getDataCallbackUserData(),
+            buffer,
+            frameCount);
+}
+
+void AudioStreamLegacy::processCallbackCommon(aaudio_callback_operation_t opcode, void *info) {
+    aaudio_data_callback_result_t callbackResult;
+    switch (opcode) {
+        case AAUDIO_CALLBACK_OPERATION_PROCESS_DATA: {
+            // Note that this code assumes an AudioTrack::Buffer is the same as AudioRecord::Buffer
+            // TODO define our own AudioBuffer and pass it from the subclasses.
+            AudioTrack::Buffer *audioBuffer = static_cast<AudioTrack::Buffer *>(info);
+            if (audioBuffer->frameCount == 0) return;
+
+            // If the caller specified an exact size then use a block size adapter.
+            if (mBlockAdapter != nullptr) {
+                int32_t byteCount = audioBuffer->frameCount * getBytesPerFrame();
+                callbackResult = mBlockAdapter->processVariableBlock((uint8_t *) audioBuffer->raw,
+                                                                     byteCount);
+            } else {
+                // Call using the AAudio callback interface.
+                callbackResult = (*getDataCallbackProc())(
+                        (AAudioStream *) this,
+                        getDataCallbackUserData(),
+                        audioBuffer->raw,
+                        audioBuffer->frameCount
+                        );
+            }
+            if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) {
+                audioBuffer->size = audioBuffer->frameCount * getBytesPerFrame();
+            } else {
+                audioBuffer->size = 0;
+            }
+        }
+            break;
+
+            // Stream got rerouted so we disconnect.
+        case AAUDIO_CALLBACK_OPERATION_DISCONNECTED: {
+            ALOGD("AudioStreamAAudio(): callbackLoop() stream disconnected");
+            if (getErrorCallbackProc() != nullptr) {
+                (*getErrorCallbackProc())(
+                        (AAudioStream *) this,
+                        getErrorCallbackUserData(),
+                        AAUDIO_OK
+                        );
+            }
+            mCallbackEnabled.store(false);
+        }
+            break;
+
+        default:
+            break;
+    }
+}
+
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.h b/media/libaaudio/src/legacy/AudioStreamLegacy.h
new file mode 100644
index 0000000..c109ee7
--- /dev/null
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LEGACY_AUDIO_STREAM_LEGACY_H
+#define LEGACY_AUDIO_STREAM_LEGACY_H
+
+
+#include <aaudio/AAudio.h>
+
+#include "AudioStream.h"
+#include "AAudioLegacy.h"
+#include "utility/FixedBlockAdapter.h"
+
+namespace aaudio {
+
+
+typedef void (*aaudio_legacy_callback_t)(int event, void* user, void *info);
+
+enum {
+    /**
+     * Request that the callback function should fill the data buffer of an output stream,
+     * or process the data of an input stream.
+     * The address parameter passed to the callback function will point to a data buffer.
+     * For an input stream, the data is read-only.
+     * The value1 parameter will be the number of frames.
+     * The value2 parameter is reserved and will be set to zero.
+     * The callback should return AAUDIO_CALLBACK_RESULT_CONTINUE or AAUDIO_CALLBACK_RESULT_STOP.
+     */
+            AAUDIO_CALLBACK_OPERATION_PROCESS_DATA,
+
+    /**
+     * Inform the callback function that the stream was disconnected.
+     * The address parameter passed to the callback function will be NULL.
+     * The value1 will be an error code or AAUDIO_OK.
+     * The value2 parameter is reserved and will be set to zero.
+     * The callback return value will be ignored.
+     */
+            AAUDIO_CALLBACK_OPERATION_DISCONNECTED,
+};
+typedef int32_t aaudio_callback_operation_t;
+
+
+class AudioStreamLegacy : public AudioStream, public FixedBlockProcessor {
+public:
+    AudioStreamLegacy();
+
+    virtual ~AudioStreamLegacy();
+
+    aaudio_legacy_callback_t getLegacyCallback();
+
+    // This is public so it can be called from the C callback function.
+    // This is called from the AudioTrack/AudioRecord client.
+    virtual void processCallback(int event, void *info) = 0;
+
+    void processCallbackCommon(aaudio_callback_operation_t opcode, void *info);
+
+    // Implement FixedBlockProcessor
+    int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override;
+
+protected:
+    FixedBlockAdapter         *mBlockAdapter = nullptr;
+    aaudio_wrapping_frames_t   mPositionWhenStarting = 0;
+    int32_t                    mCallbackBufferSize = 0;
+};
+
+} /* namespace aaudio */
+
+#endif //LEGACY_AUDIO_STREAM_LEGACY_H
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
index d380eb8..f0a6ceb 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -24,14 +24,16 @@
 #include <aaudio/AAudio.h>
 
 #include "AudioClock.h"
-#include "AudioStreamRecord.h"
-#include "utility/AAudioUtilities.h"
+#include "legacy/AudioStreamLegacy.h"
+#include "legacy/AudioStreamRecord.h"
+#include "utility/FixedBlockWriter.h"
 
 using namespace android;
 using namespace aaudio;
 
 AudioStreamRecord::AudioStreamRecord()
-    : AudioStream()
+    : AudioStreamLegacy()
+    , mFixedBlockWriter(*this)
 {
 }
 
@@ -58,7 +60,6 @@
                               ? 2 : getSamplesPerFrame();
     audio_channel_mask_t channelMask = audio_channel_in_mask_from_count(samplesPerFrame);
 
-    AudioRecord::callback_t callback = nullptr;
     audio_input_flags_t flags = (audio_input_flags_t) AUDIO_INPUT_FLAG_NONE;
 
     size_t frameCount = (builder.getBufferCapacity() == AAUDIO_UNSPECIFIED) ? 0
@@ -68,6 +69,17 @@
             ? AUDIO_FORMAT_PCM_FLOAT
             : AAudioConvert_aaudioToAndroidDataFormat(getFormat());
 
+    // Setup the callback if there is one.
+    AudioRecord::callback_t callback = nullptr;
+    void *callbackData = nullptr;
+    AudioRecord::transfer_type streamTransferType = AudioRecord::transfer_type::TRANSFER_SYNC;
+    if (builder.getDataCallbackProc() != nullptr) {
+        streamTransferType = AudioRecord::transfer_type::TRANSFER_CALLBACK;
+        callback = getLegacyCallback();
+        callbackData = this;
+    }
+    mCallbackBufferSize = builder.getFramesPerDataCallback();
+
     mAudioRecord = new AudioRecord(
             AUDIO_SOURCE_DEFAULT,
             getSampleRate(),
@@ -76,10 +88,10 @@
             mOpPackageName, // const String16& opPackageName TODO does not compile
             frameCount,
             callback,
-            nullptr, //    void* user = nullptr,
+            callbackData,
             0,    //    uint32_t notificationFrames = 0,
             AUDIO_SESSION_ALLOCATE,
-            AudioRecord::TRANSFER_DEFAULT,
+            streamTransferType,
             flags
             //   int uid = -1,
             //   pid_t pid = -1,
@@ -99,6 +111,15 @@
     setSamplesPerFrame(mAudioRecord->channelCount());
     setFormat(AAudioConvert_androidToAAudioDataFormat(mAudioRecord->format()));
 
+    // We may need to pass the data through a block size adapter to guarantee constant size.
+    if (mCallbackBufferSize != AAUDIO_UNSPECIFIED) {
+        int callbackSizeBytes = getBytesPerFrame() * mCallbackBufferSize;
+        mFixedBlockWriter.open(callbackSizeBytes);
+        mBlockAdapter = &mFixedBlockWriter;
+    } else {
+        mBlockAdapter = nullptr;
+    }
+
     setState(AAUDIO_STREAM_STATE_OPEN);
 
     return AAUDIO_OK;
@@ -111,9 +132,29 @@
         mAudioRecord.clear();
         setState(AAUDIO_STREAM_STATE_CLOSED);
     }
+    mFixedBlockWriter.close();
     return AAUDIO_OK;
 }
 
+void AudioStreamRecord::processCallback(int event, void *info) {
+
+    ALOGD("AudioStreamRecord::processCallback(), event %d", event);
+    switch (event) {
+        case AudioRecord::EVENT_MORE_DATA:
+            processCallbackCommon(AAUDIO_CALLBACK_OPERATION_PROCESS_DATA, info);
+            break;
+
+            // Stream got rerouted so we disconnect.
+        case AudioRecord::EVENT_NEW_IAUDIORECORD:
+            processCallbackCommon(AAUDIO_CALLBACK_OPERATION_DISCONNECTED, info);
+            break;
+
+        default:
+            break;
+    }
+    return;
+}
+
 aaudio_result_t AudioStreamRecord::requestStart()
 {
     if (mAudioRecord.get() == nullptr) {
@@ -124,6 +165,7 @@
     if (err != OK) {
         return AAudioConvert_androidToAAudioResult(err);
     }
+
     err = mAudioRecord->start();
     if (err != OK) {
         return AAudioConvert_androidToAAudioResult(err);
@@ -151,7 +193,7 @@
     return AAUDIO_OK;
 }
 
-aaudio_result_t AudioStreamRecord::updateState()
+aaudio_result_t AudioStreamRecord::updateStateWhileWaiting()
 {
     aaudio_result_t result = AAUDIO_OK;
     aaudio_wrapping_frames_t position;
@@ -222,7 +264,7 @@
 
 int32_t AudioStreamRecord::getFramesPerBurst() const
 {
-    return 192; // TODO add query to AudioRecord.cpp
+    return static_cast<int32_t>(mAudioRecord->getNotificationPeriodInFrames());
 }
 
 aaudio_result_t AudioStreamRecord::getTimestamp(clockid_t clockId,
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.h b/media/libaaudio/src/legacy/AudioStreamRecord.h
index 4667f05..897a5b3 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.h
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.h
@@ -23,51 +23,58 @@
 #include "AudioStreamBuilder.h"
 #include "AudioStream.h"
 #include "AAudioLegacy.h"
+#include "legacy/AudioStreamLegacy.h"
+#include "utility/FixedBlockWriter.h"
 
 namespace aaudio {
 
 /**
  * Internal stream that uses the legacy AudioTrack path.
  */
-class AudioStreamRecord : public AudioStream {
+class AudioStreamRecord : public AudioStreamLegacy {
 public:
     AudioStreamRecord();
 
     virtual ~AudioStreamRecord();
 
-    virtual aaudio_result_t open(const AudioStreamBuilder & builder) override;
-    virtual aaudio_result_t close() override;
+    aaudio_result_t open(const AudioStreamBuilder & builder) override;
+    aaudio_result_t close() override;
 
-    virtual aaudio_result_t requestStart() override;
-    virtual aaudio_result_t requestPause() override;
-    virtual aaudio_result_t requestFlush() override;
-    virtual aaudio_result_t requestStop() override;
+    aaudio_result_t requestStart() override;
+    aaudio_result_t requestPause() override;
+    aaudio_result_t requestFlush() override;
+    aaudio_result_t requestStop() override;
 
     virtual aaudio_result_t getTimestamp(clockid_t clockId,
                                          int64_t *framePosition,
                                          int64_t *timeNanoseconds) override;
 
-    virtual aaudio_result_t read(void *buffer,
+    aaudio_result_t read(void *buffer,
                              int32_t numFrames,
                              int64_t timeoutNanoseconds) override;
 
-    virtual aaudio_result_t setBufferSize(int32_t requestedFrames) override;
+    aaudio_result_t setBufferSize(int32_t requestedFrames) override;
 
-    virtual int32_t getBufferSize() const override;
+    int32_t getBufferSize() const override;
 
-    virtual int32_t getBufferCapacity() const override;
+    int32_t getBufferCapacity() const override;
 
-    virtual int32_t getXRunCount() const override;
+    int32_t getXRunCount() const override;
 
-    virtual int32_t getFramesPerBurst() const override;
+    int32_t getFramesPerBurst() const override;
 
-    virtual aaudio_result_t updateState() override;
+    aaudio_result_t updateStateWhileWaiting() override;
+
+    // This is public so it can be called from the C callback function.
+    void processCallback(int event, void *info) override;
 
 private:
     android::sp<android::AudioRecord> mAudioRecord;
+    // adapts between variable sized blocks and fixed size blocks
+    FixedBlockWriter                 mFixedBlockWriter;
+
     // TODO add 64-bit position reporting to AudioRecord and use it.
-    aaudio_wrapping_frames_t   mPositionWhenStarting = 0;
-    android::String16          mOpPackageName;
+    android::String16                mOpPackageName;
 };
 
 } /* namespace aaudio */
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 8bb6aee..ff87c28 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -20,20 +20,25 @@
 
 #include <stdint.h>
 #include <media/AudioTrack.h>
-#include <aaudio/AAudio.h>
 
-#include "utility/AudioClock.h"
-#include "AudioStreamTrack.h"
-#include "utility/AAudioUtilities.h"
+#include <aaudio/AAudio.h>
+#include "AudioClock.h"
+#include "legacy/AudioStreamLegacy.h"
+#include "legacy/AudioStreamTrack.h"
+#include "utility/FixedBlockReader.h"
 
 using namespace android;
 using namespace aaudio;
 
+// Arbitrary and somewhat generous number of bursts.
+#define DEFAULT_BURSTS_PER_BUFFER_CAPACITY     8
+
 /*
  * Create a stream that uses the AudioTrack.
  */
 AudioStreamTrack::AudioStreamTrack()
-    : AudioStream()
+    : AudioStreamLegacy()
+    , mFixedBlockReader(*this)
 {
 }
 
@@ -53,6 +58,8 @@
         return result;
     }
 
+    ALOGD("AudioStreamTrack::open = %p", this);
+
     // Try to create an AudioTrack
     // TODO Support UNSPECIFIED in AudioTrack. For now, use stereo if unspecified.
     int32_t samplesPerFrame = (getSamplesPerFrame() == AAUDIO_UNSPECIFIED)
@@ -61,16 +68,40 @@
     ALOGD("AudioStreamTrack::open(), samplesPerFrame = %d, channelMask = 0x%08x",
             samplesPerFrame, channelMask);
 
-    AudioTrack::callback_t callback = nullptr;
     // TODO add more performance options
     audio_output_flags_t flags = (audio_output_flags_t) AUDIO_OUTPUT_FLAG_FAST;
-    size_t frameCount = (builder.getBufferCapacity() == AAUDIO_UNSPECIFIED) ? 0
-                        : builder.getBufferCapacity();
+
+    int32_t frameCount = builder.getBufferCapacity();
+    ALOGD("AudioStreamTrack::open(), requested buffer capacity %d", frameCount);
+
+    int32_t notificationFrames = 0;
+
     // TODO implement an unspecified AudioTrack format then use that.
-    audio_format_t format = (getFormat() == AAUDIO_UNSPECIFIED)
+    audio_format_t format = (getFormat() == AAUDIO_FORMAT_UNSPECIFIED)
             ? AUDIO_FORMAT_PCM_FLOAT
             : AAudioConvert_aaudioToAndroidDataFormat(getFormat());
 
+    // Setup the callback if there is one.
+    AudioTrack::callback_t callback = nullptr;
+    void *callbackData = nullptr;
+    // Note that TRANSFER_SYNC does not allow FAST track
+    AudioTrack::transfer_type streamTransferType = AudioTrack::transfer_type::TRANSFER_SYNC;
+    if (builder.getDataCallbackProc() != nullptr) {
+        streamTransferType = AudioTrack::transfer_type::TRANSFER_CALLBACK;
+        callback = getLegacyCallback();
+        callbackData = this;
+
+        notificationFrames = builder.getFramesPerDataCallback();
+        // If the total buffer size is unspecified then base the size on the burst size.
+        if (frameCount == AAUDIO_UNSPECIFIED) {
+            // Take advantage of a special trick that allows us to create a buffer
+            // that is some multiple of the burst size.
+            notificationFrames = 0 - DEFAULT_BURSTS_PER_BUFFER_CAPACITY;
+        }
+    }
+    mCallbackBufferSize = builder.getFramesPerDataCallback();
+
+    ALOGD("AudioStreamTrack::open(), notificationFrames = %d", notificationFrames);
     mAudioTrack = new AudioTrack(
             (audio_stream_type_t) AUDIO_STREAM_MUSIC,
             getSampleRate(),
@@ -79,10 +110,10 @@
             frameCount,
             flags,
             callback,
-            nullptr,    // user callback data
-            0,          // notificationFrames
+            callbackData,
+            notificationFrames,
             AUDIO_SESSION_ALLOCATE,
-            AudioTrack::transfer_type::TRANSFER_SYNC // TODO - this does not allow FAST
+            streamTransferType
             );
 
     // Did we get a valid track?
@@ -97,7 +128,18 @@
     // Get the actual values from the AudioTrack.
     setSamplesPerFrame(mAudioTrack->channelCount());
     setSampleRate(mAudioTrack->getSampleRate());
-    setFormat(AAudioConvert_androidToAAudioDataFormat(mAudioTrack->format()));
+    aaudio_audio_format_t aaudioFormat =
+            AAudioConvert_androidToAAudioDataFormat(mAudioTrack->format());
+    setFormat(aaudioFormat);
+
+    // We may need to pass the data through a block size adapter to guarantee constant size.
+    if (mCallbackBufferSize != AAUDIO_UNSPECIFIED) {
+        int callbackSizeBytes = getBytesPerFrame() * mCallbackBufferSize;
+        mFixedBlockReader.open(callbackSizeBytes);
+        mBlockAdapter = &mFixedBlockReader;
+    } else {
+        mBlockAdapter = nullptr;
+    }
 
     setState(AAUDIO_STREAM_STATE_OPEN);
 
@@ -111,11 +153,32 @@
         mAudioTrack.clear(); // TODO is this right?
         setState(AAUDIO_STREAM_STATE_CLOSED);
     }
+    mFixedBlockReader.close();
     return AAUDIO_OK;
 }
 
+void AudioStreamTrack::processCallback(int event, void *info) {
+
+    switch (event) {
+        case AudioTrack::EVENT_MORE_DATA:
+            processCallbackCommon(AAUDIO_CALLBACK_OPERATION_PROCESS_DATA, info);
+            break;
+
+            // Stream got rerouted so we disconnect.
+        case AudioTrack::EVENT_NEW_IAUDIOTRACK:
+            processCallbackCommon(AAUDIO_CALLBACK_OPERATION_DISCONNECTED, info);
+            break;
+
+        default:
+            break;
+    }
+    return;
+}
+
 aaudio_result_t AudioStreamTrack::requestStart()
 {
+    std::lock_guard<std::mutex> lock(mStreamMutex);
+
     if (mAudioTrack.get() == nullptr) {
         return AAUDIO_ERROR_INVALID_STATE;
     }
@@ -124,6 +187,7 @@
     if (err != OK) {
         return AAudioConvert_androidToAAudioResult(err);
     }
+
     err = mAudioTrack->start();
     if (err != OK) {
         return AAudioConvert_androidToAAudioResult(err);
@@ -135,11 +199,14 @@
 
 aaudio_result_t AudioStreamTrack::requestPause()
 {
+    std::lock_guard<std::mutex> lock(mStreamMutex);
+
     if (mAudioTrack.get() == nullptr) {
         return AAUDIO_ERROR_INVALID_STATE;
     } else if (getState() != AAUDIO_STREAM_STATE_STARTING
             && getState() != AAUDIO_STREAM_STATE_STARTED) {
-        ALOGE("requestPause(), called when state is %s", AAudio_convertStreamStateToText(getState()));
+        ALOGE("requestPause(), called when state is %s",
+              AAudio_convertStreamStateToText(getState()));
         return AAUDIO_ERROR_INVALID_STATE;
     }
     setState(AAUDIO_STREAM_STATE_PAUSING);
@@ -152,6 +219,8 @@
 }
 
 aaudio_result_t AudioStreamTrack::requestFlush() {
+    std::lock_guard<std::mutex> lock(mStreamMutex);
+
     if (mAudioTrack.get() == nullptr) {
         return AAUDIO_ERROR_INVALID_STATE;
     } else if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
@@ -165,6 +234,8 @@
 }
 
 aaudio_result_t AudioStreamTrack::requestStop() {
+    std::lock_guard<std::mutex> lock(mStreamMutex);
+
     if (mAudioTrack.get() == nullptr) {
         return AAUDIO_ERROR_INVALID_STATE;
     }
@@ -175,7 +246,7 @@
     return AAUDIO_OK;
 }
 
-aaudio_result_t AudioStreamTrack::updateState()
+aaudio_result_t AudioStreamTrack::updateStateWhileWaiting()
 {
     status_t err;
     aaudio_wrapping_frames_t position;
@@ -272,7 +343,7 @@
 
 int32_t AudioStreamTrack::getFramesPerBurst() const
 {
-    return 192; // TODO add query to AudioTrack.cpp
+    return static_cast<int32_t>(mAudioTrack->getNotificationPeriodInFrames());
 }
 
 int64_t AudioStreamTrack::getFramesRead() {
@@ -303,7 +374,7 @@
     }
     // TODO Merge common code into AudioStreamLegacy after rebasing.
     int timebase;
-    switch(clockId) {
+    switch (clockId) {
         case CLOCK_BOOTTIME:
             timebase = ExtendedTimestamp::TIMEBASE_BOOTTIME;
             break;
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.h b/media/libaaudio/src/legacy/AudioStreamTrack.h
index 7a53022..29f5d15 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.h
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.h
@@ -17,54 +17,63 @@
 #ifndef LEGACY_AUDIO_STREAM_TRACK_H
 #define LEGACY_AUDIO_STREAM_TRACK_H
 
+#include <math.h>
 #include <media/AudioTrack.h>
 #include <aaudio/AAudio.h>
 
 #include "AudioStreamBuilder.h"
 #include "AudioStream.h"
-#include "AAudioLegacy.h"
+#include "legacy/AAudioLegacy.h"
+#include "legacy/AudioStreamLegacy.h"
+#include "utility/FixedBlockReader.h"
 
 namespace aaudio {
 
-
 /**
  * Internal stream that uses the legacy AudioTrack path.
  */
-class AudioStreamTrack : public AudioStream {
+class AudioStreamTrack : public AudioStreamLegacy {
 public:
     AudioStreamTrack();
 
     virtual ~AudioStreamTrack();
 
 
-    virtual aaudio_result_t open(const AudioStreamBuilder & builder) override;
-    virtual aaudio_result_t close() override;
+    aaudio_result_t open(const AudioStreamBuilder & builder) override;
+    aaudio_result_t close() override;
 
-    virtual aaudio_result_t requestStart() override;
-    virtual aaudio_result_t requestPause() override;
-    virtual aaudio_result_t requestFlush() override;
-    virtual aaudio_result_t requestStop() override;
+    aaudio_result_t requestStart() override;
+    aaudio_result_t requestPause() override;
+    aaudio_result_t requestFlush() override;
+    aaudio_result_t requestStop() override;
 
-    virtual aaudio_result_t getTimestamp(clockid_t clockId,
+    aaudio_result_t getTimestamp(clockid_t clockId,
                                        int64_t *framePosition,
                                        int64_t *timeNanoseconds) override;
 
-    virtual aaudio_result_t write(const void *buffer,
+    aaudio_result_t write(const void *buffer,
                              int32_t numFrames,
                              int64_t timeoutNanoseconds) override;
 
-    virtual aaudio_result_t setBufferSize(int32_t requestedFrames) override;
-    virtual int32_t getBufferSize() const override;
-    virtual int32_t getBufferCapacity() const override;
-    virtual int32_t getFramesPerBurst()const  override;
-    virtual int32_t getXRunCount() const override;
+    aaudio_result_t setBufferSize(int32_t requestedFrames) override;
+    int32_t getBufferSize() const override;
+    int32_t getBufferCapacity() const override;
+    int32_t getFramesPerBurst()const  override;
+    int32_t getXRunCount() const override;
 
-    virtual int64_t getFramesRead() override;
+    int64_t getFramesRead() override;
 
-    virtual aaudio_result_t updateState() override;
+    aaudio_result_t updateStateWhileWaiting() override;
+
+    // This is public so it can be called from the C callback function.
+    void processCallback(int event, void *info) override;
 
 private:
+
     android::sp<android::AudioTrack> mAudioTrack;
+    // adapts between variable sized blocks and fixed size blocks
+    FixedBlockReader                 mFixedBlockReader;
+
     // TODO add 64-bit position reporting to AudioRecord and use it.
     aaudio_wrapping_frames_t         mPositionWhenStarting = 0;
     aaudio_wrapping_frames_t         mPositionWhenPausing = 0;
diff --git a/media/libaaudio/src/utility/FixedBlockAdapter.cpp b/media/libaaudio/src/utility/FixedBlockAdapter.cpp
new file mode 100644
index 0000000..f4666af
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockAdapter.cpp
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#include <stdint.h>
+
+#include "FixedBlockAdapter.h"
+
+FixedBlockAdapter::~FixedBlockAdapter() {
+    close();
+}
+
+int32_t FixedBlockAdapter::open(int32_t bytesPerFixedBlock)
+{
+    mSize = bytesPerFixedBlock;
+    mStorage = new uint8_t[bytesPerFixedBlock]; // TODO use std::nothrow
+    mPosition = 0;
+    return 0;
+}
+
+int32_t FixedBlockAdapter::close()
+{
+    delete[] mStorage;
+    mStorage = nullptr;
+    mSize = 0;
+    mPosition = 0;
+    return 0;
+}
diff --git a/media/libaaudio/src/utility/FixedBlockAdapter.h b/media/libaaudio/src/utility/FixedBlockAdapter.h
new file mode 100644
index 0000000..7008b25
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockAdapter.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAUDIO_FIXED_BLOCK_ADAPTER_H
+#define AAUDIO_FIXED_BLOCK_ADAPTER_H
+
+#include <stdio.h>
+
+/**
+ * Interface for a class that needs fixed-size blocks.
+ */
+class FixedBlockProcessor {
+public:
+    virtual ~FixedBlockProcessor() = default;
+    virtual int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) = 0;
+};
+
+/**
+ * Base class for a variable-to-fixed-size block adapter.
+ */
+class FixedBlockAdapter
+{
+public:
+    FixedBlockAdapter(FixedBlockProcessor &fixedBlockProcessor)
+    : mFixedBlockProcessor(fixedBlockProcessor) {}
+
+    virtual ~FixedBlockAdapter();
+
+    /**
+     * Allocate internal resources needed for buffering data.
+     */
+    virtual int32_t open(int32_t bytesPerFixedBlock);
+
+    /**
+     * Note that if the fixed-sized blocks must be aligned, then the variable-sized blocks
+     * must have the same alignment.
+     * For example, if the fixed-size blocks must be a multiple of 8, then the variable-sized
+     * blocks must also be a multiple of 8.
+     *
+     * @param buffer
+     * @param numBytes
+     * @return zero if OK or a non-zero code
+     */
+    virtual int32_t processVariableBlock(uint8_t *buffer, int32_t numBytes) = 0;
+
+    /**
+     * Free internal resources.
+     */
+    int32_t close();
+
+protected:
+    FixedBlockProcessor  &mFixedBlockProcessor;
+    uint8_t              *mStorage = nullptr;    // Store data here while assembling buffers.
+    int32_t               mSize = 0;             // Size in bytes of the fixed size buffer.
+    int32_t               mPosition = 0;         // Offset of the last byte read or written.
+};
+
+#endif /* AAUDIO_FIXED_BLOCK_ADAPTER_H */
diff --git a/media/libaaudio/src/utility/FixedBlockReader.cpp b/media/libaaudio/src/utility/FixedBlockReader.cpp
new file mode 100644
index 0000000..21ea70e
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockReader.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#include <stdint.h>
+#include <memory.h>
+
+#include "FixedBlockAdapter.h"
+
+#include "FixedBlockReader.h"
+
+
+FixedBlockReader::FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor)
+    : FixedBlockAdapter(fixedBlockProcessor) {
+    mPosition = mSize;
+}
+
+int32_t FixedBlockReader::open(int32_t bytesPerFixedBlock) {
+    int32_t result = FixedBlockAdapter::open(bytesPerFixedBlock);
+    mPosition = mSize; // Indicate no data in storage.
+    return result;
+}
+
+int32_t FixedBlockReader::readFromStorage(uint8_t *buffer, int32_t numBytes) {
+    int32_t bytesToRead = numBytes;
+    int32_t dataAvailable = mSize - mPosition;
+    if (bytesToRead > dataAvailable) {
+        bytesToRead = dataAvailable;
+    }
+    memcpy(buffer, mStorage + mPosition, bytesToRead);
+    mPosition += bytesToRead;
+    return bytesToRead;
+}
+
+int32_t FixedBlockReader::processVariableBlock(uint8_t *buffer, int32_t numBytes) {
+    int32_t result = 0;
+    int32_t bytesLeft = numBytes;
+    while(bytesLeft > 0 && result == 0) {
+        if (mPosition < mSize) {
+            // Use up bytes currently in storage.
+            int32_t bytesRead = readFromStorage(buffer, bytesLeft);
+            buffer += bytesRead;
+            bytesLeft -= bytesRead;
+        } else if (bytesLeft >= mSize) {
+            // Read through if enough for a complete block.
+            result = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize);
+            buffer += mSize;
+            bytesLeft -= mSize;
+        } else {
+            // Just need a partial block so we have to use storage.
+            result = mFixedBlockProcessor.onProcessFixedBlock(mStorage, mSize);
+            mPosition = 0;
+        }
+    }
+    return result;
+}
+
diff --git a/media/libaaudio/src/utility/FixedBlockReader.h b/media/libaaudio/src/utility/FixedBlockReader.h
new file mode 100644
index 0000000..128dd52
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockReader.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAUDIO_FIXED_BLOCK_READER_H
+#define AAUDIO_FIXED_BLOCK_READER_H
+
+#include <stdint.h>
+
+#include "FixedBlockAdapter.h"
+
+/**
+ * Read from a fixed-size block to a variable sized block.
+ *
+ * This can be used to convert a pull data flow from fixed sized buffers to variable sized buffers.
+ * An example would be an audio output callback that reads from the app.
+ */
+class FixedBlockReader : public FixedBlockAdapter
+{
+public:
+    FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor);
+
+    virtual ~FixedBlockReader() = default;
+
+    int32_t open(int32_t bytesPerFixedBlock) override;
+
+    int32_t readFromStorage(uint8_t *buffer, int32_t numBytes);
+
+    /**
+     * Read into a variable sized block.
+     */
+    int32_t processVariableBlock(uint8_t *buffer, int32_t numBytes) override;
+};
+
+
+#endif /* AAUDIO_FIXED_BLOCK_READER_H */
diff --git a/media/libaaudio/src/utility/FixedBlockWriter.cpp b/media/libaaudio/src/utility/FixedBlockWriter.cpp
new file mode 100644
index 0000000..2ce8046
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockWriter.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#include <stdint.h>
+#include <memory.h>
+
+#include "FixedBlockAdapter.h"
+#include "FixedBlockWriter.h"
+
+FixedBlockWriter::FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor)
+        : FixedBlockAdapter(fixedBlockProcessor) {}
+
+
+int32_t FixedBlockWriter::writeToStorage(uint8_t *buffer, int32_t numBytes) {
+    int32_t bytesToStore = numBytes;
+    int32_t roomAvailable = mSize - mPosition;
+    if (bytesToStore > roomAvailable) {
+        bytesToStore = roomAvailable;
+    }
+    memcpy(mStorage + mPosition, buffer, bytesToStore);
+    mPosition += bytesToStore;
+    return bytesToStore;
+}
+
+int32_t FixedBlockWriter::processVariableBlock(uint8_t *buffer, int32_t numBytes) {
+    int32_t result = 0;
+    int32_t bytesLeft = numBytes;
+
+    // If we already have data in storage then add to it.
+    if (mPosition > 0) {
+        int32_t bytesWritten = writeToStorage(buffer, bytesLeft);
+        buffer += bytesWritten;
+        bytesLeft -= bytesWritten;
+        // If storage full then flush it out
+        if (mPosition == mSize) {
+            result = mFixedBlockProcessor.onProcessFixedBlock(mStorage, mSize);
+            mPosition = 0;
+        }
+    }
+
+    // Write through if enough for a complete block.
+    while(bytesLeft > mSize && result == 0) {
+        result = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize);
+        buffer += mSize;
+        bytesLeft -= mSize;
+    }
+
+    // Save any remaining partial block for next time.
+    if (bytesLeft > 0) {
+        writeToStorage(buffer, bytesLeft);
+    }
+
+    return result;
+}
diff --git a/media/libaaudio/src/utility/FixedBlockWriter.h b/media/libaaudio/src/utility/FixedBlockWriter.h
new file mode 100644
index 0000000..f1d917c
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockWriter.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAUDIO_FIXED_BLOCK_WRITER_H
+#define AAUDIO_FIXED_BLOCK_WRITER_H
+
+#include <stdint.h>
+
+#include "FixedBlockAdapter.h"
+
+/**
+ * This can be used to convert a push data flow from variable sized buffers to fixed sized buffers.
+ * An example would be an audio input callback.
+ */
+class FixedBlockWriter : public FixedBlockAdapter
+{
+public:
+    FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor);
+
+    virtual ~FixedBlockWriter() = default;
+
+    int32_t writeToStorage(uint8_t *buffer, int32_t numBytes);
+
+    /**
+     * Write from a variable sized block.
+     */
+    int32_t processVariableBlock(uint8_t *buffer, int32_t numBytes) override;
+};
+
+#endif /* AAUDIO_FIXED_BLOCK_WRITER_H */
diff --git a/media/libaaudio/tests/Android.mk b/media/libaaudio/tests/Android.mk
index 7899cf5..06c9364 100644
--- a/media/libaaudio/tests/Android.mk
+++ b/media/libaaudio/tests/Android.mk
@@ -4,8 +4,7 @@
 LOCAL_C_INCLUDES := \
     $(call include-path-for, audio-utils) \
     frameworks/av/media/libaaudio/include \
-    frameworks/av/media/libaaudio/src/core \
-    frameworks/av/media/libaaudio/src/utility
+    frameworks/av/media/libaaudio/src
 LOCAL_SRC_FILES:= test_handle_tracker.cpp
 LOCAL_SHARED_LIBRARIES := libaudioclient libaudioutils libbinder \
                           libcutils liblog libmedia libutils
@@ -17,13 +16,22 @@
 LOCAL_C_INCLUDES := \
     $(call include-path-for, audio-utils) \
     frameworks/av/media/libaaudio/include \
-    frameworks/av/media/libaaudio/src \
-    frameworks/av/media/libaaudio/src/core \
-    frameworks/av/media/libaaudio/src/fifo \
-    frameworks/av/media/libaaudio/src/utility
+    frameworks/av/media/libaaudio/src
 LOCAL_SRC_FILES:= test_marshalling.cpp
 LOCAL_SHARED_LIBRARIES := libaudioclient libaudioutils libbinder \
                           libcutils liblog libmedia libutils
 LOCAL_STATIC_LIBRARIES := libaaudio
-LOCAL_MODULE := test_marshalling
+LOCAL_MODULE := test_aaudio_marshalling
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/libaaudio/include \
+    frameworks/av/media/libaaudio/src
+LOCAL_SRC_FILES:= test_block_adapter.cpp
+LOCAL_SHARED_LIBRARIES := libaudioclient libaudioutils libbinder \
+                          libcutils liblog libmedia libutils
+LOCAL_STATIC_LIBRARIES := libaaudio
+LOCAL_MODULE := test_block_adapter
 include $(BUILD_NATIVE_TEST)
diff --git a/media/libaaudio/tests/test_block_adapter.cpp b/media/libaaudio/tests/test_block_adapter.cpp
new file mode 100644
index 0000000..a22abb9
--- /dev/null
+++ b/media/libaaudio/tests/test_block_adapter.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include "utility/FixedBlockAdapter.h"
+#include "utility/FixedBlockWriter.h"
+#include "utility/FixedBlockReader.h"
+
+#define FIXED_BLOCK_SIZE   47
+#define TEST_BUFFER_SIZE   103
+
+// Pass varying sized blocks.
+// Frames contain a sequential index, which are easily checked.
+class TestBlockAdapter {
+public:
+    TestBlockAdapter()
+            : mTestIndex(0), mLastIndex(0) {
+    }
+
+    ~TestBlockAdapter() = default;
+
+    void fillSequence(int32_t *indexBuffer, int32_t frameCount) {
+        ASSERT_LE(frameCount, TEST_BUFFER_SIZE);
+        for (int i = 0; i < frameCount; i++) {
+            indexBuffer[i] = mLastIndex++;
+        }
+    }
+
+    int checkSequence(const int32_t *indexBuffer, int32_t frameCount) {
+        // This is equivalent to calling an output callback.
+        for (int i = 0; i < frameCount; i++) {
+            int32_t expected = mTestIndex++;
+            int32_t actual = indexBuffer[i];
+            EXPECT_EQ(expected, actual);
+            if (actual != expected) {
+                return -1;
+            }
+        }
+        return 0;
+    }
+
+    int32_t            mTestBuffer[TEST_BUFFER_SIZE];
+    int32_t            mTestIndex;
+    int32_t            mLastIndex;
+};
+
+class TestBlockWriter : public TestBlockAdapter, FixedBlockProcessor {
+public:
+    TestBlockWriter()
+            : mFixedBlockWriter(*this) {
+        mFixedBlockWriter.open(sizeof(int32_t) * FIXED_BLOCK_SIZE);
+    }
+
+    ~TestBlockWriter() {
+        mFixedBlockWriter.close();
+    }
+
+    int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override {
+        int32_t frameCount = numBytes / sizeof(int32_t);
+        return checkSequence((int32_t *) buffer, frameCount);
+    }
+
+    // Simulate audio input from a variable sized callback.
+    int32_t testInputWrite(int32_t variableCount) {
+        fillSequence(mTestBuffer, variableCount);
+        int32_t sizeBytes = variableCount * sizeof(int32_t);
+        return mFixedBlockWriter.processVariableBlock((uint8_t *) mTestBuffer, sizeBytes);
+    }
+
+private:
+    FixedBlockWriter mFixedBlockWriter;
+};
+
+class TestBlockReader : public TestBlockAdapter, FixedBlockProcessor {
+public:
+    TestBlockReader()
+            : mFixedBlockReader(*this) {
+        mFixedBlockReader.open(sizeof(int32_t) * FIXED_BLOCK_SIZE);
+    }
+
+    ~TestBlockReader() {
+        mFixedBlockReader.close();
+    }
+
+    int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override {
+        int32_t frameCount = numBytes / sizeof(int32_t);
+        fillSequence((int32_t *) buffer, frameCount);
+        return 0;
+    }
+
+    // Simulate audio output from a variable sized callback.
+    int32_t testOutputRead(int32_t variableCount) {
+        int32_t sizeBytes = variableCount * sizeof(int32_t);
+        int32_t result = mFixedBlockReader.processVariableBlock((uint8_t *) mTestBuffer, sizeBytes);
+        if (result >= 0) {
+            result = checkSequence((int32_t *)mTestBuffer, variableCount);
+        }
+        return result;
+    }
+
+private:
+    FixedBlockReader   mFixedBlockReader;
+};
+
+
+TEST(test_block_adapter, block_adapter_write) {
+    TestBlockWriter tester;
+    int result = 0;
+    const int numLoops = 1000;
+
+    for (int i = 0; i<numLoops && result == 0; i++) {
+        long r = random();
+        int32_t size = (r % TEST_BUFFER_SIZE);
+        ASSERT_LE(size, TEST_BUFFER_SIZE);
+        ASSERT_GE(size, 0);
+        result = tester.testInputWrite(size);
+    }
+    ASSERT_EQ(0, result);
+}
+
+TEST(test_block_adapter, block_adapter_read) {
+    TestBlockReader tester;
+    int result = 0;
+    const int numLoops = 1000;
+
+    for (int i = 0; i < numLoops && result == 0; i++) {
+        long r = random();
+        int32_t size = (r % TEST_BUFFER_SIZE);
+        ASSERT_LE(size, TEST_BUFFER_SIZE);
+        ASSERT_GE(size, 0);
+        result = tester.testOutputRead(size);
+    }
+    ASSERT_EQ(0, result);
+};
+
diff --git a/media/libaaudio/tests/test_handle_tracker.cpp b/media/libaaudio/tests/test_handle_tracker.cpp
index e51c39c..e1cb676 100644
--- a/media/libaaudio/tests/test_handle_tracker.cpp
+++ b/media/libaaudio/tests/test_handle_tracker.cpp
@@ -22,7 +22,7 @@
 #include <gtest/gtest.h>
 
 #include <aaudio/AAudioDefinitions.h>
-#include "HandleTracker.h"
+#include "utility/HandleTracker.h"
 
 // Test adding one address.
 TEST(test_handle_tracker, aaudio_handle_tracker) {
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp
index 6c7cdde..5c54bb2 100644
--- a/media/libaudioclient/AudioRecord.cpp
+++ b/media/libaudioclient/AudioRecord.cpp
@@ -645,10 +645,10 @@
     mAwaitBoost = false;
     if (mFlags & AUDIO_INPUT_FLAG_FAST) {
         if (flags & AUDIO_INPUT_FLAG_FAST) {
-            ALOGI("AUDIO_INPUT_FLAG_FAST successful; frameCount %zu", frameCount);
+            ALOGI("AUDIO_INPUT_FLAG_FAST successful; frameCount %zu -> %zu", frameCount, temp);
             mAwaitBoost = true;
         } else {
-            ALOGW("AUDIO_INPUT_FLAG_FAST denied by server; frameCount %zu", frameCount);
+            ALOGW("AUDIO_INPUT_FLAG_FAST denied by server; frameCount %zu -> %zu", frameCount, temp);
             mFlags = (audio_input_flags_t) (mFlags & ~(AUDIO_INPUT_FLAG_FAST |
                     AUDIO_INPUT_FLAG_RAW));
             continue;   // retry
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index d590cb7..3a0ce5e 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -1479,12 +1479,13 @@
     mAwaitBoost = false;
     if (mFlags & AUDIO_OUTPUT_FLAG_FAST) {
         if (flags & AUDIO_OUTPUT_FLAG_FAST) {
-            ALOGV("AUDIO_OUTPUT_FLAG_FAST successful; frameCount %zu", frameCount);
+            ALOGI("AUDIO_OUTPUT_FLAG_FAST successful; frameCount %zu -> %zu", frameCount, temp);
             if (!mThreadCanCallJava) {
                 mAwaitBoost = true;
             }
         } else {
-            ALOGW("AUDIO_OUTPUT_FLAG_FAST denied by server; frameCount %zu", frameCount);
+            ALOGW("AUDIO_OUTPUT_FLAG_FAST denied by server; frameCount %zu -> %zu", frameCount,
+                    temp);
         }
     }
     mFlags = flags;
diff --git a/media/libaudioclient/AudioTrackShared.cpp b/media/libaudioclient/AudioTrackShared.cpp
index 846f8b8..2ce6c63 100644
--- a/media/libaudioclient/AudioTrackShared.cpp
+++ b/media/libaudioclient/AudioTrackShared.cpp
@@ -696,7 +696,8 @@
     ssize_t filled = rear - front;
     // pipe should not already be overfull
     if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
-        ALOGE("Shared memory control block is corrupt (filled=%zd); shutting down", filled);
+        ALOGE("Shared memory control block is corrupt (filled=%zd, mFrameCount=%zu); shutting down",
+                filled, mFrameCount);
         mIsShutdown = true;
     }
     if (mIsShutdown) {
@@ -820,7 +821,8 @@
     ssize_t filled = rear - cblk->u.mStreaming.mFront;
     // pipe should not already be overfull
     if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
-        ALOGE("Shared memory control block is corrupt (filled=%zd); shutting down", filled);
+        ALOGE("Shared memory control block is corrupt (filled=%zd, mFrameCount=%zu); shutting down",
+                filled, mFrameCount);
         mIsShutdown = true;
         return 0;
     }
diff --git a/media/libaudioclient/include/AudioRecord.h b/media/libaudioclient/include/AudioRecord.h
index 1c8746f..1b034b5 100644
--- a/media/libaudioclient/include/AudioRecord.h
+++ b/media/libaudioclient/include/AudioRecord.h
@@ -243,6 +243,13 @@
             size_t      frameSize() const   { return mFrameSize; }
             audio_source_t inputSource() const  { return mAttributes.source; }
 
+    /*
+     * Return the period of the notification callback in frames.
+     * This value is set when the AudioRecord is constructed.
+     * It can be modified if the AudioRecord is rerouted.
+     */
+            uint32_t    getNotificationPeriodInFrames() const { return mNotificationFramesAct; }
+
     /* After it's created the track is not active. Call start() to
      * make it active. If set, the callback will start being called.
      * If event is not AudioSystem::SYNC_EVENT_NONE, the capture start will be delayed until
diff --git a/media/libaudioclient/include/AudioTrack.h b/media/libaudioclient/include/AudioTrack.h
index 0358363..16eb225 100644
--- a/media/libaudioclient/include/AudioTrack.h
+++ b/media/libaudioclient/include/AudioTrack.h
@@ -348,7 +348,12 @@
             uint32_t    channelCount() const { return mChannelCount; }
             size_t      frameCount() const  { return mFrameCount; }
 
-    // TODO consider notificationFrames() if needed
+    /*
+     * Return the period of the notification callback in frames.
+     * This value is set when the AudioTrack is constructed.
+     * It can be modified if the AudioTrack is rerouted.
+     */
+            uint32_t    getNotificationPeriodInFrames() const { return mNotificationFramesAct; }
 
     /* Return effective size of audio buffer that an application writes to
      * or a negative error if the track is uninitialized.
diff --git a/media/libmediametrics/include/MediaAnalyticsItem.h b/media/libmediametrics/include/MediaAnalyticsItem.h
index f050e7f..dc501b2 100644
--- a/media/libmediametrics/include/MediaAnalyticsItem.h
+++ b/media/libmediametrics/include/MediaAnalyticsItem.h
@@ -41,6 +41,7 @@
     friend class MediaAnalyticsService;
     friend class IMediaAnalyticsService;
     friend class MediaMetricsJNI;
+    friend class MetricsSummarizer;
 
     public:
 
@@ -231,7 +232,6 @@
         size_t mPropCount;
         size_t mPropSize;
         Prop *mProps;
-
 };
 
 } // namespace android
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 95f378f..3998cf6 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -1999,10 +1999,12 @@
         mCameraSourceTimeLapse = NULL;
     }
 
-    if (mVideoEncoderSource != NULL) {
-        int64_t stopTimeUs = systemTime() / 1000;
-        sp<MetaData> meta = new MetaData;
-        err = mVideoEncoderSource->setStopStimeUs(stopTimeUs);
+    int64_t stopTimeUs = systemTime() / 1000;
+    for (const auto &source : { mAudioEncoderSource, mVideoEncoderSource }) {
+        if (source != nullptr && OK != source->setStopTimeUs(stopTimeUs)) {
+            ALOGW("Failed to set stopTime %lld us for %s",
+                    (long long)stopTimeUs, source->isVideo() ? "Video" : "Audio");
+        }
     }
 
     if (mWriter != NULL) {
diff --git a/media/libnbaio/NBLog.cpp b/media/libnbaio/NBLog.cpp
index de38e7f..adbbb74 100644
--- a/media/libnbaio/NBLog.cpp
+++ b/media/libnbaio/NBLog.cpp
@@ -31,6 +31,7 @@
 #include <utils/String8.h>
 
 #include <queue>
+#include <utility>
 
 namespace android {
 
@@ -51,12 +52,25 @@
 
 // ---------------------------------------------------------------------------
 
-NBLog::FormatEntry::FormatEntry(const uint8_t *entry) : mEntry(entry) {
-    ALOGW_IF(entry[offsetof(struct entry, type)] != EVENT_START_FMT,
-        "Created format entry with invalid event type %d", entry[offsetof(struct entry, type)]);
+/*static*/
+std::unique_ptr<NBLog::AbstractEntry> NBLog::AbstractEntry::buildEntry(const uint8_t *ptr) {
+    uint8_t type = EntryIterator(ptr)->type;
+    switch (type) {
+    case EVENT_START_FMT:
+        return std::make_unique<FormatEntry>(FormatEntry(ptr));
+    case EVENT_HISTOGRAM_FLUSH:
+    case EVENT_HISTOGRAM_ENTRY_TS:
+        return std::make_unique<HistogramEntry>(HistogramEntry(ptr));
+    default:
+        ALOGW("Tried to create AbstractEntry of type %d", type);
+        return nullptr;
+    }
 }
 
-NBLog::FormatEntry::FormatEntry(const NBLog::FormatEntry::iterator &it) : FormatEntry(it.ptr) {}
+NBLog::AbstractEntry::AbstractEntry(const uint8_t *entry) : mEntry(entry) {
+}
+
+// ---------------------------------------------------------------------------
 
 const char *NBLog::FormatEntry::formatString() const {
     return (const char*) mEntry + offsetof(entry, data);
@@ -66,12 +80,14 @@
     return mEntry[offsetof(entry, length)];
 }
 
-NBLog::FormatEntry::iterator NBLog::FormatEntry::args() const {
+NBLog::EntryIterator NBLog::FormatEntry::args() const {
     auto it = begin();
     // skip start fmt
     ++it;
     // skip timestamp
     ++it;
+    // skip hash
+    ++it;
     // Skip author if present
     if (it->type == EVENT_AUTHOR) {
         ++it;
@@ -79,19 +95,33 @@
     return it;
 }
 
-timespec NBLog::FormatEntry::timestamp() const {
+int64_t NBLog::FormatEntry::timestamp() const {
     auto it = begin();
     // skip start fmt
     ++it;
-    return it.payload<timespec>();
+    return it.payload<int64_t>();
 }
 
-pid_t NBLog::FormatEntry::author() const {
+NBLog::log_hash_t NBLog::FormatEntry::hash() const {
     auto it = begin();
     // skip start fmt
     ++it;
     // skip timestamp
     ++it;
+    // unaligned 64-bit read not supported
+    log_hash_t hash;
+    memcpy(&hash, it->data, sizeof(hash));
+    return hash;
+}
+
+int NBLog::FormatEntry::author() const {
+    auto it = begin();
+    // skip start fmt
+    ++it;
+    // skip timestamp
+    ++it;
+    // skip hash
+    ++it;
     // if there is an author entry, return it, return -1 otherwise
     if (it->type == EVENT_AUTHOR) {
         return it.payload<int>();
@@ -99,13 +129,15 @@
     return -1;
 }
 
-NBLog::FormatEntry::iterator NBLog::FormatEntry::copyWithAuthor(
+NBLog::EntryIterator NBLog::FormatEntry::copyWithAuthor(
         std::unique_ptr<audio_utils_fifo_writer> &dst, int author) const {
     auto it = begin();
     // copy fmt start entry
     it.copyTo(dst);
     // copy timestamp
     (++it).copyTo(dst);
+    // copy hash
+    (++it).copyTo(dst);
     // insert author entry
     size_t authorEntrySize = NBLog::Entry::kOverhead + sizeof(author);
     uint8_t authorEntry[authorEntrySize];
@@ -124,71 +156,107 @@
     return it;
 }
 
-void NBLog::FormatEntry::iterator::copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const {
+void NBLog::EntryIterator::copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const {
     size_t length = ptr[offsetof(entry, length)] + NBLog::Entry::kOverhead;
     dst->write(ptr, length);
 }
 
-void NBLog::FormatEntry::iterator::copyData(uint8_t *dst) const {
+void NBLog::EntryIterator::copyData(uint8_t *dst) const {
     memcpy((void*) dst, ptr + offsetof(entry, data), ptr[offsetof(entry, length)]);
 }
 
-NBLog::FormatEntry::iterator NBLog::FormatEntry::begin() const {
-    return iterator(mEntry);
+NBLog::EntryIterator NBLog::FormatEntry::begin() const {
+    return EntryIterator(mEntry);
 }
 
-NBLog::FormatEntry::iterator::iterator()
+NBLog::EntryIterator::EntryIterator()
     : ptr(nullptr) {}
 
-NBLog::FormatEntry::iterator::iterator(const uint8_t *entry)
+NBLog::EntryIterator::EntryIterator(const uint8_t *entry)
     : ptr(entry) {}
 
-NBLog::FormatEntry::iterator::iterator(const NBLog::FormatEntry::iterator &other)
+NBLog::EntryIterator::EntryIterator(const NBLog::EntryIterator &other)
     : ptr(other.ptr) {}
 
-const NBLog::FormatEntry::entry& NBLog::FormatEntry::iterator::operator*() const {
+const NBLog::entry& NBLog::EntryIterator::operator*() const {
     return *(entry*) ptr;
 }
 
-const NBLog::FormatEntry::entry* NBLog::FormatEntry::iterator::operator->() const {
+const NBLog::entry* NBLog::EntryIterator::operator->() const {
     return (entry*) ptr;
 }
 
-NBLog::FormatEntry::iterator& NBLog::FormatEntry::iterator::operator++() {
+NBLog::EntryIterator& NBLog::EntryIterator::operator++() {
     ptr += ptr[offsetof(entry, length)] + NBLog::Entry::kOverhead;
     return *this;
 }
 
-NBLog::FormatEntry::iterator& NBLog::FormatEntry::iterator::operator--() {
+NBLog::EntryIterator& NBLog::EntryIterator::operator--() {
     ptr -= ptr[NBLog::Entry::kPreviousLengthOffset] + NBLog::Entry::kOverhead;
     return *this;
 }
 
-NBLog::FormatEntry::iterator NBLog::FormatEntry::iterator::next() const {
-    iterator aux(*this);
+NBLog::EntryIterator NBLog::EntryIterator::next() const {
+    EntryIterator aux(*this);
     return ++aux;
 }
 
-NBLog::FormatEntry::iterator NBLog::FormatEntry::iterator::prev() const {
-    iterator aux(*this);
+NBLog::EntryIterator NBLog::EntryIterator::prev() const {
+    EntryIterator aux(*this);
     return --aux;
 }
 
-int NBLog::FormatEntry::iterator::operator-(const NBLog::FormatEntry::iterator &other) const {
+int NBLog::EntryIterator::operator-(const NBLog::EntryIterator &other) const {
     return ptr - other.ptr;
 }
 
-bool NBLog::FormatEntry::iterator::operator!=(const iterator &other) const {
+bool NBLog::EntryIterator::operator!=(const EntryIterator &other) const {
     return ptr != other.ptr;
 }
 
-bool NBLog::FormatEntry::iterator::hasConsistentLength() const {
+bool NBLog::EntryIterator::hasConsistentLength() const {
     return ptr[offsetof(entry, length)] == ptr[ptr[offsetof(entry, length)] +
         NBLog::Entry::kOverhead + NBLog::Entry::kPreviousLengthOffset];
 }
 
 // ---------------------------------------------------------------------------
 
+int64_t NBLog::HistogramEntry::timestamp() const {
+    return EntryIterator(mEntry).payload<HistTsEntry>().ts;
+}
+
+NBLog::log_hash_t NBLog::HistogramEntry::hash() const {
+    return EntryIterator(mEntry).payload<HistTsEntry>().hash;
+}
+
+int NBLog::HistogramEntry::author() const {
+    EntryIterator it(mEntry);
+    if (it->length == sizeof(HistTsEntryWithAuthor)) {
+        return it.payload<HistTsEntryWithAuthor>().author;
+    } else {
+        return -1;
+    }
+}
+
+NBLog::EntryIterator NBLog::HistogramEntry::copyWithAuthor(
+        std::unique_ptr<audio_utils_fifo_writer> &dst, int author) const {
+    // Current histogram entry has {type, length, struct HistTsEntry, length}.
+    // We now want {type, length, struct HistTsEntryWithAuthor, length}
+    uint8_t buffer[Entry::kOverhead + sizeof(HistTsEntryWithAuthor)];
+    // Copy content until the point we want to add the author
+    memcpy(buffer, mEntry, sizeof(entry) + sizeof(HistTsEntry));
+    // Copy the author
+    *(int*) (buffer + sizeof(entry) + sizeof(HistTsEntry)) = author;
+    // Update lengths
+    buffer[offsetof(entry, length)] = sizeof(HistTsEntryWithAuthor);
+    buffer[sizeof(buffer) + Entry::kPreviousLengthOffset] = sizeof(HistTsEntryWithAuthor);
+    // Write new buffer into FIFO
+    dst->write(buffer, sizeof(buffer));
+    return EntryIterator(mEntry).next();
+}
+
+// ---------------------------------------------------------------------------
+
 #if 0   // FIXME see note in NBLog.h
 NBLog::Timeline::Timeline(size_t size, void *shared)
     : mSize(roundup(size)), mOwn(shared == NULL),
@@ -301,13 +369,15 @@
     if (!mEnabled) {
         return;
     }
-    struct timespec ts;
-    if (!clock_gettime(CLOCK_MONOTONIC, &ts)) {
+    int64_t ts = get_monotonic_ns();
+    if (ts > 0) {
         log(EVENT_TIMESTAMP, &ts, sizeof(ts));
+    } else {
+        ALOGE("Failed to get timestamp");
     }
 }
 
-void NBLog::Writer::logTimestamp(const struct timespec &ts)
+void NBLog::Writer::logTimestamp(const int64_t ts)
 {
     if (!mEnabled) {
         return;
@@ -360,19 +430,57 @@
     log(&entry, true);
 }
 
-void NBLog::Writer::logFormat(const char *fmt, ...)
+void NBLog::Writer::logHash(log_hash_t hash)
+{
+    if (!mEnabled) {
+        return;
+    }
+    log(EVENT_HASH, &hash, sizeof(hash));
+}
+
+void NBLog::Writer::logHistTS(log_hash_t hash)
+{
+    if (!mEnabled) {
+        return;
+    }
+    HistTsEntry data;
+    data.hash = hash;
+    data.ts = get_monotonic_ns();
+    if (data.ts > 0) {
+        log(EVENT_HISTOGRAM_ENTRY_TS, &data, sizeof(data));
+    } else {
+        ALOGE("Failed to get timestamp");
+    }
+}
+
+void NBLog::Writer::logHistFlush(log_hash_t hash)
+{
+    if (!mEnabled) {
+        return;
+    }
+    HistTsEntry data;
+    data.hash = hash;
+    data.ts = get_monotonic_ns();
+    if (data.ts > 0) {
+        log(EVENT_HISTOGRAM_FLUSH, &data, sizeof(data));
+    } else {
+        ALOGE("Failed to get timestamp");
+    }
+}
+
+void NBLog::Writer::logFormat(const char *fmt, log_hash_t hash, ...)
 {
     if (!mEnabled) {
         return;
     }
 
     va_list ap;
-    va_start(ap, fmt);
-    Writer::logVFormat(fmt, ap);
+    va_start(ap, hash);
+    Writer::logVFormat(fmt, hash, ap);
     va_end(ap);
 }
 
-void NBLog::Writer::logVFormat(const char *fmt, va_list argp)
+void NBLog::Writer::logVFormat(const char *fmt, log_hash_t hash, va_list argp)
 {
     if (!mEnabled) {
         return;
@@ -381,8 +489,9 @@
     int i;
     double f;
     char* s;
-    struct timespec t;
+    int64_t t;
     Writer::logTimestamp();
+    Writer::logHash(hash);
     for (const char *p = fmt; *p != '\0'; p++) {
         // TODO: implement more complex formatting such as %.3f
         if (*p != '%') {
@@ -395,7 +504,7 @@
             break;
 
         case 't': // timestamp
-            t = va_arg(argp, struct timespec);
+            t = va_arg(argp, int64_t);
             Writer::logTimestamp(t);
             break;
 
@@ -440,16 +549,8 @@
         //      a confusion for a programmer debugging their code.
         return;
     }
-    switch (event) {
-    case EVENT_STRING:
-    case EVENT_TIMESTAMP:
-    case EVENT_INTEGER:
-    case EVENT_FLOAT:
-    case EVENT_PID:
-    case EVENT_START_FMT:
-        break;
-    case EVENT_RESERVED:
-    default:
+    // Ignore if invalid event
+    if (event == EVENT_RESERVED || event >= EVENT_UPPER_BOUND) {
         return;
     }
     Entry entry(event, data, length);
@@ -531,7 +632,7 @@
     Writer::logTimestamp();
 }
 
-void NBLog::LockedWriter::logTimestamp(const struct timespec &ts)
+void NBLog::LockedWriter::logTimestamp(const int64_t ts)
 {
     Mutex::Autolock _l(mLock);
     Writer::logTimestamp(ts);
@@ -568,6 +669,12 @@
     Writer::logEnd();
 }
 
+void NBLog::LockedWriter::logHash(log_hash_t hash)
+{
+    Mutex::Autolock _l(mLock);
+    Writer::logHash(hash);
+}
+
 bool NBLog::LockedWriter::isEnabled() const
 {
     Mutex::Autolock _l(mLock);
@@ -582,6 +689,11 @@
 
 // ---------------------------------------------------------------------------
 
+const std::set<NBLog::Event> NBLog::Reader::startingTypes {NBLog::Event::EVENT_START_FMT,
+                                                           NBLog::Event::EVENT_HISTOGRAM_ENTRY_TS};
+const std::set<NBLog::Event> NBLog::Reader::endingTypes   {NBLog::Event::EVENT_END_FMT,
+                                                           NBLog::Event::EVENT_HISTOGRAM_ENTRY_TS,
+                                                           NBLog::Event::EVENT_HISTOGRAM_FLUSH};
 NBLog::Reader::Reader(const void *shared, size_t size)
     : mShared((/*const*/ Shared *) shared), /*mIMemory*/
       mFd(-1), mIndent(0),
@@ -604,16 +716,17 @@
     delete mFifo;
 }
 
-uint8_t *NBLog::Reader::findLastEntryOfType(uint8_t *front, uint8_t *back, uint8_t type) {
+const uint8_t *NBLog::Reader::findLastEntryOfTypes(const uint8_t *front, const uint8_t *back,
+                                            const std::set<Event> &types) {
     while (back + Entry::kPreviousLengthOffset >= front) {
-        uint8_t *prev = back - back[Entry::kPreviousLengthOffset] - Entry::kOverhead;
-        if (prev < front || prev + prev[offsetof(FormatEntry::entry, length)] +
+        const uint8_t *prev = back - back[Entry::kPreviousLengthOffset] - Entry::kOverhead;
+        if (prev < front || prev + prev[offsetof(entry, length)] +
                             Entry::kOverhead != back) {
 
             // prev points to an out of limits or inconsistent entry
             return nullptr;
         }
-        if (prev[offsetof(FormatEntry::entry, type)] == type) {
+        if (types.find((const Event) prev[offsetof(entry, type)]) != types.end()) {
             return prev;
         }
         back = prev;
@@ -652,21 +765,21 @@
     // it ends in a complete entry (which is not an END_FMT). So is safe to traverse backwards.
     // TODO: handle client corruption (in the middle of a buffer)
 
-    uint8_t *back = snapshot->mData + availToRead;
-    uint8_t *front = snapshot->mData;
+    const uint8_t *back = snapshot->mData + availToRead;
+    const uint8_t *front = snapshot->mData;
 
     // Find last END_FMT. <back> is sitting on an entry which might be the middle of a FormatEntry.
     // We go backwards until we find an EVENT_END_FMT.
-    uint8_t *lastEnd = findLastEntryOfType(front, back, EVENT_END_FMT);
+    const uint8_t *lastEnd = findLastEntryOfTypes(front, back, endingTypes);
     if (lastEnd == nullptr) {
-        snapshot->mEnd = snapshot->mBegin = FormatEntry::iterator(front);
+        snapshot->mEnd = snapshot->mBegin = EntryIterator(front);
     } else {
         // end of snapshot points to after last END_FMT entry
-        snapshot->mEnd = FormatEntry::iterator(lastEnd + Entry::kOverhead);
+        snapshot->mEnd = EntryIterator(lastEnd).next();
         // find first START_FMT
-        uint8_t *firstStart = nullptr;
-        uint8_t *firstStartTmp = lastEnd;
-        while ((firstStartTmp = findLastEntryOfType(front, firstStartTmp, EVENT_START_FMT))
+        const uint8_t *firstStart = nullptr;
+        const uint8_t *firstStartTmp = snapshot->mEnd;
+        while ((firstStartTmp = findLastEntryOfTypes(front, firstStartTmp, startingTypes))
                 != nullptr) {
             firstStart = firstStartTmp;
         }
@@ -674,7 +787,7 @@
         if (firstStart == nullptr) {
             snapshot->mBegin = snapshot->mEnd;
         } else {
-            snapshot->mBegin = FormatEntry::iterator(firstStart);
+            snapshot->mBegin = EntryIterator(firstStart);
         }
     }
 
@@ -686,6 +799,10 @@
 
 }
 
+inline static int deltaMs(int64_t t1, int64_t t2) {
+    return (t2 - t1) / (1000 * 1000);
+}
+
 void NBLog::Reader::dump(int fd, size_t indent, NBLog::Reader::Snapshot &snapshot)
 {
 #if 0
@@ -712,7 +829,7 @@
     mFd = fd;
     mIndent = indent;
     String8 timestamp, body;
-    size_t lost = snapshot.lost() + (snapshot.begin() - FormatEntry::iterator(snapshot.data()));
+    size_t lost = snapshot.lost() + (snapshot.begin() - EntryIterator(snapshot.data()));
     if (lost > 0) {
         body.appendFormat("warning: lost %zu bytes worth of events", lost);
         // TODO timestamp empty here, only other choice to wait for the first timestamp event in the
@@ -730,6 +847,7 @@
     }
     bool deferredTimestamp = false;
 #endif
+
     for (auto entry = snapshot.begin(); entry != snapshot.end();) {
         switch (entry->type) {
 #if 0
@@ -801,6 +919,44 @@
             // right now, this is the only supported case
             entry = handleFormat(FormatEntry(entry), &timestamp, &body);
             break;
+        case EVENT_HISTOGRAM_ENTRY_TS: {
+            HistTsEntryWithAuthor *data = (HistTsEntryWithAuthor *) (entry->data);
+            // TODO This memcpies are here to avoid unaligned memory access crash.
+            // There's probably a more efficient way to do it
+            log_hash_t hash;
+            memcpy(&hash, &(data->hash), sizeof(hash));
+            int64_t ts;
+            memcpy(&ts, &data->ts, sizeof(ts));
+            const std::pair<log_hash_t, int> key(hash, data->author);
+            // TODO might want to filter excessively high outliers, which are usually caused
+            // by the thread being inactive.
+            mHists[key].push_back(ts);
+            ++entry;
+            break;
+        }
+        case EVENT_HISTOGRAM_FLUSH: {
+            HistogramEntry histEntry(entry);
+            // Log timestamp
+            int64_t ts = histEntry.timestamp();
+            timestamp.clear();
+            timestamp.appendFormat("[%d.%03d]", (int) (ts / (1000 * 1000 * 1000)),
+                            (int) ((ts / (1000 * 1000)) % 1000));
+            // Log histograms
+            body.appendFormat("Histogram flush - ");
+            handleAuthor(histEntry, &body);
+            body.appendFormat("\n");
+            for (auto hist = mHists.begin(); hist != mHists.end();) {
+                if (hist->first.second == histEntry.author()) {
+                    body.appendFormat("Histogram %X", (int)hist->first.first);
+                    drawHistogram(&body, hist->second, true/*logScale*/, indent + timestamp.size());
+                    hist = mHists.erase(hist);
+                } else {
+                    ++hist;
+                }
+            }
+            ++entry;
+            break;
+        }
         case EVENT_END_FMT:
             body.appendFormat("warning: got to end format event");
             ++entry;
@@ -845,10 +1001,10 @@
 }
 
 void NBLog::appendTimestamp(String8 *body, const void *data) {
-    struct timespec ts;
-    memcpy(&ts, data, sizeof(struct timespec));
-    body->appendFormat("[%d.%03d]", (int) ts.tv_sec,
-                    (int) (ts.tv_nsec / 1000000));
+    int64_t ts;
+    memcpy(&ts, data, sizeof(ts));
+    body->appendFormat("[%d.%03d]", (int) (ts / (1000 * 1000 * 1000)),
+                    (int) ((ts / (1000 * 1000)) % 1000));
 }
 
 void NBLog::appendInt(String8 *body, const void *data) {
@@ -868,20 +1024,42 @@
     body->appendFormat("<PID: %d, name: %.*s>", id, (int) (length - sizeof(pid_t)), name);
 }
 
-NBLog::FormatEntry::iterator NBLog::Reader::handleFormat(const FormatEntry &fmtEntry,
+String8 NBLog::bufferDump(const uint8_t *buffer, size_t size)
+{
+    String8 str;
+    str.append("[ ");
+    for(size_t i = 0; i < size; i++)
+    {
+        str.appendFormat("%d ", buffer[i]);
+    }
+    str.append("]");
+    return str;
+}
+
+String8 NBLog::bufferDump(const EntryIterator &it)
+{
+    return bufferDump(it, it->length + Entry::kOverhead);
+}
+
+NBLog::EntryIterator NBLog::Reader::handleFormat(const FormatEntry &fmtEntry,
                                                          String8 *timestamp,
                                                          String8 *body) {
     // log timestamp
-    struct timespec ts = fmtEntry.timestamp();
+    int64_t ts = fmtEntry.timestamp();
     timestamp->clear();
-    timestamp->appendFormat("[%d.%03d]", (int) ts.tv_sec,
-                    (int) (ts.tv_nsec / 1000000));
+    timestamp->appendFormat("[%d.%03d]", (int) (ts / (1000 * 1000 * 1000)),
+                    (int) ((ts / (1000 * 1000)) % 1000));
+
+    // log unique hash
+    log_hash_t hash = fmtEntry.hash();
+    // print only lower 16bit of hash as hex and line as int to reduce spam in the log
+    body->appendFormat("%.4X-%d ", (int)(hash >> 16) & 0xFFFF, (int) hash & 0xFFFF);
 
     // log author (if present)
     handleAuthor(fmtEntry, body);
 
     // log string
-    NBLog::FormatEntry::iterator arg = fmtEntry.args();
+    NBLog::EntryIterator arg = fmtEntry.args();
 
     const char* fmt = fmtEntry.formatString();
     size_t fmt_length = fmtEntry.formatStringLength();
@@ -954,6 +1132,108 @@
     return arg;
 }
 
+static int widthOf(int x) {
+    int width = 0;
+    while (x > 0) {
+        ++width;
+        x /= 10;
+    }
+    return width;
+}
+
+static std::map<int, int> buildBuckets(const std::vector<int64_t> &samples) {
+    // TODO allow buckets of variable resolution
+    std::map<int, int> buckets;
+    for (size_t i = 1; i < samples.size(); ++i) {
+        ++buckets[deltaMs(samples[i - 1], samples[i])];
+    }
+    return buckets;
+}
+
+static inline uint32_t log2(uint32_t x) {
+    // This works for x > 0
+    return 31 - __builtin_clz(x);
+}
+
+// TODO put this function in separate file. Make it return a std::string instead of modifying body
+/*
+Example output:
+[54.234] Histogram flush - AudioOut_D:
+Histogram 33640BF1
+            [ 1][ 1][ 1][ 3][54][69][ 1][ 2][ 1]
+        64|                      []
+        32|                  []  []
+        16|                  []  []
+         8|                  []  []
+         4|                  []  []
+         2|______________[]__[]__[]______[]____
+              4   5   6   8   9  10  11  13  15
+Notice that all values that fall in the same row have the same height (65 and 127 are displayed
+identically). That's why exact counts are added at the top.
+*/
+void NBLog::Reader::drawHistogram(String8 *body,
+                                  const std::vector<int64_t> &samples,
+                                  bool logScale,
+                                  int indent,
+                                  int maxHeight) {
+    if (samples.size() <= 1) {
+        return;
+    }
+    std::map<int, int> buckets = buildBuckets(samples);
+    // TODO consider changing all ints to uint32_t or uint64_t
+    static const char *underscores = "________________";
+    static const char *spaces = "                ";
+
+    auto it = buckets.begin();
+    int maxLabel = it->first;
+    int maxVal = it->second;
+    // Compute maximum values
+    while (++it != buckets.end()) {
+        if (it->first > maxLabel) {
+            maxLabel = it->first;
+        }
+        if (it->second > maxVal) {
+            maxVal = it->second;
+        }
+    }
+    int height = (logScale) ? log2(maxVal) + 1 : maxVal; // maxVal > 0, safe to call log2
+    int leftPadding = widthOf(maxVal);
+    int colWidth = std::max(std::max(widthOf(maxLabel) + 1, 3), leftPadding + 2);
+    int scalingFactor = 1;
+    // scale data if it exceeds maximum height
+    if (height > maxHeight) {
+        scalingFactor = (height + maxHeight) / maxHeight;
+        height /= scalingFactor;
+    }
+    // write header line with bucket values
+    body->appendFormat("\n%*s", indent, " ");
+    body->appendFormat("%*s", leftPadding + 2, " ");
+    for (auto const &x : buckets)
+    {
+        body->appendFormat("[%*d]", colWidth - 2, x.second);
+    }
+    // write histogram ascii art
+    body->appendFormat("\n%*s", indent, " ");
+    for (int row = height * scalingFactor; row > 0; row -= scalingFactor)
+    {
+        int value = ((logScale) ? (1 << row) : row);
+        body->appendFormat("%*u|", leftPadding, value);
+        for (auto const &x : buckets) {
+            body->appendFormat("%.*s%s", colWidth - 2,
+                   (row <= scalingFactor) ? underscores : spaces,
+                   x.second < value ? ((row <= scalingFactor) ? "__" : "  ") : "[]");
+        }
+        body->appendFormat("\n%*s", indent, " ");
+    }
+    // write footer with bucket labels
+    body->appendFormat("%*s", leftPadding + 1, " ");
+    for (auto const &x : buckets)
+    {
+        body->appendFormat("%*d", colWidth, x.first);
+    }
+    body->appendFormat("\n");
+}
+
 // ---------------------------------------------------------------------------
 
 NBLog::Merger::Merger(const void *shared, size_t size):
@@ -973,26 +1253,25 @@
 // composed by a timestamp and the index of the snapshot where the timestamp came from
 struct MergeItem
 {
-    struct timespec ts;
+    int64_t ts;
     int index;
-    MergeItem(struct timespec ts, int index): ts(ts), index(index) {}
+    MergeItem(int64_t ts, int index): ts(ts), index(index) {}
 };
 
 // operators needed for priority queue in merge
-bool operator>(const struct timespec &t1, const struct timespec &t2) {
-    return t1.tv_sec > t2.tv_sec || (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec);
-}
+// bool operator>(const int64_t &t1, const int64_t &t2) {
+//     return t1.tv_sec > t2.tv_sec || (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec);
+// }
 
 bool operator>(const struct MergeItem &i1, const struct MergeItem &i2) {
-    return i1.ts > i2.ts ||
-        (i1.ts.tv_sec == i2.ts.tv_sec && i1.ts.tv_nsec == i2.ts.tv_nsec && i1.index > i2.index);
+    return i1.ts > i2.ts || (i1.ts == i2.ts && i1.index > i2.index);
 }
 
 // Merge registered readers, sorted by timestamp
 void NBLog::Merger::merge() {
     int nLogs = mNamedReaders.size();
     std::vector<std::unique_ptr<NBLog::Reader::Snapshot>> snapshots(nLogs);
-    std::vector<NBLog::FormatEntry::iterator> offsets(nLogs);
+    std::vector<NBLog::EntryIterator> offsets(nLogs);
     for (int i = 0; i < nLogs; ++i) {
         snapshots[i] = mNamedReaders[i].reader()->getSnapshot();
         offsets[i] = snapshots[i]->begin();
@@ -1004,7 +1283,7 @@
     for (int i = 0; i < nLogs; ++i)
     {
         if (offsets[i] != snapshots[i]->end()) {
-            timespec ts = FormatEntry(offsets[i]).timestamp();
+            int64_t ts = AbstractEntry::buildEntry(offsets[i])->timestamp();
             timestamps.emplace(ts, i);
         }
     }
@@ -1013,11 +1292,12 @@
         // find minimum timestamp
         int index = timestamps.top().index;
         // copy it to the log, increasing offset
-        offsets[index] = FormatEntry(offsets[index]).copyWithAuthor(mFifoWriter, index);
+        offsets[index] = AbstractEntry::buildEntry(offsets[index])->copyWithAuthor(mFifoWriter,
+                                                                                   index);
         // update data structures
         timestamps.pop();
         if (offsets[index] != snapshots[index]->end()) {
-            timespec ts = FormatEntry(offsets[index]).timestamp();
+            int64_t ts = AbstractEntry::buildEntry(offsets[index])->timestamp();
             timestamps.emplace(ts, index);
         }
     }
@@ -1030,11 +1310,10 @@
 NBLog::MergeReader::MergeReader(const void *shared, size_t size, Merger &merger)
     : Reader(shared, size), mNamedReaders(merger.getNamedReaders()) {}
 
-size_t NBLog::MergeReader::handleAuthor(const NBLog::FormatEntry &fmtEntry, String8 *body) {
-    int author = fmtEntry.author();
+void NBLog::MergeReader::handleAuthor(const NBLog::AbstractEntry &entry, String8 *body) {
+    int author = entry.author();
     const char* name = (*mNamedReaders)[author].name();
     body->appendFormat("%s: ", name);
-    return NBLog::Entry::kOverhead + sizeof(author);
 }
 
 NBLog::MergeThread::MergeThread(NBLog::Merger &merger)
diff --git a/media/libnbaio/include/NBLog.h b/media/libnbaio/include/NBLog.h
index 59b77bd..2893dc9 100644
--- a/media/libnbaio/include/NBLog.h
+++ b/media/libnbaio/include/NBLog.h
@@ -24,6 +24,8 @@
 #include <utils/Mutex.h>
 #include <utils/threads.h>
 
+#include <map>
+#include <set>
 #include <vector>
 
 namespace android {
@@ -34,12 +36,14 @@
 
 public:
 
+typedef uint64_t log_hash_t;
+
 class Writer;
 class Reader;
 
 private:
 
-enum Event {
+enum Event : uint8_t {
     EVENT_RESERVED,
     EVENT_STRING,               // ASCII string, not NUL-terminated
     // TODO: make timestamp optional
@@ -50,7 +54,13 @@
     EVENT_AUTHOR,               // author index (present in merged logs) tracks entry's original log
     EVENT_START_FMT,            // logFormat start event: entry includes format string, following
                                 // entries contain format arguments
+    EVENT_HASH,                 // unique HASH of log origin, originates from hash of file name
+                                // and line number
+    EVENT_HISTOGRAM_ENTRY_TS,   // single datum for timestamp histogram
+    EVENT_HISTOGRAM_FLUSH,      // show histogram on log
     EVENT_END_FMT,              // end of logFormat argument list
+
+    EVENT_UPPER_BOUND,          // to check for invalid events
 };
 
 
@@ -60,93 +70,142 @@
 // a formatted entry has the following structure:
 //    * START_FMT entry, containing the format string
 //    * TIMESTAMP entry
+//    * HASH entry
 //    * author entry of the thread that generated it (optional, present in merged log)
 //    * format arg1
 //    * format arg2
 //    * ...
 //    * END_FMT entry
 
-class FormatEntry {
+// entry representation in memory
+struct entry {
+    const uint8_t type;
+    const uint8_t length;
+    const uint8_t data[0];
+};
+
+// entry tail representation (after data)
+struct ending {
+    uint8_t length;
+    uint8_t next[0];
+};
+
+// entry iterator
+class EntryIterator {
 public:
-    // build a Format Entry starting in the given pointer
-    class iterator;
-    explicit FormatEntry(const uint8_t *entry);
-    explicit FormatEntry(const iterator &it);
+    EntryIterator();
+    explicit EntryIterator(const uint8_t *entry);
+    EntryIterator(const EntryIterator &other);
 
-    // entry representation in memory
-    struct entry {
-        const uint8_t type;
-        const uint8_t length;
-        const uint8_t data[0];
-    };
+    // dereference underlying entry
+    const entry&    operator*() const;
+    const entry*    operator->() const;
+    // advance to next entry
+    EntryIterator&       operator++(); // ++i
+    // back to previous entry
+    EntryIterator&       operator--(); // --i
+    EntryIterator        next() const;
+    EntryIterator        prev() const;
+    bool            operator!=(const EntryIterator &other) const;
+    int             operator-(const EntryIterator &other) const;
 
-    // entry tail representation (after data)
-    struct ending {
-        uint8_t length;
-        uint8_t next[0];
-    };
+    bool            hasConsistentLength() const;
+    void            copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const;
+    void            copyData(uint8_t *dst) const;
 
-    // entry iterator
-    class iterator {
-    public:
-        iterator();
-        iterator(const uint8_t *entry);
-        iterator(const iterator &other);
+    template<typename T>
+    inline const T& payload() {
+        return *reinterpret_cast<const T *>(ptr + offsetof(entry, data));
+    }
 
-        // dereference underlying entry
-        const entry&    operator*() const;
-        const entry*    operator->() const;
-        // advance to next entry
-        iterator&       operator++(); // ++i
-        // back to previous entry
-        iterator&       operator--(); // --i
-        iterator        next() const;
-        iterator        prev() const;
-        bool            operator!=(const iterator &other) const;
-        int             operator-(const iterator &other) const;
+    inline operator const uint8_t*() const {
+        return ptr;
+    }
 
-        bool            hasConsistentLength() const;
-        void            copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const;
-        void            copyData(uint8_t *dst) const;
+private:
+    const uint8_t  *ptr;
+};
 
-        template<typename T>
-        inline const T& payload() {
-            return *reinterpret_cast<const T *>(ptr + offsetof(entry, data));
-        }
+class AbstractEntry {
+public:
 
-    private:
-        friend class FormatEntry;
-        const uint8_t  *ptr;
-    };
+    // Entry starting in the given pointer
+    explicit AbstractEntry(const uint8_t *entry);
 
-    // Entry's format string
-    const char* formatString() const;
-
-    // Enrty's format string length
-    size_t      formatStringLength() const;
-
-    // Format arguments (excluding format string, timestamp and author)
-    iterator    args() const;
+    // build concrete entry of appropriate class from pointer
+    static std::unique_ptr<AbstractEntry> buildEntry(const uint8_t *ptr);
 
     // get format entry timestamp
-    timespec    timestamp() const;
+    // TODO consider changing to uint64_t
+    virtual int64_t      timestamp() const = 0;
+
+    // get format entry's unique id
+    virtual log_hash_t   hash() const = 0;
 
     // entry's author index (-1 if none present)
     // a Merger has a vector of Readers, author simply points to the index of the
     // Reader that originated the entry
-    int         author() const;
+    // TODO consider changing to uint32_t
+    virtual int          author() const = 0;
 
-    // copy entry, adding author before timestamp, returns size of original entry
-    iterator    copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst, int author) const;
+    // copy entry, adding author before timestamp, returns iterator to end of entry
+    virtual EntryIterator    copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst,
+                                       int author) const = 0;
 
-    iterator    begin() const;
-
-private:
+protected:
     // copies ordinary entry from src to dst, and returns length of entry
     // size_t      copyEntry(audio_utils_fifo_writer *dst, const iterator &it);
     const uint8_t  *mEntry;
 };
 
+class FormatEntry : public AbstractEntry {
+public:
+    // explicit FormatEntry(const EntryIterator &it);
+    explicit FormatEntry(const uint8_t *ptr) : AbstractEntry(ptr) {}
+
+    // Entry's format string
+    const   char* formatString() const;
+
+    // Enrty's format string length
+            size_t      formatStringLength() const;
+
+    // Format arguments (excluding format string, timestamp and author)
+            EntryIterator    args() const;
+
+    // get format entry timestamp
+    virtual int64_t     timestamp() const override;
+
+    // get format entry's unique id
+    virtual log_hash_t  hash() const override;
+
+    // entry's author index (-1 if none present)
+    // a Merger has a vector of Readers, author simply points to the index of the
+    // Reader that originated the entry
+    virtual int         author() const override;
+
+    // copy entry, adding author before timestamp, returns size of original entry
+    virtual EntryIterator    copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst,
+                                       int author) const override;
+
+            EntryIterator    begin() const;
+};
+
+class HistogramEntry : public AbstractEntry {
+public:
+    explicit HistogramEntry(const uint8_t *ptr) : AbstractEntry(ptr) {
+    }
+
+    virtual int64_t     timestamp() const override;
+
+    virtual log_hash_t  hash() const override;
+
+    virtual int         author() const override;
+
+    virtual EntryIterator    copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst,
+                                       int author) const override;
+
+};
+
 // ---------------------------------------------------------------------------
 
 // representation of a single log entry in private memory
@@ -165,12 +224,28 @@
     static const size_t kMaxLength = 255;
 public:
     // mEvent, mLength, mData[...], duplicate mLength
-    static const size_t kOverhead = sizeof(FormatEntry::entry) + sizeof(FormatEntry::ending);
+    static const size_t kOverhead = sizeof(entry) + sizeof(ending);
     // endind length of previous entry
-    static const size_t kPreviousLengthOffset = - sizeof(FormatEntry::ending) +
-                                                offsetof(FormatEntry::ending, length);
+    static const size_t kPreviousLengthOffset = - sizeof(ending) +
+                                                offsetof(ending, length);
 };
 
+struct HistTsEntry {
+    log_hash_t hash;
+    int64_t ts;
+}; //TODO __attribute__((packed));
+
+struct HistTsEntryWithAuthor {
+    log_hash_t hash;
+    int64_t ts;
+    int author;
+}; //TODO __attribute__((packed));
+
+struct HistIntEntry {
+    log_hash_t hash;
+    int value;
+}; //TODO __attribute__((packed));
+
 // representation of a single log entry in shared memory
 //  byte[0]             mEvent
 //  byte[1]             mLength
@@ -187,7 +262,8 @@
     static void    appendPID(String8 *body, const void *data, size_t length);
     static void    appendTimestamp(String8 *body, const void *data);
     static size_t  fmtEntryLength(const uint8_t *data);
-
+    static String8 bufferDump(const uint8_t *buffer, size_t size);
+    static String8 bufferDump(const EntryIterator &it);
 public:
 
 // Located in shared memory, must be POD.
@@ -248,15 +324,17 @@
     virtual void    logf(const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
     virtual void    logvf(const char *fmt, va_list ap);
     virtual void    logTimestamp();
-    virtual void    logTimestamp(const struct timespec &ts);
+    virtual void    logTimestamp(const int64_t ts);
     virtual void    logInteger(const int x);
     virtual void    logFloat(const float x);
     virtual void    logPID();
-    virtual void    logFormat(const char *fmt, ...);
-    virtual void    logVFormat(const char *fmt, va_list ap);
+    virtual void    logFormat(const char *fmt, log_hash_t hash, ...);
+    virtual void    logVFormat(const char *fmt, log_hash_t hash, va_list ap);
     virtual void    logStart(const char *fmt);
     virtual void    logEnd();
-
+    virtual void    logHash(log_hash_t hash);
+    virtual void    logHistTS(log_hash_t hash);
+    virtual void    logHistFlush(log_hash_t hash);
 
     virtual bool    isEnabled() const;
 
@@ -298,12 +376,13 @@
     virtual void    logf(const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
     virtual void    logvf(const char *fmt, va_list ap);
     virtual void    logTimestamp();
-    virtual void    logTimestamp(const struct timespec &ts);
+    virtual void    logTimestamp(const int64_t ts);
     virtual void    logInteger(const int x);
     virtual void    logFloat(const float x);
     virtual void    logPID();
     virtual void    logStart(const char *fmt);
     virtual void    logEnd();
+    virtual void    logHash(log_hash_t hash);
 
     virtual bool    isEnabled() const;
     virtual bool    setEnabled(bool enabled);
@@ -334,18 +413,18 @@
 
         // iterator to beginning of readable segment of snapshot
         // data between begin and end has valid entries
-        FormatEntry::iterator begin() { return mBegin; }
+        EntryIterator begin() { return mBegin; }
 
         // iterator to end of readable segment of snapshot
-        FormatEntry::iterator end() { return mEnd; }
+        EntryIterator end() { return mEnd; }
 
 
     private:
         friend class Reader;
         uint8_t              *mData;
         size_t                mLost;
-        FormatEntry::iterator mBegin;
-        FormatEntry::iterator mEnd;
+        EntryIterator mBegin;
+        EntryIterator mEnd;
     };
 
     // Input parameter 'size' is the desired size of the timeline in byte units.
@@ -364,6 +443,8 @@
     bool     isIMemory(const sp<IMemory>& iMemory) const;
 
 private:
+    static const std::set<Event> startingTypes;
+    static const std::set<Event> endingTypes;
     /*const*/ Shared* const mShared;    // raw pointer to shared memory, actually const but not
                                         // declared as const because audio_utils_fifo() constructor
     sp<IMemory> mIMemory;       // ref-counted version, assigned only in constructor
@@ -374,17 +455,23 @@
     audio_utils_fifo_reader * const mFifoReader;    // used to read from FIFO,
                                                     // non-NULL unless constructor fails
 
+    std::map<std::pair<log_hash_t, int>, std::vector<int64_t>> mHists;
+
     void    dumpLine(const String8& timestamp, String8& body);
 
-    FormatEntry::iterator   handleFormat(const FormatEntry &fmtEntry,
+    EntryIterator   handleFormat(const FormatEntry &fmtEntry,
                                          String8 *timestamp,
                                          String8 *body);
     // dummy method for handling absent author entry
-    virtual size_t handleAuthor(const FormatEntry &fmtEntry, String8 *body) { return 0; }
+    virtual void handleAuthor(const AbstractEntry &fmtEntry, String8 *body) {}
+
+    static void drawHistogram(String8 *body, const std::vector<int64_t> &samples,
+                              bool logScale, int indent = 0, int maxHeight = 10);
 
     // Searches for the last entry of type <type> in the range [front, back)
     // back has to be entry-aligned. Returns nullptr if none enconuntered.
-    static uint8_t *findLastEntryOfType(uint8_t *front, uint8_t *back, uint8_t type);
+    static const uint8_t *findLastEntryOfTypes(const uint8_t *front, const uint8_t *back,
+                                         const std::set<Event> &types);
 
     static const size_t kSquashTimestamp = 5; // squash this many or more adjacent timestamps
 };
@@ -426,8 +513,6 @@
     Shared * const mShared;
     std::unique_ptr<audio_utils_fifo> mFifo;
     std::unique_ptr<audio_utils_fifo_writer> mFifoWriter;
-
-    static struct timespec getTimestamp(const uint8_t *data);
 };
 
 class MergeReader : public Reader {
@@ -437,7 +522,7 @@
     const std::vector<NamedReader> *mNamedReaders;
     // handle author entry by looking up the author's name and appending it to the body
     // returns number of bytes read from fmtEntry
-    size_t handleAuthor(const FormatEntry &fmtEntry, String8 *body);
+    void handleAuthor(const AbstractEntry &fmtEntry, String8 *body);
 };
 
 // MergeThread is a thread that contains a Merger. It works as a retriggerable one-shot:
@@ -479,6 +564,15 @@
 
 };  // class NBLog
 
+// TODO put somewhere else
+static inline int64_t get_monotonic_ns() {
+    timespec ts;
+    if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+        return (uint64_t) ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec;
+    }
+    return 0; // should not happen.
+}
+
 }   // namespace android
 
 #endif  // ANDROID_MEDIA_NBLOG_H
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
index 4ccd2d0..6a5a229 100644
--- a/media/libstagefright/AudioSource.cpp
+++ b/media/libstagefright/AudioSource.cpp
@@ -58,6 +58,8 @@
       mOutSampleRate(outSampleRate > 0 ? outSampleRate : sampleRate),
       mTrackMaxAmplitude(false),
       mStartTimeUs(0),
+      mStopSystemTimeUs(-1),
+      mLastFrameTimestampUs(0),
       mMaxAmplitude(0),
       mPrevSampleTimeUs(0),
       mInitialReadTimeUs(0),
@@ -175,6 +177,7 @@
     }
 
     mStarted = false;
+    mStopSystemTimeUs = -1;
     mFrameAvailableCondition.signal();
 
     mRecord->stop();
@@ -286,6 +289,21 @@
     return OK;
 }
 
+status_t AudioSource::setStopTimeUs(int64_t stopTimeUs) {
+    Mutex::Autolock autoLock(mLock);
+    ALOGV("Set stoptime: %lld us", (long long)stopTimeUs);
+
+    if (stopTimeUs < -1) {
+        ALOGE("Invalid stop time %lld us", (long long)stopTimeUs);
+        return BAD_VALUE;
+    } else if (stopTimeUs == -1) {
+        ALOGI("reset stopTime to be -1");
+    }
+
+    mStopSystemTimeUs = stopTimeUs;
+    return OK;
+}
+
 void AudioSource::signalBufferReturned(MediaBuffer *buffer) {
     ALOGV("signalBufferReturned: %p", buffer->data());
     Mutex::Autolock autoLock(mLock);
@@ -338,6 +356,12 @@
         return OK;
     }
 
+    if (mStopSystemTimeUs != -1 && timeUs >= mStopSystemTimeUs) {
+        ALOGV("Drop Audio frame at %lld  stop time: %lld us",
+                (long long)timeUs, (long long)mStopSystemTimeUs);
+        return OK;
+    }
+
     if (mNumFramesReceived == 0 && mPrevSampleTimeUs == 0) {
         mInitialReadTimeUs = timeUs;
         // Initial delay
@@ -346,6 +370,7 @@
         }
         mPrevSampleTimeUs = mStartTimeUs;
     }
+    mLastFrameTimestampUs = timeUs;
 
     size_t numLostBytes = 0;
     if (mNumFramesReceived > 0) {  // Ignore earlier frame lost
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index a569f5d..61a2b5f 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -220,6 +220,7 @@
       mNumFramesEncoded(0),
       mTimeBetweenFrameCaptureUs(0),
       mFirstFrameTimeUs(0),
+      mStopSystemTimeUs(-1),
       mNumFramesDropped(0),
       mNumGlitches(0),
       mGlitchDurationThresholdUs(200000),
@@ -879,6 +880,7 @@
     {
         Mutex::Autolock autoLock(mLock);
         mStarted = false;
+        mStopSystemTimeUs = -1;
         mFrameAvailableCondition.signal();
 
         int64_t token;
@@ -1095,12 +1097,33 @@
     return OK;
 }
 
+status_t CameraSource::setStopTimeUs(int64_t stopTimeUs) {
+    Mutex::Autolock autoLock(mLock);
+    ALOGV("Set stoptime: %lld us", (long long)stopTimeUs);
+
+    if (stopTimeUs < -1) {
+        ALOGE("Invalid stop time %lld us", (long long)stopTimeUs);
+        return BAD_VALUE;
+    } else if (stopTimeUs == -1) {
+        ALOGI("reset stopTime to be -1");
+    }
+
+    mStopSystemTimeUs = stopTimeUs;
+    return OK;
+}
+
 bool CameraSource::shouldSkipFrameLocked(int64_t timestampUs) {
     if (!mStarted || (mNumFramesReceived == 0 && timestampUs < mStartTimeUs)) {
         ALOGV("Drop frame at %lld/%lld us", (long long)timestampUs, (long long)mStartTimeUs);
         return true;
     }
 
+    if (mStopSystemTimeUs != -1 && timestampUs >= mStopSystemTimeUs) {
+        ALOGV("Drop Camera frame at %lld  stop time: %lld us",
+                (long long)timestampUs, (long long)mStopSystemTimeUs);
+        return true;
+    }
+
     // May need to skip frame or modify timestamp. Currently implemented
     // by the subclass CameraSourceTimeLapse.
     if (skipCurrentFrame(timestampUs)) {
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index cafedba..82aa04d 100755
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -2123,13 +2123,17 @@
     if (mDone) {
         return OK;
     }
-    mDone = true;
+
     if (stopSource) {
         ALOGD("%s track source stopping", getTrackType());
         mSource->stop();
         ALOGD("%s track source stopped", getTrackType());
     }
 
+    // Set mDone to be true after sucessfully stop mSource as mSource may be still outputting
+    // buffers to the writer.
+    mDone = true;
+
     void *dummy;
     pthread_join(mThread, &dummy);
     status_t err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
diff --git a/media/libstagefright/MediaCodecSource.cpp b/media/libstagefright/MediaCodecSource.cpp
index bb20850..5424372 100644
--- a/media/libstagefright/MediaCodecSource.cpp
+++ b/media/libstagefright/MediaCodecSource.cpp
@@ -54,7 +54,7 @@
     void stopSource();
     void pause();
     void resume();
-
+    status_t setStopTimeUs(int64_t stopTimeUs);
     bool readBuffer(MediaBuffer **buffer);
 
 protected:
@@ -66,6 +66,7 @@
         kWhatStart = 'msta',
         kWhatStop,
         kWhatPull,
+        kWhatSetStopTimeUs,
     };
 
     sp<MediaSource> mSource;
@@ -161,6 +162,12 @@
     return err;
 }
 
+status_t MediaCodecSource::Puller::setStopTimeUs(int64_t stopTimeUs) {
+    sp<AMessage> msg = new AMessage(kWhatSetStopTimeUs, this);
+    msg->setInt64("stop-time-us", stopTimeUs);
+    return postSynchronouslyAndReturnError(msg);
+}
+
 status_t MediaCodecSource::Puller::start(const sp<MetaData> &meta, const sp<AMessage> &notify) {
     ALOGV("puller (%s) start", mIsAudio ? "audio" : "video");
     mLooper->start(
@@ -250,6 +257,20 @@
             break;
         }
 
+        case kWhatSetStopTimeUs:
+        {
+            sp<AReplyToken> replyID;
+            CHECK(msg->senderAwaitsResponse(&replyID));
+            int64_t stopTimeUs;
+            CHECK(msg->findInt64("stop-time-us", &stopTimeUs));
+            status_t err = mSource->setStopTimeUs(stopTimeUs);
+
+            sp<AMessage> response = new AMessage;
+            response->setInt32("err", err);
+            response->postReply(replyID);
+            break;
+        }
+
         case kWhatStop:
         {
             mSource->stop();
@@ -364,11 +385,8 @@
 }
 
 
-status_t MediaCodecSource::setStopStimeUs(int64_t stopTimeUs) {
-    if (!(mFlags & FLAG_USE_SURFACE_INPUT)) {
-        return OK;
-    }
-    sp<AMessage> msg = new AMessage(kWhatSetStopTimeOffset, mReflector);
+status_t MediaCodecSource::setStopTimeUs(int64_t stopTimeUs) {
+    sp<AMessage> msg = new AMessage(kWhatSetStopTimeUs, mReflector);
     msg->setInt64("stop-time-us", stopTimeUs);
     return postSynchronouslyAndReturnError(msg);
 }
@@ -1055,7 +1073,7 @@
         response->postReply(replyID);
         break;
     }
-    case kWhatSetStopTimeOffset:
+    case kWhatSetStopTimeUs:
     {
         sp<AReplyToken> replyID;
         CHECK(msg->senderAwaitsResponse(&replyID));
@@ -1063,11 +1081,13 @@
         int64_t stopTimeUs;
         CHECK(msg->findInt64("stop-time-us", &stopTimeUs));
 
-        // Propagate the timestamp offset to GraphicBufferSource.
+        // Propagate the stop time to GraphicBufferSource.
         if (mFlags & FLAG_USE_SURFACE_INPUT) {
             sp<AMessage> params = new AMessage;
             params->setInt64("stop-time-us", stopTimeUs);
             err = mEncoder->setParameters(params);
+        } else {
+            err = mPuller->setStopTimeUs(stopTimeUs);
         }
 
         sp<AMessage> response = new AMessage;
diff --git a/media/libstagefright/include/AudioSource.h b/media/libstagefright/include/AudioSource.h
index f20c2cd..07a51bf 100644
--- a/media/libstagefright/include/AudioSource.h
+++ b/media/libstagefright/include/AudioSource.h
@@ -53,6 +53,7 @@
 
     virtual status_t read(
             MediaBuffer **buffer, const ReadOptions *options = NULL);
+    virtual status_t setStopTimeUs(int64_t stopTimeUs);
 
     status_t dataCallback(const AudioRecord::Buffer& buffer);
     virtual void signalBufferReturned(MediaBuffer *buffer);
@@ -85,6 +86,8 @@
 
     bool mTrackMaxAmplitude;
     int64_t mStartTimeUs;
+    int64_t mStopSystemTimeUs;
+    int64_t mLastFrameTimestampUs;
     int16_t mMaxAmplitude;
     int64_t mPrevSampleTimeUs;
     int64_t mInitialReadTimeUs;
diff --git a/media/libstagefright/include/CameraSource.h b/media/libstagefright/include/CameraSource.h
index aa56d27..2aaa884 100644
--- a/media/libstagefright/include/CameraSource.h
+++ b/media/libstagefright/include/CameraSource.h
@@ -98,6 +98,7 @@
     virtual status_t stop() { return reset(); }
     virtual status_t read(
             MediaBuffer **buffer, const ReadOptions *options = NULL);
+    virtual status_t setStopTimeUs(int64_t stopTimeUs);
 
     /**
      * Check whether a CameraSource object is properly initialized.
@@ -253,6 +254,7 @@
     List<int64_t> mFrameTimes;
 
     int64_t mFirstFrameTimeUs;
+    int64_t mStopSystemTimeUs;
     int32_t mNumFramesDropped;
     int32_t mNumGlitches;
     int64_t mGlitchDurationThresholdUs;
diff --git a/media/libstagefright/include/MediaCodecSource.h b/media/libstagefright/include/MediaCodecSource.h
index 5e99b78..2259d05 100644
--- a/media/libstagefright/include/MediaCodecSource.h
+++ b/media/libstagefright/include/MediaCodecSource.h
@@ -59,6 +59,8 @@
     virtual status_t read(
             MediaBuffer **buffer,
             const ReadOptions *options = NULL);
+    virtual status_t setStopTimeUs(int64_t stopTimeUs);
+
 
     // MediaBufferObserver
     virtual void signalBufferReturned(MediaBuffer *buffer);
@@ -66,11 +68,7 @@
     // for AHandlerReflector
     void onMessageReceived(const sp<AMessage> &msg);
 
-    // Set GraphicBufferSource stop time. GraphicBufferSource will stop
-    // after receiving a buffer with timestamp larger or equal than stopTimeUs.
-    // All the buffers with timestamp larger or equal to stopTimeUs will be
-    // discarded. stopTimeUs uses SYSTEM_TIME_MONOTONIC time base.
-    status_t setStopStimeUs(int64_t stopTimeUs);
+
 
 protected:
     virtual ~MediaCodecSource();
@@ -85,7 +83,7 @@
         kWhatStop,
         kWhatPause,
         kWhatSetInputBufferTimeOffset,
-        kWhatSetStopTimeOffset,
+        kWhatSetStopTimeUs,
         kWhatGetFirstSampleSystemTimeUs,
         kWhatStopStalled,
     };
diff --git a/media/libstagefright/include/MediaSource.h b/media/libstagefright/include/MediaSource.h
index 1bd3ed0..14adb05 100644
--- a/media/libstagefright/include/MediaSource.h
+++ b/media/libstagefright/include/MediaSource.h
@@ -75,6 +75,23 @@
         return ERROR_UNSUPPORTED;
     }
 
+    // The consumer of this media source requests the source stops sending
+    // buffers with timestamp larger than or equal to stopTimeUs. stopTimeUs
+    // must be in the same time base as the startTime passed in start(). If
+    // source does not support this request, ERROR_UNSUPPORTED will be returned.
+    // If stopTimeUs is invalid, BAD_VALUE will be returned. This could be
+    // called at any time even before source starts and it could be called
+    // multiple times. Setting stopTimeUs to be -1 will effectively cancel the stopTimeUs
+    // set previously. If stopTimeUs is larger than or equal to last buffer's timestamp,
+    // source will start to drop buffer when it gets a buffer with timestamp larger
+    // than or equal to stopTimeUs. If stopTimeUs is smaller than or equal to last
+    // buffer's timestamp, source will drop all the incoming buffers immediately.
+    // After setting stopTimeUs, source may still stop sending buffers with timestamp
+    // less than stopTimeUs if it is stopped by the consumer.
+    virtual status_t setStopTimeUs(int64_t /* stopTimeUs */) {
+        return ERROR_UNSUPPORTED;
+    }
+
 protected:
     virtual ~MediaSource();
 
diff --git a/media/mtp/tests/Android.mk b/media/mtp/tests/Android.mk
index ace0d40..884518c 100644
--- a/media/mtp/tests/Android.mk
+++ b/media/mtp/tests/Android.mk
@@ -4,6 +4,7 @@
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 
 LOCAL_MODULE := mtp_ffs_handle_test
+LOCAL_COMPATIBILITY_SUITE := device-tests
 
 LOCAL_MODULE_TAGS := tests
 
diff --git a/media/mtp/tests/AndroidTest.xml b/media/mtp/tests/AndroidTest.xml
new file mode 100644
index 0000000..c1f4753
--- /dev/null
+++ b/media/mtp/tests/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for mtp_ffs_handle_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="mtp_ffs_handle_test->/data/local/tmp/mtp_ffs_handle_test" />
+    </target_preparer>
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="mtp_ffs_handle_test" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 7eb179a..4b2e643 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -2044,7 +2044,7 @@
 
             // the first primary output opened designates the primary hw device
             if ((mPrimaryHardwareDev == NULL) && (flags & AUDIO_OUTPUT_FLAG_PRIMARY)) {
-                ALOGI("Using module %d has the primary audio interface", module);
+                ALOGI("Using module %d as the primary audio interface", module);
                 mPrimaryHardwareDev = playbackThread->getOutput()->audioHwDev;
 
                 AutoMutex lock(mHardwareLock);
diff --git a/services/audioflinger/FastThread.cpp b/services/audioflinger/FastThread.cpp
index caf7905..cf9fce3 100644
--- a/services/audioflinger/FastThread.cpp
+++ b/services/audioflinger/FastThread.cpp
@@ -170,7 +170,7 @@
                 }
                 int policy = sched_getscheduler(0) & ~SCHED_RESET_ON_FORK;
                 if (!(policy == SCHED_FIFO || policy == SCHED_RR)) {
-                    ALOGE("did not receive expected priority boost");
+                    ALOGE("did not receive expected priority boost on time");
                 }
                 // This may be overly conservative; there could be times that the normal mixer
                 // requests such a brief cold idle that it doesn't require resetting this flag.
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 3b1edec..6a75bb0 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -447,6 +447,8 @@
         return "RECORD";
     case OFFLOAD:
         return "OFFLOAD";
+    case MMAP:
+        return "MMAP";
     default:
         return "unknown";
     }
@@ -533,7 +535,7 @@
 {
     status_t status = initCheck();
     if (status == NO_ERROR) {
-        ALOGI("AudioFlinger's thread %p ready to run", this);
+        ALOGI("AudioFlinger's thread %p tid=%d ready to run", this, getTid());
     } else {
         ALOGE("No working audio driver found.");
     }
@@ -809,14 +811,15 @@
     char buffer[SIZE];
     String8 result;
 
+    dprintf(fd, "\n%s thread %p, name %s, tid %d, type %d (%s):\n", isOutput() ? "Output" : "Input",
+            this, mThreadName, getTid(), type(), threadTypeToString(type()));
+
     bool locked = AudioFlinger::dumpTryLock(mLock);
     if (!locked) {
-        dprintf(fd, "thread %p may be deadlocked\n", this);
+        dprintf(fd, "  Thread may be deadlocked\n");
     }
 
-    dprintf(fd, "  Thread name: %s\n", mThreadName);
     dprintf(fd, "  I/O handle: %d\n", mId);
-    dprintf(fd, "  TID: %d\n", getTid());
     dprintf(fd, "  Standby: %s\n", mStandby ? "yes" : "no");
     dprintf(fd, "  Sample rate: %u Hz\n", mSampleRate);
     dprintf(fd, "  HAL frame count: %zu\n", mFrameCount);
@@ -1778,8 +1781,6 @@
 
 void AudioFlinger::PlaybackThread::dumpInternals(int fd, const Vector<String16>& args)
 {
-    dprintf(fd, "\nOutput thread %p type %d (%s):\n", this, type(), threadTypeToString(type()));
-
     dumpBase(fd, args);
 
     dprintf(fd, "  Normal frame count: %zu\n", mNormalFrameCount);
@@ -4714,34 +4715,42 @@
     dprintf(fd, "  AudioMixer tracks: 0x%08x\n", mAudioMixer->trackNames());
     dprintf(fd, "  Master mono: %s\n", mMasterMono ? "on" : "off");
 
-    // Make a non-atomic copy of fast mixer dump state so it won't change underneath us
-    // while we are dumping it.  It may be inconsistent, but it won't mutate!
-    // This is a large object so we place it on the heap.
-    // FIXME 25972958: Need an intelligent copy constructor that does not touch unused pages.
-    const FastMixerDumpState *copy = new FastMixerDumpState(mFastMixerDumpState);
-    copy->dump(fd);
-    delete copy;
+    if (hasFastMixer()) {
+        dprintf(fd, "  FastMixer thread %p tid=%d", mFastMixer.get(), mFastMixer->getTid());
+
+        // Make a non-atomic copy of fast mixer dump state so it won't change underneath us
+        // while we are dumping it.  It may be inconsistent, but it won't mutate!
+        // This is a large object so we place it on the heap.
+        // FIXME 25972958: Need an intelligent copy constructor that does not touch unused pages.
+        const FastMixerDumpState *copy = new FastMixerDumpState(mFastMixerDumpState);
+        copy->dump(fd);
+        delete copy;
 
 #ifdef STATE_QUEUE_DUMP
-    // Similar for state queue
-    StateQueueObserverDump observerCopy = mStateQueueObserverDump;
-    observerCopy.dump(fd);
-    StateQueueMutatorDump mutatorCopy = mStateQueueMutatorDump;
-    mutatorCopy.dump(fd);
+        // Similar for state queue
+        StateQueueObserverDump observerCopy = mStateQueueObserverDump;
+        observerCopy.dump(fd);
+        StateQueueMutatorDump mutatorCopy = mStateQueueMutatorDump;
+        mutatorCopy.dump(fd);
 #endif
 
+#ifdef AUDIO_WATCHDOG
+        if (mAudioWatchdog != 0) {
+            // Make a non-atomic copy of audio watchdog dump so it won't change underneath us
+            AudioWatchdogDump wdCopy = mAudioWatchdogDump;
+            wdCopy.dump(fd);
+        }
+#endif
+
+    } else {
+        dprintf(fd, "  No FastMixer\n");
+    }
+
 #ifdef TEE_SINK
     // Write the tee output to a .wav file
     dumpTee(fd, mTeeSource, mId);
 #endif
 
-#ifdef AUDIO_WATCHDOG
-    if (mAudioWatchdog != 0) {
-        // Make a non-atomic copy of audio watchdog dump so it won't change underneath us
-        AudioWatchdogDump wdCopy = mAudioWatchdogDump;
-        wdCopy.dump(fd);
-    }
-#endif
 }
 
 uint32_t AudioFlinger::MixerThread::idleSleepTimeUs() const
@@ -6872,8 +6881,6 @@
 
 void AudioFlinger::RecordThread::dumpInternals(int fd, const Vector<String16>& args)
 {
-    dprintf(fd, "\nInput thread %p:\n", this);
-
     dumpBase(fd, args);
 
     AudioStreamIn *input = mInput;
@@ -8124,8 +8131,6 @@
 
 void AudioFlinger::MmapThread::dumpInternals(int fd, const Vector<String16>& args)
 {
-    dprintf(fd, "\nMmap thread %p:\n", this);
-
     dumpBase(fd, args);
 
     dprintf(fd, "  Attributes: content type %d usage %d source %d\n",
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 8270e74..cc66cad 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -31,6 +31,7 @@
         RECORD,             // Thread class is RecordThread
         OFFLOAD,            // Thread class is OffloadThread
         MMAP                // control thread for MMAP stream
+        // If you add any values here, also update ThreadBase::threadTypeToString()
     };
 
     static const char *threadTypeToString(type_t type);
diff --git a/services/audioflinger/TypedLogger.h b/services/audioflinger/TypedLogger.h
index 0b23c7c..cc28095 100644
--- a/services/audioflinger/TypedLogger.h
+++ b/services/audioflinger/TypedLogger.h
@@ -19,7 +19,72 @@
 #define ANDROID_TYPED_LOGGER_H
 
 #include <media/nbaio/NBLog.h>
-#define LOGT(fmt, ...) logWriterTLS->logFormat(fmt, ##__VA_ARGS__) // TODO: check null pointer
+#include <algorithm>
+
+/*
+Fowler-Noll-Vo (FNV-1a) hash function for the file name.
+Hashes at compile time. FNV-1a iterative function:
+
+hash = offset_basis
+for each byte to be hashed
+        hash = hash xor byte
+        hash = hash * FNV_prime
+return hash
+
+offset_basis and FNV_prime values depend on the size of the hash output
+Following values are defined by FNV and should not be changed arbitrarily
+*/
+
+template<typename T>
+constexpr T offset_basis();
+
+template<typename T>
+constexpr T FNV_prime();
+
+template<>
+constexpr uint32_t offset_basis<uint32_t>() {
+    return 2166136261u;
+}
+
+template<>
+constexpr uint32_t FNV_prime<uint32_t>() {
+    return 16777619u;
+}
+
+template<>
+constexpr uint64_t offset_basis<uint64_t>() {
+    return 14695981039346656037ull;
+}
+
+template<>
+constexpr uint64_t FNV_prime<uint64_t>() {
+    return 1099511628211ull;
+}
+
+template <typename T, size_t n>
+constexpr T fnv1a(const char (&file)[n], int i = n - 1) {
+    return i == -1 ? offset_basis<T>() : (fnv1a<T>(file, i - 1) ^ file[i]) * FNV_prime<T>();
+}
+
+template <size_t n>
+constexpr uint64_t hash(const char (&file)[n], uint32_t line) {
+    // Line numbers over or equal to 2^16 are clamped to 2^16 - 1. This way increases collisions
+    // compared to wrapping around, but is easy to identify because it doesn't produce aliasing.
+    // It's a very unlikely case anyways.
+    return ((fnv1a<uint64_t>(file) << 16) ^ ((fnv1a<uint64_t>(file) >> 32) & 0xFFFF0000)) |
+           std::min(line, 0xFFFFu);
+}
+
+// Write formatted entry to log
+#define LOGT(fmt, ...) logWriterTLS->logFormat((fmt), \
+                                               hash(__FILE__, __LINE__), \
+                                               ##__VA_ARGS__) // TODO: check null pointer
+
+// Write histogram timestamp entry
+#define LOG_HIST_TS() logWriterTLS->logHistTS(hash(__FILE__, __LINE__))
+
+// flush all histogram
+#define LOG_HIST_FLUSH() logWriterTLS->logHistFlush(hash(__FILE__, __LINE__))
 
 namespace android {
 extern "C" {
diff --git a/services/mediaanalytics/Android.mk b/services/mediaanalytics/Android.mk
index f7197af..9e2813e 100644
--- a/services/mediaanalytics/Android.mk
+++ b/services/mediaanalytics/Android.mk
@@ -5,7 +5,12 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
-    main_mediametrics.cpp          \
+    main_mediametrics.cpp              \
+    MetricsSummarizerCodec.cpp         \
+    MetricsSummarizerExtractor.cpp     \
+    MetricsSummarizerPlayer.cpp        \
+    MetricsSummarizerRecorder.cpp      \
+    MetricsSummarizer.cpp              \
     MediaAnalyticsService.cpp
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index 35c1f5b..876c685 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -20,6 +20,7 @@
 #define LOG_TAG "MediaAnalyticsService"
 #include <utils/Log.h>
 
+#include <stdint.h>
 #include <inttypes.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -70,11 +71,28 @@
 
 #include "MediaAnalyticsService.h"
 
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerCodec.h"
+#include "MetricsSummarizerExtractor.h"
+#include "MetricsSummarizerPlayer.h"
+#include "MetricsSummarizerRecorder.h"
+
 
 namespace android {
 
 
-#define DEBUG_QUEUE     0
+
+// summarized records
+// up to 48 sets, each covering an hour -- at least 2 days of coverage
+// (will be longer if there are hours without any media action)
+static const nsecs_t kNewSetIntervalNs = 3600*(1000*1000*1000ll);
+static const int kMaxRecordSets = 48;
+// individual records kept in memory
+static const int kMaxRecords    = 100;
+
+
+static const char *kServiceName = "media.metrics";
+
 
 //using android::status_t;
 //using android::OK;
@@ -85,18 +103,67 @@
 
 void MediaAnalyticsService::instantiate() {
     defaultServiceManager()->addService(
-            String16("media.metrics"), new MediaAnalyticsService());
+            String16(kServiceName), new MediaAnalyticsService());
 }
 
-// XXX: add dynamic controls for mMaxRecords
+// handle sets of summarizers
+MediaAnalyticsService::SummarizerSet::SummarizerSet() {
+    mSummarizers = new List<MetricsSummarizer *>();
+}
+MediaAnalyticsService::SummarizerSet::~SummarizerSet() {
+    // empty the list
+    List<MetricsSummarizer *> *l = mSummarizers;
+    while (l->size() > 0) {
+        MetricsSummarizer *summarizer = *(l->begin());
+        l->erase(l->begin());
+        delete summarizer;
+    }
+}
+
+void MediaAnalyticsService::newSummarizerSet() {
+    ALOGD("MediaAnalyticsService::newSummarizerSet");
+    MediaAnalyticsService::SummarizerSet *set = new MediaAnalyticsService::SummarizerSet();
+    nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+    set->setStarted(now);
+
+    set->appendSummarizer(new MetricsSummarizerExtractor("extractor"));
+    set->appendSummarizer(new MetricsSummarizerCodec("codec"));
+    set->appendSummarizer(new MetricsSummarizerPlayer("nuplayer"));
+    set->appendSummarizer(new MetricsSummarizerRecorder("recorder"));
+
+    // ALWAYS at the end, since it catches everything
+    set->appendSummarizer(new MetricsSummarizer(NULL));
+
+    // inject this set at the BACK of the list.
+    mSummarizerSets->push_back(set);
+    mCurrentSet = set;
+
+    // limit the # that we have
+    if (mMaxRecordSets > 0) {
+        List<SummarizerSet *> *l = mSummarizerSets;
+        while (l->size() > (size_t) mMaxRecordSets) {
+            ALOGD("Deleting oldest record set....");
+            MediaAnalyticsService::SummarizerSet *oset = *(l->begin());
+            l->erase(l->begin());
+            delete oset;
+            mSetsDiscarded++;
+        }
+    }
+}
+
 MediaAnalyticsService::MediaAnalyticsService()
-        : mMaxRecords(100) {
+        : mMaxRecords(kMaxRecords),
+          mMaxRecordSets(kMaxRecordSets),
+          mNewSetInterval(kNewSetIntervalNs) {
 
     ALOGD("MediaAnalyticsService created");
     // clear our queues
     mOpen = new List<MediaAnalyticsItem *>();
     mFinalized = new List<MediaAnalyticsItem *>();
 
+    mSummarizerSets = new List<MediaAnalyticsService::SummarizerSet *>();
+    newSummarizerSet();
+
     mItemsSubmitted = 0;
     mItemsFinalized = 0;
     mItemsDiscarded = 0;
@@ -109,7 +176,13 @@
 MediaAnalyticsService::~MediaAnalyticsService() {
         ALOGD("MediaAnalyticsService destroyed");
 
-    // XXX: clean out mOpen and mFinalized
+    // clean out mOpen and mFinalized
+    delete mOpen;
+    mOpen = NULL;
+    delete mFinalized;
+    mFinalized = NULL;
+
+    // XXX: clean out the summaries
 }
 
 
@@ -145,7 +218,7 @@
         case AID_MEDIA_EX:
         case AID_MEDIA_DRM:
             // trusted source, only override default values
-            isTrusted = true;
+                isTrusted = true;
             if (uid_given == (-1)) {
                 item->setUid(uid);
             }
@@ -197,10 +270,12 @@
                 oitem = NULL;
             } else {
                 oitem->setFinalized(true);
+                summarize(oitem);
                 saveItem(mFinalized, oitem, 0);
             }
             // new record could itself be marked finalized...
             if (finalizing) {
+                summarize(item);
                 saveItem(mFinalized, item, 0);
                 mItemsFinalized++;
             } else {
@@ -211,6 +286,7 @@
             // combine the records, send it to finalized if appropriate
             oitem->merge(item);
             if (finalizing) {
+                summarize(oitem);
                 saveItem(mFinalized, oitem, 0);
                 mItemsFinalized++;
             }
@@ -229,6 +305,7 @@
                 delete item;
                 item = NULL;
             } else {
+                summarize(item);
                 saveItem(mFinalized, item, 0);
                 mItemsFinalized++;
             }
@@ -239,26 +316,6 @@
     return id;
 }
 
-List<MediaAnalyticsItem *> *MediaAnalyticsService::getMediaAnalyticsItemList(bool finished, nsecs_t ts) {
-    // this might never get called; the binder interface maps to the full parm list
-    // on the client side before making the binder call.
-    // but this lets us be sure...
-    List<MediaAnalyticsItem*> *list;
-    list = getMediaAnalyticsItemList(finished, ts, MediaAnalyticsItem::kKeyAny);
-    return list;
-}
-
-List<MediaAnalyticsItem *> *MediaAnalyticsService::getMediaAnalyticsItemList(bool , nsecs_t , MediaAnalyticsItem::Key ) {
-
-    // XXX: implement the get-item-list semantics
-
-    List<MediaAnalyticsItem *> *list = NULL;
-    // set up our query on the persistent data
-    // slurp in all of the pieces
-    // return that
-    return list;
-}
-
 status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
 {
     const size_t SIZE = 512;
@@ -277,15 +334,21 @@
 
     // crack any parameters
     bool clear = false;
+    bool summary = false;
     nsecs_t ts_since = 0;
+    String16 summaryOption("-summary");
     String16 clearOption("-clear");
     String16 sinceOption("-since");
     String16 helpOption("-help");
+    String16 onlyOption("-only");
+    const char *only = NULL;
     int n = args.size();
     for (int i = 0; i < n; i++) {
         String8 myarg(args[i]);
         if (args[i] == clearOption) {
             clear = true;
+        } else if (args[i] == summaryOption) {
+            summary = true;
         } else if (args[i] == sinceOption) {
             i++;
             if (i < n) {
@@ -301,12 +364,27 @@
             }
             // command line is milliseconds; internal units are nano-seconds
             ts_since *= 1000*1000;
+        } else if (args[i] == onlyOption) {
+            i++;
+            if (i < n) {
+                String8 value(args[i]);
+                const char *p = value.string();
+                char *q = strdup(p);
+                if (q != NULL) {
+                    if (only != NULL) {
+                        free((void*)only);
+                    }
+                only = q;
+                }
+            }
         } else if (args[i] == helpOption) {
             result.append("Recognized parameters:\n");
             result.append("-help        this help message\n");
+            result.append("-summary     show summary info\n");
             result.append("-clear       clears out saved records\n");
-            result.append("-since XXX   include records since XXX\n");
-            result.append("             (XXX is milliseconds since the UNIX epoch)\n");
+            result.append("-only X      process records for component X\n");
+            result.append("-since X     include records since X\n");
+            result.append("             (X is milliseconds since the UNIX epoch)\n");
             write(fd, result.string(), result.size());
             return NO_ERROR;
         }
@@ -314,9 +392,42 @@
 
     Mutex::Autolock _l(mLock);
 
-    snprintf(buffer, SIZE, "Dump of the mediametrics process:\n");
+    // we ALWAYS dump this piece
+    snprintf(buffer, SIZE, "Dump of the %s process:\n", kServiceName);
     result.append(buffer);
 
+    dumpHeaders(result, ts_since);
+
+    // only want 1, to avoid confusing folks that parse the output
+    if (summary) {
+        dumpSummaries(result, ts_since, only);
+    } else {
+        dumpRecent(result, ts_since, only);
+    }
+
+
+    if (clear) {
+        // remove everything from the finalized queue
+        while (mFinalized->size() > 0) {
+            MediaAnalyticsItem * oitem = *(mFinalized->begin());
+            mFinalized->erase(mFinalized->begin());
+            delete oitem;
+            mItemsDiscarded++;
+        }
+
+        // shall we clear the summary data too?
+
+    }
+
+    write(fd, result.string(), result.size());
+    return NO_ERROR;
+}
+
+// dump headers
+void MediaAnalyticsService::dumpHeaders(String8 &result, nsecs_t ts_since) {
+    const size_t SIZE = 512;
+    char buffer[SIZE];
+
     int enabled = MediaAnalyticsItem::isEnabled();
     if (enabled) {
         snprintf(buffer, SIZE, "Metrics gathering: enabled\n");
@@ -331,50 +442,71 @@
         " Discarded: %" PRId64 "\n",
         mItemsSubmitted, mItemsFinalized, mItemsDiscarded);
     result.append(buffer);
+    snprintf(buffer, SIZE,
+        "Summary Sets Discarded: %" PRId64 "\n", mSetsDiscarded);
+    result.append(buffer);
     if (ts_since != 0) {
         snprintf(buffer, SIZE,
             "Dumping Queue entries more recent than: %" PRId64 "\n",
             (int64_t) ts_since);
         result.append(buffer);
     }
+}
+
+// dump summary info
+void MediaAnalyticsService::dumpSummaries(String8 &result, nsecs_t ts_since, const char *only) {
+    const size_t SIZE = 512;
+    char buffer[SIZE];
+    int slot = 0;
+
+    snprintf(buffer, SIZE, "\nSummarized Metrics:\n");
+    result.append(buffer);
+
+    // have each of the distillers dump records
+    if (mSummarizerSets != NULL) {
+        List<SummarizerSet *>::iterator itSet = mSummarizerSets->begin();
+        for (; itSet != mSummarizerSets->end(); itSet++) {
+            nsecs_t when = (*itSet)->getStarted();
+            if (when < ts_since) {
+                continue;
+            }
+            List<MetricsSummarizer *> *list = (*itSet)->getSummarizers();
+            List<MetricsSummarizer *>::iterator it = list->begin();
+            for (; it != list->end(); it++) {
+                if (only != NULL && strcmp(only, (*it)->getKey()) != 0) {
+                    ALOGV("Told to omit '%s'", (*it)->getKey());
+                }
+                AString distilled = (*it)->dumpSummary(slot, only);
+                result.append(distilled.c_str());
+            }
+        }
+    }
+}
+
+// the recent, detailed queues
+void MediaAnalyticsService::dumpRecent(String8 &result, nsecs_t ts_since, const char * only) {
+    const size_t SIZE = 512;
+    char buffer[SIZE];
 
     // show the recently recorded records
     snprintf(buffer, sizeof(buffer), "\nFinalized Metrics (oldest first):\n");
     result.append(buffer);
-    result.append(this->dumpQueue(mFinalized, ts_since));
+    result.append(this->dumpQueue(mFinalized, ts_since, only));
 
     snprintf(buffer, sizeof(buffer), "\nIn-Progress Metrics (newest first):\n");
     result.append(buffer);
-    result.append(this->dumpQueue(mOpen, ts_since));
+    result.append(this->dumpQueue(mOpen, ts_since, only));
 
     // show who is connected and injecting records?
     // talk about # records fed to the 'readers'
     // talk about # records we discarded, perhaps "discarded w/o reading" too
-
-    if (clear) {
-        // remove everything from the finalized queue
-        while (mFinalized->size() > 0) {
-            MediaAnalyticsItem * oitem = *(mFinalized->begin());
-            if (DEBUG_QUEUE) {
-                ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
-                    oitem->getKey().c_str(), oitem->getSessionID(),
-                    oitem->getTimestamp());
-            }
-            mFinalized->erase(mFinalized->begin());
-            mItemsDiscarded++;
-        }
-    }
-
-    write(fd, result.string(), result.size());
-    return NO_ERROR;
 }
-
 // caller has locked mLock...
 String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList) {
-    return dumpQueue(theList, (nsecs_t) 0);
+    return dumpQueue(theList, (nsecs_t) 0, NULL);
 }
 
-String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList, nsecs_t ts_since) {
+String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList, nsecs_t ts_since, const char * only) {
     String8 result;
     int slot = 0;
 
@@ -387,6 +519,11 @@
             if (when < ts_since) {
                 continue;
             }
+            if (only != NULL &&
+                strcmp(only, (*it)->getKey().c_str()) != 0) {
+                ALOGV("Omit '%s', it's not '%s'", (*it)->getKey().c_str(), only);
+                continue;
+            }
             AString entry = (*it)->toString();
             result.appendFormat("%5d: %s\n", slot, entry.c_str());
             slot++;
@@ -405,13 +542,6 @@
 
     Mutex::Autolock _l(mLock);
 
-    if (DEBUG_QUEUE) {
-        ALOGD("Inject a record: session %" PRId64 " ts %" PRId64 "",
-            item->getSessionID(), item->getTimestamp());
-        String8 before = dumpQueue(l);
-        ALOGD("Q before insert: %s", before.string());
-    }
-
     // adding at back of queue (fifo order)
     if (front)  {
         l->push_front(item);
@@ -419,30 +549,15 @@
         l->push_back(item);
     }
 
-    if (DEBUG_QUEUE) {
-        String8 after = dumpQueue(l);
-        ALOGD("Q after insert: %s", after.string());
-    }
-
     // keep removing old records the front until we're in-bounds
     if (mMaxRecords > 0) {
         while (l->size() > (size_t) mMaxRecords) {
             MediaAnalyticsItem * oitem = *(l->begin());
-            if (DEBUG_QUEUE) {
-                ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
-                    oitem->getKey().c_str(), oitem->getSessionID(),
-                    oitem->getTimestamp());
-            }
             l->erase(l->begin());
             delete oitem;
             mItemsDiscarded++;
         }
     }
-
-    if (DEBUG_QUEUE) {
-        String8 after = dumpQueue(l);
-        ALOGD("Q after cleanup: %s", after.string());
-    }
 }
 
 // are they alike enough that nitem can be folded into oitem?
@@ -515,29 +630,14 @@
 
     Mutex::Autolock _l(mLock);
 
-    if(DEBUG_QUEUE) {
-        String8 before = dumpQueue(l);
-        ALOGD("Q before delete: %s", before.string());
-    }
-
     for (List<MediaAnalyticsItem *>::iterator it = l->begin();
         it != l->end(); it++) {
         if ((*it)->getSessionID() != item->getSessionID())
             continue;
-
-        if (DEBUG_QUEUE) {
-            ALOGD(" --- removing record for SessionID %" PRId64 "", item->getSessionID());
-            ALOGD("drop record at %s:%d", __FILE__, __LINE__);
-        }
         delete *it;
         l->erase(it);
         break;
     }
-
-    if (DEBUG_QUEUE) {
-        String8 after = dumpQueue(l);
-        ALOGD("Q after delete: %s", after.string());
-    }
 }
 
 static AString allowedKeys[] =
@@ -579,5 +679,43 @@
     return false;
 }
 
+// insert into the appropriate summarizer.
+// we make our own copy to save/summarize
+void MediaAnalyticsService::summarize(MediaAnalyticsItem *item) {
+
+    ALOGV("MediaAnalyticsService::summarize()");
+
+    if (item == NULL) {
+        return;
+    }
+
+    nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+    if (mCurrentSet == NULL
+        || (mCurrentSet->getStarted() + mNewSetInterval < now)) {
+        newSummarizerSet();
+    }
+
+    if (mCurrentSet == NULL) {
+        return;
+    }
+
+    List<MetricsSummarizer *> *summarizers = mCurrentSet->getSummarizers();
+    List<MetricsSummarizer *>::iterator it = summarizers->begin();
+    for (; it != summarizers->end(); it++) {
+        if ((*it)->isMine(*item)) {
+            break;
+        }
+    }
+    if (it == summarizers->end()) {
+        ALOGD("no handler for type %s", item->getKey().c_str());
+        return;               // no handler
+    }
+
+    // invoke the summarizer. summarizer will make whatever copies
+    // it wants; the caller retains ownership of item.
+
+    (*it)->handleRecord(item);
+
+}
 
 } // namespace android
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index d2b0f09..6685967 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -28,6 +28,8 @@
 
 #include <media/IMediaAnalyticsService.h>
 
+#include "MetricsSummarizer.h"
+
 
 namespace android {
 
@@ -39,12 +41,6 @@
     // on this side, caller surrenders ownership
     virtual int64_t submit(MediaAnalyticsItem *item, bool forcenew);
 
-    virtual List<MediaAnalyticsItem *>
-            *getMediaAnalyticsItemList(bool finished, int64_t ts);
-    virtual List<MediaAnalyticsItem *>
-            *getMediaAnalyticsItemList(bool finished, int64_t ts, MediaAnalyticsItem::Key key);
-
-
     static  void            instantiate();
     virtual status_t        dump(int fd, const Vector<String16>& args);
 
@@ -58,6 +54,7 @@
     int64_t mItemsSubmitted;
     int64_t mItemsFinalized;
     int64_t mItemsDiscarded;
+    int64_t mSetsDiscarded;
     MediaAnalyticsItem::SessionID_t mLastSessionID;
 
     // partitioned a bit so we don't over serialize
@@ -67,6 +64,10 @@
     // the most we hold in memory
     // up to this many in each queue (open, finalized)
     int32_t mMaxRecords;
+    // # of sets of summaries
+    int32_t mMaxRecordSets;
+    // nsecs until we start a new record set
+    nsecs_t mNewSetInterval;
 
     // input validation after arrival from client
     bool contentValid(MediaAnalyticsItem *item, bool isTrusted);
@@ -82,12 +83,47 @@
     MediaAnalyticsItem *findItem(List<MediaAnalyticsItem *> *,
                                      MediaAnalyticsItem *, bool removeit);
 
+    // summarizers
+    void summarize(MediaAnalyticsItem *item);
+    class SummarizerSet {
+        nsecs_t mStarted;
+        List<MetricsSummarizer *> *mSummarizers;
+
+      public:
+        void appendSummarizer(MetricsSummarizer *s) {
+            if (s) {
+                mSummarizers->push_back(s);
+            }
+        };
+        nsecs_t getStarted() { return mStarted;}
+        void setStarted(nsecs_t started) {mStarted = started;}
+        List<MetricsSummarizer *> *getSummarizers() { return mSummarizers;}
+
+        SummarizerSet();
+        ~SummarizerSet();
+    };
+    void newSummarizerSet();
+    List<SummarizerSet *> *mSummarizerSets;
+    SummarizerSet *mCurrentSet;
+    List<MetricsSummarizer *> *getFirstSet() {
+        List<SummarizerSet *>::iterator first = mSummarizerSets->begin();
+        if (first != mSummarizerSets->end()) {
+            return (*first)->getSummarizers();
+        }
+        return NULL;
+    }
+
     void saveItem(MediaAnalyticsItem);
     void saveItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *, int);
     void deleteItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *);
 
+    // support for generating output
     String8 dumpQueue(List<MediaAnalyticsItem*> *);
-    String8 dumpQueue(List<MediaAnalyticsItem*> *, nsecs_t);
+    String8 dumpQueue(List<MediaAnalyticsItem*> *, nsecs_t, const char *only);
+
+    void dumpHeaders(String8 &result, nsecs_t ts_since);
+    void dumpSummaries(String8 &result, nsecs_t ts_since, const char * only);
+    void dumpRecent(String8 &result, nsecs_t ts_since, const char * only);
 
 };
 
diff --git a/services/mediaanalytics/MetricsSummarizer.cpp b/services/mediaanalytics/MetricsSummarizer.cpp
new file mode 100644
index 0000000..fc8f594
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizer.cpp
@@ -0,0 +1,285 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "MetricsSummarizer"
+#include <utils/Log.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+#define DEBUG_SORT      0
+#define DEBUG_QUEUE     0
+
+
+MetricsSummarizer::MetricsSummarizer(const char *key)
+    : mIgnorables(NULL)
+{
+    ALOGV("MetricsSummarizer::MetricsSummarizer");
+
+    if (key == NULL) {
+        mKey = key;
+    } else {
+        mKey = strdup(key);
+    }
+
+    mSummaries = new List<MediaAnalyticsItem *>();
+}
+
+MetricsSummarizer::~MetricsSummarizer()
+{
+    ALOGV("MetricsSummarizer::~MetricsSummarizer");
+    if (mKey) {
+        free((void *)mKey);
+        mKey = NULL;
+    }
+
+    // clear the list of items we have saved
+    while (mSummaries->size() > 0) {
+        MediaAnalyticsItem * oitem = *(mSummaries->begin());
+        if (DEBUG_QUEUE) {
+            ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
+                oitem->getKey().c_str(), oitem->getSessionID(),
+                oitem->getTimestamp());
+        }
+        mSummaries->erase(mSummaries->begin());
+        delete oitem;
+    }
+}
+
+// so we know what summarizer we were using
+const char *MetricsSummarizer::getKey() {
+    const char *value = mKey;
+    if (value == NULL) {
+        value = "unknown";
+    }
+    return value;
+}
+
+// should the record be given to this summarizer
+bool MetricsSummarizer::isMine(MediaAnalyticsItem &item)
+{
+    const char *incoming = item.getKey().c_str();
+    if (incoming == NULL) {
+        incoming = "unspecified";
+    }
+    if (mKey == NULL)
+        return true;
+    if (strcmp(mKey, incoming) != 0) {
+        return false;
+    }
+    // since nothing failed....
+    return true;
+}
+
+AString MetricsSummarizer::dumpSummary(int &slot)
+{
+    return dumpSummary(slot, NULL);
+}
+
+AString MetricsSummarizer::dumpSummary(int &slot, const char *only)
+{
+    AString value = "";
+
+    List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
+    if (it != mSummaries->end()) {
+        char buf[16];   // enough for "#####: "
+        for (; it != mSummaries->end(); it++) {
+            if (only != NULL && strcmp(only, (*it)->getKey().c_str()) != 0) {
+                continue;
+            }
+            AString entry = (*it)->toString();
+            snprintf(buf, sizeof(buf), "%5d: ", slot);
+            value.append(buf);
+            value.append(entry.c_str());
+            value.append("\n");
+            slot++;
+        }
+    }
+    return value;
+}
+
+void MetricsSummarizer::setIgnorables(const char **ignorables) {
+    mIgnorables = ignorables;
+}
+
+const char **MetricsSummarizer::getIgnorables() {
+    return mIgnorables;
+}
+
+void MetricsSummarizer::handleRecord(MediaAnalyticsItem *item) {
+
+    ALOGV("MetricsSummarizer::handleRecord() for %s",
+                item == NULL ? "<nothing>" : item->toString().c_str());
+
+    if (item == NULL) {
+        return;
+    }
+
+    List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
+    for (; it != mSummaries->end(); it++) {
+        bool good = sameAttributes((*it), item, getIgnorables());
+        ALOGV("Match against %s says %d",
+              (*it)->toString().c_str(), good);
+        if (good)
+            break;
+    }
+    if (it == mSummaries->end()) {
+            ALOGV("save new record");
+            item = item->dup();
+            if (item == NULL) {
+                ALOGE("unable to save MediaMetrics record");
+            }
+            sortProps(item);
+            item->setInt32("count",1);
+            mSummaries->push_back(item);
+    } else {
+            ALOGV("increment existing record");
+            (*it)->addInt32("count",1);
+            mergeRecord(*(*it), *item);
+    }
+}
+
+void MetricsSummarizer::mergeRecord(MediaAnalyticsItem &/*have*/, MediaAnalyticsItem &/*item*/) {
+    // default is no further massaging.
+    ALOGV("MetricsSummarizer::mergeRecord() [default]");
+    return;
+}
+
+
+//
+// Comparators
+//
+
+// testing that all of 'single' is in 'summ'
+// and that the values match.
+// 'summ' may have extra fields.
+// 'ignorable' is a set of things that we don't worry about matching up
+// (usually time- or count-based values we'll sum elsewhere)
+bool MetricsSummarizer::sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
+
+    if (single == NULL || summ == NULL) {
+        return false;
+    }
+    ALOGV("MetricsSummarizer::sameAttributes(): summ %s", summ->toString().c_str());
+    ALOGV("MetricsSummarizer::sameAttributes(): single %s", single->toString().c_str());
+
+    // this can be made better.
+    for(size_t i=0;i<single->mPropCount;i++) {
+        MediaAnalyticsItem::Prop *prop1 = &(single->mProps[i]);
+        const char *attrName = prop1->mName;
+        ALOGV("compare on attr '%s'", attrName);
+
+        // is it something we should ignore
+        if (ignorable != NULL) {
+            const char **ig = ignorable;
+            while (*ig) {
+                if (strcmp(*ig, attrName) == 0) {
+                    break;
+                }
+                ig++;
+            }
+            if (*ig) {
+                ALOGV("we don't mind that it has attr '%s'", attrName);
+                continue;
+            }
+        }
+
+        MediaAnalyticsItem::Prop *prop2 = summ->findProp(attrName);
+        if (prop2 == NULL) {
+            ALOGV("summ doesn't have this attr");
+            return false;
+        }
+        if (prop1->mType != prop2->mType) {
+            ALOGV("mismatched attr types");
+            return false;
+        }
+        switch (prop1->mType) {
+            case MediaAnalyticsItem::kTypeInt32:
+                if (prop1->u.int32Value != prop2->u.int32Value)
+                    return false;
+                break;
+            case MediaAnalyticsItem::kTypeInt64:
+                if (prop1->u.int64Value != prop2->u.int64Value)
+                    return false;
+                break;
+            case MediaAnalyticsItem::kTypeDouble:
+                // XXX: watch out for floating point comparisons!
+                if (prop1->u.doubleValue != prop2->u.doubleValue)
+                    return false;
+                break;
+            case MediaAnalyticsItem::kTypeCString:
+                if (strcmp(prop1->u.CStringValue, prop2->u.CStringValue) != 0)
+                    return false;
+                break;
+            case MediaAnalyticsItem::kTypeRate:
+                if (prop1->u.rate.count != prop2->u.rate.count)
+                    return false;
+                if (prop1->u.rate.duration != prop2->u.rate.duration)
+                    return false;
+                break;
+            default:
+                return false;
+        }
+    }
+
+    return true;
+}
+
+bool MetricsSummarizer::sameAttributesId(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
+
+    // verify same user
+    if (summ->mPid != single->mPid)
+        return false;
+
+    // and finally do the more expensive validation of the attributes
+    return sameAttributes(summ, single, ignorable);
+}
+
+int MetricsSummarizer::PropSorter(const void *a, const void *b) {
+    MediaAnalyticsItem::Prop *ai = (MediaAnalyticsItem::Prop *)a;
+    MediaAnalyticsItem::Prop *bi = (MediaAnalyticsItem::Prop *)b;
+    return strcmp(ai->mName, bi->mName);
+}
+
+// we sort in the summaries so that it looks pretty in the dumpsys
+void MetricsSummarizer::sortProps(MediaAnalyticsItem *item) {
+    if (item->mPropCount != 0) {
+        if (DEBUG_SORT) {
+            ALOGD("sortProps(pre): %s", item->toString().c_str());
+        }
+        qsort(item->mProps, item->mPropCount,
+              sizeof(MediaAnalyticsItem::Prop), MetricsSummarizer::PropSorter);
+        if (DEBUG_SORT) {
+            ALOGD("sortProps(pst): %s", item->toString().c_str());
+        }
+    }
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizer.h b/services/mediaanalytics/MetricsSummarizer.h
new file mode 100644
index 0000000..0b64eac
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizer.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZER_H
+#define ANDROID_METRICSSUMMARIZER_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+
+namespace android {
+
+class MetricsSummarizer
+{
+
+ public:
+
+    MetricsSummarizer(const char *key);
+    virtual ~MetricsSummarizer();
+
+    // show the key
+    const char * getKey();
+
+    // should the record be given to this summarizer
+    bool isMine(MediaAnalyticsItem &item);
+
+    // hand the record to this summarizer
+    void handleRecord(MediaAnalyticsItem *item);
+
+    virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
+
+    // dump the summarized records (for dumpsys)
+    AString dumpSummary(int &slot);
+    AString dumpSummary(int &slot, const char *only);
+
+    void setIgnorables(const char **);
+    const char **getIgnorables();
+
+ protected:
+
+    // various comparators
+    // "do these records have same attributes and values in those attrs"
+    // ditto, but watch for "error" fields
+    bool sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
+    // attributes + from the same app/userid
+    bool sameAttributesId(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
+
+    static int PropSorter(const void *a, const void *b);
+    void sortProps(MediaAnalyticsItem *item);
+
+ private:
+    const char *mKey;
+    const char **mIgnorables;
+    List<MediaAnalyticsItem *> *mSummaries;
+
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZER_H
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.cpp b/services/mediaanalytics/MetricsSummarizerCodec.cpp
new file mode 100644
index 0000000..8c74782
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerCodec.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "MetricsSummarizerCodec"
+#include <utils/Log.h>
+
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerCodec.h"
+
+
+
+
+namespace android {
+
+MetricsSummarizerCodec::MetricsSummarizerCodec(const char *key)
+    : MetricsSummarizer(key)
+{
+    ALOGV("MetricsSummarizerCodec::MetricsSummarizerCodec");
+}
+
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.h b/services/mediaanalytics/MetricsSummarizerCodec.h
new file mode 100644
index 0000000..c01196f
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerCodec.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZERCODEC_H
+#define ANDROID_METRICSSUMMARIZERCODEC_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerCodec : public MetricsSummarizer
+{
+
+ public:
+
+    MetricsSummarizerCodec(const char *key);
+    virtual ~MetricsSummarizerCodec() {};
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZERCODEC_H
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.cpp b/services/mediaanalytics/MetricsSummarizerExtractor.cpp
new file mode 100644
index 0000000..190f87d
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerExtractor.cpp
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "MetricsSummarizerExtractor"
+#include <utils/Log.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerExtractor.h"
+
+
+
+
+namespace android {
+
+MetricsSummarizerExtractor::MetricsSummarizerExtractor(const char *key)
+    : MetricsSummarizer(key)
+{
+    ALOGV("MetricsSummarizerExtractor::MetricsSummarizerExtractor");
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.h b/services/mediaanalytics/MetricsSummarizerExtractor.h
new file mode 100644
index 0000000..eee052b
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerExtractor.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZEREXTRACTOR_H
+#define ANDROID_METRICSSUMMARIZEREXTRACTOR_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerExtractor : public MetricsSummarizer
+{
+
+ public:
+
+    MetricsSummarizerExtractor(const char *key);
+    virtual ~MetricsSummarizerExtractor() {};
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZEREXTRACTOR_H
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.cpp b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
new file mode 100644
index 0000000..5162059
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "MetricsSummarizerPlayer"
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerPlayer.h"
+
+
+
+
+namespace android {
+
+static const char *player_ignorable[] = {
+    "android.media.mediaplayer.durationMs",
+    "android.media.mediaplayer.playingMs",
+    "android.media.mediaplayer.frames",
+    "android.media.mediaplayer.dropped",
+    0
+};
+
+MetricsSummarizerPlayer::MetricsSummarizerPlayer(const char *key)
+    : MetricsSummarizer(key)
+{
+    ALOGV("MetricsSummarizerPlayer::MetricsSummarizerPlayer");
+    setIgnorables(player_ignorable);
+}
+
+void MetricsSummarizerPlayer::mergeRecord(MediaAnalyticsItem &summation, MediaAnalyticsItem &item) {
+
+    ALOGV("MetricsSummarizerPlayer::mergeRecord()");
+
+    //
+    // we sum time & frames.
+    // be careful about our special "-1" values that indicate 'unknown'
+    // treat those as 0 [basically, not summing them into the totals].
+    int64_t duration = 0;
+    if (item.getInt64("android.media.mediaplayer.durationMs", &duration)) {
+        ALOGV("found durationMs of %" PRId64, duration);
+        summation.addInt64("android.media.mediaplayer.durationMs",duration);
+    }
+    int64_t playing = 0;
+    if (item.getInt64("android.media.mediaplayer.playingMs", &playing))
+        ALOGV("found playingMs of %" PRId64, playing);
+        if (playing >= 0) {
+            summation.addInt64("android.media.mediaplayer.playingMs",playing);
+        }
+    int64_t frames = 0;
+    if (item.getInt64("android.media.mediaplayer.frames", &frames))
+        ALOGV("found framess of %" PRId64, frames);
+        if (frames >= 0) {
+            summation.addInt64("android.media.mediaplayer.frames",frames);
+        }
+    int64_t dropped = 0;
+    if (item.getInt64("android.media.mediaplayer.dropped", &dropped))
+        ALOGV("found dropped of %" PRId64, dropped);
+        if (dropped >= 0) {
+            summation.addInt64("android.media.mediaplayer.dropped",dropped);
+        }
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.h b/services/mediaanalytics/MetricsSummarizerPlayer.h
new file mode 100644
index 0000000..ad1bf74
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerPlayer.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZERPLAYER_H
+#define ANDROID_METRICSSUMMARIZERPLAYER_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerPlayer : public MetricsSummarizer
+{
+
+ public:
+
+    MetricsSummarizerPlayer(const char *key);
+    virtual ~MetricsSummarizerPlayer() {};
+
+    virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZERPLAYER_H
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.cpp b/services/mediaanalytics/MetricsSummarizerRecorder.cpp
new file mode 100644
index 0000000..c2919c3
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerRecorder.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "MetricsSummarizerRecorder"
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerRecorder.h"
+
+
+
+
+namespace android {
+
+MetricsSummarizerRecorder::MetricsSummarizerRecorder(const char *key)
+    : MetricsSummarizer(key)
+{
+    ALOGV("MetricsSummarizerRecorder::MetricsSummarizerRecorder");
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.h b/services/mediaanalytics/MetricsSummarizerRecorder.h
new file mode 100644
index 0000000..963baab
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerRecorder.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZERRECORDER_H
+#define ANDROID_METRICSSUMMARIZERRECORDER_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerRecorder : public MetricsSummarizer
+{
+
+ public:
+
+    MetricsSummarizerRecorder(const char *key);
+    virtual ~MetricsSummarizerRecorder() {};
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZERRECORDER_H
diff --git a/services/medialog/MediaLogService.h b/services/medialog/MediaLogService.h
index c6b99f1..06c721f 100644
--- a/services/medialog/MediaLogService.h
+++ b/services/medialog/MediaLogService.h
@@ -49,7 +49,8 @@
     // Internal dump
     static const int kDumpLockRetries = 50;
     static const int kDumpLockSleepUs = 20000;
-    static const size_t kMergeBufferSize = 16 * 1024; // TODO determine good value for this
+    // Size of merge buffer, in bytes
+    static const size_t kMergeBufferSize = 64 * 1024; // TODO determine good value for this
     static bool dumpTryLock(Mutex& mutex);
 
     Mutex               mLock;