Merge "Update tee sink"
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index 6d42143..0855db6 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -66,14 +66,6 @@
 
 LOCAL_CFLAGS += -UFAST_TRACKS_AT_NON_NATIVE_SAMPLE_RATE
 
-# uncomment for dumpsys to write most recent audio output to .wav file
-# 47.5 seconds at 44.1 kHz, 8 megabytes
-# LOCAL_CFLAGS += -DTEE_SINK_FRAMES=0x200000
-
-# uncomment for dumpsys to write most recent audio input to .wav file
-# 47.5 seconds at 44.1 kHz, 8 megabytes
-# LOCAL_CFLAGS += -DTEE_SINK_INPUT_FRAMES=0x200000
-
 # uncomment to enable the audio watchdog
 # LOCAL_SRC_FILES += AudioWatchdog.cpp
 # LOCAL_CFLAGS += -DAUDIO_WATCHDOG
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 47c2772..e0ab8cd 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -19,6 +19,7 @@
 #define LOG_TAG "AudioFlinger"
 //#define LOG_NDEBUG 0
 
+#include <dirent.h>
 #include <math.h>
 #include <signal.h>
 #include <sys/time.h>
@@ -61,6 +62,9 @@
 
 #include <media/IMediaLogService.h>
 
+#include <media/nbaio/Pipe.h>
+#include <media/nbaio/PipeReader.h>
+
 // ----------------------------------------------------------------------------
 
 // Note: the following macro is used for extremely verbose logging message.  In
@@ -86,6 +90,14 @@
 
 uint32_t AudioFlinger::mScreenState;
 
+bool AudioFlinger::mTeeSinkInputEnabled = false;
+bool AudioFlinger::mTeeSinkOutputEnabled = false;
+bool AudioFlinger::mTeeSinkTrackEnabled = false;
+
+size_t AudioFlinger::mTeeSinkInputFrames = kTeeSinkInputFramesDefault;
+size_t AudioFlinger::mTeeSinkOutputFrames = kTeeSinkOutputFramesDefault;
+size_t AudioFlinger::mTeeSinkTrackFrames = kTeeSinkTrackFramesDefault;
+
 // ----------------------------------------------------------------------------
 
 static int load_audio_interface(const char *if_name, audio_hw_device_t **dev)
@@ -134,6 +146,19 @@
     if (doLog) {
         mLogMemoryDealer = new MemoryDealer(kLogMemorySize, "LogWriters");
     }
+    (void) property_get("ro.debuggable", value, "0");
+    int debuggable = atoi(value);
+    int teeEnabled = 0;
+    if (debuggable) {
+        (void) property_get("af.tee", value, "0");
+        teeEnabled = atoi(value);
+    }
+    if (teeEnabled & 1)
+        mTeeSinkInputEnabled = true;
+    if (teeEnabled & 2)
+        mTeeSinkOutputEnabled = true;
+    if (teeEnabled & 4)
+        mTeeSinkTrackEnabled = true;
 }
 
 void AudioFlinger::onFirstRef()
@@ -1602,7 +1627,6 @@
         // Try to re-use most recently used Pipe to archive a copy of input for dumpsys,
         // or (re-)create if current Pipe is idle and does not match the new format
         sp<NBAIO_Sink> teeSink;
-#ifdef TEE_SINK_INPUT_FRAMES
         enum {
             TEE_SINK_NO,    // don't copy input
             TEE_SINK_NEW,   // copy input using a new pipe
@@ -1610,7 +1634,9 @@
         } kind;
         NBAIO_Format format = Format_from_SR_C(inStream->common.get_sample_rate(&inStream->common),
                                         popcount(inStream->common.get_channels(&inStream->common)));
