Merge "Skip track if verification fails" into klp-dev am: ab2c5046ef am: f233415199 am: 87f1a3041d am: 222dca470e am: ff9e1b0303 am: 6d050827c3  -s ours am: 84b2e0717c  -s ours am: 0a78268055  -s ours am: 8d68455f69  -s ours am: 369ad623bc  -s ours am: 920df6ca00  -s ours am: e21ab964c1  -s ours am: 4566e87270  -s ours am: 6d719ca88c  -s ours am: d6fba171b7  -s ours am: 8158fccafd  -s ours am: 4c32e865d3  -s ours am: 5b2a157387  -s ours
am: 1dbdcd7dc2  -s ours

Change-Id: Ic26454807040249bacf18b7be26430ab6fe85b05
diff --git a/include/media/nbaio/ReportPerformance.h b/include/media/nbaio/ReportPerformance.h
new file mode 120000
index 0000000..bd596e3
--- /dev/null
+++ b/include/media/nbaio/ReportPerformance.h
@@ -0,0 +1 @@
+../../../media/libnbaio/include/media/nbaio/ReportPerformance.h
\ No newline at end of file
diff --git a/media/libaudioclient/AudioEffect.cpp b/media/libaudioclient/AudioEffect.cpp
index a5f9ab6..b1cb0e7 100644
--- a/media/libaudioclient/AudioEffect.cpp
+++ b/media/libaudioclient/AudioEffect.cpp
@@ -135,7 +135,11 @@
             &mStatus, &mId, &enabled);
 
     if (iEffect == 0 || (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS)) {
-        ALOGE("set(): AudioFlinger could not create effect, status: %d", mStatus);
+        char typeBuffer[64], uuidBuffer[64];
+        guidToString(type, typeBuffer, sizeof(typeBuffer));
+        guidToString(uuid, uuidBuffer, sizeof(uuidBuffer));
+        ALOGE("set(): AudioFlinger could not create effect %s / %s, status: %d",
+                typeBuffer, uuidBuffer, mStatus);
         if (iEffect == 0) {
             mStatus = NO_INIT;
         }
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index b976721..efe947a 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -649,8 +649,12 @@
 status_t MediaPlayer::reset()
 {
     ALOGV("reset");
+    mLockThreadId = getThreadId();
     Mutex::Autolock _l(mLock);
-    return reset_l();
+    status_t result = reset_l();
+    mLockThreadId = 0;
+
+    return result;
 }
 
 status_t MediaPlayer::setAudioStreamType(audio_stream_type_t type)
@@ -860,7 +864,7 @@
     // this will deadlock.
     //
     // The threadId hack below works around this for the care of prepare,
-    // seekTo and start within the same process.
+    // seekTo, start, and reset within the same process.
     // FIXME: Remember, this is a hack, it's not even a hack that is applied
     // consistently for all use-cases, this needs to be revisited.
     if (mLockThreadId != getThreadId()) {
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index aa21fff..2c5b22f 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -847,10 +847,6 @@
 
 status_t NuPlayer::GenericSource::dequeueAccessUnit(
         bool audio, sp<ABuffer> *accessUnit) {
-    if (audio && !mStarted) {
-        return -EWOULDBLOCK;
-    }
-
     // If has gone through stop/releaseDrm sequence, we no longer send down any buffer b/c
     // the codec's crypto object has gone away (b/37960096).
     // Note: This will be unnecessary when stop() changes behavior and releases codec (b/35248283).
diff --git a/media/libnbaio/NBLog.cpp b/media/libnbaio/NBLog.cpp
index 2f639d2..c73632c 100644
--- a/media/libnbaio/NBLog.cpp
+++ b/media/libnbaio/NBLog.cpp
@@ -49,51 +49,47 @@
 *
 * 2) reading the data from shared memory
 * Thread::threadloop()
-*     TODO: add description?
 * NBLog::MergeThread::threadLoop()
-*     calls NBLog::Merger::merge
+*     Waits on a mutex, called periodically
+*     Calls NBLog::Merger::merge and MergeReader.getAndProcessSnapshot.
 * NBLog::Merger::merge
 *     Merges snapshots sorted by timestamp
-*     for each reader in vector of class NamedReader,
-*     callsNamedReader::reader()->getSnapshot
-*     TODO: check whether the rest of this function is relevant
+*     Calls Reader::getSnapshot on each individual thread buffer to in shared
+*     memory and writes all their data to the single FIFO stored in mMerger.
 * NBLog::Reader::getSnapshot
 *     copies snapshot of reader's fifo buffer into its own buffer
 *     calls mFifoReader->obtain to find readable data
 *     sets snapshot.begin() and .end() iterators to boundaries of valid entries
 *     moves the fifo reader index to after the last entry read
 *     in this case, the buffer is in shared memory. in (4), the buffer is private
+* NBLog::MergeThread::getAndProcessSnapshot
+*     Iterates through the entries in the local FIFO. Processes the data in
+*     specific ways depending on the entry type. If the data is a histogram
+*     timestamp or an audio on/off signal, writes to a map of PerformanceAnalysis
+*     class instances, where the wakeup() intervals are stored as histograms
+*     and analyzed.
 *
-* 3) reading the data from private buffer
-* MediaLogService::dump
-*     calls NBLog::Reader::dump(CONSOLE)
-*     The private buffer contains all logs for all readers in shared memory
-* NBLog::Reader::dump(int)
-*     calls getSnapshot on the current reader
-*     calls dump(int, size_t, Snapshot)
-* NBLog::Reader::dump(int, size, snapshot)
-*     iterates through snapshot's events and switches based on their type
-*     (string, timestamp, etc...)
-*     In the case of EVENT_HISTOGRAM_ENTRY_TS, adds a list of timestamp sequences
-*     (histogram entry) to NBLog::mHists
-*     TODO: add every HISTOGRAM_ENTRY_TS to two
-*     circular buffers: one short-term and one long-term (can add even longer-term
-*     structures in the future). When dump is called, print everything currently
-*     in the buffer.
-* NBLog::drawHistogram
-*     input: timestamp array
-*     buckets this to a histogram and prints
-*
+* 3) Dumpsys media.log call to report the data
+* MediaLogService::dump in MediaLogService.cpp
+*     calls NBLog::Reader::dump, which calls ReportPerformance::dump
+* ReportPerformance::dump
+*     calls PerformanceAnalysis::ReportPerformance
+*     and ReportPerformance::WriteToFile
+* PerformanceAnalysis::ReportPerformance
+*     for each thread/source file location instance of PerformanceAnalysis data,
+*     combines all histograms into a single one and prints it to the console
+*     along with outlier data
+* ReportPerformance::WriteToFile
+*     writes histogram, outlier, and peak information to file separately for each
+*     instance of PerformanceAnalysis data.
 */
 
 #define LOG_TAG "NBLog"
-// #define LOG_NDEBUG 0
 
 #include <algorithm>
 #include <climits>
 #include <deque>
 #include <fstream>
-// #include <inttypes.h>
 #include <iostream>
 #include <math.h>
 #include <numeric>
@@ -109,7 +105,7 @@
 #include <media/nbaio/NBLog.h>
 #include <media/nbaio/PerformanceAnalysis.h>
 #include <media/nbaio/ReportPerformance.h>
-// #include <utils/CallStack.h> // used to print callstack
+#include <utils/CallStack.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
 
@@ -760,14 +756,15 @@
 // ---------------------------------------------------------------------------
 
 const std::set<NBLog::Event> NBLog::Reader::startingTypes {NBLog::Event::EVENT_START_FMT,
-                                                           NBLog::Event::EVENT_HISTOGRAM_ENTRY_TS};
+        NBLog::Event::EVENT_HISTOGRAM_ENTRY_TS,
+        NBLog::Event::EVENT_AUDIO_STATE};
 const std::set<NBLog::Event> NBLog::Reader::endingTypes   {NBLog::Event::EVENT_END_FMT,
-                                                           NBLog::Event::EVENT_HISTOGRAM_ENTRY_TS,
-                                                           NBLog::Event::EVENT_AUDIO_STATE};
+        NBLog::Event::EVENT_HISTOGRAM_ENTRY_TS,
+        NBLog::Event::EVENT_AUDIO_STATE};
 
 NBLog::Reader::Reader(const void *shared, size_t size)
-    : mShared((/*const*/ Shared *) shared), /*mIMemory*/
-      mFd(-1), mIndent(0),
+    : mFd(-1), mIndent(0), mLost(0),
+      mShared((/*const*/ Shared *) shared), /*mIMemory*/
       mFifo(mShared != NULL ?
         new audio_utils_fifo(size, sizeof(uint8_t),
             mShared->mBuffer, mShared->mRear, NULL /*throttlesFront*/) : NULL),
@@ -805,6 +802,9 @@
     return nullptr; // no entry found
 }
 
+// Copies content of a Reader FIFO into its Snapshot
+// The Snapshot has the same raw data, but represented as a sequence of entries
+// and an EntryIterator making it possible to process the data.
 std::unique_ptr<NBLog::Reader::Snapshot> NBLog::Reader::getSnapshot()
 {
     if (mFifoReader == NULL) {
@@ -870,23 +870,18 @@
 
 }
 
-// TODO: move this to PerformanceAnalysis
-// TODO: make call to dump periodic so that data in shared FIFO does not get overwritten
-void NBLog::Reader::dump(int fd, size_t indent, NBLog::Reader::Snapshot &snapshot)
+// Takes raw content of the local merger FIFO, processes log entries, and
+// writes the data to a map of class PerformanceAnalysis, based on their thread ID.
+void NBLog::MergeReader::getAndProcessSnapshot(NBLog::Reader::Snapshot &snapshot)
 {
-    mFd = fd;
-    mIndent = indent;
     String8 timestamp, body;
-    // FIXME: this is not thread safe
-    // TODO: need a separate instance of performanceAnalysis for each thread
-    // used to store data and to call analysis functions
-    static ReportPerformance::PerformanceAnalysis performanceAnalysis;
+    // TODO: check: is this thread safe?
+    // TODO: add lost data information and notification to ReportPerformance
     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
-        //      log to push it out.  Consider keeping the timestamp/body between calls to copyEntryDataAt().
-        dumpLine(timestamp, body);
+        // TODO: ultimately, this will be += and reset to 0. TODO: check that this is
+        // obsolete now that Merger::merge is called periodically. No data should be lost
+        mLost = lost;
     }
 
     for (auto entry = snapshot.begin(); entry != snapshot.end();) {
@@ -902,12 +897,22 @@
             memcpy(&hash, &(data->hash), sizeof(hash));
             int64_t ts;
             memcpy(&ts, &data->ts, sizeof(ts));
-            performanceAnalysis.logTsEntry(ts);
+            // TODO: hash for histogram ts and audio state need to match
+            // and correspond to audio production source file location
+            mThreadPerformanceAnalysis[data->author][0 /*hash*/].logTsEntry(ts);
             ++entry;
             break;
         }
         case EVENT_AUDIO_STATE: {
-            performanceAnalysis.handleStateChange();
+            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));
+            // TODO: remove ts if unused
+            int64_t ts;
+            memcpy(&ts, &data->ts, sizeof(ts));
+            mThreadPerformanceAnalysis[data->author][0 /*hash*/].handleStateChange();
             ++entry;
             break;
         }
@@ -922,19 +927,25 @@
             break;
         }
     }
-    performanceAnalysis.reportPerformance(&body);
+    // FIXME: decide whether to print the warnings here or elsewhere
     if (!body.isEmpty()) {
         dumpLine(timestamp, body);
     }
 }
 
-void NBLog::Reader::dump(int fd, size_t indent)
+void NBLog::MergeReader::getAndProcessSnapshot()
 {
-    // get a snapshot, dump it
+    // get a snapshot, process it
     std::unique_ptr<Snapshot> snap = getSnapshot();
-    dump(fd, indent, *snap);
+    getAndProcessSnapshot(*snap);
 }
 
