Home | History | Annotate | Download | only in audio_utils
      1 /*
      2  * Copyright 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 // #define LOG_NDEBUG 0
     18 #define LOG_TAG "audio_utils_PowerLog"
     19 #include <log/log.h>
     20 
     21 #include <algorithm>
     22 #include <iomanip>
     23 #include <math.h>
     24 #include <sstream>
     25 #include <stdint.h>
     26 #include <unistd.h>
     27 
     28 #include <audio_utils/clock.h>
     29 #include <audio_utils/power.h>
     30 #include <audio_utils/PowerLog.h>
     31 
     32 namespace android {
     33 
     34 // TODO move to separate file
     35 template <typename T, size_t N>
     36 constexpr size_t array_size(const T(&)[N])
     37 {
     38     return N;
     39 }
     40 
     41 PowerLog::PowerLog(uint32_t sampleRate,
     42         uint32_t channelCount,
     43         audio_format_t format,
     44         size_t entries,
     45         size_t framesPerEntry)
     46     : mCurrentTime(0)
     47     , mCurrentEnergy(0)
     48     , mCurrentFrames(0)
     49     , mIdx(0)
     50     , mConsecutiveZeroes(0)
     51     , mSampleRate(sampleRate)
     52     , mChannelCount(channelCount)
     53     , mFormat(format)
     54     , mFramesPerEntry(framesPerEntry)
     55     , mEntries(entries)
     56 {
     57     (void)mSampleRate; // currently unused, for future use
     58     LOG_ALWAYS_FATAL_IF(!audio_utils_is_compute_power_format_supported(format),
     59             "unsupported format: %#x", format);
     60 }
     61 
     62 void PowerLog::log(const void *buffer, size_t frames, int64_t nowNs)
     63 {
     64     std::lock_guard<std::mutex> guard(mLock);
     65 
     66     const size_t bytes_per_sample = audio_bytes_per_sample(mFormat);
     67     while (frames > 0) {
     68         // check partial computation
     69         size_t required = mFramesPerEntry - mCurrentFrames;
     70         size_t process = std::min(required, frames);
     71 
     72         if (mCurrentTime == 0) {
     73             mCurrentTime = nowNs;
     74         }
     75         mCurrentEnergy +=
     76                 audio_utils_compute_energy_mono(buffer, mFormat, process * mChannelCount);
     77         mCurrentFrames += process;
     78 
     79         ALOGV("nowNs:%lld, required:%zu, process:%zu, mCurrentEnergy:%f, mCurrentFrames:%zu",
     80                 (long long)nowNs, required, process, mCurrentEnergy, mCurrentFrames);
     81         if (process < required) {
     82             return;
     83         }
     84 
     85         // We store the data as normalized energy per sample. The energy sequence is
     86         // zero terminated. Consecutive zeroes are ignored.
     87         if (mCurrentEnergy == 0.f) {
     88             if (mConsecutiveZeroes++ == 0) {
     89                 mEntries[mIdx++] = std::make_pair(nowNs, 0.f);
     90                 // zero terminate the signal sequence.
     91             }
     92         } else {
     93             mConsecutiveZeroes = 0;
     94             mEntries[mIdx++] = std::make_pair(mCurrentTime, mCurrentEnergy);
     95             ALOGV("writing %lld %f", (long long)mCurrentTime, mCurrentEnergy);
     96         }
     97         if (mIdx >= mEntries.size()) {
     98             mIdx -= mEntries.size();
     99         }
    100         mCurrentTime = 0;
    101         mCurrentEnergy = 0;
    102         mCurrentFrames = 0;
    103         frames -= process;
    104         buffer = (const uint8_t *)buffer + mCurrentFrames * mChannelCount * bytes_per_sample;
    105     }
    106 }
    107 
    108 std::string PowerLog::dumpToString(const char *prefix, size_t lines, int64_t limitNs) const
    109 {
    110     std::lock_guard<std::mutex> guard(mLock);
    111 
    112     const size_t maxColumns = 10;
    113     const size_t numberOfEntries = mEntries.size();
    114     if (lines == 0) lines = SIZE_MAX;
    115 
    116     // compute where to start logging
    117     enum {
    118         AT_END,
    119         IN_SIGNAL,
    120     } state = IN_SIGNAL;
    121     size_t count = 1;
    122     size_t column = 0;
    123     size_t nonzeros = 0;
    124     ssize_t offset; // TODO doesn't dump if # entries exceeds SSIZE_MAX
    125     for (offset = 0; offset < (ssize_t)numberOfEntries && count < lines; ++offset) {
    126         const size_t idx = (mIdx + numberOfEntries - offset - 1) % numberOfEntries; // reverse direction
    127         const int64_t time = mEntries[idx].first;
    128         const float energy = mEntries[idx].second;
    129 
    130         if (state == AT_END) {
    131             if (energy == 0.f) {
    132                 ALOGV("two zeroes detected");
    133                 break; // normally single zero terminated - two zeroes means no more data.
    134             }
    135             state = IN_SIGNAL;
    136         } else { // IN_SIGNAL
    137             if (energy == 0.f) {
    138                 if (column != 0) {
    139                     column = 0;
    140                     ++count;
    141                 }
    142                 state = AT_END;
    143                 continue;
    144             }
    145         }
    146         if (column == 0 && time < limitNs) {
    147             break;
    148         }
    149         ++nonzeros;
    150         if (++column == maxColumns) {
    151             column = 0;
    152             // TODO ideally we would peek the previous entry to see if it is 0
    153             // to ensure we properly put in a starting signal bracket.
    154             // We don't do that because it would complicate the logic here.
    155             ++count;
    156         }
    157     }
    158     if (offset > 0) {
    159         --offset;
    160     }
    161     // We accumulate the log info into a string, and write to the fd once.
    162     std::stringstream ss;
    163     ss << std::fixed << std::setprecision(1);
    164     // ss << std::scientific;
    165     if (nonzeros == 0) {
    166         ss << prefix << "Signal power history: (none)\n";
    167     } else {
    168         ss << prefix << "Signal power history:\n";
    169 
    170         size_t column = 0;
    171         bool first = true;
    172         bool start = false;
    173         float cumulative = 0.f;
    174         for (; offset >= 0; --offset) {
    175             const size_t idx = (mIdx + numberOfEntries - offset - 1) % numberOfEntries;
    176             const int64_t time = mEntries[idx].first;
    177             const float energy = mEntries[idx].second;
    178 
    179             if (energy == 0.f) {
    180                 if (!first) {
    181                     ss << " ] sum(" << audio_utils_power_from_energy(cumulative) << ")";
    182                 }
    183                 cumulative = 0.f;
    184                 column = 0;
    185                 start = true;
    186                 continue;
    187             }
    188             if (column == 0) {
    189                 // print time if at start of column
    190                 if (!first) {
    191                     ss << "\n";
    192                 }
    193                 ss << prefix << " " << audio_utils_time_string_from_ns(time).time
    194                         << (start ? ": [ ": ":   ");
    195                 first = false;
    196                 start = false;
    197             }  else {
    198                 ss << " ";
    199             }
    200             if (++column >= maxColumns) {
    201                 column = 0;
    202             }
    203 
    204             cumulative += energy;
    205             // convert energy to power and print
    206             const float power =
    207                     audio_utils_power_from_energy(energy / (mChannelCount * mFramesPerEntry));
    208             ss << std::setw(6) << power;
    209             ALOGV("state: %d %lld %f", state, (long long)time, power);
    210         }
    211         ss << "\n";
    212     }
    213     return ss.str();
    214 }
    215 
    216 status_t PowerLog::dump(int fd, const char *prefix, size_t lines, int64_t limitNs) const
    217 {
    218     // Since dumpToString and write are thread safe, this function
    219     // is conceptually thread-safe but simultaneous calls to dump
    220     // by different threads to the same file descriptor may not write
    221     // the two logs in time order.
    222     const std::string s = dumpToString(prefix, lines, limitNs);
    223     if (s.size() > 0 && write(fd, s.c_str(), s.size()) < 0) {
    224         return -errno;
    225     }
    226     return NO_ERROR;
    227 }
    228 
    229 } // namespace android
    230 
    231 using namespace android;
    232 
    233 power_log_t *power_log_create(uint32_t sample_rate,
    234         uint32_t channel_count, audio_format_t format, size_t entries, size_t frames_per_entry)
    235 {
    236     if (!audio_utils_is_compute_power_format_supported(format)) {
    237         return nullptr;
    238     }
    239     return reinterpret_cast<power_log_t *>
    240             (new(std::nothrow)
    241                     PowerLog(sample_rate, channel_count, format, entries, frames_per_entry));
    242 }
    243 
    244 void power_log_log(power_log_t *power_log,
    245         const void *buffer, size_t frames, int64_t now_ns)
    246 {
    247     if (power_log == nullptr) {
    248         return;
    249     }
    250     reinterpret_cast<PowerLog *>(power_log)->log(buffer, frames, now_ns);
    251 }
    252 
    253 int power_log_dump(
    254         power_log_t *power_log, int fd, const char *prefix, size_t lines, int64_t limit_ns)
    255 {
    256     if (power_log == nullptr) {
    257         return BAD_VALUE;
    258     }
    259     return reinterpret_cast<PowerLog *>(power_log)->dump(fd, prefix, lines, limit_ns);
    260 }
    261 
    262 void power_log_destroy(power_log_t *power_log)
    263 {
    264     delete reinterpret_cast<PowerLog *>(power_log);
    265 }
    266