OSDN Git Service

Add power logging
authorAndy Hung <hunga@google.com>
Sat, 11 Mar 2017 01:07:23 +0000 (17:07 -0800)
committerAndy Hung <hunga@google.com>
Fri, 17 Mar 2017 22:08:30 +0000 (15:08 -0700)
PowerLog captures the audio data power (measured in dBFS) over time.

Test: power_tests
Bug: 30572472
Change-Id: If8977f9b8f9d2d15ea07bc32bed7f802513a742b

audio_utils/Android.bp
audio_utils/PowerLog.cpp [new file with mode: 0644]
audio_utils/include/audio_utils/PowerLog.h [new file with mode: 0644]
audio_utils/include/audio_utils/clock.h [new file with mode: 0644]
audio_utils/include/audio_utils/power.h [new file with mode: 0644]
audio_utils/power.cpp [new file with mode: 0644]
audio_utils/tests/Android.bp
audio_utils/tests/power_tests.cpp [new file with mode: 0644]

index 11ed72c..b6d01ea 100644 (file)
@@ -24,6 +24,8 @@ cc_library {
         "format.c",
         "limiter.c",
         "minifloat.c",
+        "power.cpp",
+        "PowerLog.cpp",
         "primitives.c",
         "roundup.c",
     ],
diff --git a/audio_utils/PowerLog.cpp b/audio_utils/PowerLog.cpp
new file mode 100644 (file)
index 0000000..64e3c65
--- /dev/null
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "audio_utils_PowerLog"
+#include <log/log.h>
+
+#include <algorithm>
+#include <iomanip>
+#include <math.h>
+#include <sstream>
+#include <stdint.h>
+
+#include <audio_utils/clock.h>
+#include <audio_utils/power.h>
+#include <audio_utils/PowerLog.h>
+
+namespace android {
+
+// TODO move to separate file
+template <typename T, size_t N>
+constexpr size_t array_size(const T(&)[N])
+{
+    return N;
+}
+
+PowerLog::PowerLog(uint32_t sampleRate,
+        uint32_t channelCount,
+        audio_format_t format,
+        size_t entries,
+        size_t framesPerEntry)
+    : mCurrentTime(0)
+    , mCurrentEnergy(0)
+    , mCurrentFrames(0)
+    , mIdx(0)
+    , mConsecutiveZeroes(0)
+    , mSampleRate(sampleRate)
+    , mChannelCount(channelCount)
+    , mFormat(format)
+    , mFramesPerEntry(framesPerEntry)
+    , mEntries(entries)
+{
+    (void)mSampleRate; // currently unused, for future use
+    LOG_ALWAYS_FATAL_IF(!audio_utils_is_compute_power_format_supported(format),
+            "unsupported format: %#x", format);
+}
+
+void PowerLog::log(const void *buffer, size_t frames, int64_t nowNs)
+{
+    std::lock_guard<std::mutex> guard(mLock);
+
+    const size_t bytes_per_sample = audio_bytes_per_sample(mFormat);
+    while (frames > 0) {
+        // check partial computation
+        size_t required = mFramesPerEntry - mCurrentFrames;
+        size_t process = std::min(required, frames);
+
+        if (mCurrentTime == 0) {
+            mCurrentTime = nowNs;
+        }
+        mCurrentEnergy +=
+                audio_utils_compute_energy_mono(buffer, mFormat, process * mChannelCount);
+        mCurrentFrames += process;
+
+        ALOGV("nowNs:%lld, required:%zu, process:%zu, mCurrentEnergy:%f, mCurrentFrames:%zu",
+                (long long)nowNs, required, process, mCurrentEnergy, mCurrentFrames);
+        if (process < required) {
+            return;
+        }
+
+        // We store the data as normalized energy per sample. The energy sequence is
+        // zero terminated. Consecutive zeroes are ignored.
+        if (mCurrentEnergy == 0.f) {
+            if (mConsecutiveZeroes++ == 0) {
+                mEntries[mIdx++] = std::make_pair(nowNs, 0.f);
+                // zero terminate the signal sequence.
+            }
+        } else {
+            mConsecutiveZeroes = 0;
+            mEntries[mIdx++] = std::make_pair(mCurrentTime, mCurrentEnergy);
+            ALOGV("writing %lld %f", (long long)mCurrentTime, mCurrentEnergy);
+        }
+        if (mIdx >= mEntries.size()) {
+            mIdx -= mEntries.size();
+        }
+        mCurrentTime = 0;
+        mCurrentEnergy = 0;
+        mCurrentFrames = 0;
+        frames -= process;
+        buffer = (const uint8_t *)buffer + mCurrentFrames * mChannelCount * bytes_per_sample;
+    }
+}
+
+std::string PowerLog::dumpToString(size_t lines, int64_t limitNs) const
+{
+    std::lock_guard<std::mutex> guard(mLock);
+
+    const size_t maxColumns = 10;
+    const size_t numberOfEntries = mEntries.size();
+    if (lines == 0) lines = SIZE_MAX;
+
+    // compute where to start logging
+    enum {
+        AT_END,
+        IN_SIGNAL,
+    } state = IN_SIGNAL;
+    size_t count = 1;
+    size_t column = 0;
+    size_t nonzeros = 0;
+    ssize_t offset; // TODO doesn't dump if # entries exceeds SSIZE_MAX
+    for (offset = 0; offset < (ssize_t)numberOfEntries && count < lines; ++offset) {
+        const size_t idx = (mIdx + numberOfEntries - offset - 1) % numberOfEntries; // reverse direction
+        const int64_t time = mEntries[idx].first;
+        const float energy = mEntries[idx].second;
+
+        if (state == AT_END) {
+            if (energy == 0.f) {
+                ALOGV("two zeroes detected");
+                break; // normally single zero terminated - two zeroes means no more data.
+            }
+            state = IN_SIGNAL;
+        } else { // IN_SIGNAL
+            if (energy == 0.f) {
+                if (column != 0) {
+                    column = 0;
+                    ++count;
+                }
+                state = AT_END;
+                continue;
+            }
+        }
+        if (column == 0 && time <= limitNs) {
+            break;
+        }
+        ++nonzeros;
+        if (++column == maxColumns) {
+            column = 0;
+            // TODO ideally we would peek the previous entry to see if it is 0
+            // to ensure we properly put in a starting signal bracket.
+            // We don't do that because it would complicate the logic here.
+            ++count;
+        }
+    }
+    if (offset > 0) {
+        --offset;
+    }
+    // We accumulate the log info into a string, and write to the fd once.
+    std::stringstream ss;
+    ss << std::fixed << std::setprecision(1);
+    // ss << std::scientific;
+    if (nonzeros == 0) {
+        ss << "      Signal power history: (none)\n";
+    } else {
+        ss << "      Signal power history:\n";
+
+        size_t column = 0;
+        bool first = true;
+        bool start = false;
+        float cumulative = 0.f;
+        for (; offset >= 0; --offset) {
+            const size_t idx = (mIdx + numberOfEntries - offset - 1) % numberOfEntries;
+            const int64_t time = mEntries[idx].first;
+            const float energy = mEntries[idx].second;
+
+            if (energy == 0.f) {
+                if (!first) {
+                    ss << " ] sum(" << audio_utils_power_from_energy(cumulative) << ")";
+                }
+                cumulative = 0.f;
+                column = 0;
+                start = true;
+                continue;
+            }
+            if (column == 0) {
+                // print time if at start of column
+                char timeinfo[32];
+                audio_utils_ns_to_string(time, timeinfo, array_size(timeinfo));
+                if (!first) {
+                    ss << "\n";
+                }
+                ss << timeinfo << (start ? ": [ ": ":   ");
+                first = false;
+                start = false;
+            }  else {
+                ss << " ";
+            }
+            if (++column >= maxColumns) {
+                column = 0;
+            }
+
+            cumulative += energy;
+            // convert energy to power and print
+            const float power =
+                    audio_utils_power_from_energy(energy / (mChannelCount * mFramesPerEntry));
+            ss << std::setw(6) << power;
+            ALOGV("state: %d %lld %f", state, (long long)time, power);
+        }
+        ss << "\n";
+    }
+    return ss.str();
+}
+
+status_t PowerLog::dump(int fd, size_t lines, int64_t limitNs) const
+{
+    // Since dumpToString and write are thread safe, this function
+    // is conceptually thread-safe but simultaneous calls to dump
+    // by different threads to the same file descriptor may not write
+    // the two logs in time order.
+    const std::string s = dumpToString(lines, limitNs);
+    if (s.size() > 0 && write(fd, s.c_str(), s.size()) < 0) {
+        return -errno;
+    }
+    return NO_ERROR;
+}
+
+} // namespace android
+
+using namespace android;
+
+power_log_t *power_log_create(uint32_t sample_rate,
+        uint32_t channel_count, audio_format_t format, size_t entries, size_t frames_per_entry)
+{
+    if (!audio_utils_is_compute_power_format_supported(format)) {
+        return nullptr;
+    }
+    return reinterpret_cast<power_log_t *>
+            (new PowerLog(sample_rate, channel_count, format, entries, frames_per_entry));
+}
+
+void power_log_log(power_log_t *power_log,
+        const void *buffer, size_t frames, int64_t now_ns)
+{
+    if (power_log == nullptr) {
+        return;
+    }
+    reinterpret_cast<PowerLog *>(power_log)->log(buffer, frames, now_ns);
+}
+
+int power_log_dump(power_log_t *power_log, int fd, size_t lines, int64_t limit_ns)
+{
+    if (power_log == nullptr) {
+        return BAD_VALUE;
+    }
+    return reinterpret_cast<PowerLog *>(power_log)->dump(fd, lines, limit_ns);
+}
+
+void power_log_destroy(power_log_t *power_log)
+{
+    delete reinterpret_cast<PowerLog *>(power_log);
+}
diff --git a/audio_utils/include/audio_utils/PowerLog.h b/audio_utils/include/audio_utils/PowerLog.h
new file mode 100644 (file)
index 0000000..497bb3c
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUDIO_POWER_LOG_H
+#define ANDROID_AUDIO_POWER_LOG_H
+
+#ifdef __cplusplus
+
+#include <mutex>
+#include <vector>
+
+#include <utils/Errors.h>
+
+namespace android {
+
+/**
+ * PowerLog captures the audio data power (measured in dBFS) over time.
+ *
+ * For the purposes of power evaluation, the audio data is divided into "bins",
+ * and grouped by signals consisting of consecutive non-zero energy bins.
+ * The sum energy in dB of each signal is computed for comparison purposes.
+ *
+ * No distinction is made between channels in an audio frame; they are all
+ * summed together for energy purposes.
+ *
+ * The public methods are internally protected by a mutex to be thread-safe.
+ */
+class PowerLog {
+public:
+    /**
+     * \brief Creates a PowerLog object.
+     *
+     * \param sampleRate        sample rate of the audio data.
+     * \param channelCount      channel count of the audio data.
+     * \param format            format of the audio data. It must be allowed by
+     *                          audio_utils_is_compute_power_format_supported()
+     *                          else the constructor will abort.
+     * \param entries           total number of energy entries "bins" to use.
+     * \param framesPerEntry    total number of audio frames used in each entry.
+     */
+    explicit PowerLog(uint32_t sampleRate,
+            uint32_t channelCount,
+            audio_format_t format,
+            size_t entries,
+            size_t framesPerEntry);
+
+    /**
+     * \brief Adds new audio data to the power log.
+     *
+     * \param buffer            pointer to the audio data buffer.
+     * \param frames            buffer size in audio frames.
+     * \param nowNs             current time in nanoseconds.
+     */
+    void log(const void *buffer, size_t frames, int64_t nowNs);
+
+    /**
+     * \brief Dumps the log to a std::string.
+     *
+     * \param lines             maximum number of lines to output (0 disables).
+     * \param limitNs           limit dump to data more recent than limitNs (0 disables).
+     * \return the std::string for the log.
+     */
+    std::string dumpToString(size_t lines = 0, int64_t limitNs = 0) const;
+
+    /**
+     * \brief Dumps the log to a raw file descriptor.
+     *
+     * \param fd                file descriptor to use.
+     * \param lines             maximum number of lines to output (0 disables).
+     * \param limitNs           limit dump to data more recent than limitNs (0 disables).
+     * \return
+     *   NO_ERROR on success or a negative number (-errno) on failure of write().
+     */
+    status_t dump(int fd, size_t lines = 0, int64_t limitNs = 0) const;
+
+private:
+    mutable std::mutex mLock;     // monitor mutex
+    int64_t mCurrentTime;         // time of first frame in buffer
+    float mCurrentEnergy;         // local energy accumulation
+    size_t mCurrentFrames;        // number of frames in the energy
+    size_t mIdx;                  // next usable index in mEntries
+    size_t mConsecutiveZeroes;    // current run of consecutive zero entries
+    const uint32_t mSampleRate;   // audio data sample rate
+    const uint32_t mChannelCount; // audio data channel count
+    const audio_format_t mFormat; // audio data format
+    const size_t mFramesPerEntry; // number of audio frames per entry
+    std::vector<std::pair<int64_t /* real time ns */, float /* energy */>> mEntries;
+};
+
+} // namespace android
+
+#endif // __cplusplus
+
+/** \cond */
+__BEGIN_DECLS
+/** \endcond */
+
+// C API (see C++ api above for details)
+
+typedef struct power_log_t power_log_t;
+
+/**
+ * \brief Creates a power log object.
+ *
+ * \param sample_rate       sample rate of the audio data.
+ * \param channel_count     channel count of the audio data.
+ * \param format            format of the audio data. It must be allowed by
+ *                          audio_utils_is_compute_power_format_supported().
+ * \param entries           total number of energy entries "bins" to use.
+ * \param frames_per_entry  total number of audio frames used in each entry.
+ *
+ * \return power log object or NULL on failure.
+ */
+power_log_t *power_log_create(uint32_t sample_rate,
+        uint32_t channel_count, audio_format_t format, size_t entries, size_t frames_per_entry);
+
+/**
+ * \brief Adds new audio data to the power log.
+ *
+ * \param power_log         object returned by create, if NULL nothing happens.
+ * \param buffer            pointer to the audio data buffer.
+ * \param frames            buffer size in audio frames.
+ * \param now_ns            current time in nanoseconds.
+ */
+void power_log_log(power_log_t *power_log, const void *buffer, size_t frames, int64_t now_ns);
+
+/**
+ * \brief Dumps the log to a raw file descriptor.
+ *
+ * \param power_log         object returned by create, if NULL nothing happens.
+ * \param fd                file descriptor to use.
+ * \param lines             maximum number of lines to output (0 disables).
+ * \param limit_ns          limit dump to data more recent than limit_ns (0 disables).
+ * \return
+ *   NO_ERROR on success or a negative number (-errno) on failure of write().
+ *   if power_log is NULL, BAD_VALUE is returned.
+ */
+int power_log_dump(power_log_t *power_log, int fd, size_t lines, int64_t limit_ns);
+
+/**
+ * \brief Destroys the power log object.
+ *
+ * \param power_log         object returned by create, if NULL nothing happens.
+ */
+void power_log_destroy(power_log_t *power_log);
+
+/** \cond */
+__END_DECLS
+/** \endcond */
+
+#endif // !ANDROID_AUDIO_POWER_LOG_H
diff --git a/audio_utils/include/audio_utils/clock.h b/audio_utils/include/audio_utils/clock.h
new file mode 100644 (file)
index 0000000..149ac8c
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUDIO_CLOCK_H
+#define ANDROID_AUDIO_CLOCK_H
+
+#include <time.h>
+
+/**
+ * \brief Converts time in ns to a time string, with format similar to logcat.
+ * \param ns          input time in nanoseconds to convert.
+ * \param buffer      caller allocated string buffer, buffer_length must be >= 19 chars
+ *                    in order to fully fit in time.  The string is always returned
+ *                    null terminated if buffer_size is greater than zero.
+ * \param buffer_size size of buffer.
+ */
+static inline void audio_utils_ns_to_string(int64_t ns, char *buffer, int buffer_size)
+{
+    const int one_second = 1000000000;
+    const time_t sec = ns / one_second;
+    struct tm tm;
+    localtime_r(&sec, &tm);
+    if (snprintf(buffer, buffer_size, "%02d-%02d %02d:%02d:%02d.%03d",
+        tm.tm_mon + 1, // localtime_r uses months in 0 - 11 range
+        tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+        (int)(ns % one_second / 1000000)) < 0) {
+        buffer[0] = '\0'; // null terminate on format error, which should not happen
+    }
+}
+
+/**
+ * \brief Converts a timespec to nanoseconds.
+ * \param ts   input timespec to convert.
+ * \return     timespec converted to nanoseconds.
+ */
+static inline int64_t audio_utils_ns_from_timespec(const struct timespec *ts)
+{
+    return ts->tv_sec * 1000000000LL + ts->tv_nsec;
+}
+
+/**
+ * \brief Gets the real time clock in nanoseconds.
+ * \return the real time clock in nanoseconds, or 0 on error.
+ */
+static inline int64_t audio_utils_get_real_time_ns() {
+    struct timespec now_ts;
+    if (clock_gettime(CLOCK_REALTIME, &now_ts) == 0) {
+        return audio_utils_ns_from_timespec(&now_ts);
+    }
+    return 0; // should not happen.
+}
+
+#endif  // !ANDROID_AUDIO_CLOCK_H
diff --git a/audio_utils/include/audio_utils/power.h b/audio_utils/include/audio_utils/power.h
new file mode 100644 (file)
index 0000000..385b6a7
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUDIO_POWER_H
+#define ANDROID_AUDIO_POWER_H
+
+#include <math.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+#include <system/audio.h>
+
+/** \cond */
+__BEGIN_DECLS
+/** \endcond */
+
+/**
+ * \brief Compute signal power on a scale of 0 dBFS.
+ *
+ *   \param buffer       buffer of samples.
+ *   \param format       one of AUDIO_FORMAT_PCM_8_BIT, AUDIO_FORMAT_PCM_16_BIT,
+ *                       AUDIO_FORMAT_PCM_24_BIT_PACKED, AUDIO_FORMAT_PCM_8_24_BIT,
+ *                       AUDIO_FORMAT_PCM_32_BIT, AUDIO_FORMAT_PCM_FLOAT.
+ *   \param samples      number of samples in buffer.  This is not audio frames;
+ *                       usually the number of samples is the number of audio frames
+ *                       multiplied by channel count.
+ *
+ * \return
+ *   signal power of the samples in the buffer. It is possible to return negative infinity
+ *   if the power is zero.
+ */
+
+float audio_utils_compute_power_mono(const void *buffer, audio_format_t format, size_t samples);
+
+/**
+ * \brief Compute signal energy (sum of squared amplitudes).
+ *
+ *   \param buffer       buffer of samples.
+ *   \param format       one of AUDIO_FORMAT_PCM_8_BIT, AUDIO_FORMAT_PCM_16_BIT,
+ *                       AUDIO_FORMAT_PCM_24_BIT_PACKED, AUDIO_FORMAT_PCM_8_24_BIT,
+ *                       AUDIO_FORMAT_PCM_32_BIT, AUDIO_FORMAT_PCM_FLOAT.
+ *   \param samples      number of samples in buffer.  This is not audio frames;
+ *                       usually the number of samples is the number of audio frames
+ *                       multiplied by channel count.
+ *
+ * \return
+ *   signal energy of the samples in the buffer (sum of squares) where each sample is
+ *   normalized to peak to peak range of 1.f.
+ */
+
+float audio_utils_compute_energy_mono(const void *buffer, audio_format_t format, size_t samples);
+
+/**
+ * \brief  Returns true if the format is supported for compute_energy_for_mono()
+ *         and compute_power_for_mono().
+ * \param  format        format under consideration.
+ * \return true if supported.
+ */
+bool audio_utils_is_compute_power_format_supported(audio_format_t format);
+
+/**
+ * \brief  Returns the signal power from amplitude.
+ * \param  amplitude the signal amplitude. A negative amplitude is treated
+ *                   the same as a positive amplitude.
+ * \return signal power in dB. It is possible to return negative infinity
+ *         if the input is zero.
+ */
+static inline float audio_utils_power_from_amplitude(float amplitude)
+{
+    return 20.f * log10f(fabsf(amplitude));
+}
+
+/**
+ * \brief  Returns the signal power from energy.
+ * \param  energy the signal energy. This should be non-negative.
+ * \return signal power in dB. It is possible to return NaN if the input is
+ *         negative, or negative infinity if the input is zero.
+ */
+static inline float audio_utils_power_from_energy(float energy)
+{
+    return 10.f * log10f(energy);
+}
+
+/** \cond */
+__END_DECLS
+/** \endcond */
+
+#endif // !ANDROID_AUDIO_POWER_H
diff --git a/audio_utils/power.cpp b/audio_utils/power.cpp
new file mode 100644 (file)
index 0000000..e801da0
--- /dev/null
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "audio_utils_power"
+#include <log/log.h>
+
+#include <math.h>
+
+#include <audio_utils/power.h>
+#include <audio_utils/primitives.h>
+
+#if defined(__aarch64__) || defined(__ARM_NEON__)
+#include <arm_neon.h>
+#define USE_NEON
+#endif
+
+namespace {
+
+constexpr inline bool isFormatSupported(audio_format_t format) {
+    switch (format) {
+    case AUDIO_FORMAT_PCM_8_BIT:
+    case AUDIO_FORMAT_PCM_16_BIT:
+    case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+    case AUDIO_FORMAT_PCM_8_24_BIT:
+    case AUDIO_FORMAT_PCM_32_BIT:
+    case AUDIO_FORMAT_PCM_FLOAT:
+        return true;
+    default:
+        return false;
+    }
+}
+
+template <typename T>
+inline T getPtrPtrValueAndIncrement(const void **data)
+{
+    return *(*reinterpret_cast<const T **>(data))++;
+}
+
+template <audio_format_t FORMAT>
+inline float convertToFloatAndIncrement(const void **data)
+{
+    switch (FORMAT) {
+    case AUDIO_FORMAT_PCM_8_BIT:
+        return float_from_u8(getPtrPtrValueAndIncrement<uint8_t>(data));
+
+    case AUDIO_FORMAT_PCM_16_BIT:
+        return float_from_i16(getPtrPtrValueAndIncrement<int16_t>(data));
+
+    case AUDIO_FORMAT_PCM_24_BIT_PACKED: {
+        const uint8_t *uptr = reinterpret_cast<const uint8_t *>(*data);
+        *data = uptr + 3;
+        return float_from_p24(uptr);
+    }
+
+    case AUDIO_FORMAT_PCM_8_24_BIT:
+        return float_from_q8_23(getPtrPtrValueAndIncrement<int32_t>(data));
+
+    case AUDIO_FORMAT_PCM_32_BIT:
+        return float_from_i32(getPtrPtrValueAndIncrement<int32_t>(data));
+
+    case AUDIO_FORMAT_PCM_FLOAT:
+        return getPtrPtrValueAndIncrement<float>(data);
+
+    default:
+        // static_assert cannot use false because the compiler may interpret it
+        // even though this code path may never be taken.
+        static_assert(isFormatSupported(FORMAT), "unsupported format");
+    }
+}
+
+// used to normalize integer fixed point value to the floating point equivalent.
+template <audio_format_t FORMAT>
+constexpr inline float normalizeAmplitude()
+{
+    switch (FORMAT) {
+    case AUDIO_FORMAT_PCM_8_BIT:
+        return 1.f / (1 << 7);
+
+    case AUDIO_FORMAT_PCM_16_BIT:
+        return 1.f / (1 << 15);
+
+    case AUDIO_FORMAT_PCM_24_BIT_PACKED: // fall through
+    case AUDIO_FORMAT_PCM_8_24_BIT:
+        return 1.f / (1 << 23);
+
+    case AUDIO_FORMAT_PCM_32_BIT:
+        return 1.f / (1U << 31);
+
+    case AUDIO_FORMAT_PCM_FLOAT:
+         return 1.f;
+
+    default:
+        // static_assert cannot use false because the compiler may interpret it
+        // even though this code path may never be taken.
+        static_assert(isFormatSupported(FORMAT), "unsupported format");
+    }
+}
+
+template <audio_format_t FORMAT>
+constexpr inline float normalizeEnergy()
+{
+    const float val = normalizeAmplitude<FORMAT>();
+    return val * val;
+}
+
+template <audio_format_t FORMAT>
+inline float energyMonoRef(const void *amplitudes, size_t size)
+{
+    float accum(0.f);
+    for (size_t i = 0; i < size; ++i) {
+        const float amplitude = convertToFloatAndIncrement<FORMAT>(&amplitudes);
+        accum += amplitude * amplitude;
+    }
+    return accum;
+}
+
+template <audio_format_t FORMAT>
+inline float energyMono(const void *amplitudes, size_t size)
+{
+    return energyMonoRef<FORMAT>(amplitudes, size);
+}
+
+// fast float power computation for ARM processors that support NEON.
+#ifdef USE_NEON
+
+template <>
+inline float energyMono<AUDIO_FORMAT_PCM_FLOAT>(const void *amplitudes, size_t size)
+{
+    float32x4_t *famplitudes = (float32x4_t *)amplitudes;
+
+    // clear accumulator
+    float32x4_t accum = vdupq_n_f32(0);
+
+    // iterate over array getting sum of squares in 4 lanes.
+    size_t i;
+    for (i = 0; i < (size & ~3); i += 4) {
+        accum = vmlaq_f32(accum, *famplitudes, *famplitudes);
+        ++famplitudes;
+    }
+
+    // narrow 4 lanes of floats
+    float32x2_t accum2 = vadd_f32(vget_low_f32(accum), vget_high_f32(accum)); // get stereo volume
+    accum2 = vpadd_f32(accum2, accum2); // combine to mono
+
+    // accumulate remainder
+    float value = vget_lane_f32(accum2, 0);
+    for (; i < size; ++i) {
+        const float amplitude = ((float *)amplitudes)[i];
+        value +=  amplitude * amplitude;
+    }
+
+    return value;
+}
+
+template <>
+inline float energyMono<AUDIO_FORMAT_PCM_16_BIT>(const void *amplitudes, size_t size)
+{
+    int16x4_t *samplitudes = (int16x4_t *)amplitudes;
+
+    // clear accumulator
+    float32x4_t accum = vdupq_n_f32(0);
+
+    // iterate over array getting sum of squares in 4 lanes.
+    size_t i;
+    for (i = 0; i < (size & ~3); i += 4) {
+        // expand s16 to s32
+        int32x4_t amplitude = vmovl_s16(*samplitudes);
+        ++samplitudes;
+        // convert s32 to f32
+        float32x4_t famplitude = vcvtq_f32_s32(amplitude);
+        accum = vmlaq_f32(accum, famplitude, famplitude);
+    }
+
+    // narrow 4 lanes of floats
+    float32x2_t accum2 = vadd_f32(vget_low_f32(accum), vget_high_f32(accum)); // get stereo volume
+    accum2 = vpadd_f32(accum2, accum2); // combine to mono
+
+    // accumulate remainder
+    float value = vget_lane_f32(accum2, 0);
+    for (; i < size; ++i) {
+        const float amplitude = (float)((int16_t *)amplitudes)[i];
+        value +=  amplitude * amplitude;
+    }
+
+    return value * normalizeEnergy<AUDIO_FORMAT_PCM_16_BIT>();
+}
+
+// fast int32_t power computation for PCM_32
+template <>
+inline float energyMono<AUDIO_FORMAT_PCM_32_BIT>(const void *amplitudes, size_t size)
+{
+    int32x4_t *samplitudes = (int32x4_t *)amplitudes;
+
+    // clear accumulator
+    float32x4_t accum = vdupq_n_f32(0);
+
+    // iterate over array getting sum of squares in 4 lanes.
+    size_t i;
+    for (i = 0; i < (size & ~3); i += 4) {
+        // convert s32 to f32
+        float32x4_t famplitude = vcvtq_f32_s32(*samplitudes);
+        ++samplitudes;
+        accum = vmlaq_f32(accum, famplitude, famplitude);
+    }
+
+    // narrow 4 lanes of floats
+    float32x2_t accum2 = vadd_f32(vget_low_f32(accum), vget_high_f32(accum)); // get stereo volume
+    accum2 = vpadd_f32(accum2, accum2); // combine to mono
+
+    // accumulate remainder
+    float value = vget_lane_f32(accum2, 0);
+    for (; i < size; ++i) {
+        const float amplitude = (float)((int32_t *)amplitudes)[i];
+        value +=  amplitude * amplitude;
+    }
+
+    return value * normalizeEnergy<AUDIO_FORMAT_PCM_32_BIT>();
+}
+
+// fast int32_t power computation for PCM_8_24 (essentially identical to PCM_32 above)
+template <>
+inline float energyMono<AUDIO_FORMAT_PCM_8_24_BIT>(const void *amplitudes, size_t size)
+{
+    int32x4_t *samplitudes = (int32x4_t *)amplitudes;
+
+    // clear accumulator
+    float32x4_t accum = vdupq_n_f32(0);
+
+    // iterate over array getting sum of squares in 4 lanes.
+    size_t i;
+    for (i = 0; i < (size & ~3); i += 4) {
+        // convert s32 to f32
+        float32x4_t famplitude = vcvtq_f32_s32(*samplitudes);
+        ++samplitudes;
+        accum = vmlaq_f32(accum, famplitude, famplitude);
+    }
+
+    // narrow 4 lanes of floats
+    float32x2_t accum2 = vadd_f32(vget_low_f32(accum), vget_high_f32(accum)); // get stereo volume
+    accum2 = vpadd_f32(accum2, accum2); // combine to mono
+
+    // accumulate remainder
+    float value = vget_lane_f32(accum2, 0);
+    for (; i < size; ++i) {
+        const float amplitude = (float)((int32_t *)amplitudes)[i];
+        value +=  amplitude * amplitude;
+    }
+
+    return value * normalizeEnergy<AUDIO_FORMAT_PCM_8_24_BIT>();
+}
+
+#endif // USE_NEON
+
+} // namespace
+
+float audio_utils_compute_energy_mono(const void *buffer, audio_format_t format, size_t samples)
+{
+    switch (format) {
+    case AUDIO_FORMAT_PCM_8_BIT:
+        return energyMono<AUDIO_FORMAT_PCM_8_BIT>(buffer, samples);
+
+    case AUDIO_FORMAT_PCM_16_BIT:
+        return energyMono<AUDIO_FORMAT_PCM_16_BIT>(buffer, samples);
+
+    case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+        return energyMono<AUDIO_FORMAT_PCM_24_BIT_PACKED>(buffer, samples);
+
+    case AUDIO_FORMAT_PCM_8_24_BIT:
+        return energyMono<AUDIO_FORMAT_PCM_8_24_BIT>(buffer, samples);
+
+    case AUDIO_FORMAT_PCM_32_BIT:
+        return energyMono<AUDIO_FORMAT_PCM_32_BIT>(buffer, samples);
+
+    case AUDIO_FORMAT_PCM_FLOAT:
+        return energyMono<AUDIO_FORMAT_PCM_FLOAT>(buffer, samples);
+
+    default:
+        LOG_ALWAYS_FATAL("invalid format: %#x", format);
+    }
+}
+
+float audio_utils_compute_power_mono(const void *buffer, audio_format_t format, size_t samples)
+{
+    return audio_utils_power_from_energy(
+            audio_utils_compute_energy_mono(buffer, format, samples) / samples);
+}
+
+bool audio_utils_is_compute_power_format_supported(audio_format_t format)
+{
+    return isFormatSupported(format);
+}
+
index 6fa3f82..91eef0f 100644 (file)
@@ -74,3 +74,27 @@ cc_binary_host {
         "-UNDEBUG",
     ],
 }
