"format.c",
"limiter.c",
"minifloat.c",
+ "power.cpp",
+ "PowerLog.cpp",
"primitives.c",
"roundup.c",
],
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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>(&litudes);
+ 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);
+}
+
"-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"],
+ },
+ }
+}
--- /dev/null
+/*
+ * 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)));
+}