StreamHal: Add power logging

Test: adb shell dumpsys media.audio_flinger
Bug: 62307033
Bug: 62381511
Change-Id: Ie15863f3c207e5651ce0b8818b9e38737bad77d8
diff --git a/media/libaudiohal/Android.mk b/media/libaudiohal/Android.mk
index e592169..827908e 100644
--- a/media/libaudiohal/Android.mk
+++ b/media/libaudiohal/Android.mk
@@ -3,6 +3,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_SHARED_LIBRARIES := \
+    libaudioutils \
     libcutils   \
     liblog      \
     libutils    \
diff --git a/media/libaudiohal/StreamHalHidl.cpp b/media/libaudiohal/StreamHalHidl.cpp
index 42785d5..176abee 100644
--- a/media/libaudiohal/StreamHalHidl.cpp
+++ b/media/libaudiohal/StreamHalHidl.cpp
@@ -48,6 +48,18 @@
         : ConversionHelperHidl("Stream"),
           mStream(stream),
           mHalThreadPriority(HAL_THREAD_PRIORITY_DEFAULT) {
+
+    // Instrument audio signal power logging.
+    // Note: This assumes channel mask, format, and sample rate do not change after creation.
+    if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) {
+        // Obtain audio properties (see StreamHalHidl::getAudioProperties() below).
+        Return<void> ret = mStream->getAudioProperties(
+                [&](uint32_t sr, AudioChannelMask m, AudioFormat f) {
+                mStreamPowerLog.init(sr,
+                        static_cast<audio_channel_mask_t>(m),
+                        static_cast<audio_format_t>(f));
+            });
+    }
 }
 
 StreamHalHidl::~StreamHalHidl() {
@@ -135,6 +147,7 @@
     hidlHandle->data[0] = fd;
     Return<void> ret = mStream->debugDump(hidlHandle);
     native_handle_delete(hidlHandle);
+    mStreamPowerLog.dump(fd);
     return processReturn("dump", ret);
 }
 
@@ -311,7 +324,7 @@
         return status;
     }
 
-    return callWriterThread(
+    status = callWriterThread(
             WriteCommand::WRITE, "write", static_cast<const uint8_t*>(buffer), bytes,
             [&] (const WriteStatus& writeStatus) {
                 *written = writeStatus.reply.written;
@@ -320,6 +333,8 @@
                         "hal reports more bytes written than asked for: %lld > %lld",
                         (long long)*written, (long long)bytes);
             });
