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
32namespace android {
33
34// TODO move to separate file
35template <typename T, size_t N>
36constexpr size_t array_size(const T(&)[N])
37{
38    return N;
39}
40
41PowerLog::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
62void 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
108std::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
216status_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
231using namespace android;
232
233power_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
244void 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
253int 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
262void power_log_destroy(power_log_t *power_log)
263{
264    delete reinterpret_cast<PowerLog *>(power_log);
265}
266