+void NBLog::MergeReader::dump(int fd, int indent) {
+    // TODO: add a mutex around media.log dump
+    ReportPerformance::dump(fd, indent, mThreadPerformanceAnalysis);
+}
+
+// Writes a string to the console
 void NBLog::Reader::dumpLine(const String8 &timestamp, String8 &body)
 {
     if (mFd >= 0) {
@@ -1093,6 +1104,7 @@
       {}
 
 void NBLog::Merger::addReader(const NBLog::NamedReader &reader) {
+
     // FIXME This is called by binder thread in MediaLogService::registerWriter
     //       but the access to shared variable mNamedReaders is not yet protected by a lock.
     mNamedReaders.push_back(reader);
@@ -1116,7 +1128,7 @@
     return i1.ts > i2.ts || (i1.ts == i2.ts && i1.index > i2.index);
 }
 
-// Merge registered readers, sorted by timestamp
+// Merge registered readers, sorted by timestamp, and write data to a single FIFO in local memory
 void NBLog::Merger::merge() {
     // FIXME This is called by merge thread
     //       but the access to shared variable mNamedReaders is not yet protected by a lock.
@@ -1173,8 +1185,9 @@
 
 // ---------------------------------------------------------------------------
 
-NBLog::MergeThread::MergeThread(NBLog::Merger &merger)
+NBLog::MergeThread::MergeThread(NBLog::Merger &merger, NBLog::MergeReader &mergeReader)
     : mMerger(merger),
+      mMergeReader(mergeReader),
       mTimeoutUs(0) {}
 
 NBLog::MergeThread::~MergeThread() {
@@ -1196,7 +1209,12 @@
         mTimeoutUs -= kThreadSleepPeriodUs;
     }
     if (doMerge) {
+        // Merge data from all the readers
         mMerger.merge();
+        // Process the data collected by mMerger and write it to PerformanceAnalysis
+        // FIXME: decide whether to call getAndProcessSnapshot every time
+        // or whether to have a separate thread that calls it with a lower frequency
+        mMergeReader.getAndProcessSnapshot();
     }
     return true;
 }
diff --git a/media/libnbaio/PerformanceAnalysis.cpp b/media/libnbaio/PerformanceAnalysis.cpp
index fb3bddc..e5e444e 100644
--- a/media/libnbaio/PerformanceAnalysis.cpp
+++ b/media/libnbaio/PerformanceAnalysis.cpp
@@ -36,7 +36,6 @@
 #include <media/nbaio/NBLog.h>
 #include <media/nbaio/PerformanceAnalysis.h>
 #include <media/nbaio/ReportPerformance.h>
-// #include <utils/CallStack.h> // used to print callstack
 #include <utils/Log.h>
 #include <utils/String8.h>
 
@@ -47,246 +46,236 @@
 
 namespace ReportPerformance {
 
-PerformanceAnalysis::PerformanceAnalysis() {
-    // These variables will be (FIXME) learned from the data
-    kPeriodMs = 4; // typical buffer period (mode)
-    // average number of Ms spent processing buffer
-    kPeriodMsCPU = static_cast<int>(kPeriodMs * kRatio);
-}
-
-// converts a time series into a map. key: buffer period length. value: count
-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 int widthOf(int x) {
-    int width = 0;
-    while (x > 0) {
-        ++width;
-        x /= 10;
-    }
-    return width;
-}
-
-// Given a series of audio processing wakeup timestamps,
-// buckets the time intervals into a histogram, searches for
+// Given a the most recent timestamp of a series of audio processing
+// wakeup timestamps,
+// buckets the time interval into a histogram, searches for
 // outliers, analyzes the outlier series for unexpectedly
-// small or large values and stores these as peaks, and flushes
-// the timestamp series from memory.
-void PerformanceAnalysis::processAndFlushTimeStampSeries() {
-    // 1) analyze the series to store all outliers and their exact timestamps:
-    storeOutlierData(mTimeStampSeries);
-
-    // 2) detect peaks in the outlier series
-    detectPeaks();
-
-    // 3) compute its histogram, append to mRecentHists and clear the time series
-    mRecentHists.emplace_back(static_cast<timestamp>(mTimeStampSeries[0]),
-                              buildBuckets(mTimeStampSeries));
-    // do not let mRecentHists exceed capacity
-    // ALOGD("mRecentHists size: %d", static_cast<int>(mRecentHists.size()));
-    if (mRecentHists.size() >= kRecentHistsCapacity) {
-        //  ALOGD("popped back mRecentHists");
-        mRecentHists.pop_front();
+// small or large values and stores these as peaks
+void PerformanceAnalysis::logTsEntry(timestamp ts) {
+    // after a state change, start a new series and do not
+    // record time intervals in-between
+    if (mBufferPeriod.mPrevTs == 0) {
+        mBufferPeriod.mPrevTs = ts;
+        return;
     }
-    mTimeStampSeries.clear();
+
+    // calculate time interval between current and previous timestamp
+    const msInterval diffMs = static_cast<msInterval>(
+        deltaMs(mBufferPeriod.mPrevTs, ts));
+
+    const int diffJiffy = deltaJiffy(mBufferPeriod.mPrevTs, ts);
+
+    // old versus new weight ratio when updating the buffer period mean
+    static constexpr double exponentialWeight = 0.999;
+    // update buffer period mean with exponential weighting
+    mBufferPeriod.mMean = (mBufferPeriod.mMean < 0) ? diffMs :
+            exponentialWeight * mBufferPeriod.mMean + (1.0 - exponentialWeight) * diffMs;
+    // set mOutlierFactor to a smaller value for the fastmixer thread
+    const int kFastMixerMax = 10;
+    // NormalMixer times vary much more than FastMixer times.
+    // TODO: mOutlierFactor values are set empirically based on what appears to be
+    // an outlier. Learn these values from the data.
+    mBufferPeriod.mOutlierFactor = mBufferPeriod.mMean < kFastMixerMax ? 1.8 : 2.5;
+    // set outlier threshold
+    mBufferPeriod.mOutlier = mBufferPeriod.mMean * mBufferPeriod.mOutlierFactor;
+
+    // Check whether the time interval between the current timestamp
+    // and the previous one is long enough to count as an outlier
+    const bool isOutlier = detectAndStoreOutlier(diffMs);
+    // If an outlier was found, check whether it was a peak
+    if (isOutlier) {
+        /*bool isPeak =*/ detectAndStorePeak(
+            mOutlierData[0].first, mOutlierData[0].second);
+        // TODO: decide whether to insert a new empty histogram if a peak
+        // TODO: remove isPeak if unused to avoid "unused variable" error
+        // occurred at the current timestamp
+    }
+
+    // Insert a histogram to mHists if it is empty, or
+    // close the current histogram and insert a new empty one if
+    // if the current histogram has spanned its maximum time interval.
+    if (mHists.empty() ||
+        deltaMs(mHists[0].first, ts) >= kMaxLength.HistTimespanMs) {
+        mHists.emplace_front(ts, std::map<int, int>());
+        // When memory is full, delete oldest histogram
+        // TODO: use a circular buffer
+        if (mHists.size() >= kMaxLength.Hists) {
+            mHists.resize(kMaxLength.Hists);
+        }
+    }
+    // add current time intervals to histogram
+    ++mHists[0].second[diffJiffy];
+    // update previous timestamp
+    mBufferPeriod.mPrevTs = ts;
 }
 
+
 // forces short-term histogram storage to avoid adding idle audio time interval
 // to buffer period data
 void PerformanceAnalysis::handleStateChange() {
-    ALOGD("handleStateChange");
-    processAndFlushTimeStampSeries();
+    mBufferPeriod.mPrevTs = 0;
     return;
 }
 
-// Takes a single buffer period timestamp entry information and stores it in a
-// temporary series of timestamps. Once the series is full, the data is analyzed,
-// stored, and emptied.
-void PerformanceAnalysis::logTsEntry(int64_t ts) {
-    // TODO might want to filter excessively high outliers, which are usually caused
-    // by the thread being inactive.
-    // Store time series data for each reader in order to bucket it once there
-    // is enough data. Then, write to recentHists as a histogram.
-    mTimeStampSeries.push_back(ts);
-    // if length of the time series has reached kShortHistSize samples,
-    // analyze the data and flush the timestamp series from memory
-    if (mTimeStampSeries.size() >= kShortHistSize) {
-        processAndFlushTimeStampSeries();
-    }
-}
 
-// When the short-term histogram array mRecentHists has reached capacity,
-// merge histograms for data compression and store them in mLongTermHists
-// clears mRecentHists
-// TODO: have logTsEntry write directly to mLongTermHists, discard mRecentHists,
-// start a new histogram when a peak occurs
-void PerformanceAnalysis::processAndFlushRecentHists() {
-
-    // Buckets is used to aggregate short-term histograms.
-    Histogram buckets;
-    timestamp startingTs = mRecentHists[0].first;
-
-    for (const auto &shortHist: mRecentHists) {
-        // If the time between starting and ending timestamps has reached the maximum,
-        // add the current histogram (buckets) to the long-term histogram buffer,
-        // clear buckets, and start a new long-term histogram aggregation process.
-        if (deltaMs(startingTs, shortHist.first) >= kMaxHistTimespanMs) {
-            mLongTermHists.emplace_back(startingTs, std::move(buckets));
-            buckets.clear();
-            startingTs = shortHist.first;
-            // When memory is full, delete oldest histogram
-            // TODO use a circular buffer
-            if (mLongTermHists.size() >= kLongTermHistsCapacity) {
-                mLongTermHists.pop_front();
-            }
-        }
-
-        // add current histogram to buckets
-        for (const auto &countPair : shortHist.second) {
-            buckets[countPair.first] += countPair.second;
-        }
-    }
-    mRecentHists.clear();
-    // TODO: decide when/where to call writeToFile
-    // TODO: add a thread-specific extension to the file name
-    static const char* const kName = (const char *) "/data/misc/audioserver/sample_results.txt";
-    writeToFile(mOutlierData, mLongTermHists, kName, false);
-}
-
-// Given a series of outlier intervals (mOutlier data),
+// Checks whether the time interval between two outliers is far enough from
+// a typical delta to be considered a peak.
 // looks for changes in distribution (peaks), which can be either positive or negative.
 // The function sets the mean to the starting value and sigma to 0, and updates
 // them as long as no peak is detected. When a value is more than 'threshold'
 // standard deviations from the mean, a peak is detected and the mean and sigma
 // are set to the peak value and 0.
-void PerformanceAnalysis::detectPeaks() {
+bool PerformanceAnalysis::detectAndStorePeak(msInterval diff, timestamp ts) {
+    bool isPeak = false;
     if (mOutlierData.empty()) {
-        return;
+        return false;
     }
+    // Update mean of the distribution
+    // TypicalDiff is used to check whether a value is unusually large
+    // when we cannot use standard deviations from the mean because the sd is set to 0.
+    mOutlierDistribution.mTypicalDiff = (mOutlierDistribution.mTypicalDiff *
+            (mOutlierData.size() - 1) + diff) / mOutlierData.size();
 
-    // compute mean of the distribution. Used to check whether a value is large
-    const double kTypicalDiff = std::accumulate(
-        mOutlierData.begin(), mOutlierData.end(), 0,
-        [](auto &a, auto &b){return a + b.first;}) / mOutlierData.size();
-    // ALOGD("typicalDiff %f", kTypicalDiff);
+    // Initialize short-term mean at start of program
+    if (mOutlierDistribution.mMean == 0) {
+        mOutlierDistribution.mMean = diff;
+    }
+    // Update length of current sequence of outliers
+    mOutlierDistribution.mN++;
 
-    // iterator at the beginning of a sequence, or updated to the most recent peak
-    std::deque<std::pair<uint64_t, uint64_t>>::iterator start = mOutlierData.begin();
-    // the mean and standard deviation are updated every time a peak is detected
-    // initialize first time. The mean from the previous sequence is stored
-    // for the next sequence. Here, they are initialized for the first time.
-    if (mPeakDetectorMean < 0) {
-        mPeakDetectorMean = static_cast<double>(start->first);
-        mPeakDetectorSd = 0;
-    }
-    auto sqr = [](auto x){ return x * x; };
-    for (auto it = mOutlierData.begin(); it != mOutlierData.end(); ++it) {
-        // no surprise occurred:
-        // the new element is a small number of standard deviations from the mean
-        if ((fabs(it->first - mPeakDetectorMean) < kStddevThreshold * mPeakDetectorSd) ||
-             // or: right after peak has been detected, the delta is smaller than average
-            (mPeakDetectorSd == 0 && fabs(it->first - mPeakDetectorMean) < kTypicalDiff)) {
-            // update the mean and sd:
-            // count number of elements (distance between start interator and current)
-            const int kN = std::distance(start, it) + 1;
-            // usual formulas for mean and sd
-            mPeakDetectorMean = std::accumulate(start, it + 1, 0.0,
-                                   [](auto &a, auto &b){return a + b.first;}) / kN;
-            mPeakDetectorSd = sqrt(std::accumulate(start, it + 1, 0.0,
-                      [=](auto &a, auto &b){ return a + sqr(b.first - mPeakDetectorMean);})) /
-                      ((kN > 1)? kN - 1 : kN); // kN - 1: mean is correlated with variance
+    // Check whether a large deviation from the mean occurred.
+    // If the standard deviation has been reset to zero, the comparison is
+    // instead to the mean of the full mOutlierInterval sequence.
+    if ((fabs(diff - mOutlierDistribution.mMean) <
+            mOutlierDistribution.kMaxDeviation * mOutlierDistribution.mSd) ||
+            (mOutlierDistribution.mSd == 0 &&
+            fabs(diff - mOutlierDistribution.mMean) <
+            mOutlierDistribution.mTypicalDiff)) {
+        // update the mean and sd using online algorithm
+        // https://en.wikipedia.org/wiki/
+        // Algorithms_for_calculating_variance#Online_algorithm
+        mOutlierDistribution.mN++;
+        const double kDelta = diff - mOutlierDistribution.mMean;
+        mOutlierDistribution.mMean += kDelta / mOutlierDistribution.mN;
+        const double kDelta2 = diff - mOutlierDistribution.mMean;
+        mOutlierDistribution.mM2 += kDelta * kDelta2;
+        mOutlierDistribution.mSd = (mOutlierDistribution.mN < 2) ? 0 :
+                sqrt(mOutlierDistribution.mM2 / (mOutlierDistribution.mN - 1));
+    } else {
+        // new value is far from the mean:
+        // store peak timestamp and reset mean, sd, and short-term sequence
+        isPeak = true;
+        mPeakTimestamps.emplace_front(ts);
+        // if mPeaks has reached capacity, delete oldest data
+        // Note: this means that mOutlierDistribution values do not exactly
+        // match the data we have in mPeakTimestamps, but this is not an issue
+        // in practice for estimating future peaks.
+        // TODO: turn this into a circular buffer
+        if (mPeakTimestamps.size() >= kMaxLength.Peaks) {
+            mPeakTimestamps.resize(kMaxLength.Peaks);
         }
-        // surprising value: store peak timestamp and reset mean, sd, and start iterator
-        else {
-            mPeakTimestamps.emplace_back(it->second);
-            // TODO: remove pop_front once a circular buffer is in place
-            if (mPeakTimestamps.size() >= kPeakSeriesSize) {
-                mPeakTimestamps.pop_front();
-            }
-            mPeakDetectorMean = static_cast<double>(it->first);
-            mPeakDetectorSd = 0;
-            start = it;
-        }
+        mOutlierDistribution.mMean = 0;
+        mOutlierDistribution.mSd = 0;
+        mOutlierDistribution.mN = 0;
+        mOutlierDistribution.mM2 = 0;
     }
-    return;
+    return isPeak;
 }
 
-// Called by LogTsEntry. The input is a vector of timestamps.
-// Finds outliers and writes to mOutlierdata.
-// Each value in mOutlierdata consists of: <outlier timestamp, time elapsed since previous outlier>.
+
+// Determines whether the difference between a timestamp and the previous
+// one is beyond a threshold. If yes, stores the timestamp as an outlier
+// and writes to mOutlierdata in the following format:
+// Time elapsed since previous outlier: Timestamp of start of outlier
 // e.g. timestamps (ms) 1, 4, 5, 16, 18, 28 will produce pairs (4, 5), (13, 18).
-// This function is applied to the time series before it is converted into a histogram.
-void PerformanceAnalysis::storeOutlierData(const std::vector<int64_t> &timestamps) {
-    if (timestamps.size() < 1) {
-        return;
-    }
-    // first pass: need to initialize
-    if (mElapsed == 0) {
-        mPrevNs = timestamps[0];
-    }
-    for (const auto &ts: timestamps) {
-        const uint64_t diffMs = static_cast<uint64_t>(deltaMs(mPrevNs, ts));
-        if (diffMs >= static_cast<uint64_t>(kOutlierMs)) {
-            mOutlierData.emplace_back(mElapsed, static_cast<uint64_t>(mPrevNs));
-            // Remove oldest value if the vector is full
-            // TODO: remove pop_front once circular buffer is in place
-            // FIXME: make sure kShortHistSize is large enough that that data will never be lost
-            // before being written to file or to a FIFO
-            if (mOutlierData.size() >= kOutlierSeriesSize) {
-                mOutlierData.pop_front();
-            }
-            mElapsed = 0;
+// TODO: learn what timestamp sequences correlate with glitches instead of
+// manually designing a heuristic.
+bool PerformanceAnalysis::detectAndStoreOutlier(const msInterval diffMs) {
+    bool isOutlier = false;
+    if (diffMs >= mBufferPeriod.mOutlier) {
+        isOutlier = true;
+        mOutlierData.emplace_front(
+                mOutlierDistribution.mElapsed, mBufferPeriod.mPrevTs);
+        // Remove oldest value if the vector is full
+        // TODO: turn this into a circular buffer
+        // TODO: make sure kShortHistSize is large enough that that data will never be lost
+        // before being written to file or to a FIFO
+        if (mOutlierData.size() >= kMaxLength.Outliers) {
+            mOutlierData.resize(kMaxLength.Outliers);
         }
-        mElapsed += diffMs;
-        mPrevNs = ts;
+        mOutlierDistribution.mElapsed = 0;
     }
+    mOutlierDistribution.mElapsed += diffMs;
+    return isOutlier;
 }
 
-
-// FIXME: delete this temporary test code, recycled for various new functions
-void PerformanceAnalysis::testFunction() {
-    // produces values (4: 5000000), (13: 18000000)
-    // ns timestamps of buffer periods
-    const std::vector<int64_t>kTempTestData = {1000000, 4000000, 5000000,
-                                               16000000, 18000000, 28000000};
-    PerformanceAnalysis::storeOutlierData(kTempTestData);
-    for (const auto &outlier: mOutlierData) {
-        ALOGE("PerformanceAnalysis test %lld: %lld",
-              static_cast<long long>(outlier.first), static_cast<long long>(outlier.second));
+static int widthOf(int x) {
+    int width = 0;
+    if (x < 0) {
+        width++;
+        x = x == INT_MIN ? INT_MAX : -x;
     }
-    detectPeaks();
+    // assert (x >= 0)
+    do {
+        ++width;
+        x /= 10;
+    } while (x > 0);
+    return width;
 }
 
-// TODO Make it return a std::string instead of modifying body --> is this still relevant?
-// TODO consider changing all ints to uint32_t or uint64_t
-// TODO: move this to ReportPerformance, probably make it a friend function of PerformanceAnalysis
-void PerformanceAnalysis::reportPerformance(String8 *body, int maxHeight) {
-    if (mRecentHists.size() < 1) {
-        ALOGD("reportPerformance: mRecentHists is empty");
+// computes the column width required for a specific histogram value
+inline int numberWidth(double number, int leftPadding) {
+    // Added values account for whitespaces needed around numbers, and for the
+    // dot and decimal digit not accounted for by widthOf
+    return std::max(std::max(widthOf(static_cast<int>(number)) + 3, 2), leftPadding + 1);
+}
+
+// rounds value to precision based on log-distance from mean
+inline double logRound(double x, double mean) {
+    // Larger values increase range of high resolution
+    constexpr double kBase = 2;
+    const double power = floor(
+        log(abs(x - mean) / mean) / log(kBase)) + 1;
+    // do not round values close to the mean
+    if (power < 1) {
+        return x;
+    }
+    const int factor = static_cast<int>(pow(10, power));
+    return (static_cast<int>(x) * factor) / factor;
+}
+
+// TODO Make it return a std::string instead of modifying body
+// TODO: move this to ReportPerformance, probably make it a friend function
+// of PerformanceAnalysis
+void PerformanceAnalysis::reportPerformance(String8 *body, int author, log_hash_t hash,
+                                            int maxHeight) {
+    if (mHists.empty()) {
         return;
     }
-    ALOGD("reportPerformance: hists size %d", static_cast<int>(mRecentHists.size()));
-    // TODO: more elaborate data analysis
-    std::map<int, int> buckets;
-    for (const auto &shortHist: mRecentHists) {
+
+    // ms of active audio in displayed histogram
+    double elapsedMs = 0;
+    // starting timestamp of histogram
+    timestamp startingTs = mHists[0].first;
+
+    // histogram which stores .1 precision ms counts instead of Jiffy multiple counts
+    // TODO: when there is more data, print many histograms, possibly separated at peaks
+    std::map<double, int> buckets;
+    for (const auto &shortHist: mHists) {
         for (const auto &countPair : shortHist.second) {
-            buckets[countPair.first] += countPair.second;
+            const double ms = static_cast<double>(countPair.first) / kJiffyPerMs;
+            buckets[logRound(ms, mBufferPeriod.mMean)] += countPair.second;
+            elapsedMs += ms;
         }
     }
 
     // underscores and spaces length corresponds to maximum width of histogram
-    static const int kLen = 40;
+    static const int kLen = 200;
     std::string underscores(kLen, '_');
     std::string spaces(kLen, ' ');
 
     auto it = buckets.begin();
-    int maxDelta = it->first;
+    double maxDelta = it->first;
     int maxCount = it->second;
     // Compute maximum values
     while (++it != buckets.end()) {
@@ -299,18 +288,23 @@
     }
     int height = log2(maxCount) + 1; // maxCount > 0, safe to call log2
     const int leftPadding = widthOf(1 << height);
-    const int colWidth = std::max(std::max(widthOf(maxDelta) + 1, 3), leftPadding + 2);
+    const int bucketWidth = numberWidth(maxDelta, leftPadding);
     int scalingFactor = 1;
     // scale data if it exceeds maximum height
     if (height > maxHeight) {
         scalingFactor = (height + maxHeight) / maxHeight;
         height /= scalingFactor;
     }
-    body->appendFormat("\n%*s", leftPadding + 11, "Occurrences");
+    body->appendFormat("\n%*s %3.2f %s", leftPadding + 11,
+            "Occurrences in", (elapsedMs / kMsPerSec), "seconds of audio:");
+    body->appendFormat("\n%*s%d, %lld, %lld\n", leftPadding + 11,
+            "Thread, hash, starting timestamp: ", author,
+            static_cast<long long int>(hash), static_cast<long long int>(startingTs));
     // write histogram label line with bucket values
     body->appendFormat("\n%s", " ");
     body->appendFormat("%*s", leftPadding, " ");
     for (auto const &x : buckets) {
+        const int colWidth = numberWidth(x.first, leftPadding);
         body->appendFormat("%*d", colWidth, x.second);
     }
     // write histogram ascii art
@@ -319,54 +313,60 @@
         const int value = 1 << row;
         body->appendFormat("%.*s", leftPadding, spaces.c_str());
         for (auto const &x : buckets) {
-          body->appendFormat("%.*s%s", colWidth - 1, spaces.c_str(), x.second < value ? " " : "|");
+            const int colWidth = numberWidth(x.first, leftPadding);
+            body->appendFormat("%.*s%s", colWidth - 1,
+                               spaces.c_str(), x.second < value ? " " : "|");
         }
         body->appendFormat("\n%s", " ");
     }
     // print x-axis
     const int columns = static_cast<int>(buckets.size());
     body->appendFormat("%*c", leftPadding, ' ');
-    body->appendFormat("%.*s", (columns + 1) * colWidth, underscores.c_str());
+    body->appendFormat("%.*s", (columns + 1) * bucketWidth, underscores.c_str());
     body->appendFormat("\n%s", " ");
 
     // write footer with bucket labels
     body->appendFormat("%*s", leftPadding, " ");
     for (auto const &x : buckets) {
-        body->appendFormat("%*d", colWidth, x.first);
+        const int colWidth = numberWidth(x.first, leftPadding);
+        body->appendFormat("%*.*f", colWidth, 1, x.first);
     }
-    body->appendFormat("%.*s%s", colWidth, spaces.c_str(), "ms\n");
+    body->appendFormat("%.*s%s", bucketWidth, spaces.c_str(), "ms\n");
 
     // Now report glitches
-    body->appendFormat("\ntime elapsed between glitches and glitch timestamps\n");
+    body->appendFormat("\ntime elapsed between glitches and glitch timestamps:\n");
     for (const auto &outlier: mOutlierData) {
         body->appendFormat("%lld: %lld\n", static_cast<long long>(outlier.first),
                            static_cast<long long>(outlier.second));
     }
+}
 
+//------------------------------------------------------------------------------
+
+// writes summary of performance into specified file descriptor
+void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis) {
+    String8 body;
+    const char* const kDirectory = "/data/misc/audioserver/";
+    for (auto & thread : threadPerformanceAnalysis) {
+        for (auto & hash: thread.second) {
+            PerformanceAnalysis& curr = hash.second;
+            // write performance data to console
+            curr.reportPerformance(&body, thread.first, hash.first);
+            if (!body.isEmpty()) {
+                dumpLine(fd, indent, body);
+                body.clear();
+            }
+            // write to file
+            writeToFile(curr.mHists, curr.mOutlierData, curr.mPeakTimestamps,
+                        kDirectory, false, thread.first, hash.first);
+        }
+    }
 }
 
 
-// Produces a log warning if the timing of recent buffer periods caused a glitch
-// Computes sum of running window of three buffer periods
-// Checks whether the buffer periods leave enough CPU time for the next one
-// e.g. if a buffer period is expected to be 4 ms and a buffer requires 3 ms of CPU time,
-// here are some glitch cases:
-// 4 + 4 + 6 ; 5 + 4 + 5; 2 + 2 + 10
-// TODO: develop this code to track changes in histogram distribution in addition
-// to / instead of glitches.
-void PerformanceAnalysis::alertIfGlitch(const std::vector<int64_t> &samples) {
-    std::deque<int> periods(kNumBuff, kPeriodMs);
-    for (size_t i = 2; i < samples.size(); ++i) { // skip first time entry
-        periods.push_front(deltaMs(samples[i - 1], samples[i]));
-        periods.pop_back();
-        // TODO: check that all glitch cases are covered
-        if (std::accumulate(periods.begin(), periods.end(), 0) > kNumBuff * kPeriodMs +
-            kPeriodMs - kPeriodMsCPU) {
-                ALOGW("A glitch occurred");
-                periods.assign(kNumBuff, kPeriodMs);
-        }
-    }
-    return;
+// Writes a string into specified file descriptor
+void dumpLine(int fd, int indent, const String8 &body) {
+    dprintf(fd, "%.*s%s \n", indent, "", body.string());
 }
 
 } // namespace ReportPerformance
diff --git a/media/libnbaio/ReportPerformance.cpp b/media/libnbaio/ReportPerformance.cpp
index dc50ada..e64a6d3 100644
--- a/media/libnbaio/ReportPerformance.cpp
+++ b/media/libnbaio/ReportPerformance.cpp
@@ -23,12 +23,13 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
+#include <sstream>
 #include <sys/prctl.h>
+#include <sys/time.h>
 #include <utility>
 #include <media/nbaio/NBLog.h>
 #include <media/nbaio/PerformanceAnalysis.h>
 #include <media/nbaio/ReportPerformance.h>
-// #include <utils/CallStack.h> // used to print callstack
 #include <utils/Log.h>
 #include <utils/String8.h>
 
@@ -36,37 +37,83 @@
 
 namespace ReportPerformance {
 
-// Writes outlier intervals, timestamps, and histograms spanning long time intervals to a file.
-// TODO: format the data efficiently and write different types of data to different files
-void writeToFile(std::deque<std::pair<outlierInterval, timestamp>> &outlierData,
-                                    std::deque<std::pair<timestamp, Histogram>> &hists,
-                                    const char * kName,
-                                    bool append) {
-    ALOGD("writing performance data to file");
-    if (outlierData.empty() || hists.empty()) {
+// Writes outlier intervals, timestamps, and histograms spanning long time intervals to file.
+// TODO: write data in binary format
+void writeToFile(const std::deque<std::pair<timestamp, Histogram>> &hists,
+                 const std::deque<std::pair<msInterval, timestamp>> &outlierData,
+                 const std::deque<timestamp> &peakTimestamps,
+                 const char * directory, bool append, int author, log_hash_t hash) {
+
+    // TODO: remove old files, implement rotating files as in AudioFlinger.cpp
+
+    if (outlierData.empty() && hists.empty() && peakTimestamps.empty()) {
+        ALOGW("No data, returning.");
         return;
     }
 
-    std::ofstream ofs;
-    ofs.open(kName, append ? std::ios::app : std::ios::trunc);
-    if (!ofs.is_open()) {
-        ALOGW("couldn't open file %s", kName);
+    std::stringstream outlierName;
+    std::stringstream histogramName;
+    std::stringstream peakName;
+
+    // get current time
+    char currTime[16]; //YYYYMMDDHHMMSS + '\0' + one unused
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    struct tm tm;
+    localtime_r(&tv.tv_sec, &tm);
+    strftime(currTime, sizeof(currTime), "%Y%m%d%H%M%S", &tm);
+
+    // generate file names
+    std::stringstream common;
+    common << author << "_" << hash << "_" << currTime << ".csv";
+
+    histogramName << directory << "histograms_" << common.str();
+    outlierName << directory << "outliers_" << common.str();
+    peakName << directory << "peaks_" << common.str();
+
+    std::ofstream hfs;
+    hfs.open(histogramName.str(), append ? std::ios::app : std::ios::trunc);
+    if (!hfs.is_open()) {
+        ALOGW("couldn't open file %s", histogramName.str().c_str());
         return;
     }
-    ofs << "Outlier data: interval and timestamp\n";
-    for (const auto &outlier : outlierData) {
-        ofs << outlier.first << ": " << outlier.second << "\n";
-    }
-    ofs << "Histogram data\n";
+    // each histogram is written as a line where the first value is the timestamp and
+    // subsequent values are pairs of buckets and counts. Each value is separated
+    // by a comma, and each histogram is separated by a newline.
     for (const auto &hist : hists) {
-        ofs << "\ttimestamp\n";
-        ofs << hist.first << "\n";
-        ofs << "\tbuckets and counts\n";
+        hfs << hist.first << ", ";
         for (const auto &bucket : hist.second) {
-            ofs << bucket.first << ": " << bucket.second << "\n";
+            hfs << bucket.first / static_cast<double>(kJiffyPerMs)
+                    << ", " << bucket.second << ", ";
         }
+        hfs << "\n";
+    }
+    hfs.close();
+
+    std::ofstream ofs;
+    ofs.open(outlierName.str(), append ? std::ios::app : std::ios::trunc);
+    if (!ofs.is_open()) {
+        ALOGW("couldn't open file %s", outlierName.str().c_str());
+        return;
+    }
+    // outliers are written as pairs separated by newlines, where each
+    // pair's values are separated by a comma
+    for (const auto &outlier : outlierData) {
+        ofs << outlier.first << ", " << outlier.second << "\n";
     }
     ofs.close();
+
+    std::ofstream pfs;
+    pfs.open(peakName.str(), append ? std::ios::app : std::ios::trunc);
+    if (!pfs.is_open()) {
+        ALOGW("couldn't open file %s", peakName.str().c_str());
+        return;
+    }
+    // peaks are simply timestamps separated by commas
+    for (const auto &peak : peakTimestamps) {
+        pfs << peak << ", ";
+    }
+    pfs.close();
 }
 
 } // namespace ReportPerformance
diff --git a/media/libnbaio/include/media/nbaio/NBLog.h b/media/libnbaio/include/media/nbaio/NBLog.h
index 3e48ee1..5549728 100644
--- a/media/libnbaio/include/media/nbaio/NBLog.h
+++ b/media/libnbaio/include/media/nbaio/NBLog.h
@@ -19,16 +19,18 @@
 #ifndef ANDROID_MEDIA_NBLOG_H
 #define ANDROID_MEDIA_NBLOG_H
 
-#include <binder/IMemory.h>
-#include <audio_utils/fifo.h>
-#include <utils/Mutex.h>
-#include <utils/threads.h>
-
-#include <map>
 #include <deque>
+#include <map>
 #include <set>
 #include <vector>
 
+#include <audio_utils/fifo.h>
+#include <binder/IMemory.h>
+#include <media/nbaio/PerformanceAnalysis.h>
+#include <media/nbaio/ReportPerformance.h>
+#include <utils/Mutex.h>
+#include <utils/threads.h>
+
 namespace android {
 
 class String8;
@@ -37,234 +39,233 @@
 
 public:
 
-typedef uint64_t log_hash_t;
+    using log_hash_t = ReportPerformance::log_hash_t;
 
-// FIXME Everything needed for client (writer API and registration) should be isolated
-//       from the rest of the implementation.
-class Writer;
-class Reader;
+    // FIXME Everything needed for client (writer API and registration) should be isolated
+    //       from the rest of the implementation.
+    class Writer;
+    class Reader;
 
-enum Event : uint8_t {
-    EVENT_RESERVED,
-    EVENT_STRING,               // ASCII string, not NUL-terminated
-    // TODO: make timestamp optional
-    EVENT_TIMESTAMP,            // clock_gettime(CLOCK_MONOTONIC)
-    EVENT_INTEGER,              // integer value entry
-    EVENT_FLOAT,                // floating point value entry
-    EVENT_PID,                  // process ID and process name
-    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_AUDIO_STATE,          // audio on/off event: logged upon FastMixer::onStateChange() call
-    EVENT_END_FMT,              // end of logFormat argument list
+    enum Event : uint8_t {
+        EVENT_RESERVED,
+        EVENT_STRING,               // ASCII string, not NUL-terminated
+                                    // TODO: make timestamp optional
+        EVENT_TIMESTAMP,            // clock_gettime(CLOCK_MONOTONIC)
+        EVENT_INTEGER,              // integer value entry
+        EVENT_FLOAT,                // floating point value entry
+        EVENT_PID,                  // process ID and process name
+        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_AUDIO_STATE,          // audio on/off event: logged on FastMixer::onStateChange call
+        EVENT_END_FMT,              // end of logFormat argument list
 
-    EVENT_UPPER_BOUND,          // to check for invalid events
-};
+        EVENT_UPPER_BOUND,          // to check for invalid events
+    };
 
 private:
 
-// ---------------------------------------------------------------------------
-// API for handling format entry operations
+    // ---------------------------------------------------------------------------
+    // API for handling format entry operations
 
-// 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
+    // 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
 
-// entry representation in memory
-struct entry {
-    const uint8_t type;
-    const uint8_t length;
-    const uint8_t data[0];
-};
+    // 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 tail representation (after data)
+    struct ending {
+        uint8_t length;
+        uint8_t next[0];
+    };
 
-// entry iterator
-class EntryIterator {
-public:
-    EntryIterator();
-    explicit EntryIterator(const uint8_t *entry);
-    EntryIterator(const EntryIterator &other);
+    // entry iterator
+    class EntryIterator {
+    public:
+        EntryIterator();
+        explicit EntryIterator(const uint8_t *entry);
+        EntryIterator(const EntryIterator &other);
 
-    // 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;
+        // 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;
 
-    bool            hasConsistentLength() const;
-    void            copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const;
-    void            copyData(uint8_t *dst) const;
+        bool            hasConsistentLength() const;
+        void            copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const;
+        void            copyData(uint8_t *dst) const;
 
-    template<typename T>
-    inline const T& payload() {
-        return *reinterpret_cast<const T *>(ptr + offsetof(entry, data));
-    }
+        template<typename T>
+        inline const T& payload() {
+            return *reinterpret_cast<const T *>(ptr + offsetof(entry, data));
+        }
 
-    inline operator const uint8_t*() const {
-        return ptr;
-    }
+        inline operator const uint8_t*() const {
+            return ptr;
+        }
 
-private:
-    const uint8_t  *ptr;
-};
+    private:
+        const uint8_t  *ptr;
+    };
 
-class AbstractEntry {
-public:
+    class AbstractEntry {
+    public:
 
-    // Entry starting in the given pointer
-    explicit AbstractEntry(const uint8_t *entry);
-    virtual ~AbstractEntry() {}
+        // Entry starting in the given pointer
+        explicit AbstractEntry(const uint8_t *entry);
+        virtual ~AbstractEntry() {}
 
-    // build concrete entry of appropriate class from pointer
-    static std::unique_ptr<AbstractEntry> buildEntry(const uint8_t *ptr);
+        // build concrete entry of appropriate class from pointer
+        static std::unique_ptr<AbstractEntry> buildEntry(const uint8_t *ptr);
 
-    // get format entry timestamp
-    // TODO consider changing to uint64_t
-    virtual int64_t      timestamp() const = 0;
+        // get format entry timestamp
+        virtual int64_t      timestamp() const = 0;
 
-    // get format entry's unique id
-    virtual log_hash_t   hash() 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
-    // TODO consider changing to uint32_t
-    virtual int          author() 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
+        // TODO consider changing to uint32_t
+        virtual int          author() const = 0;
 
-    // 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;
+        // 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;
 
-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;
-};
+    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) {}
-    virtual ~FormatEntry() {}
+    class FormatEntry : public AbstractEntry {
+    public:
+        // explicit FormatEntry(const EntryIterator &it);
+        explicit FormatEntry(const uint8_t *ptr) : AbstractEntry(ptr) {}
+        virtual ~FormatEntry() {}
 
-    EntryIterator begin() const;
+        EntryIterator begin() const;
 
-    // Entry's format string
-    const   char* formatString() const;
+        // Entry's format string
+        const   char* formatString() const;
 
-    // Enrty's format string length
-            size_t      formatStringLength() const;
+        // Enrty's format string length
+        size_t      formatStringLength() const;
 
-    // Format arguments (excluding format string, timestamp and author)
-            EntryIterator    args() 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 timestamp
+        virtual int64_t     timestamp() const override;
 
-    // get format entry's unique id
-    virtual log_hash_t  hash() 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;
+        // 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;
+        // 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;
 
-};
+    };
 
-class HistogramEntry : public AbstractEntry {
-public:
-    explicit HistogramEntry(const uint8_t *ptr) : AbstractEntry(ptr) {
-    }
-    virtual ~HistogramEntry() {}
+    class HistogramEntry : public AbstractEntry {
+    public:
+        explicit HistogramEntry(const uint8_t *ptr) : AbstractEntry(ptr) {
+        }
+        virtual ~HistogramEntry() {}
 
-    virtual int64_t     timestamp() const override;
+        virtual int64_t     timestamp() const override;
 
-    virtual log_hash_t  hash() const override;
+        virtual log_hash_t  hash() const override;
 
-    virtual int         author() const override;
+        virtual int         author() const override;
 
-    virtual EntryIterator    copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst,
-                                       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
-struct Entry {
-    Entry(Event event, const void *data, size_t length)
-        : mEvent(event), mLength(length), mData(data) { }
-    /*virtual*/ ~Entry() { }
+    // representation of a single log entry in private memory
+    struct Entry {
+        Entry(Event event, const void *data, size_t length)
+            : mEvent(event), mLength(length), mData(data) { }
+        /*virtual*/ ~Entry() { }
 
-    // used during writing to format Entry information as follows: [type][length][data ... ][length]
-    int     copyEntryDataAt(size_t offset) const;
+        // used during writing to format Entry information as follows:
+        // [type][length][data ... ][length]
+        int     copyEntryDataAt(size_t offset) const;
 
-private:
-    friend class Writer;
-    Event       mEvent;     // event type
-    uint8_t     mLength;    // length of additional data, 0 <= mLength <= kMaxLength
-    const void *mData;      // event type-specific data
-    static const size_t kMaxLength = 255;
-public:
-    // mEvent, mLength, mData[...], duplicate mLength
-    static const size_t kOverhead = sizeof(entry) + sizeof(ending);
-    // endind length of previous entry
-    static const size_t kPreviousLengthOffset = - sizeof(ending) +
-                                                offsetof(ending, length);
-};
+    private:
+        friend class Writer;
+        Event       mEvent;     // event type
+        uint8_t     mLength;    // length of additional data, 0 <= mLength <= kMaxLength
+        const void *mData;      // event type-specific data
+        static const size_t kMaxLength = 255;
+    public:
+        // mEvent, mLength, mData[...], duplicate mLength
+        static const size_t kOverhead = sizeof(entry) + sizeof(ending);
+        // endind length of previous entry
+        static const size_t kPreviousLengthOffset = - sizeof(ending) +
+            offsetof(ending, length);
+    };
 
-struct HistTsEntry {
-    log_hash_t hash;
-    int64_t ts;
-}; //TODO __attribute__((packed));
+    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 HistTsEntryWithAuthor {
+        log_hash_t hash;
+        int64_t ts;
+        int author;
+    }; //TODO __attribute__((packed));
 
-using StateTsEntryWithAuthor = HistTsEntryWithAuthor;
+    struct HistIntEntry {
+        log_hash_t hash;
+        int value;
+    }; //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
-//  byte[2]             mData[0]
-//  ...
-//  byte[2+i]           mData[i]
-//  ...
-//  byte[2+mLength-1]   mData[mLength-1]
-//  byte[2+mLength]     duplicate copy of mLength to permit reverse scan
-//  byte[3+mLength]     start of next log entry
+    // representation of a single log entry in shared memory
+    //  byte[0]             mEvent
+    //  byte[1]             mLength
+    //  byte[2]             mData[0]
+    //  ...
+    //  byte[2+i]           mData[i]
+    //  ...
+    //  byte[2+mLength-1]   mData[mLength-1]
+    //  byte[2+mLength]     duplicate copy of mLength to permit reverse scan
+    //  byte[3+mLength]     start of next log entry
 
     static void    appendInt(String8 *body, const void *data);
     static void    appendFloat(String8 *body, const void *data);
@@ -275,309 +276,332 @@
     static String8 bufferDump(const EntryIterator &it);
 public:
 
-// Located in shared memory, must be POD.
-// Exactly one process must explicitly call the constructor or use placement new.
-// Since this is a POD, the destructor is empty and unnecessary to call it explicitly.
-struct Shared {
-    Shared() /* mRear initialized via default constructor */ { }
-    /*virtual*/ ~Shared() { }
+    // Located in shared memory, must be POD.
+    // Exactly one process must explicitly call the constructor or use placement new.
+    // Since this is a POD, the destructor is empty and unnecessary to call it explicitly.
+    struct Shared {
+        Shared() /* mRear initialized via default constructor */ { }
+        /*virtual*/ ~Shared() { }
 
-    audio_utils_fifo_index  mRear;  // index one byte past the end of most recent Entry
-    char    mBuffer[0];             // circular buffer for entries
-};
-
-public:
-
-// ---------------------------------------------------------------------------
-
-// FIXME Timeline was intended to wrap Writer and Reader, but isn't actually used yet.
-// For now it is just a namespace for sharedSize().
-class Timeline : public RefBase {
-public:
-#if 0
-    Timeline(size_t size, void *shared = NULL);
-    virtual ~Timeline();
-#endif
-
-    // Input parameter 'size' is the desired size of the timeline in byte units.
-    // Returns the size rounded up to a power-of-2, plus the constant size overhead for indices.
-    static size_t sharedSize(size_t size);
-
-#if 0
-private:
-    friend class    Writer;
-    friend class    Reader;
-
-    const size_t    mSize;      // circular buffer size in bytes, must be a power of 2
-    bool            mOwn;       // whether I own the memory at mShared
-    Shared* const   mShared;    // pointer to shared memory
-#endif
-};
-
-// ---------------------------------------------------------------------------
-
-// Writer is thread-safe with respect to Reader, but not with respect to multiple threads
-// calling Writer methods.  If you need multi-thread safety for writing, use LockedWriter.
-class Writer : public RefBase {
-public:
-    Writer();                   // dummy nop implementation without shared memory
-
-    // Input parameter 'size' is the desired size of the timeline in byte units.
-    // The size of the shared memory must be at least Timeline::sharedSize(size).
-    Writer(void *shared, size_t size);
-    Writer(const sp<IMemory>& iMemory, size_t size);
-
-    virtual ~Writer();
-
-    // FIXME needs comments, and some should be private
-    virtual void    log(const char *string);
-    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 int64_t ts);
-    virtual void    logInteger(const int x);
-    virtual void    logFloat(const float x);
-    virtual void    logPID();
-    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    logEventHistTs(Event event, log_hash_t hash);
-
-    virtual bool    isEnabled() const;
-
-    // return value for all of these is the previous isEnabled()
-    virtual bool    setEnabled(bool enabled);   // but won't enable if no shared memory
-            bool    enable()    { return setEnabled(true); }
-            bool    disable()   { return setEnabled(false); }
-
-    sp<IMemory>     getIMemory() const  { return mIMemory; }
-
-private:
-    // 0 <= length <= kMaxLength
-    // writes a single Entry to the FIFO
-    void    log(Event event, const void *data, size_t length);
-    // checks validity of an event before calling log above this one
-    void    log(const Entry *entry, bool trusted = false);
-
-    Shared* const   mShared;    // raw pointer to shared memory
-    sp<IMemory>     mIMemory;   // ref-counted version, initialized in constructor and then const
-    audio_utils_fifo * const mFifo;                 // FIFO itself,
-                                                    // non-NULL unless constructor fails
-    audio_utils_fifo_writer * const mFifoWriter;    // used to write to FIFO,
-                                                    // non-NULL unless dummy constructor used
-    bool            mEnabled;   // whether to actually log
-
-    // cached pid and process name to use in %p format specifier
-    // total tag length is mPidTagSize and process name is not zero terminated
-    char   *mPidTag;
-    size_t  mPidTagSize;
-};
-
-// ---------------------------------------------------------------------------
-
-// Similar to Writer, but safe for multiple threads to call concurrently
-class LockedWriter : public Writer {
-public:
-    LockedWriter();
-    LockedWriter(void *shared, size_t size);
-
-    virtual void    log(const char *string);
-    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 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);
-
-private:
-    mutable Mutex   mLock;
-};
-
-// ---------------------------------------------------------------------------
-
-class Reader : public RefBase {
-public:
-
-    // A snapshot of a readers buffer
-    // This is raw data. No analysis has been done on it
-    class Snapshot {
-    public:
-        Snapshot() : mData(NULL), mLost(0) {}
-
-        Snapshot(size_t bufferSize) : mData(new uint8_t[bufferSize]) {}
-
-        ~Snapshot() { delete[] mData; }
-
-        // copy of the buffer
-        uint8_t *data() const { return mData; }
-
-        // amount of data lost (given by audio_utils_fifo_reader)
-        size_t   lost() const { return mLost; }
-
-        // iterator to beginning of readable segment of snapshot
-        // data between begin and end has valid entries
-        EntryIterator begin() { return mBegin; }
-
-        // iterator to end of readable segment of snapshot
-        EntryIterator end() { return mEnd; }
-
-    private:
-        friend class Reader;
-        uint8_t              *mData;
-        size_t                mLost;
-        EntryIterator mBegin;
-        EntryIterator mEnd;
+        audio_utils_fifo_index  mRear;  // index one byte past the end of most recent Entry
+        char    mBuffer[0];             // circular buffer for entries
     };
 
-    // Input parameter 'size' is the desired size of the timeline in byte units.
-    // The size of the shared memory must be at least Timeline::sharedSize(size).
-    Reader(const void *shared, size_t size);
-    Reader(const sp<IMemory>& iMemory, size_t size);
-
-    virtual ~Reader();
-
-    // get snapshot of readers fifo buffer, effectively consuming the buffer
-    std::unique_ptr<Snapshot> getSnapshot();
-    // dump a particular snapshot of the reader
-    // TODO: move dump to PerformanceAnalysis. Model/view/controller design
-    void     dump(int fd, size_t indent, Snapshot & snap);
-    // dump the current content of the reader's buffer (call getSnapshot() and previous dump())
-    void     dump(int fd, size_t indent = 0);
-    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
-    int     mFd;                // file descriptor
-    int     mIndent;            // indentation level
-    audio_utils_fifo * const mFifo;                 // FIFO itself,
-                                                    // non-NULL unless constructor fails
-    audio_utils_fifo_reader * const mFifoReader;    // used to read from FIFO,
-                                                    // non-NULL unless constructor fails
-
-    // TODO: it might be clearer, instead of a direct map from source location to vector of
-    // timestamps, if we instead first mapped from source location to an object that
-    // represented that location. And one_of its fields would be a vector of timestamps.
-    // That would allow us to record other information about the source location beyond timestamps.
-    void    dumpLine(const String8& timestamp, String8& body);
-
-    EntryIterator   handleFormat(const FormatEntry &fmtEntry,
-                                         String8 *timestamp,
-                                         String8 *body);
-    // dummy method for handling absent author entry
-    virtual void handleAuthor(const AbstractEntry& /*fmtEntry*/, String8* /*body*/) {}
-
-    // 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 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
-};
-
-// Wrapper for a reader with a name. Contains a pointer to the reader and a pointer to the name
-class NamedReader {
 public:
-    NamedReader() { mName[0] = '\0'; } // for Vector
-    NamedReader(const sp<NBLog::Reader>& reader, const char *name) :
-        mReader(reader)
-        { strlcpy(mName, name, sizeof(mName)); }
-    ~NamedReader() { }
-    const sp<NBLog::Reader>&  reader() const { return mReader; }
-    const char*               name() const { return mName; }
 
-private:
-    sp<NBLog::Reader>   mReader;
-    static const size_t kMaxName = 32;
-    char                mName[kMaxName];
-};
+    // ---------------------------------------------------------------------------
 
-// ---------------------------------------------------------------------------
+    // FIXME Timeline was intended to wrap Writer and Reader, but isn't actually used yet.
+    // For now it is just a namespace for sharedSize().
+    class Timeline : public RefBase {
+    public:
+#if 0
+        Timeline(size_t size, void *shared = NULL);
+        virtual ~Timeline();
+#endif
 
-class Merger : public RefBase {
-public:
-    Merger(const void *shared, size_t size);
+        // Input parameter 'size' is the desired size of the timeline in byte units.
+        // Returns the size rounded up to a power-of-2, plus the constant size overhead for indices.
+        static size_t sharedSize(size_t size);
 
-    virtual ~Merger() {}
+#if 0
+    private:
+        friend class    Writer;
+        friend class    Reader;
 
-    void addReader(const NamedReader &reader);
-    // TODO add removeReader
-    void merge();
-    // FIXME This is returning a reference to a shared variable that needs a lock
-    const std::vector<NamedReader>& getNamedReaders() const;
-private:
-    // vector of the readers the merger is supposed to merge from.
-    // every reader reads from a writer's buffer
-    // FIXME Needs to be protected by a lock
-    std::vector<NamedReader> mNamedReaders;
+        const size_t    mSize;      // circular buffer size in bytes, must be a power of 2
+        bool            mOwn;       // whether I own the memory at mShared
+        Shared* const   mShared;    // pointer to shared memory
+#endif
+    };
 
-    // TODO Need comments on all of these
-    Shared * const mShared;
-    std::unique_ptr<audio_utils_fifo> mFifo;
-    std::unique_ptr<audio_utils_fifo_writer> mFifoWriter;
-};
+    // ---------------------------------------------------------------------------
 
-class MergeReader : public Reader {
-public:
-    MergeReader(const void *shared, size_t size, Merger &merger);
-private:
-    // FIXME Needs to be protected by a lock,
-    //       because even though our use of it is read-only there may be asynchronous updates
-    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
-    void handleAuthor(const AbstractEntry &fmtEntry, String8 *body);
-};
+    // Writer is thread-safe with respect to Reader, but not with respect to multiple threads
+    // calling Writer methods.  If you need multi-thread safety for writing, use LockedWriter.
+    class Writer : public RefBase {
+    public:
+        Writer();                   // dummy nop implementation without shared memory
 
-// MergeThread is a thread that contains a Merger. It works as a retriggerable one-shot:
-// when triggered, it awakes for a lapse of time, during which it periodically merges; if
-// retriggered, the timeout is reset.
-// The thread is triggered on AudioFlinger binder activity.
-class MergeThread : public Thread {
-public:
-    MergeThread(Merger &merger);
-    virtual ~MergeThread() override;
+        // Input parameter 'size' is the desired size of the timeline in byte units.
+        // The size of the shared memory must be at least Timeline::sharedSize(size).
+        Writer(void *shared, size_t size);
+        Writer(const sp<IMemory>& iMemory, size_t size);
 
-    // Reset timeout and activate thread to merge periodically if it's idle
-    void wakeup();
+        virtual ~Writer();
 
-    // Set timeout period until the merging thread goes idle again
-    void setTimeoutUs(int time);
+        // FIXME needs comments, and some should be private
+        virtual void    log(const char *string);
+        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 int64_t ts);
+        virtual void    logInteger(const int x);
+        virtual void    logFloat(const float x);
+        virtual void    logPID();
+        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    logEventHistTs(Event event, log_hash_t hash);
 
-private:
-    virtual bool threadLoop() override;
+        virtual bool    isEnabled() const;
 
-    // the merger who actually does the work of merging the logs
-    Merger&     mMerger;
+        // return value for all of these is the previous isEnabled()
+        virtual bool    setEnabled(bool enabled);   // but won't enable if no shared memory
+        bool    enable()    { return setEnabled(true); }
+        bool    disable()   { return setEnabled(false); }
 
-    // mutex for the condition variable
-    Mutex       mMutex;
+        sp<IMemory>     getIMemory() const  { return mIMemory; }
 
-    // condition variable to activate merging on timeout >= 0
-    Condition   mCond;
+    private:
+        // 0 <= length <= kMaxLength
+        // writes a single Entry to the FIFO
+        void    log(Event event, const void *data, size_t length);
+        // checks validity of an event before calling log above this one
+        void    log(const Entry *entry, bool trusted = false);
 
-    // time left until the thread blocks again (in microseconds)
-    int         mTimeoutUs;
+        Shared* const   mShared;    // raw pointer to shared memory
+        sp<IMemory>     mIMemory;   // ref-counted version, initialized in constructor
+                                    // and then const
+        audio_utils_fifo * const mFifo;                 // FIFO itself, non-NULL
+                                                        // unless constructor fails
+        audio_utils_fifo_writer * const mFifoWriter;    // used to write to FIFO, non-NULL
+                                                        // unless dummy constructor used
+        bool            mEnabled;   // whether to actually log
 
-    // merging period when the thread is awake
-    static const int  kThreadSleepPeriodUs = 1000000 /*1s*/;
+        // cached pid and process name to use in %p format specifier
+        // total tag length is mPidTagSize and process name is not zero terminated
+        char   *mPidTag;
+        size_t  mPidTagSize;
+    };
 
-    // initial timeout value when triggered
-    static const int  kThreadWakeupPeriodUs = 3000000 /*3s*/;
-};
+    // ---------------------------------------------------------------------------
+
+    // Similar to Writer, but safe for multiple threads to call concurrently
+    class LockedWriter : public Writer {
+    public:
+        LockedWriter();
+        LockedWriter(void *shared, size_t size);
+
+        virtual void    log(const char *string);
+        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 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);
+
+    private:
+        mutable Mutex   mLock;
+    };
+
+    // ---------------------------------------------------------------------------
+
+    class Reader : public RefBase {
+    public:
+        // A snapshot of a readers buffer
+        // This is raw data. No analysis has been done on it
+        class Snapshot {
+        public:
+            Snapshot() : mData(NULL), mLost(0) {}
+
+            Snapshot(size_t bufferSize) : mData(new uint8_t[bufferSize]) {}
+
+            ~Snapshot() { delete[] mData; }
+
+            // copy of the buffer
+            uint8_t *data() const { return mData; }
+
+            // amount of data lost (given by audio_utils_fifo_reader)
+            size_t   lost() const { return mLost; }
+
+            // iterator to beginning of readable segment of snapshot
+            // data between begin and end has valid entries
+            EntryIterator begin() { return mBegin; }
+
+            // iterator to end of readable segment of snapshot
+            EntryIterator end() { return mEnd; }
+
+        private:
+            friend class MergeReader;
+            friend class Reader;
+            uint8_t              *mData;
+            size_t                mLost;
+            EntryIterator mBegin;
+            EntryIterator mEnd;
+        };
+
+        // Input parameter 'size' is the desired size of the timeline in byte units.
+        // The size of the shared memory must be at least Timeline::sharedSize(size).
+        Reader(const void *shared, size_t size);
+        Reader(const sp<IMemory>& iMemory, size_t size);
+
+        virtual ~Reader();
+
+        // get snapshot of readers fifo buffer, effectively consuming the buffer
+        std::unique_ptr<Snapshot> getSnapshot();
+
+        bool     isIMemory(const sp<IMemory>& iMemory) const;
+
+    protected:
+        // print a summary of the performance to the console
+        void    dumpLine(const String8& timestamp, String8& body);
+        EntryIterator   handleFormat(const FormatEntry &fmtEntry,
+                                     String8 *timestamp,
+                                     String8 *body);
+        int mFd;                // file descriptor
+        int mIndent;            // indentation level
+        int mLost;              // bytes of data lost before buffer was read
+
+    private:
+        static const std::set<Event> startingTypes;
+        static const std::set<Event> endingTypes;
+
+        // declared as const because audio_utils_fifo() constructor
+        sp<IMemory> mIMemory;       // ref-counted version, assigned only in constructor
+
+        /*const*/ Shared* const mShared;    // raw pointer to shared memory, actually const but not
+        audio_utils_fifo * const mFifo;                 // FIFO itself,
+        // non-NULL unless constructor fails
+        audio_utils_fifo_reader * const mFifoReader;    // used to read from FIFO,
+        // non-NULL unless constructor fails
+
+        // TODO: it might be clearer, instead of a direct map from source location to vector of
+        // timestamps, if we instead first mapped from source location to an object that
+        // represented that location. And one_of its fields would be a vector of timestamps.
+        // That would allow us to record other information about the source location beyond
+        // timestamps.
+
+        // 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 const uint8_t *findLastEntryOfTypes(const uint8_t *front, const uint8_t *back,
+                                                   const std::set<Event> &types);
+
+        // dummy method for handling absent author entry
+        virtual void handleAuthor(const AbstractEntry& /*fmtEntry*/, String8* /*body*/) {}
+    };
+
+    // Wrapper for a reader with a name. Contains a pointer to the reader and a pointer to the name
+    class NamedReader {
+    public:
+        NamedReader() { mName[0] = '\0'; } // for Vector
+        NamedReader(const sp<NBLog::Reader>& reader, const char *name) :
+            mReader(reader)
+            { strlcpy(mName, name, sizeof(mName)); }
+        ~NamedReader() { }
+        const sp<NBLog::Reader>&  reader() const { return mReader; }
+        const char*               name() const { return mName; }
+
+    private:
+        sp<NBLog::Reader>   mReader;
+        static const size_t kMaxName = 32;
+        char                mName[kMaxName];
+    };
+
+    // ---------------------------------------------------------------------------
+
+    // This class is used to read data from each thread's individual FIFO in shared memory
+    // and write it to a single FIFO in local memory.
+    class Merger : public RefBase {
+    public:
+        Merger(const void *shared, size_t size);
+
+        virtual ~Merger() {}
+
+        void addReader(const NamedReader &reader);
+        // TODO add removeReader
+        void merge();
+
+        // FIXME This is returning a reference to a shared variable that needs a lock
+        const std::vector<NamedReader>& getNamedReaders() const;
+
+    private:
+        // vector of the readers the merger is supposed to merge from.
+        // every reader reads from a writer's buffer
+        // FIXME Needs to be protected by a lock
+        std::vector<NamedReader> mNamedReaders;
+
+        Shared * const mShared; // raw pointer to shared memory
+        std::unique_ptr<audio_utils_fifo> mFifo; // FIFO itself
+        std::unique_ptr<audio_utils_fifo_writer> mFifoWriter; // used to write to FIFO
+    };
+
+    // This class has a pointer to the FIFO in local memory which stores the merged
+    // data collected by NBLog::Merger from all NamedReaders. It is used to process
+    // this data and write the result to PerformanceAnalysis.
+    class MergeReader : public Reader {
+    public:
+        MergeReader(const void *shared, size_t size, Merger &merger);
+
+        void dump(int fd, int indent = 0);
+        // process a particular snapshot of the reader
+        void getAndProcessSnapshot(Snapshot & snap);
+        // call getSnapshot of the content of the reader's buffer and process the data
+        void getAndProcessSnapshot();
+
+    private:
+        // FIXME Needs to be protected by a lock,
+        //       because even though our use of it is read-only there may be asynchronous updates
+        const std::vector<NamedReader>& mNamedReaders;
+
+        // analyzes, compresses and stores the merged data
+        // contains a separate instance for every author (thread), and for every source file
+        // location within each author
+        ReportPerformance::PerformanceAnalysisMap mThreadPerformanceAnalysis;
+
+        // handle author entry by looking up the author's name and appending it to the body
+        // returns number of bytes read from fmtEntry
+        void handleAuthor(const AbstractEntry &fmtEntry, String8 *body);
+    };
+
+    // MergeThread is a thread that contains a Merger. It works as a retriggerable one-shot:
+    // when triggered, it awakes for a lapse of time, during which it periodically merges; if
+    // retriggered, the timeout is reset.
+    // The thread is triggered on AudioFlinger binder activity.
+    class MergeThread : public Thread {
+    public:
+        MergeThread(Merger &merger, MergeReader &mergeReader);
+        virtual ~MergeThread() override;
+
+        // Reset timeout and activate thread to merge periodically if it's idle
+        void wakeup();
+
+        // Set timeout period until the merging thread goes idle again
+        void setTimeoutUs(int time);
+
+    private:
+        virtual bool threadLoop() override;
+
+        // the merger who actually does the work of merging the logs
+        Merger&     mMerger;
+
+        // the mergereader used to process data merged by mMerger
+        MergeReader& mMergeReader;
+
+        // mutex for the condition variable
+        Mutex       mMutex;
+
+        // condition variable to activate merging on timeout >= 0
+        Condition   mCond;
+
+        // time left until the thread blocks again (in microseconds)
+        int         mTimeoutUs;
+
+        // merging period when the thread is awake
+        static const int  kThreadSleepPeriodUs = 1000000 /*1s*/;
+
+        // initial timeout value when triggered
+        static const int  kThreadWakeupPeriodUs = 3000000 /*3s*/;
+    };
 
 };  // class NBLog
 
diff --git a/media/libnbaio/include/media/nbaio/PerformanceAnalysis.h b/media/libnbaio/include/media/nbaio/PerformanceAnalysis.h
index b0dc148..a673151 100644
--- a/media/libnbaio/include/media/nbaio/PerformanceAnalysis.h
+++ b/media/libnbaio/include/media/nbaio/PerformanceAnalysis.h
@@ -14,21 +14,25 @@
  * limitations under the License.
  */
 
-// Non-blocking event logger intended for safe communication between processes via shared memory
-
 #ifndef ANDROID_MEDIA_PERFORMANCEANALYSIS_H
 #define ANDROID_MEDIA_PERFORMANCEANALYSIS_H
 
-#include <map>
 #include <deque>
+#include <map>
 #include <vector>
-#include "NBLog.h"
-#include "ReportPerformance.h"
+
+#include <media/nbaio/ReportPerformance.h>
 
 namespace android {
 
 namespace ReportPerformance {
 
+class PerformanceAnalysis;
+
+// a map of PerformanceAnalysis instances
+// The outer key is for the thread, the inner key for the source file location.
+using PerformanceAnalysisMap = std::map<int, std::map<log_hash_t, PerformanceAnalysis>>;
+
 class PerformanceAnalysis {
     // This class stores and analyzes audio processing wakeup timestamps from NBLog
     // FIXME: currently, all performance data is stored in deques. Need to add a mutex.
@@ -36,7 +40,10 @@
     // the fifo writer utilities.
 public:
 
-    PerformanceAnalysis();
+    PerformanceAnalysis() {};
+
+    friend void dump(int fd, int indent,
+                     PerformanceAnalysisMap &threadPerformanceAnalysis);
 
     // Given a series of audio processing wakeup timestamps,
     // compresses and and analyzes the data, and flushes
@@ -49,96 +56,80 @@
     // effectively discarding the idle audio time interval
     void handleStateChange();
 
-    // When the short-term histogram array mRecentHists has reached capacity,
-    // merges histograms for data compression and stores them in mLongTermHists
-    void processAndFlushRecentHists();
-
     // Writes wakeup timestamp entry to log and runs analysis
     // TODO: make this thread safe. Each thread should have its own instance
     // of PerformanceAnalysis.
-    void logTsEntry(timestamp_raw ts);
+    void logTsEntry(timestamp ts);
 
     // FIXME: make peakdetector and storeOutlierData a single function
     // Input: mOutlierData. Looks at time elapsed between outliers
     // finds significant changes in the distribution
     // writes timestamps of significant changes to mPeakTimestamps
-    void detectPeaks();
+    bool detectAndStorePeak(msInterval delta, timestamp ts);
 
     // runs analysis on timestamp series before it is converted to a histogram
     // finds outliers
     // writes to mOutlierData <time elapsed since previous outlier, outlier timestamp>
-    void storeOutlierData(const std::vector<timestamp_raw> &timestamps);
+    bool detectAndStoreOutlier(const msInterval diffMs);
 
-    // input: series of short histograms. Generates a string of analysis of the buffer periods
+    // Generates a string of analysis of the buffer periods and prints to console
     // TODO: WIP write more detailed analysis
     // FIXME: move this data visualization to a separate class. Model/view/controller
-    void reportPerformance(String8 *body, int maxHeight = 10);
-
-    // TODO: delete this. temp for testing
-    void testFunction();
-
-    // This function used to detect glitches in a time series
-    // TODO incorporate this into the analysis (currently unused)
-    void alertIfGlitch(const std::vector<timestamp_raw> &samples);
+    void reportPerformance(String8 *body, int author, log_hash_t hash,
+                           int maxHeight = 10);
 
 private:
 
-    // stores outlier analysis: <elapsed time between outliers in ms, outlier timestamp>
-    std::deque<std::pair<outlierInterval, timestamp>> mOutlierData;
+    // TODO use a circular buffer for the deques and vectors below
+
+    // stores outlier analysis:
+    // <elapsed time between outliers in ms, outlier beginning timestamp>
+    std::deque<std::pair<msInterval, timestamp>> mOutlierData;
 
     // stores each timestamp at which a peak was detected
     // a peak is a moment at which the average outlier interval changed significantly
     std::deque<timestamp> mPeakTimestamps;
 
-    // TODO: turn these into circular buffers for better data flow
-    // FIFO of small histograms
-    // stores fixed-size short buffer period histograms with timestamp of first sample
-    std::deque<std::pair<timestamp, Histogram>> mRecentHists;
-
-    // FIFO of small histograms
-    // stores fixed-size long-term buffer period histograms with timestamp of first sample
-    std::deque<std::pair<timestamp, Histogram>> mLongTermHists;
-
-    // vector of timestamps, collected from NBLog for a (TODO) specific thread
-    // when a vector reaches its maximum size, the data is processed and flushed
-    std::vector<timestamp_raw> mTimeStampSeries;
-
-    static const int kMsPerSec = 1000;
+    // stores buffer period histograms with timestamp of first sample
+    std::deque<std::pair<timestamp, Histogram>> mHists;
 
     // Parameters used when detecting outliers
-    // TODO: learn some of these from the data, delete unused ones
-    // FIXME: decide whether to make kPeriodMs static.
-    static const int kNumBuff = 3; // number of buffers considered in local history
-    int kPeriodMs; // current period length is ideally 4 ms
-    static const int kOutlierMs = 7; // values greater or equal to this cause glitches
-    // DAC processing time for 4 ms buffer
-    static constexpr double kRatio = 0.75; // estimate of CPU time as ratio of period length
-    int kPeriodMsCPU; // compute based on kPeriodLen and kRatio
-
-    // Peak detection: number of standard deviations from mean considered a significant change
-    static const int kStddevThreshold = 5;
+    struct BufferPeriod {
+        double    mMean = -1;          // average time between audio processing wakeups
+        double    mOutlierFactor = -1; // values > mMean * mOutlierFactor are outliers
+        double    mOutlier = -1;       // this is set to mMean * mOutlierFactor
+        timestamp mPrevTs = -1;        // previous timestamp
+    } mBufferPeriod;
 
     // capacity allocated to data structures
-    // TODO: adjust all of these values
-    static const int kRecentHistsCapacity = 100; // number of short-term histograms stored in memory
-    static const int kShortHistSize = 50; // number of samples in a short-term histogram
-    static const int kOutlierSeriesSize = 100; // number of values stored in outlier array
-    static const int kPeakSeriesSize = 100; // number of values stored in peak array
-    static const int kLongTermHistsCapacity = 20; // number of long-term histogram stored in memory
-    // maximum elapsed time between first and last timestamp of a long-term histogram
-    static const int kMaxHistTimespanMs = 5 * kMsPerSec;
+    // TODO: make these values longer when testing is finished
+    struct MaxLength {
+        size_t Hists; // number of histograms stored in memory
+        size_t TimeStamps; // histogram size
+        size_t Outliers; // number of values stored in outlier array
+        size_t Peaks; // number of values stored in peak array
+        int HistTimespanMs; // maximum histogram timespan
+    };
+    static constexpr MaxLength kMaxLength = {.Hists = 20, .TimeStamps = 1000,
+            .Outliers = 100, .Peaks = 100, .HistTimespanMs = 5 * kMsPerSec };
 
-    // these variables are stored in-class to ensure continuity while analyzing the timestamp
-    // series one short sequence at a time: the variables are not re-initialized every time.
-    // FIXME: create inner class for these variables and decide which other ones to add to it
-    double mPeakDetectorMean = -1;
-    double mPeakDetectorSd = -1;
-    // variables for storeOutlierData
-    uint64_t mElapsed = 0;
-    int64_t mPrevNs = -1;
-
+    // these variables ensure continuity while analyzing the timestamp
+    // series one sample at a time.
+    // TODO: change this to a running variance/mean class
+    struct OutlierDistribution {
+        msInterval mMean = 0;         // sample mean since previous peak
+        msInterval mSd = 0;           // sample sd since previous peak
+        msInterval mElapsed = 0;      // time since previous detected outlier
+        const int  kMaxDeviation = 5; // standard deviations from the mean threshold
+        msInterval mTypicalDiff = 0;  // global mean of outliers
+        double     mN = 0;            // length of sequence since the last peak
+        double     mM2 = 0;           // used to calculate sd
+    } mOutlierDistribution;
 };
 
+void dump(int fd, int indent, PerformanceAnalysisMap &threadPerformanceAnalysis);
+void dumpLine(int fd, int indent, const String8 &body);
+
 } // namespace ReportPerformance
 
 }   // namespace android
diff --git a/media/libnbaio/include/media/nbaio/ReportPerformance.h b/media/libnbaio/include/media/nbaio/ReportPerformance.h
index 27d2810..0d4a7b9 100644
--- a/media/libnbaio/include/media/nbaio/ReportPerformance.h
+++ b/media/libnbaio/include/media/nbaio/ReportPerformance.h
@@ -24,29 +24,35 @@
 namespace android {
 
 // This class is used by reportPerformance function
-// TODO move reportPerformance function to ReportPerformance.cpp
+// TODO move PerformanceAnalysis::reportPerformance function to ReportPerformance.cpp
 class String8;
 
 namespace ReportPerformance {
 
-// stores a histogram: key: observed buffer period. value: count
+constexpr int kMsPerSec = 1000;
+
+constexpr int kJiffyPerMs = 10; // time unit for histogram as a multiple of milliseconds
+
+// stores a histogram: key: observed buffer period (multiple of jiffy). value: count
 // TODO: unsigned, unsigned
 using Histogram = std::map<int, int>;
 
-using outlierInterval = uint64_t;
-// int64_t timestamps are converted to uint64_t in PerformanceAnalysis::storeOutlierData,
-// and all analysis functions use uint64_t.
-using timestamp = uint64_t;
-using timestamp_raw = int64_t;
+using msInterval = double;
+using jiffyInterval = double;
 
-// FIXME: decide whether to use 64 or 32 bits
-// TODO: the code has a mix of typedef and using. Standardize to one or the other.
-typedef uint64_t log_hash_t;
+using timestamp = int64_t;
 
+using log_hash_t = uint64_t;
+
+// TODO: should this return an int64_t?
 static inline int deltaMs(int64_t ns1, int64_t ns2) {
     return (ns2 - ns1) / (1000 * 1000);
 }
 
+static inline int deltaJiffy(int64_t ns1, int64_t ns2) {
+    return (kJiffyPerMs * (ns2 - ns1)) / (1000 * 1000);
+}
+
 static inline uint32_t log2(uint32_t x) {
     // This works for x > 0
     return 31 - __builtin_clz(x);
@@ -54,10 +60,10 @@
 
 // Writes outlier intervals, timestamps, and histograms spanning long time
 // intervals to a file.
-void writeToFile(std::deque<std::pair<outlierInterval, timestamp>> &outlierData,
-                 std::deque<std::pair<timestamp, Histogram>> &hists,
-                 const char * kName,
-                 bool append);
+void writeToFile(const std::deque<std::pair<timestamp, Histogram>> &hists,
+                 const std::deque<std::pair<msInterval, timestamp>> &outlierData,
+                 const std::deque<timestamp> &peakTimestamps,
+                 const char * kDirectory, bool append, int author, log_hash_t hash);
 
 } // namespace ReportPerformance
 
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 19973bd..7070bdb 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -156,6 +156,7 @@
 }
 
 subdirs = [
+    "codec2",
     "codecs/*",
     "colorconversion",
     "filters",
diff --git a/media/libstagefright/codec2/Android.bp b/media/libstagefright/codec2/Android.bp
new file mode 100644
index 0000000..e5bc4b3
--- /dev/null
+++ b/media/libstagefright/codec2/Android.bp
@@ -0,0 +1,27 @@
+cc_library_shared {
+    name: "libstagefright_codec2",
+
+    srcs: ["C2.cpp"],
+
+    include_dirs: [
+        "frameworks/av/media/libstagefright/codec2/include",
+        "frameworks/native/include/media/hardware",
+    ],
+
+    sanitize: {
+        misc_undefined: [
+            "unsigned-integer-overflow",
+            "signed-integer-overflow",
+        ],
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
+
+    ldflags: ["-Wl,-Bsymbolic"],
+}
+
+subdirs = [
+    "tests",
+]
diff --git a/media/libstagefright/codec2/Android.mk b/media/libstagefright/codec2/Android.mk
deleted file mode 100644
index ef06ed7..0000000
--- a/media/libstagefright/codec2/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
-        C2.cpp    \
-
-LOCAL_C_INCLUDES += \
-        $(TOP)/frameworks/av/media/libstagefright/codec2/include \
-        $(TOP)/frameworks/native/include/media/hardware \
-
-LOCAL_MODULE:= libstagefright_codec2
-LOCAL_CFLAGS += -Werror -Wall
-LOCAL_CLANG := true
-LOCAL_SANITIZE := unsigned-integer-overflow signed-integer-overflow cfi
-LOCAL_SANITIZE_DIAG := cfi
-
-include $(BUILD_SHARED_LIBRARY)
-
-################################################################################
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/libstagefright/codec2/tests/Android.bp b/media/libstagefright/codec2/tests/Android.bp
new file mode 100644
index 0000000..1dc6a58
--- /dev/null
+++ b/media/libstagefright/codec2/tests/Android.bp
@@ -0,0 +1,31 @@
+cc_test {
+    name: "codec2_test",
+
+    tags: [
+        "tests",
+    ],
+
+    srcs: [
+        "vndk/C2UtilTest.cpp",
+        "C2_test.cpp",
+        "C2Param_test.cpp",
+    ],
+
+    include_dirs: [
+        "frameworks/av/media/libstagefright/codec2/include",
+        "frameworks/av/media/libstagefright/codec2/vndk/include",
+        "frameworks/native/include/media/openmax",
+    ],
+
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libstagefright_codec2",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-std=c++14",
+    ],
+}
diff --git a/media/libstagefright/codec2/tests/Android.mk b/media/libstagefright/codec2/tests/Android.mk
deleted file mode 100644
index 49c4253..0000000
--- a/media/libstagefright/codec2/tests/Android.mk
+++ /dev/null
@@ -1,37 +0,0 @@
-# Build the unit tests.
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-LOCAL_MODULE := codec2_test
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := \
-	vndk/C2UtilTest.cpp \
-	C2_test.cpp \
-	C2Param_test.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
-	libcutils \
-	libstagefright_codec2 \
-	liblog
-
-LOCAL_C_INCLUDES := \
-	frameworks/av/media/libstagefright/codec2/include \
-	frameworks/av/media/libstagefright/codec2/vndk/include \
-	$(TOP)/frameworks/native/include/media/openmax \
-
-LOCAL_CFLAGS += -Werror -Wall -std=c++14
-LOCAL_CLANG := true
-
-include $(BUILD_NATIVE_TEST)
-
-# Include subdirectory makefiles
-# ============================================================
-
-# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
-# team really wants is to build the stuff defined by this makefile.
-ifeq (,$(ONE_SHOT_MAKEFILE))
-include $(call first-makefiles-under,$(LOCAL_PATH))
-endif
diff --git a/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp b/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
index b1af17b..55ae60d 100644
--- a/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
+++ b/media/libstagefright/codecs/avcenc/SoftAVCEnc.cpp
@@ -629,8 +629,10 @@
         level = 30;
     } else if (displaySizeY > (352 * 288)) {
         level = 21;
-    } else {
+    } else if (displaySizeY > (176 * 144)) {
         level = 20;
+    } else {
+        level = 10;
     }
     mAVCEncLevel = MAX(level, mAVCEncLevel);
 
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 0df9a39..3a95a3b 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -2603,7 +2603,7 @@
             while (ec->mEffects.size()) {
                 sp<EffectModule> effect = ec->mEffects[0];
                 effect->unPin();
-                t->removeEffect_l(effect);
+                t->removeEffect_l(effect, /*release*/ true);
                 if (effect->purgeHandles()) {
                     t->checkSuspendOnEffectEnabled_l(effect, false, effect->sessionId());
                 }
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index f2c1c4f..bd5f146 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -25,6 +25,7 @@
 #include <system/audio_effects/effect_ns.h>
 #include <system/audio_effects/effect_visualizer.h>
 #include <audio_utils/primitives.h>
+#include <media/AudioEffect.h>
 #include <media/audiohal/EffectHalInterface.h>
 #include <media/audiohal/EffectsFactoryHalInterface.h>
 
@@ -109,7 +110,10 @@
 {
     ALOGV("Destructor %p", this);
     if (mEffectInterface != 0) {
-        ALOGW("EffectModule %p destructor called with unreleased interface", this);
+        char uuidStr[64];
+        AudioEffect::guidToString(&mDescriptor.uuid, uuidStr, sizeof(uuidStr));
+        ALOGW("EffectModule %p destructor called with unreleased interface, effect %s",
+                this, uuidStr);
         release_l();
     }
 
@@ -1081,18 +1085,12 @@
     result.append(buffer);
 
     result.append("\t\tDescriptor:\n");
-    snprintf(buffer, SIZE, "\t\t- UUID: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n",
-            mDescriptor.uuid.timeLow, mDescriptor.uuid.timeMid, mDescriptor.uuid.timeHiAndVersion,
-            mDescriptor.uuid.clockSeq, mDescriptor.uuid.node[0], mDescriptor.uuid.node[1],
-                    mDescriptor.uuid.node[2],
-            mDescriptor.uuid.node[3],mDescriptor.uuid.node[4],mDescriptor.uuid.node[5]);
+    char uuidStr[64];
+    AudioEffect::guidToString(&mDescriptor.uuid, uuidStr, sizeof(uuidStr));
+    snprintf(buffer, SIZE, "\t\t- UUID: %s\n", uuidStr);
     result.append(buffer);
-    snprintf(buffer, SIZE, "\t\t- TYPE: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n",
-                mDescriptor.type.timeLow, mDescriptor.type.timeMid,
-                    mDescriptor.type.timeHiAndVersion,
-                mDescriptor.type.clockSeq, mDescriptor.type.node[0], mDescriptor.type.node[1],
-                    mDescriptor.type.node[2],
-                mDescriptor.type.node[3],mDescriptor.type.node[4],mDescriptor.type.node[5]);
+    AudioEffect::guidToString(&mDescriptor.type, uuidStr, sizeof(uuidStr));
+    snprintf(buffer, SIZE, "\t\t- TYPE: %s\n", uuidStr);
     result.append(buffer);
     snprintf(buffer, SIZE, "\t\t- apiVersion: %08X\n\t\t- flags: %08X (%s)\n",
             mDescriptor.apiVersion,
@@ -1306,11 +1304,10 @@
     if (thread != 0) {
         thread->disconnectEffectHandle(this, unpinIfLast);
     } else {
-        ALOGW("%s Effect handle %p disconnected after thread destruction", __FUNCTION__, this);
         // try to cleanup as much as we can
         sp<EffectModule> effect = mEffect.promote();
-        if (effect != 0) {
-            effect->disconnectHandle(this, unpinIfLast);
+        if (effect != 0 && effect->disconnectHandle(this, unpinIfLast) > 0) {
+            ALOGW("%s Effect handle %p disconnected after thread destruction", __FUNCTION__, this);
         }
     }
 
diff --git a/services/audioflinger/FastMixer.cpp b/services/audioflinger/FastMixer.cpp
index c10fa05..ace586c 100644
--- a/services/audioflinger/FastMixer.cpp
+++ b/services/audioflinger/FastMixer.cpp
@@ -138,8 +138,6 @@
 
 void FastMixer::onStateChange()
 {
-    // log that audio was turned on/off
-    LOG_AUDIO_STATE();
     const FastMixerState * const current = (const FastMixerState *) mCurrent;
     const FastMixerState * const previous = (const FastMixerState *) mPrevious;
     FastMixerDumpState * const dumpState = (FastMixerDumpState *) mDumpState;
@@ -336,7 +334,13 @@
 
 void FastMixer::onWork()
 {
-    LOG_HIST_TS();
+    // TODO: pass an ID parameter to indicate which time series we want to write to in NBLog.cpp
+    // Or: pass both of these into a single call with a boolean
+    if (mIsWarm) {
+        LOG_HIST_TS();
+    } else {
+        LOG_AUDIO_STATE();
+    }
     const FastMixerState * const current = (const FastMixerState *) mCurrent;
     FastMixerDumpState * const dumpState = (FastMixerDumpState *) mDumpState;
     const FastMixerState::Command command = mCommand;
diff --git a/services/audioflinger/OWNERS b/services/audioflinger/OWNERS
index 703e4d2..d02d9e0 100644
--- a/services/audioflinger/OWNERS
+++ b/services/audioflinger/OWNERS
@@ -1,3 +1,4 @@
 hunga@google.com
 jmtrivi@google.com
 mnaganov@google.com
+gkasten@google.com
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 459e4fb..6378a14 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -2636,6 +2636,7 @@
 // shared by MIXER and DIRECT, overridden by DUPLICATING
 ssize_t AudioFlinger::PlaybackThread::threadLoop_write()
 {
+    LOG_HIST_TS();
     mInWrite = true;
     ssize_t bytesWritten;
     const size_t offset = mCurrentWriteLength - mBytesRemaining;
@@ -3114,6 +3115,10 @@
 
                     threadLoop_standby();
 
+                    // This is where we go into standby
+                    if (!mStandby) {
+                        LOG_AUDIO_STATE();
+                    }
                     mStandby = true;
                 }
 
@@ -5429,7 +5434,7 @@
         mPausedWriteLength(0), mPausedBytesRemaining(0), mKeepWakeLock(true),
         mOffloadUnderrunPosition(~0LL)
 {
-    //FIXME: mStandby should be set to true by ThreadBase constructor
+    //FIXME: mStandby should be set to true by ThreadBase constructo
     mStandby = true;
     mKeepWakeLock = property_get_bool("ro.audio.offload_wakelock", true /* default_value */);
 }
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 32f2bc4..613d08c 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -485,6 +485,7 @@
                 // Updated by updateSuspendedSessions_l() only.
                 KeyedVector< audio_session_t, KeyedVector< int, sp<SuspendedSessionDesc> > >
                                         mSuspendedSessions;
+                // TODO: add comment and adjust size as needed
                 static const size_t     kLogSize = 4 * 1024;
                 sp<NBLog::Writer>       mNBLogWriter;
                 bool                    mSystemReady;
@@ -984,6 +985,7 @@
     sp<NBAIO_Source>        mTeeSource;
 #endif
     uint32_t                mScreenState;   // cached copy of gScreenState
+    // TODO: add comment and adjust size as needed
     static const size_t     kFastMixerLogSize = 8 * 1024;
     sp<NBLog::Writer>       mFastMixerNBLogWriter;
 
@@ -1456,6 +1458,7 @@
             // If a fast capture is present, the Pipe as IMemory, otherwise clear
             sp<IMemory>                         mPipeMemory;
 
+            // TODO: add comment and adjust size as needed
             static const size_t                 kFastCaptureLogSize = 4 * 1024;
             sp<NBLog::Writer>                   mFastCaptureNBLogWriter;
 
diff --git a/services/audiopolicy/config/audio_policy_configuration.xml b/services/audiopolicy/config/audio_policy_configuration.xml
index 7af2f81..73efe8e 100644
--- a/services/audiopolicy/config/audio_policy_configuration.xml
+++ b/services/audiopolicy/config/audio_policy_configuration.xml
@@ -163,37 +163,16 @@
                        sources="primary output,deep_buffer,compressed_offload,BT SCO Headset Mic,Telephony Rx"/>
                 <route type="mix" sink="Wired Headphones"
                        sources="primary output,deep_buffer,compressed_offload,BT SCO Headset Mic,Telephony Rx"/>
-                <route type="mix" sink="Telephony Tx"
-                       sources="voice_tx"/>
                 <route type="mix" sink="primary input"
                        sources="Built-In Mic,Built-In Back Mic,Wired Headset Mic,BT SCO Headset Mic"/>
                 <route type="mix" sink="Telephony Tx"
-                       sources="Built-In Mic,Built-In Back Mic,Wired Headset Mic,BT SCO Headset Mic"/>
+                       sources="Built-In Mic,Built-In Back Mic,Wired Headset Mic,BT SCO Headset Mic, voice_tx"/>
                 <route type="mix" sink="voice_rx"
                        sources="Telephony Rx"/>
             </routes>
 
         </module>
 
-        <!-- HDMI Audio HAL -->
-        <module description="HDMI Audio HAL" name="hdmi" version="2.0">
-            <mixPorts>
-                <mixPort name="hdmi output" role="source">
-                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" samplingRates="48000"/>
-                </mixPort>
-            </mixPorts>
-            <devicePorts>
-                <devicePort tagName="HDMI Out" type="AUDIO_DEVICE_OUT_AUX_DIGITAL" role="sink">
-                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
-                             samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
-                </devicePort>
-            </devicePorts>
-            <routes>
-                <route type="mix" sink="HDMI Out"
-                       sources="hdmi output"/>
-            </routes>
-        </module>
-
         <!-- A2dp Audio HAL -->
         <xi:include href="a2dp_audio_policy_configuration.xml"/>
 
diff --git a/services/medialog/MediaLogService.cpp b/services/medialog/MediaLogService.cpp
index a5512e1..fe19b6b 100644
--- a/services/medialog/MediaLogService.cpp
+++ b/services/medialog/MediaLogService.cpp
@@ -26,13 +26,19 @@
 
 namespace android {
 
- static const char kDeadlockedString[] = "MediaLogService may be deadlocked\n";
+static const char kDeadlockedString[] = "MediaLogService may be deadlocked\n";
+
+// mMerger, mMergeReader, and mMergeThread all point to the same location in memory
+// mMergerShared. This is the local memory FIFO containing data merged from all
+// individual thread FIFOs in shared memory. mMergeThread is used to periodically
+// call NBLog::Merger::merge() to collect the data and write it to the FIFO, and call
+// NBLog::MergeReader::getAndProcessSnapshot to process the merged data.
 MediaLogService::MediaLogService() :
     BnMediaLogService(),
     mMergerShared((NBLog::Shared*) malloc(NBLog::Timeline::sharedSize(kMergeBufferSize))),
     mMerger(mMergerShared, kMergeBufferSize),
     mMergeReader(mMergerShared, kMergeBufferSize, mMerger),
-    mMergeThread(new NBLog::MergeThread(mMerger))
+    mMergeThread(new NBLog::MergeThread(mMerger, mMergeReader))
 {
     mMergeThread->run("MergeThread");
 }
@@ -123,15 +129,10 @@
                 } else {
                     ALOGI("%s:", namedReader.name());
                 }
-                // TODO This code is for testing, remove it when done
-                // namedReader.reader()->dump(fd, 0 /*indent*/);
             }
-
             mLock.unlock();
         }
     }
-
-    // FIXME request merge to make sure log is up to date
     mMergeReader.dump(fd);
     return NO_ERROR;
 }