+
+cc_test {
+    name: "power_tests",
+    host_supported: true,
+
+    shared_libs: [
+        "libcutils",
+        "liblog",
+    ],
+    srcs: ["power_tests.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+    ],
+    target: {
+        android: {
+            shared_libs: ["libaudioutils"],
+        },
+        host: {
+            static_libs: ["libaudioutils"],
+        },
+    }
+}
diff --git a/audio_utils/tests/power_tests.cpp b/audio_utils/tests/power_tests.cpp
new file mode 100644 (file)
index 0000000..b86dac3
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audio_utils_power_tests"
+
+#include <cmath>
+#include <math.h>
+
+#include <audio_utils/power.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+typedef struct { uint8_t c[3]; } __attribute__((__packed__)) uint8x3_t;
+
+void testFloatValue(float f_value, size_t length) {
+    const float power = audio_utils_power_from_amplitude(f_value);
+    float f_ary[length];
+    uint8_t u8_ary[length];
+    int16_t i16_ary[length];
+    int32_t i32_ary[length];
+    int32_t q8_23_ary[length];
+    uint8x3_t p24_ary[length];
+
+    // magic formulas to convert floating point to fixed point representations.
+    // we negate the floating point value to ensure full integer range for 1.f.
+    const uint8_t u8_value((1.f - f_value) * 128);
+    const int16_t i16_value(f_value * INT16_MIN);
+    const int32_t i32_value (f_value * INT32_MIN);
+    const int32_t q8_23_value(f_value * -(1 << 23));
+
+    // PCM_24_BIT_PACKED is native endian.
+#if HAVE_BIG_ENDIAN
+    const uint8x3_t p24_value{{
+        uint8_t(q8_23_value >> 16),
+                uint8_t(q8_23_value >> 8),
+                uint8_t(q8_23_value),
+    }};
+#else
+    const uint8x3_t p24_value{{
+        uint8_t(q8_23_value),
+                uint8_t(q8_23_value >> 8),
+                uint8_t(q8_23_value >> 16),
+    }};
+#endif
+
+    for (size_t i = 0; i < length; ++i) {
+        f_ary[i] = f_value;
+        u8_ary[i] = u8_value;
+        i16_ary[i] = i16_value;
+        i32_ary[i] = i32_value;
+        q8_23_ary[i] = q8_23_value;
+        p24_ary[i] = p24_value;
+    }
+
+    EXPECT_EQ(power,
+            audio_utils_compute_power_mono(f_ary, AUDIO_FORMAT_PCM_FLOAT, length));
+    EXPECT_EQ(power,
+            audio_utils_compute_power_mono(u8_ary, AUDIO_FORMAT_PCM_8_BIT, length));
+    EXPECT_EQ(power,
+            audio_utils_compute_power_mono(i16_ary, AUDIO_FORMAT_PCM_16_BIT, length));
+    EXPECT_EQ(power,
+            audio_utils_compute_power_mono(i32_ary, AUDIO_FORMAT_PCM_32_BIT, length));
+    EXPECT_EQ(power,
+            audio_utils_compute_power_mono(q8_23_ary, AUDIO_FORMAT_PCM_8_24_BIT, length));
+    EXPECT_EQ(power,
+            audio_utils_compute_power_mono(p24_ary, AUDIO_FORMAT_PCM_24_BIT_PACKED, length));
+}
+
+void testFloatRamp(size_t length) {
+    float f_ary[length];
+    uint8_t u8_ary[length];
+    int16_t i16_ary[length];
+    int32_t i32_ary[length];
+    int32_t q8_23_ary[length];
+    uint8x3_t p24_ary[length];
+
+    for (size_t i = 0; i < length; ++i) {
+        // must be expressed cleanly in uint8_t
+        const float f_value = (int(length & 0xff) - 128) / 128.f;
+
+        // magic formulas to convert floating point to fixed point representations.
+        // we negate the floating point value to ensure full integer range for 1.f.
+        const uint8_t u8_value((1.f - f_value) * 128);
+        const int16_t i16_value(f_value * INT16_MIN);
+        const int32_t i32_value (f_value * INT32_MIN);
+        const int32_t q8_23_value(f_value * -(1 << 23));
+
+        // PCM_24_BIT_PACKED is native endian.
+    #if HAVE_BIG_ENDIAN
+        const uint8x3_t p24_value{{
+            uint8_t(q8_23_value >> 16),
+                    uint8_t(q8_23_value >> 8),
+                    uint8_t(q8_23_value),
+        }};
+    #else
+        const uint8x3_t p24_value{{
+            uint8_t(q8_23_value),
+                    uint8_t(q8_23_value >> 8),
+                    uint8_t(q8_23_value >> 16),
+        }};
+    #endif
+
+        f_ary[i] = f_value;
+        u8_ary[i] = u8_value;
+        i16_ary[i] = i16_value;
+        i32_ary[i] = i32_value;
+        q8_23_ary[i] = q8_23_value;
+        p24_ary[i] = p24_value;
+    }
+
+    const float power8 =  audio_utils_compute_power_mono(u8_ary, AUDIO_FORMAT_PCM_8_BIT, length);
+
+    EXPECT_EQ(power8,
+            audio_utils_compute_power_mono(f_ary, AUDIO_FORMAT_PCM_FLOAT, length));
+    EXPECT_EQ(power8,
+            audio_utils_compute_power_mono(i16_ary, AUDIO_FORMAT_PCM_16_BIT, length));
+    EXPECT_EQ(power8,
+            audio_utils_compute_power_mono(i32_ary, AUDIO_FORMAT_PCM_32_BIT, length));
+    EXPECT_EQ(power8,
+            audio_utils_compute_power_mono(q8_23_ary, AUDIO_FORMAT_PCM_8_24_BIT, length));
+    EXPECT_EQ(power8,
+            audio_utils_compute_power_mono(p24_ary, AUDIO_FORMAT_PCM_24_BIT_PACKED, length));
+}
+
+// power_mono implicitly tests energy_mono
+TEST(audio_utils_power, power_mono) {
+    // f_values should have limited mantissa
+    for (float f_value : { 0.f, 0.25f, 0.5f, 0.75f, 1.f }) {
+        const float power = audio_utils_power_from_amplitude(f_value);
+        printf("power_mono: amplitude: %f  power: %f\n", f_value, power);
+
+        for (size_t length : { 1, 3, 5, 7, 16, 21, 32, 37 }) {
+            testFloatValue(f_value, length);
+        }
+    }
+}
+
+// power_mono implicitly tests energy_mono
+TEST(audio_utils_power, power_mono_ramp) {
+    for (size_t length : { 1, 3, 5, 7, 16, 21, 32, 37, 297 }) {
+        testFloatRamp(length);
+    }
+}
+
+TEST(audio_utils_power, power_from) {
+    EXPECT_EQ(0.f, audio_utils_power_from_amplitude(1.f));
+    EXPECT_EQ(-INFINITY, audio_utils_power_from_amplitude(0.f));
+    EXPECT_EQ(0.f, audio_utils_power_from_amplitude(-1.f));
+
+    EXPECT_EQ(0.f, audio_utils_power_from_energy(1.f));
+    EXPECT_EQ(-INFINITY, audio_utils_power_from_energy(0.f));
+    EXPECT_TRUE(std::isnan(audio_utils_power_from_energy(-1.f)));
+}