From 13082f80dcee5f119cdb68a4dbc972cd2b939668 Mon Sep 17 00:00:00 2001 From: Andy Hung Date: Fri, 10 Mar 2017 17:07:23 -0800 Subject: [PATCH] Add power logging PowerLog captures the audio data power (measured in dBFS) over time. Test: power_tests Bug: 30572472 Change-Id: If8977f9b8f9d2d15ea07bc32bed7f802513a742b --- audio_utils/Android.bp | 2 + audio_utils/PowerLog.cpp | 263 +++++++++++++++++++++++++ audio_utils/include/audio_utils/PowerLog.h | 164 ++++++++++++++++ audio_utils/include/audio_utils/clock.h | 66 +++++++ audio_utils/include/audio_utils/power.h | 100 ++++++++++ audio_utils/power.cpp | 306 +++++++++++++++++++++++++++++ audio_utils/tests/Android.bp | 24 +++ audio_utils/tests/power_tests.cpp | 167 ++++++++++++++++ 8 files changed, 1092 insertions(+) create mode 100644 audio_utils/PowerLog.cpp create mode 100644 audio_utils/include/audio_utils/PowerLog.h create mode 100644 audio_utils/include/audio_utils/clock.h create mode 100644 audio_utils/include/audio_utils/power.h create mode 100644 audio_utils/power.cpp create mode 100644 audio_utils/tests/power_tests.cpp diff --git a/audio_utils/Android.bp b/audio_utils/Android.bp index 11ed72cf..b6d01ea5 100644 --- a/audio_utils/Android.bp +++ b/audio_utils/Android.bp @@ -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 index 00000000..64e3c650 --- /dev/null +++ b/audio_utils/PowerLog.cpp @@ -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 + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace android { + +// TODO move to separate file +template +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 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 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 + (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(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(power_log)->dump(fd, lines, limit_ns); +} + +void power_log_destroy(power_log_t *power_log) +{ + delete reinterpret_cast(power_log); +} diff --git a/audio_utils/include/audio_utils/PowerLog.h b/audio_utils/include/audio_utils/PowerLog.h new file mode 100644 index 00000000..497bb3c9 --- /dev/null +++ b/audio_utils/include/audio_utils/PowerLog.h @@ -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 +#include + +#include + +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> 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 index 00000000..149ac8cc --- /dev/null +++ b/audio_utils/include/audio_utils/clock.h @@ -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 + +/** + * \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 index 00000000..385b6a79 --- /dev/null +++ b/audio_utils/include/audio_utils/power.h @@ -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 +#include +#include +#include + +/** \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 index 00000000..e801da05 --- /dev/null +++ b/audio_utils/power.cpp @@ -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 + +#include + +#include +#include + +#if defined(__aarch64__) || defined(__ARM_NEON__) +#include +#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 +inline T getPtrPtrValueAndIncrement(const void **data) +{ + return *(*reinterpret_cast(data))++; +} + +template +inline float convertToFloatAndIncrement(const void **data) +{ + switch (FORMAT) { + case AUDIO_FORMAT_PCM_8_BIT: + return float_from_u8(getPtrPtrValueAndIncrement(data)); + + case AUDIO_FORMAT_PCM_16_BIT: + return float_from_i16(getPtrPtrValueAndIncrement(data)); + + case AUDIO_FORMAT_PCM_24_BIT_PACKED: { + const uint8_t *uptr = reinterpret_cast(*data); + *data = uptr + 3; + return float_from_p24(uptr); + } + + case AUDIO_FORMAT_PCM_8_24_BIT: + return float_from_q8_23(getPtrPtrValueAndIncrement(data)); + + case AUDIO_FORMAT_PCM_32_BIT: + return float_from_i32(getPtrPtrValueAndIncrement(data)); + + case AUDIO_FORMAT_PCM_FLOAT: + return getPtrPtrValueAndIncrement(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 +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 +constexpr inline float normalizeEnergy() +{ + const float val = normalizeAmplitude(); + return val * val; +} + +template +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(&litudes); + accum += amplitude * amplitude; + } + return accum; +} + +template +inline float energyMono(const void *amplitudes, size_t size) +{ + return energyMonoRef(amplitudes, size); +} + +// fast float power computation for ARM processors that support NEON. +#ifdef USE_NEON + +template <> +inline float energyMono(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(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(); +} + +// fast int32_t power computation for PCM_32 +template <> +inline float energyMono(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(); +} + +// fast int32_t power computation for PCM_8_24 (essentially identical to PCM_32 above) +template <> +inline float energyMono(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(); +} + +#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(buffer, samples); + + case AUDIO_FORMAT_PCM_16_BIT: + return energyMono(buffer, samples); + + case AUDIO_FORMAT_PCM_24_BIT_PACKED: + return energyMono(buffer, samples); + + case AUDIO_FORMAT_PCM_8_24_BIT: + return energyMono(buffer, samples); + + case AUDIO_FORMAT_PCM_32_BIT: + return energyMono(buffer, samples); + + case AUDIO_FORMAT_PCM_FLOAT: + return energyMono(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); +} + diff --git a/audio_utils/tests/Android.bp b/audio_utils/tests/Android.bp index 6fa3f829..91eef0f0 100644 --- a/audio_utils/tests/Android.bp +++ b/audio_utils/tests/Android.bp @@ -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 index 00000000..b86dac30 --- /dev/null +++ b/audio_utils/tests/power_tests.cpp @@ -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 +#include + +#include +#include +#include + +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))); +} -- 2.11.0