-        if (format == Format_Invalid) {
+        if (!mTeeSinkInputEnabled) {
+            kind = TEE_SINK_NO;
+        } else if (format == Format_Invalid) {
             kind = TEE_SINK_NO;
         } else if (mRecordTeeSink == 0) {
             kind = TEE_SINK_NEW;
@@ -1623,7 +1649,7 @@
         }
         switch (kind) {
         case TEE_SINK_NEW: {
-            Pipe *pipe = new Pipe(TEE_SINK_INPUT_FRAMES, format);
+            Pipe *pipe = new Pipe(mTeeSinkInputFrames, format);
             size_t numCounterOffers = 0;
             const NBAIO_Format offers[1] = {format};
             ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
@@ -1644,7 +1670,7 @@
         default:
             break;
         }
-#endif
+
         AudioStreamIn *input = new AudioStreamIn(inHwDev, inStream);
 
         // Start record thread
@@ -2199,19 +2225,80 @@
     return NO_ERROR;
 }
 
+struct Entry {
+#define MAX_NAME 32     // %Y%m%d%H%M%S_%d.wav
+    char mName[MAX_NAME];
+};
+
+int comparEntry(const void *p1, const void *p2)
+{
+    return strcmp(((const Entry *) p1)->mName, ((const Entry *) p2)->mName);
+}
+
 void AudioFlinger::dumpTee(int fd, const sp<NBAIO_Source>& source, audio_io_handle_t id)
 {
     NBAIO_Source *teeSource = source.get();
     if (teeSource != NULL) {
+        // .wav rotation
+        // There is a benign race condition if 2 threads call this simultaneously.
+        // They would both traverse the directory, but the result would simply be
+        // failures at unlink() which are ignored.  It's also unlikely since
+        // normally dumpsys is only done by bugreport or from the command line.
+        char teePath[32+256];
+        strcpy(teePath, "/data/misc/media");
+        size_t teePathLen = strlen(teePath);
+        DIR *dir = opendir(teePath);
+        teePath[teePathLen++] = '/';
+        if (dir != NULL) {
+#define MAX_SORT 20 // number of entries to sort
+#define MAX_KEEP 10 // number of entries to keep
+            struct Entry entries[MAX_SORT];
+            size_t entryCount = 0;
+            while (entryCount < MAX_SORT) {
+                struct dirent de;
+                struct dirent *result = NULL;
+                int rc = readdir_r(dir, &de, &result);
+                if (rc != 0) {
+                    ALOGW("readdir_r failed %d", rc);
+                    break;
+                }
+                if (result == NULL) {
+                    break;
+                }
+                if (result != &de) {
+                    ALOGW("readdir_r returned unexpected result %p != %p", result, &de);
+                    break;
+                }
+                // ignore non .wav file entries
+                size_t nameLen = strlen(de.d_name);
+                if (nameLen <= 4 || nameLen >= MAX_NAME ||
+                        strcmp(&de.d_name[nameLen - 4], ".wav")) {
+                    continue;
+                }
+                strcpy(entries[entryCount++].mName, de.d_name);
+            }
+            (void) closedir(dir);
+            if (entryCount > MAX_KEEP) {
+                qsort(entries, entryCount, sizeof(Entry), comparEntry);
+                for (size_t i = 0; i < entryCount - MAX_KEEP; ++i) {
+                    strcpy(&teePath[teePathLen], entries[i].mName);
+                    (void) unlink(teePath);
+                }
+            }
+        } else {
+            if (fd >= 0) {
+                fdprintf(fd, "unable to rotate tees in %s: %s\n", teePath, strerror(errno));
+            }
+        }
         char teeTime[16];
         struct timeval tv;
         gettimeofday(&tv, NULL);
         struct tm tm;
         localtime_r(&tv.tv_sec, &tm);
-        strftime(teeTime, sizeof(teeTime), "%T", &tm);
-        char teePath[64];
-        sprintf(teePath, "/data/misc/media/%s_%d.wav", teeTime, id);
-        int teeFd = open(teePath, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+        strftime(teeTime, sizeof(teeTime), "%Y%m%d%H%M%S", &tm);
+        snprintf(&teePath[teePathLen], sizeof(teePath) - teePathLen, "%s_%d.wav", teeTime, id);
+        // if 2 dumpsys are done within 1 second, and rotation didn't work, then discard 2nd
+        int teeFd = open(teePath, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, S_IRUSR | S_IWUSR);
         if (teeFd >= 0) {
             char wavHeader[44];
             memcpy(wavHeader,
@@ -2253,9 +2340,13 @@
             temp =  total * channelCount * sizeof(short);
             write(teeFd, &temp, sizeof(temp));
             close(teeFd);
-            fdprintf(fd, "FastMixer tee copied to %s\n", teePath);
+            if (fd >= 0) {
+                fdprintf(fd, "tee copied to %s\n", teePath);
+            }
         } else {
-            fdprintf(fd, "FastMixer unable to create tee %s: \n", strerror(errno));
+            if (fd >= 0) {
+                fdprintf(fd, "unable to create tee %s: %s\n", teePath, strerror(errno));
+            }
         }
     }
 }
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index c3f08f6..44bd260 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -593,7 +593,24 @@
     sp<NBAIO_Source> mRecordTeeSource;
 
 public:
+    // tee sink, if enabled by property, allows dumpsys to write most recent audio to .wav file
     static void dumpTee(int fd, const sp<NBAIO_Source>& source, audio_io_handle_t id = 0);
+
+    // whether tee sink is enabled by property
+    static bool mTeeSinkInputEnabled;
+    static bool mTeeSinkOutputEnabled;
+    static bool mTeeSinkTrackEnabled;
+
+    // runtime configured size of each tee sink pipe, in frames
+    static size_t mTeeSinkInputFrames;
+    static size_t mTeeSinkOutputFrames;
+    static size_t mTeeSinkTrackFrames;
+
+    // compile-time default size of tee sink pipes, in frames
+    // 0x200000 stereo 16-bit PCM frames = 47.5 seconds at 44.1 kHz, 8 megabytes
+    static const size_t kTeeSinkInputFramesDefault = 0x200000;
+    static const size_t kTeeSinkOutputFramesDefault = 0x200000;
+    static const size_t kTeeSinkTrackFramesDefault = 0x1000;
 };
 
 #undef INCLUDING_FROM_AUDIOFLINGER_H
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index ba848d7..1209ea6 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -2124,19 +2124,19 @@
                 (monoPipe->maxFrames() * 7) / 8 : mNormalFrameCount * 2);
         mPipeSink = monoPipe;
 
-#ifdef TEE_SINK_FRAMES
-        // create a Pipe to archive a copy of FastMixer's output for dumpsys
-        Pipe *teeSink = new Pipe(TEE_SINK_FRAMES, format);
-        numCounterOffers = 0;
-        index = teeSink->negotiate(offers, 1, NULL, numCounterOffers);
-        ALOG_ASSERT(index == 0);
-        mTeeSink = teeSink;
-        PipeReader *teeSource = new PipeReader(*teeSink);
-        numCounterOffers = 0;
-        index = teeSource->negotiate(offers, 1, NULL, numCounterOffers);
-        ALOG_ASSERT(index == 0);
-        mTeeSource = teeSource;
-#endif
+        if (mTeeSinkOutputEnabled) {
+            // create a Pipe to archive a copy of FastMixer's output for dumpsys
+            Pipe *teeSink = new Pipe(mTeeSinkOutputFrames, format);
+            numCounterOffers = 0;
+            index = teeSink->negotiate(offers, 1, NULL, numCounterOffers);
+            ALOG_ASSERT(index == 0);
+            mTeeSink = teeSink;
+            PipeReader *teeSource = new PipeReader(*teeSink);
+            numCounterOffers = 0;
+            index = teeSource->negotiate(offers, 1, NULL, numCounterOffers);
+            ALOG_ASSERT(index == 0);
+            mTeeSource = teeSource;
+        }
 
         // create fast mixer and configure it initially with just one fast track for our submix
         mFastMixer = new FastMixer();
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index e0bd97a..fecbfda 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -141,4 +141,7 @@
     Vector < sp<SyncEvent> >mSyncEvents;
     const bool          mIsOut;
     ServerProxy*        mServerProxy;
+    const int           mId;
+    sp<NBAIO_Sink>      mTeeSink;
+    sp<NBAIO_Source>    mTeeSource;
 };
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 315cbbc..724ce38 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -32,6 +32,9 @@
 #include "AudioFlinger.h"
 #include "ServiceUtilities.h"
 
