| /* |
| * 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 <audio_utils/PowerLog.h> |
| |
| #include <algorithm> |
| #include <iomanip> |
| #include <math.h> |
| #include <sstream> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <vector> |
| |
| #include <audio_utils/clock.h> |
| #include <audio_utils/LogPlot.h> |
| #include <audio_utils/power.h> |
| |
| namespace android { |
| |
| PowerLogBase::PowerLogBase(uint32_t sampleRate, |
| uint32_t channelCount, |
| audio_format_t format, |
| size_t entries, |
| size_t framesPerEntry) |
| : mSampleRate(sampleRate) |
| , mChannelCount(channelCount) |
| , mFormat(format) |
| , mFramesPerEntry(framesPerEntry) |
| , mEntryTimeNs(framesPerEntry * 1e9 / sampleRate) |
| , mMaxTimeSlipNs(std::min((int64_t)200'000'000, mEntryTimeNs)) |
| , mEntries(entries) |
| { |
| (void)mFormat; // currently unused, for future use |
| LOG_ALWAYS_FATAL_IF(!audio_utils_is_compute_power_format_supported(format), |
| "unsupported format: %#x", format); |
| } |
| |
| 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(); |
| } |
| |
| mCurrentEnergy += energy; |
| |
| // if we are in a zero run, do not advance. |
| if (mCurrentEnergy == 0.f && mConsecutiveZeroes > 0) return; |
| |
| 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 PowerLogBase::dumpToString( |
| const char *prefix, size_t lines, int64_t limitNs, bool logPlot) const |
| { |
| 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 << prefix << "Signal power history: (none)\n"; |
| } else { |
| // First value is power, second value is whether value is start of |
| // a new time stamp. |
| std::vector<std::pair<float, bool>> plotEntries; |
| const float timeResolution = mFramesPerEntry * 1000.f / mSampleRate; |
| ss << prefix << "Signal power history (resolution: " << timeResolution << " ms):\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) << ")"; |
| // Add an entry to denote the start of a new time stamp series. |
| if (!plotEntries.empty()) { |
| // First value should be between min and max of all graph entries |
| // so that it doesn't mess with y-axis scaling. |
| plotEntries.emplace_back(plotEntries.back().first, true); |
| } |
| } |
| cumulative = 0.f; |
| column = 0; |
| start = true; |
| continue; |
| } |
| if (column == 0) { |
| // print time if at start of column |
| if (!first) { |
| ss << "\n"; |
| } |
| ss << prefix << " " << audio_utils_time_string_from_ns(time).time |
| << (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); |
| // Add an entry to the ASCII art power log graph. |
| // false indicates the value doesn't have a new series time stamp. |
| plotEntries.emplace_back(power, false); |
| } |
| if (logPlot) { |
| ss << "\n" << audio_utils_log_plot(plotEntries.begin(), plotEntries.end()); |
| } |
| ss << "\n"; |
| } |
| 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 |
| { |
| // 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(prefix, lines, limitNs, logPlot); |
| 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(std::nothrow) |
| 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, const char *prefix, size_t lines, int64_t limit_ns) |
| { |
| if (power_log == nullptr) { |
| return BAD_VALUE; |
| } |
| return reinterpret_cast<PowerLog *>(power_log)->dump(fd, prefix, lines, limit_ns); |
| } |
| |
| void power_log_destroy(power_log_t *power_log) |
| { |
| delete reinterpret_cast<PowerLog *>(power_log); |
| } |