diff options
author | Andy Hung <hunga@google.com> | 2023-12-27 20:39:12 -0800 |
---|---|---|
committer | Andy Hung <hunga@google.com> | 2024-03-07 20:42:11 -0800 |
commit | 70b3bee847b186f563a3a612aa19571e1034c7e3 (patch) | |
tree | eeafa8914d0a90d7d5ce603763ffc5c947247a8e | |
parent | d105e18c4009945780e4fda8db98e52206f1bcd2 (diff) | |
download | media-70b3bee847b186f563a3a612aa19571e1034c7e3.tar.gz |
PowerLog: Prepare for multiresolution mode.
Fix energy computation when split across boundaries.
Improve time accuracy.
Add PowerLogBase helper class.
Test: adb shell dumpsys media.audio_flinger
Test: atest powerlog_tests
Bug: 271143713
Merged-In: I4bdbc44fcde4eace14151dfe36b1627e3546e26a
Change-Id: I4bdbc44fcde4eace14151dfe36b1627e3546e26a
-rw-r--r-- | audio_utils/PowerLog.cpp | 175 | ||||
-rw-r--r-- | audio_utils/include/audio_utils/PowerLog.h | 81 | ||||
-rw-r--r-- | audio_utils/tests/powerlog_tests.cpp | 5 |
3 files changed, 188 insertions, 73 deletions
diff --git a/audio_utils/PowerLog.cpp b/audio_utils/PowerLog.cpp index 730a0467..2283e7fb 100644 --- a/audio_utils/PowerLog.cpp +++ b/audio_utils/PowerLog.cpp @@ -18,6 +18,8 @@ #define LOG_TAG "audio_utils_PowerLog" #include <log/log.h> +#include <audio_utils/PowerLog.h> + #include <algorithm> #include <iomanip> #include <math.h> @@ -29,89 +31,55 @@ #include <audio_utils/clock.h> #include <audio_utils/LogPlot.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, +PowerLogBase::PowerLogBase(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) + : mSampleRate(sampleRate) , mChannelCount(channelCount) , mFormat(format) , mFramesPerEntry(framesPerEntry) + , mEntryTimeNs(framesPerEntry * 1e9 / sampleRate) + , mMaxTimeSlipNs(std::min((int64_t)200'000'000, mEntryTimeNs)) , mEntries(entries) { - (void)mSampleRate; // currently unused, for future use + (void)mFormat; // 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); +void PowerLogBase::processEnergy(size_t frames, float energy, int64_t nowNs) { + // For big entries (i.e. 1 second+) we want to ensure we don't have new data + // accumulating into a previous energy segment. + if (mCurrentTime > 0 + && nowNs > mCurrentTime + mCurrentFrames * 1e9 / mSampleRate + mMaxTimeSlipNs) { + flushEntry(); + } - if (mCurrentTime == 0) { - mCurrentTime = nowNs; - } - mCurrentEnergy += - audio_utils_compute_energy_mono(buffer, mFormat, process * mChannelCount); - mCurrentFrames += process; + mCurrentEnergy += energy; - ALOGV("nowNs:%lld, required:%zu, process:%zu, mCurrentEnergy:%f, mCurrentFrames:%zu", - (long long)nowNs, required, process, mCurrentEnergy, mCurrentFrames); - if (process < required) { - return; - } + // if we are in a zero run, do not advance. + if (mCurrentEnergy == 0.f && mConsecutiveZeroes > 0) 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; + mCurrentFrames += frames; + if (mCurrentTime == 0) { + mCurrentTime = nowNs; } + + ALOGV("%s: nowNs:%lld, frames:%zu, mCurrentEnergy:%f, mCurrentFrames:%zu", + __func__, (long long)nowNs, frames, mCurrentEnergy, mCurrentFrames); + if (mCurrentFrames < mFramesPerEntry) return; + + flushEntry(); } -std::string PowerLog::dumpToString( +std::string PowerLogBase::dumpToString( const char *prefix, size_t lines, int64_t limitNs, bool logPlot) 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; @@ -172,7 +140,8 @@ std::string PowerLog::dumpToString( // First value is power, second value is whether value is start of // a new time stamp. std::vector<std::pair<float, bool>> plotEntries; - ss << prefix << "Signal power history:\n"; + const float timeResolution = mFramesPerEntry * 1000.f / mSampleRate; + ss << prefix << "Signal power history (resolution: " << timeResolution << " ms):\n"; size_t column = 0; bool first = true; @@ -232,6 +201,92 @@ std::string PowerLog::dumpToString( return ss.str(); } +void PowerLogBase::flushEntry() { + // We store the data as normalized energy per sample. The energy sequence is + // zero terminated. Consecutive zero entries are ignored. + if (mCurrentEnergy == 0.f) { + if (mConsecutiveZeroes++ == 0) { + mEntries[mIdx++] = std::make_pair(mCurrentTime, 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; +} + +void PowerLog::log(const void *buffer, size_t frames, int64_t nowNs) { + if (frames == 0) return; + std::lock_guard <std::mutex> guard(mMutex); + + const size_t bytes_per_sample = audio_bytes_per_sample(mFormat); + while (true) { + // limit the number of frames to process from the requirements + // of each log base. + size_t processFrames = mBase[0]->framesToProcess(frames); + for (size_t i = 1; i < std::size(mBase); ++i) { + processFrames = std::min(processFrames, mBase[i]->framesToProcess(frames)); + } + const float energy = audio_utils_compute_energy_mono(buffer, mFormat, + processFrames * mChannelCount); + for (const auto& base : mBase) { + base->processEnergy(processFrames, energy, nowNs); + } + frames -= processFrames; + if (frames == 0) return; + buffer = (const uint8_t *) buffer + processFrames * mChannelCount * bytes_per_sample; + nowNs += processFrames * NANOS_PER_SECOND / mSampleRate; + } +} + +std::string PowerLog::dumpToString( + const char *prefix, size_t lines, int64_t limitNs, bool logPlot) const +{ + // Determine how to distribute lines among the logs. + const size_t logs = mBase.size(); + std::vector<size_t> sublines(logs); + size_t start = 0; + + if (lines > 0) { + // we compute the # of lines per PowerLogBase starting from + // largest time granularity / resolution to the finest resolution. + // + // The largest granularity has the fewest lines, doubling + // as the granularity gets finer. + // The finest 2 levels have identical number of lines. + size_t norm = 1 << (logs - 1); + if (logs > 2) norm += (1 << (logs - 2)) - 1; + size_t alloc = 0; + for (size_t i = 0; i < logs - 1; ++i) { + const size_t l = (1 << i) * lines / norm; + if (l == 0) { + start = i + 1; + } else { + sublines[i] = l; + alloc += l; + } + } + sublines[logs - 1] = lines - alloc; + } + + // Our PowerLogBase vector is stored from finest granularity / resolution to largest + // granularity. We dump the logs in reverse order (logs - 1 - "index"). + std::string s = mBase[logs - 1 - start]->dumpToString( + prefix, sublines[start], limitNs, start == logs - 1 ? logPlot : false); + for (size_t i = start + 1; i < logs; ++i) { + s.append(mBase[logs - 1 - i]->dumpToString( + prefix, sublines[i], limitNs, i == logs - 1 ? logPlot : false)); + } + return s; +} + status_t PowerLog::dump( int fd, const char *prefix, size_t lines, int64_t limitNs, bool logPlot) const { diff --git a/audio_utils/include/audio_utils/PowerLog.h b/audio_utils/include/audio_utils/PowerLog.h index 6d82c995..105f3b3d 100644 --- a/audio_utils/include/audio_utils/PowerLog.h +++ b/audio_utils/include/audio_utils/PowerLog.h @@ -27,6 +27,53 @@ namespace android { /** + * PowerLogBase logs power at a given frame resolution. + * + * Generally this class is not directly accessed, rather it is embedded + * as a helper object in PowerLog, which uses multiple PowerLogBase objects to + * log at different frame resolutions. + * + * Call framesToProcess() to determine the maximum number of frames to process. + * Then call processEnergy() with a frame count, and the energy, and the time. + */ +class PowerLogBase { +public: + PowerLogBase(uint32_t sampleRate, + uint32_t channelCount, + audio_format_t format, + size_t entries, + size_t framesPerEntry); + + size_t framesToProcess(size_t frames) const { + const size_t required = mFramesPerEntry - mCurrentFrames; + return std::min(required, frames); + } + + void processEnergy(size_t frames, float energy, int64_t nowNs); + + std::string dumpToString(const char* prefix = "", size_t lines = 0, int64_t limitNs = 0, + bool logPlot = true) const; + +private: + void flushEntry(); + + 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 + const int64_t mEntryTimeNs; // the entry time span in ns + const int64_t mMaxTimeSlipNs; // maximum time incoming audio can + // be offset by before we flush current entry + + int64_t mCurrentTime = 0; // time of first frame in buffer + float mCurrentEnergy = 0.f; // local energy accumulation + size_t mCurrentFrames = 0; // number of frames in the energy + size_t mIdx = 0; // next usable index in mEntries + size_t mConsecutiveZeroes = 1; // current run of consecutive zero entries + std::vector<std::pair<int64_t /* real time ns */, float /* energy */>> mEntries; +}; + +/** * PowerLog captures the audio data power (measured in dBFS) over time. * * For the purposes of power evaluation, the audio data is divided into "bins", @@ -40,6 +87,7 @@ namespace android { */ class PowerLog { public: + /** * \brief Creates a PowerLog object. * @@ -50,12 +98,29 @@ public: * 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. + * \param levels number of resolution levels for the log (typically 1 or 2). */ PowerLog(uint32_t sampleRate, uint32_t channelCount, audio_format_t format, size_t entries, - size_t framesPerEntry); + size_t framesPerEntry, + size_t levels = 1) + : mChannelCount(channelCount) + , mFormat(format) + , mSampleRate(sampleRate) + , mBase{[=]() { + // create a vector of PowerLogBases starting from the + // finest granularity to the largest granularity. + std::vector<std::shared_ptr<PowerLogBase>> v(levels); + size_t scale = 1; + for (size_t i = 0; i < levels; ++i) { + v[i] = std::make_shared<PowerLogBase>( + sampleRate, channelCount, format, entries, framesPerEntry * scale); + scale *= 20; // each level's entry is 20x the temporal width of the prior. + } + return v; + }()} {} /** * \brief Adds new audio data to the power log. @@ -92,18 +157,12 @@ public: status_t dump(int fd, const char *prefix = "", size_t lines = 0, int64_t limitNs = 0, bool logPlot = true) 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; + const uint32_t mSampleRate; + + mutable std::mutex mMutex; // monitor mutex governs access through mBase. + const std::vector<std::shared_ptr<PowerLogBase>> mBase; }; } // namespace android diff --git a/audio_utils/tests/powerlog_tests.cpp b/audio_utils/tests/powerlog_tests.cpp index 6a84134a..1c43b180 100644 --- a/audio_utils/tests/powerlog_tests.cpp +++ b/audio_utils/tests/powerlog_tests.cpp @@ -28,13 +28,14 @@ static size_t countNewLines(const std::string &s) { return std::count(s.begin(), s.end(), '\n'); } -TEST(audio_utils_powerlog, basic) { +TEST(audio_utils_powerlog, basic_level_1) { auto plog = std::make_unique<PowerLog>( 48000 /* sampleRate */, 1 /* channelCount */, AUDIO_FORMAT_PCM_16_BIT, 100 /* entries */, - 1 /* framesPerEntry */); + 1 /* framesPerEntry */, + 1 /* levels */); // header EXPECT_EQ((size_t)1, countNewLines(plog->dumpToString())); |