+#include <media/nbaio/Pipe.h>
+#include <media/nbaio/PipeReader.h>
+
 // ----------------------------------------------------------------------------
 
 // Note: the following macro is used for extremely verbose logging message.  In
@@ -53,6 +56,8 @@
 //      TrackBase
 // ----------------------------------------------------------------------------
 
+static volatile int32_t nextTrackId = 55;
+
 // TrackBase constructor must be called with AudioFlinger::mLock held
 AudioFlinger::ThreadBase::TrackBase::TrackBase(
             ThreadBase *thread,
@@ -82,7 +87,8 @@
         mStepServerFailed(false),
         mSessionId(sessionId),
         mIsOut(isOut),
-        mServerProxy(NULL)
+        mServerProxy(NULL),
+        mId(android_atomic_inc(&nextTrackId))
 {
     // client == 0 implies sharedBuffer == 0
     ALOG_ASSERT(!(client == 0 && sharedBuffer != 0));
@@ -134,11 +140,30 @@
         }
         mBufferEnd = (uint8_t *)mBuffer + bufferSize;
         mServerProxy = new ServerProxy(mCblk, mBuffer, frameCount, mFrameSize, isOut);
+
+        if (mTeeSinkTrackEnabled) {
+        NBAIO_Format pipeFormat = Format_from_SR_C(mSampleRate, mChannelCount);
+        if (pipeFormat != Format_Invalid) {
+            Pipe *pipe = new Pipe(mTeeSinkTrackFrames, pipeFormat);
+            size_t numCounterOffers = 0;
+            const NBAIO_Format offers[1] = {pipeFormat};
+            ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
+            ALOG_ASSERT(index == 0);
+            PipeReader *pipeReader = new PipeReader(*pipe);
+            numCounterOffers = 0;
+            index = pipeReader->negotiate(offers, 1, NULL, numCounterOffers);
+            ALOG_ASSERT(index == 0);
+            mTeeSink = pipe;
+            mTeeSource = pipeReader;
+        }
+        }
+
     }
 }
 
 AudioFlinger::ThreadBase::TrackBase::~TrackBase()
 {
+    dumpTee(-1, mTeeSource, mId);
     // delete the proxy before deleting the shared memory it refers to, to avoid dangling reference
     delete mServerProxy;
     if (mCblk != NULL) {
@@ -164,6 +189,10 @@
 // This implementation of releaseBuffer() is used by Track and RecordTrack, but not TimedTrack
 void AudioFlinger::ThreadBase::TrackBase::releaseBuffer(AudioBufferProvider::Buffer* buffer)
 {
+    if (mTeeSink != 0) {
+        (void) mTeeSink->write(buffer->raw, buffer->frameCount);
+    }
+
     buffer->raw = NULL;
     mStepCount = buffer->frameCount;
     // FIXME See note at getNextBuffer()