+    mStreamPowerLog.log(buffer, *written);
+    return status;
 }
 
 status_t StreamOutHalHidl::callWriterThread(
@@ -580,7 +595,7 @@
     ReadParameters params;
     params.command = ReadCommand::READ;
     params.params.read = bytes;
-    return callReaderThread(params, "read",
+    status = callReaderThread(params, "read",
             [&](const ReadStatus& readStatus) {
                 const size_t availToRead = mDataMQ->availableToRead();
                 if (!mDataMQ->read(static_cast<uint8_t*>(buffer), std::min(bytes, availToRead))) {
@@ -591,6 +606,8 @@
                         (int32_t)availToRead, (int32_t)readStatus.reply.read);
                 *read = readStatus.reply.read;
             });
+    mStreamPowerLog.log(buffer, *read);
+    return status;
 }
 
 status_t StreamInHalHidl::callReaderThread(
diff --git a/media/libaudiohal/StreamHalHidl.h b/media/libaudiohal/StreamHalHidl.h
index a7df276..a69cce8 100644
--- a/media/libaudiohal/StreamHalHidl.h
+++ b/media/libaudiohal/StreamHalHidl.h
@@ -27,6 +27,7 @@
 #include <media/audiohal/StreamHalInterface.h>
 
 #include "ConversionHelperHidl.h"
+#include "StreamPowerLog.h"
 
 using ::android::hardware::audio::V2_0::IStream;
 using ::android::hardware::audio::V2_0::IStreamIn;
@@ -103,6 +104,9 @@
 
     bool requestHalThreadPriority(pid_t threadPid, pid_t threadId);
 
+    // mStreamPowerLog is used for audio signal power logging.
+    StreamPowerLog mStreamPowerLog;
+
   private:
     const int HAL_THREAD_PRIORITY_DEFAULT = -1;
     IStream *mStream;
diff --git a/media/libaudiohal/StreamHalLocal.cpp b/media/libaudiohal/StreamHalLocal.cpp
index 05800a0..dc17f5c 100644
--- a/media/libaudiohal/StreamHalLocal.cpp
+++ b/media/libaudiohal/StreamHalLocal.cpp
@@ -27,7 +27,15 @@
 namespace android {
 
 StreamHalLocal::StreamHalLocal(audio_stream_t *stream, sp<DeviceHalLocal> device)
-        : mDevice(device), mStream(stream) {
+        : mDevice(device),
+          mStream(stream) {
+    // Instrument audio signal power logging.
+    // Note: This assumes channel mask, format, and sample rate do not change after creation.
+    if (mStream != nullptr && mStreamPowerLog.isUserDebugOrEngBuild()) {
+        mStreamPowerLog.init(mStream->get_sample_rate(mStream),
+                mStream->get_channels(mStream),
+                mStream->get_format(mStream));
+    }
 }
 
 StreamHalLocal::~StreamHalLocal() {
@@ -95,7 +103,9 @@
 }
 
 status_t StreamHalLocal::dump(int fd) {
-    return mStream->dump(mStream, fd);
+    status_t status = mStream->dump(mStream, fd);
+    mStreamPowerLog.dump(fd);
+    return status;
 }
 
 status_t StreamHalLocal::setHalThreadPriority(int) {
@@ -133,6 +143,7 @@
     ssize_t writeResult = mStream->write(mStream, buffer, bytes);
     if (writeResult > 0) {
         *written = writeResult;
+        mStreamPowerLog.log(buffer, *written);
         return OK;
     } else {
         *written = 0;
@@ -266,6 +277,7 @@
     ssize_t readResult = mStream->read(mStream, buffer, bytes);
     if (readResult > 0) {
         *read = readResult;
+        mStreamPowerLog.log( buffer, *read);
         return OK;
     } else {
         *read = 0;
diff --git a/media/libaudiohal/StreamHalLocal.h b/media/libaudiohal/StreamHalLocal.h
index 8c96c1f..c7136df 100644
--- a/media/libaudiohal/StreamHalLocal.h
+++ b/media/libaudiohal/StreamHalLocal.h
@@ -18,6 +18,7 @@
 #define ANDROID_HARDWARE_STREAM_HAL_LOCAL_H
 
 #include <media/audiohal/StreamHalInterface.h>
+#include "StreamPowerLog.h"
 
 namespace android {
 
@@ -83,6 +84,9 @@
 
     sp<DeviceHalLocal> mDevice;
 
+    // mStreamPowerLog is used for audio signal power logging.
+    StreamPowerLog mStreamPowerLog;
+
   private:
     audio_stream_t *mStream;
 };
diff --git a/media/libaudiohal/StreamPowerLog.h b/media/libaudiohal/StreamPowerLog.h
new file mode 100644
index 0000000..a78b1aa
--- /dev/null
+++ b/media/libaudiohal/StreamPowerLog.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_STREAM_POWER_LOG_H
+#define ANDROID_HARDWARE_STREAM_POWER_LOG_H
+
+#include <audio_utils/clock.h>
+#include <audio_utils/PowerLog.h>
+#include <cutils/properties.h>
+#include <system/audio.h>
+
+namespace android {
+
+class StreamPowerLog {
+public:
+    StreamPowerLog() :
+        mIsUserDebugOrEngBuild(is_userdebug_or_eng_build()),
+        mPowerLog(nullptr),
+        mFrameSize(0) {
+        // use init() to set up the power log.
+    }
+
+    ~StreamPowerLog() {
+        power_log_destroy(mPowerLog); // OK for null mPowerLog
+        mPowerLog = nullptr;
+    }
+
+    // A one-time initialization (do not call twice) before using StreamPowerLog.
+    void init(uint32_t sampleRate, audio_channel_mask_t channelMask, audio_format_t format) {
+        if (mPowerLog == nullptr) {
+            // Note: A way to get channel count for both input and output channel masks
+            // but does not check validity of the channel mask.
+            const uint32_t channelCount = popcount(audio_channel_mask_get_bits(channelMask));
+            mFrameSize = channelCount * audio_bytes_per_sample(format);
+            if (mFrameSize > 0) {
+                const size_t kPowerLogFramesPerEntry =
+                        (long long)sampleRate * kPowerLogSamplingIntervalMs / 1000;
+                mPowerLog = power_log_create(
+                        sampleRate,
+                        channelCount,
+                        format,
+                        kPowerLogEntries,
+                        kPowerLogFramesPerEntry);
+            }
+        }
+        // mPowerLog may be NULL (not the right build, format not accepted, etc.).
+    }
+
+    // Dump the power log to fd.
+    void dump(int fd) const {
+        // OK for null mPowerLog
+        (void)power_log_dump(
+                mPowerLog, fd, "      " /* prefix */, kPowerLogLines, 0 /* limit_ns */);
+    }
+
+    // Log the audio data contained in buffer.
+    void log(const void *buffer, size_t sizeInBytes) const {
+        if (mPowerLog != nullptr) { // mFrameSize is always nonzero if mPowerLog exists.
+            power_log_log(
+                    mPowerLog, buffer, sizeInBytes / mFrameSize, audio_utils_get_real_time_ns());
+        }
+    }
+
+    bool isUserDebugOrEngBuild() const {
+        return mIsUserDebugOrEngBuild;
+    }
+
+private:
+
+    static inline bool is_userdebug_or_eng_build() {
+        char value[PROPERTY_VALUE_MAX];
+        (void)property_get("ro.build.type", value, "unknown"); // ignore actual length
+        return strcmp(value, "userdebug") == 0 || strcmp(value, "eng") == 0;
+    }
+
+    // Audio signal power log configuration.
+    static const size_t kPowerLogLines = 40;
+    static const size_t kPowerLogSamplingIntervalMs = 50;
+    static const size_t kPowerLogEntries = (1 /* minutes */ * 60 /* seconds */ * 1000 /* msec */
+            / kPowerLogSamplingIntervalMs);
+
+    const bool mIsUserDebugOrEngBuild;
+    power_log_t *mPowerLog;
+    size_t mFrameSize;
+};
+
+} // namespace android
+
+#endif // ANDROID_HARDWARE_STREAM_POWER_LOG_H