summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Hung <hunga@google.com>2024-03-08 23:25:45 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-03-08 23:25:45 +0000
commit8e7c06cddadbda23547e60b40a000dcfe6a62df4 (patch)
treeeeafa8914d0a90d7d5ce603763ffc5c947247a8e
parentd0c3ff3c62e3fdb873dc1f8cc66c0a9f68ffc7b0 (diff)
parent70b3bee847b186f563a3a612aa19571e1034c7e3 (diff)
downloadmedia-8e7c06cddadbda23547e60b40a000dcfe6a62df4.tar.gz
Merge "PowerLog: Prepare for multiresolution mode." into main
-rw-r--r--audio_utils/PowerLog.cpp175
-rw-r--r--audio_utils/include/audio_utils/PowerLog.h81
-rw-r--r--audio_utils/tests/powerlog_tests.cpp5
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()));