OSDN Git Service

ASCII art string of signal power history
authorEric Tan <taneric@google.com>
Fri, 22 Jun 2018 16:24:22 +0000 (09:24 -0700)
committerEric Tan <taneric@google.com>
Mon, 2 Jul 2018 17:35:11 +0000 (10:35 -0700)
Add autoscale and width limit to power log graph
Change PowerLogGraph API and name (now LogPlot).
Plot now shows most recent data when cutting off.

Test: play music through audio headset while adjusting volume, dumpsys media.audio_flinger
Bug: 110794851
Change-Id: I3e046c0a29260dbefbebffb1125ed05c9b60c475

audio_utils/PowerLog.cpp
audio_utils/include/audio_utils/LogPlot.h [new file with mode: 0644]
audio_utils/tests/Makefile [new file with mode: 0644]
audio_utils/tests/logplot_tests.cpp [new file with mode: 0644]

index 15048ba..01c60c9 100644 (file)
 #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>
 #include <audio_utils/PowerLog.h>
 
@@ -165,6 +167,9 @@ std::string PowerLog::dumpToString(const char *prefix, size_t lines, int64_t lim
     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;
         ss << prefix << "Signal power history:\n";
 
         size_t column = 0;
@@ -179,6 +184,12 @@ std::string PowerLog::dumpToString(const char *prefix, size_t lines, int64_t lim
             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;
@@ -207,7 +218,11 @@ std::string PowerLog::dumpToString(const char *prefix, size_t lines, int64_t lim
                     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);
         }
+        ss << "\n" << audio_utils_log_plot(plotEntries.begin(), plotEntries.end());
         ss << "\n";
     }
     return ss.str();
diff --git a/audio_utils/include/audio_utils/LogPlot.h b/audio_utils/include/audio_utils/LogPlot.h
new file mode 100644 (file)
index 0000000..8bf21ef
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef LOG_PLOT_H
+#define LOG_PLOT_H
+
+#include <algorithm>
+#include <cmath>
+#include <iomanip>      // setw
+#include <iostream>
+#include <sstream>
+#include <string>
+
+// TODO Make a class called LogPlot and put this functionality in it.
+/**
+ * \brief Creates a std::string graph representation of equally-spaced time-series data points.
+ *
+ * \param first     RandomAccessIterator iterator to initial position of sequence.
+ *                  Iterator shall point to a pair<float, bool>, where the float is the data value
+ *                  and the bool is whether the data value is the start of a new data point in time
+ *                  (i.e. a break in time continuity).
+ * \param last      RandomAccessIterator iterator to final position of sequence.
+ * \return the std::string of the graph.
+ *
+ */
+template <class RandomAccessIterator>
+std::string audio_utils_log_plot(RandomAccessIterator first, RandomAccessIterator last)
+{
+    using T = decltype((*first).first);
+
+    constexpr int HEIGHT = 14;                    // Character height of the plot
+    // Leave 20% display space before min and after max data points
+    constexpr float RANGE_BUFFER_ROOM = 0.2f;
+    // Minimum range of lowest and highest y-axis value to display
+    constexpr int RANGE_MIN = 14;
+    constexpr unsigned int WIDTH_MAX = 200U;      // Max character width of plot
+    const size_t size = last - first;
+
+    if (size <= 0) {
+        return "";
+    }
+
+    // Find min and max element in the vector.
+    const auto result = std::minmax_element(first, last);
+    const T minVal = (*result.first).first;
+    const T maxVal = (*result.second).first;
+
+    const T range = maxVal - minVal;
+    T graphMin, graphMax;
+    if (range < RANGE_MIN) {
+        T avg = (maxVal + minVal) / 2;
+        graphMin = avg - RANGE_MIN / 2;
+        graphMax = avg + RANGE_MIN / 2;
+    } else {
+        graphMin = minVal - range * RANGE_BUFFER_ROOM;
+        graphMax = maxVal + range * RANGE_BUFFER_ROOM;
+    }
+
+    // Value of one character height increase on the graph
+    const T increment = (graphMax - graphMin) / HEIGHT;
+    // Something went wrong if we reached this statement..
+    if (increment <= 0.0f) {
+        return "";
+    }
+
+    std::stringstream ss;
+    ss << std::fixed << std::setprecision(1);
+
+    // Start storing the graph into string.
+    // TODO store everything into a preallocated string rather than use stringstream.
+    // This may make the code easier to maintain.
+    ss << "\n";
+    for (int height = HEIGHT - 1; height >= 0; height--) {
+        int spaces = 1;     // Amount of spaces before the data point
+        ss << std::setw(9) << graphMin + increment * height;
+        ss << std::setw(3) << "-|";
+        auto it = size <= WIDTH_MAX ? first : first + size - WIDTH_MAX;
+        for (; it < last; ++it) {
+            const T power = it->first;
+            const bool start = it->second;
+            // TODO explicitly do type conversion for parameter passed to round()?
+            int px = (int)round((power - graphMin) / increment);
+            // The it != last - 1 is a temporary workaround to prevent vertical bar
+            // separators after the last data point entry.
+            if ((start || px == height) && it != last - 1) {
+                ss << std::setw(spaces) << (start ? "|" : "*");
+                spaces = 1;
+            } else {
+                spaces++;
+            }
+        }
+        ss << "\n";
+    }
+    ss << std::setw(12) << "|";
+    ss << std::string(std::min(size - (size_t)1, (size_t)WIDTH_MAX), '_') << "\n\n";
+
+    return ss.str();
+}
+
+#endif // !LOG_PLOT_H
diff --git a/audio_utils/tests/Makefile b/audio_utils/tests/Makefile
new file mode 100644 (file)
index 0000000..e2ec83b
--- /dev/null
@@ -0,0 +1,9 @@
+# TODO Incorporate testing into audio_utils/tests/Android.bp and remove this file.
+
+INC=../include/audio_utils
+
+main : logplot_tests.cpp $(INC)/LogPlot.h
+       g++ -I$(INC) -o logplot_tests logplot_tests.cpp
+
+clean :
+       rm -f logplot_tests
diff --git a/audio_utils/tests/logplot_tests.cpp b/audio_utils/tests/logplot_tests.cpp
new file mode 100644 (file)
index 0000000..4e6c1c8
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <iostream>
+#include <vector>
+
+// FIXME couldn't get <audio_utils/LogPlot.h> to work yet.
+#include "LogPlot.h"
+
+// TODO Make more rigorous unit tests.
+int main(int argc, char **argv)
+{
+    static const float data[] = {-61.4, -61.7, -56.2, -54.5, -47.7, -51.1, -49.7, -47.2,
+        -47.8, -42.3, -38.9, -40.5, -39.4, -33.9, -26.3, -20.9};
+    size_t data_size = sizeof(data) / sizeof(*data);
+    std::vector<std::pair<float, bool>> vdata;
+    for (int i = 0; i < data_size; i++) {
+        vdata.push_back(std::make_pair(data[i], (i + 1) % 10 == 0));
+    }
+
+    std::string graphstr = audio_utils_log_plot(vdata.begin(), vdata.end());
+    std::cout << graphstr << std::endl;
+
+    return EXIT_SUCCESS;
+}