Buffer Log functionality for AudioFlinger

Similar to ALOGV, this allows to capture to file audio samples from
within audioservice for debuggin purposes.

Test: manual. Mostly used for debugging
Change-Id: I4d43f573926805a27be910e343476c3f1be51579
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index f35373f..1657c08 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -31,7 +31,8 @@
     AudioMixer.cpp.arm          \
     BufferProviders.cpp         \
     PatchPanel.cpp              \
-    StateQueue.cpp
+    StateQueue.cpp              \
+    BufLog.cpp
 
 LOCAL_C_INCLUDES := \
     $(TOPDIR)frameworks/av/services/audiopolicy \
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 04fb8ba..7e10e48 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -67,6 +67,9 @@
 #include <mediautils/BatteryNotifier.h>
 #include <private/android_filesystem_config.h>
 
+//#define BUFLOG_NDEBUG 0
+#include <BufLog.h>
+
 // ----------------------------------------------------------------------------
 
 // Note: the following macro is used for extremely verbose logging message.  In
@@ -441,6 +444,8 @@
         }
 #endif
 
+        BUFLOG_RESET;
+
         if (locked) {
             mLock.unlock();
         }
diff --git a/services/audioflinger/BufLog.cpp b/services/audioflinger/BufLog.cpp
new file mode 100644
index 0000000..9680eb5
--- /dev/null
+++ b/services/audioflinger/BufLog.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "BufLog.h"
+#define LOG_TAG "BufLog"
+//#define LOG_NDEBUG 0
+
+#include <errno.h>
+#include "log/log.h"
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+// ------------------------------
+// BufLogSingleton
+// ------------------------------
+pthread_once_t onceControl = PTHREAD_ONCE_INIT;
+
+BufLog *BufLogSingleton::mInstance = NULL;
+
+void BufLogSingleton::initOnce() {
+    mInstance = new BufLog();
+    ALOGW("=====================================\n" \
+            "Warning: BUFLOG is defined in some part of your code.\n" \
+            "This will create large audio dumps in %s.\n" \
+            "=====================================\n", BUFLOG_BASE_PATH);
+}
+
+BufLog *BufLogSingleton::instance() {
+    pthread_once(&onceControl, initOnce);
+    return mInstance;
+}
+
+bool BufLogSingleton::instanceExists() {
+    return mInstance != NULL;
+}
+
+// ------------------------------
+// BufLog
+// ------------------------------
+
+BufLog::BufLog() {
+    memset(mStreams, 0, sizeof(mStreams));
+}
+
+BufLog::~BufLog() {
+    android::Mutex::Autolock autoLock(mLock);
+
+    for (unsigned int id = 0; id < BUFLOG_MAXSTREAMS; id++) {
+        BufLogStream *pBLStream = mStreams[id];
+        if (pBLStream != NULL) {
+            delete pBLStream ;
+            mStreams[id] = NULL;
+        }
+    }
+}
+
+size_t BufLog::write(int streamid, const char *tag, int format, int channels,
+        int samplingRate, size_t maxBytes, const void *buf, size_t size) {
+    unsigned int id = streamid % BUFLOG_MAXSTREAMS;
+    android::Mutex::Autolock autoLock(mLock);
+
+    BufLogStream *pBLStream = mStreams[id];
+
+    if (pBLStream == NULL) {
+        pBLStream = mStreams[id] = new BufLogStream(id, tag, format, channels,
+                samplingRate, maxBytes);
+        ALOG_ASSERT(pBLStream != NULL, "BufLogStream Failed to be created");
+    }
+
+    return pBLStream->write(buf, size);
+}
+
+void BufLog::reset() {
+    android::Mutex::Autolock autoLock(mLock);
+    ALOGV("Resetting all BufLogs");
+    int count = 0;
+
+    for (unsigned int id = 0; id < BUFLOG_MAXSTREAMS; id++) {
+        BufLogStream *pBLStream = mStreams[id];
+        if (pBLStream != NULL) {
+            delete pBLStream;
+            mStreams[id] = NULL;
+            count++;
+        }
+    }
+    ALOGV("Reset %d BufLogs", count);
+}
+
+// ------------------------------
+// BufLogStream
+// ------------------------------
+
+BufLogStream::BufLogStream(unsigned int id,
+        const char *tag,
+        unsigned int format,
+        unsigned int channels,
+        unsigned int samplingRate,
+        size_t maxBytes = 0) : mId(id), mFormat(format), mChannels(channels),
+                mSamplingRate(samplingRate), mMaxBytes(maxBytes) {
+    mByteCount = 0l;
+    mPaused = false;
+    if (tag != NULL) {
+        strncpy(mTag, tag, BUFLOGSTREAM_MAX_TAGSIZE);
+    } else {
+        mTag[0] = 0;
+    }
+    ALOGV("Creating BufLogStream id:%d tag:%s format:%d ch:%d sr:%d maxbytes:%zu", mId, mTag,
+            mFormat, mChannels, mSamplingRate, mMaxBytes);
+
+    //open file (s), info about tag, format, etc.
+    //timestamp
+    char timeStr[16];   //size 16: format %Y%m%d%H%M%S 14 chars + string null terminator
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    struct tm tm;
+    localtime_r(&tv.tv_sec, &tm);
+    strftime(timeStr, sizeof(timeStr), "%Y%m%d%H%M%S", &tm);
+    char logPath[BUFLOG_MAX_PATH_SIZE];
+    snprintf(logPath, BUFLOG_MAX_PATH_SIZE, "%s/%s_%d_%s_%d_%d_%d.raw", BUFLOG_BASE_PATH, timeStr,
+            mId, mTag, mFormat, mChannels, mSamplingRate);
+    ALOGV("data output: %s", logPath);
+
+    mFile = fopen(logPath, "wb");
+    if (mFile != NULL) {
+        ALOGV("Success creating file at: %p", mFile);
+    } else {
+        ALOGE("Error: could not create file BufLogStream %s", strerror(errno));
+    }
+}
+
+void BufLogStream::closeStream_l() {
+    ALOGV("Closing BufLogStream id:%d tag:%s", mId, mTag);
+    if (mFile != NULL) {
+        fclose(mFile);
+        mFile = NULL;
+    }
+}
+
+BufLogStream::~BufLogStream() {
+    ALOGV("Destroying BufLogStream id:%d tag:%s", mId, mTag);
+    android::Mutex::Autolock autoLock(mLock);
+    closeStream_l();
+}
+
+size_t BufLogStream::write(const void *buf, size_t size) {
+
+    size_t bytes = 0;
+    if (!mPaused && mFile != NULL) {
+        if (size > 0 && buf != NULL) {
+            android::Mutex::Autolock autoLock(mLock);
+            if (mMaxBytes > 0) {
+                size = MIN(size, mMaxBytes - mByteCount);
+            }
+            bytes = fwrite(buf, 1, size, mFile);
+            mByteCount += bytes;
+            if (mMaxBytes > 0 && mMaxBytes == mByteCount) {
+                closeStream_l();
+            }
+        }
+        ALOGV("wrote %zu/%zu bytes to BufLogStream %d tag:%s. Total Bytes: %zu", bytes, size, mId,
+                mTag, mByteCount);
+    } else {
+        ALOGV("Warning: trying to write to %s BufLogStream id:%d tag:%s",
+                mPaused ? "paused" : "closed", mId, mTag);
+    }
+    return bytes;
+}
+
+bool BufLogStream::setPause(bool pause) {
+    bool old = mPaused;
+    mPaused = pause;
+    return old;
+}
+
+void BufLogStream::finalize() {
+    android::Mutex::Autolock autoLock(mLock);
+    closeStream_l();
+}
diff --git a/services/audioflinger/BufLog.h b/services/audioflinger/BufLog.h
new file mode 100644
index 0000000..1b402f4
--- /dev/null
+++ b/services/audioflinger/BufLog.h
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUDIO_BUFLOG_H
+#define ANDROID_AUDIO_BUFLOG_H
+
+/*
+ * BUFLOG creates up to BUFLOG_MAXSTREAMS simultaneous streams [0:15] of audio buffer data
+ * and saves them to disk. The files are stored in the path specified in BUFLOG_BASE_PATH and
+ * are named following this format:
+ *   YYYYMMDDHHMMSS_id_format_channels_samplingrate.raw
+ *
+ * Normally we strip BUFLOG dumps from release builds.
+ * You can modify this (for example with "#define BUFLOG_NDEBUG 0"
+ * at the top of your source file) to change that behavior.
+ *
+ * usage:
+ * - Add this to the top of the source file you want to debug:
+ *   #define BUFLOG_NDEBUG 0
+ *   #include "BufLog.h"
+ *
+ * - dump an audio buffer
+ *  BUFLOG(buff_id, buff_tag, format, channels, sampling_rate, max_bytes, buff_pointer, buff_size);
+ *
+ *  buff_id:   int [0:15]   buffer id. If a buffer doesn't exist, it is created the first time.
+ *  buff_tag:  char*        string tag used on stream filename and logs
+ *  format:    int          Audio format (audio_format_t see audio.h)
+ *  channels:  int          Channel Count
+ *  sampling_rate:  int     Sampling rate in Hz. e.g. 8000, 16000, 44100, 48000, etc
+ *  max_bytes: int [0 or positive number]
+ *                          Maximum size of the file (in bytes) to be output.
+ *                          If the value is 0, no limit.
+ *  buff_pointer: void *    Pointer to audio buffer.
+ *  buff_size:  int         Size (in bytes) of the current audio buffer to be stored.
+ *
+ *
+ *  Example usage:
+ *    int format       = mConfig.outputCfg.format;
+ *    int channels     = audio_channel_count_from_out_mask(mConfig.outputCfg.channels);
+ *    int samplingRate = mConfig.outputCfg.samplingRate;
+ *    int frameCount   = mConfig.outputCfg.buffer.frameCount;
+ *    int frameSize    = audio_bytes_per_sample((audio_format_t)format) * channels;
+ *    int buffSize     = frameCount * frameSize;
+ *    long maxBytes = 10 * samplingRate * frameSize; //10 seconds max
+ *  BUFLOG(11, "loudnes_enhancer_out", format, channels, samplingRate, maxBytes,
+ *                               mConfig.outputCfg.buffer.raw, buffSize);
+ *
+ *  Other macros:
+ *  BUFLOG_EXISTS       returns true if there is an instance of BufLog
+ *
+ *  BUFLOG_RESET        If an instance of BufLog exists, it stops the capture and closes all
+ *                      streams.
+ *                      If a new call to BUFLOG(..) is done, new streams are created.
+ */
+
+#ifndef BUFLOG_NDEBUG
+#ifdef NDEBUG
+#define BUFLOG_NDEBUG 1
+#else
+#define BUFLOG_NDEBUG 0
+#endif
+#endif
+
+/*
+ * Simplified macro to send a buffer.
+ */
+#ifndef BUFLOG
+#define __BUFLOG(STREAMID, TAG, FORMAT, CHANNELS, SAMPLINGRATE, MAXBYTES, BUF, SIZE) \
+    BufLogSingleton::instance()->write(STREAMID, TAG, FORMAT, CHANNELS, SAMPLINGRATE, MAXBYTES, \
+            BUF, SIZE)
+#if BUFLOG_NDEBUG
+#define BUFLOG(STREAMID, TAG, FORMAT, CHANNELS, SAMPLINGRATE, MAXBYTES, BUF, SIZE) \
+    do { if (0) {  } } while (0)
+#else
+#define BUFLOG(STREAMID, TAG, FORMAT, CHANNELS, SAMPLINGRATE, MAXBYTES, BUF, SIZE) \
+    __BUFLOG(STREAMID, TAG, FORMAT, CHANNELS, SAMPLINGRATE, MAXBYTES, BUF, SIZE)
+#endif
+#endif
+
+#ifndef BUFLOG_EXISTS
+#define BUFLOG_EXISTS BufLogSingleton::instanceExists()
+#endif
+
+#ifndef BUFLOG_RESET
+#define BUFLOG_RESET do { if (BufLogSingleton::instanceExists()) { \
+    BufLogSingleton::instance()->reset(); } } while (0)
+#endif
+
+
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <utils/Mutex.h>
+
+//BufLog configuration
+#define BUFLOGSTREAM_MAX_TAGSIZE    32
+#define BUFLOG_BASE_PATH            "/data/misc/audioserver"
+#define BUFLOG_MAX_PATH_SIZE        300
+
+class BufLogStream {
+public:
+    BufLogStream(unsigned int id,
+            const char *tag,
+            unsigned int format,
+            unsigned int channels,
+            unsigned int samplingRate,
+            size_t maxBytes);
+    ~BufLogStream();
+
+    // write buffer to stream
+    //  buf:  pointer to buffer
+    //  size: number of bytes to write
+    size_t          write(const void *buf, size_t size);
+
+    // pause/resume stream
+    //  pause: true = paused, false = not paused
+    //  return value: previous state of stream (paused or not).
+    bool            setPause(bool pause);
+
+    // will stop the stream and close any open file
+    // the stream can't be reopen. Instead, a new stream (and file) should be created.
+    void            finalize();
+
+private:
+    bool                mPaused;
+    const unsigned int  mId;
+    char                mTag[BUFLOGSTREAM_MAX_TAGSIZE + 1];
+    const unsigned int  mFormat;
+    const unsigned int  mChannels;
+    const unsigned int  mSamplingRate;
+    const size_t        mMaxBytes;
+    size_t              mByteCount;
+    FILE                *mFile;
+    mutable android::Mutex mLock;
+
+    void            closeStream_l();
+};
+
+
+class BufLog {
+public:
+    BufLog();
+    ~BufLog();
+    BufLog(BufLog const&) {};
+
+    //  streamid:      int [0:BUFLOG_MAXSTREAMS-1]   buffer id.
+    //                  If a buffer doesn't exist, it is created the first time is referenced
+    //  tag:           char*  string tag used on stream filename and logs
+    //  format:        int Audio format (audio_format_t see audio.h)
+    //  channels:      int          Channel Count
+    //  samplingRate:  int Sampling rate in Hz. e.g. 8000, 16000, 44100, 48000, etc
+    //  maxBytes:      int [0 or positive number]
+    //                  Maximum size of the file (in bytes) to be output.
+    //                  If the value is 0, no limit.
+    //  size:          int Size (in bytes) of the current audio buffer to be written.
+    //  buf:           void *    Pointer to audio buffer.
+    size_t          write(int streamid,
+                        const char *tag,
+                        int format,
+                        int channels,
+                        int samplingRate,
+                        size_t maxBytes,
+                        const void *buf,
+                        size_t size);
+
+    // reset will stop and close all active streams, thus finalizing any open file.
+    //  New streams will be created if write() is called again.
+    void            reset();
+
+protected:
+    static const unsigned int BUFLOG_MAXSTREAMS = 16;
+    BufLogStream    *mStreams[BUFLOG_MAXSTREAMS];
+    mutable android::Mutex mLock;
+};
+
+class BufLogSingleton {
+public:
+    static BufLog   *instance();
+    static bool     instanceExists();
+
+private:
+    static void     initOnce();
+    static BufLog   *mInstance;
+};
+
+#endif //ANDROID_AUDIO_BUFLOG_H