aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2018-02-19 11:24:26 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2018-02-19 11:24:26 +0100
commiteff41c50e52d6ce7ef1d3d7be8072a6c27875df4 (patch)
treea3511dc28d469d63decd70f799a7483acfb0b28a
parent07b303a738c82c53c835221f675d6d3b82948357 (diff)
downloaddabmux-eff41c50e52d6ce7ef1d3d7be8072a6c27875df4.tar.gz
dabmux-eff41c50e52d6ce7ef1d3d7be8072a6c27875df4.tar.bz2
dabmux-eff41c50e52d6ce7ef1d3d7be8072a6c27875df4.zip
ManagementServer: make audio_levels statistic more responsive
-rw-r--r--src/ManagementServer.cpp164
-rw-r--r--src/ManagementServer.h76
2 files changed, 125 insertions, 115 deletions
diff --git a/src/ManagementServer.cpp b/src/ManagementServer.cpp
index d299085..25a3f49 100644
--- a/src/ManagementServer.cpp
+++ b/src/ManagementServer.cpp
@@ -35,14 +35,81 @@
#include <limits>
#include <sstream>
#include <algorithm>
-#include <ctime>
#include <boost/thread.hpp>
#include <boost/version.hpp>
#include "ManagementServer.h"
#include "Log.h"
-static constexpr
-auto stats_keep_duration = std::chrono::seconds(INPUT_NODATA_TIMEOUT);
+#define MIN_FILL_BUFFER_UNDEF (-1)
+
+/* For silence detection, we count the number of occurrences the audio level
+ * falls below a threshold.
+ *
+ * The counter is decreased for each frame that has good audio level.
+ *
+ * The counter saturates, and this value defines for how long the
+ * input will be considered silent after a cut.
+ *
+ * If the count reaches a certain value, the input changes state
+ * to Silence. */
+#define INPUT_AUDIO_LEVEL_THRESHOLD -50 // dB
+#define INPUT_AUDIO_LEVEL_SILENCE_COUNT 100 // superframes (120ms)
+#define INPUT_AUDIO_LEVEL_COUNT_SATURATION 500 // superframes (120ms)
+
+/* An example of how the state changes work.
+ * The timeout is set to expire in 30 minutes
+ * at each under-/overrun.
+ *
+ * The glitch counter is increased by one for each glitch (can be a
+ * saturating counter), and set to zero when the counter timeout expires.
+ *
+ * The state is then simply depending on the glitch counter value.
+ *
+ * Graphical example:
+
+ state STREAMING | UNSTABLE | STREAMING
+ xruns U U U
+ glitch
+ counter 0 1 2 3 0
+ reset
+ timeout \ |\ |\ |\
+ \ | \ | \ | \
+ \ | \ | \ | \
+ \| \ | \| \
+ ` \| ` \
+ ` \
+ \
+ \
+ \
+ \
+ timeout expires ___________________\
+ <--30min-->
+ */
+
+/* The delay after which the glitch counter is reset */
+static constexpr auto
+INPUT_COUNTER_RESET_TIME = std::chrono::minutes(30);
+
+/* How many glitches we tolerate in Streaming state before
+ * we consider the input Unstable */
+static constexpr int
+INPUT_UNSTABLE_THRESHOLD = 3;
+
+/* For how long the input buffers must be empty before we move an input to the
+ * NoData state. */
+static constexpr auto
+INPUT_NODATA_TIMEOUT = std::chrono::seconds(30);
+
+/* Keep 30s of min/max buffer fill information so that we can catch meaningful
+ * values even if we have a slow poller */
+static constexpr auto
+BUFFER_STATS_KEEP_DURATION = std::chrono::seconds(30);
+
+/* Audio level information changes faster than buffer levels, so it makes sense
+ * to poll much faster. If we keep too much data, we will hide the interesting
+ * short-time fluctuations. */
+static constexpr auto
+PEAK_STATS_KEEP_DURATION = std::chrono::milliseconds(500);
ManagementServer& get_mgmt_server()
{
@@ -197,8 +264,6 @@ void ManagementServer::handle_message(zmq::message_t& zmq_message)
std::stringstream answer;
std::string data((char*)zmq_message.data(), zmq_message.size());
- etiLog.level(debug) << "ManagementServer: '" << data << "' request";
-
try {
if (data == "info") {
@@ -251,18 +316,17 @@ InputStat::InputStat(const std::string& name)
: m_name(name)
{
/* Statistics */
- num_underruns = 0;
- num_overruns = 0;
+ m_num_underruns = 0;
+ m_num_overruns = 0;
/* State handling */
- time_t now = time(NULL);
- m_time_last_event = now;
+ m_time_last_event = std::chrono::steady_clock::now();
m_glitch_counter = 0;
m_silence_counter = 0;
- buffer_fill_stats.clear();
- peaks_left.clear();
- peaks_right.clear();
+ m_buffer_fill_stats.clear();
+ m_peaks_left.clear();
+ m_peaks_right.clear();
}
InputStat::~InputStat()
@@ -279,48 +343,48 @@ void InputStat::notifyBuffer(long bufsize)
{
boost::mutex::scoped_lock lock(m_mutex);
- buffer_fill_stats.push_back(bufsize);
+ m_buffer_fill_stats.push_back(bufsize);
using namespace std::chrono;
const auto time_now = steady_clock::now();
- if (buffer_fill_stats.size() > 1) {
- auto insertion_interval = time_now - time_last_buffer_notify;
- auto total_length = insertion_interval * buffer_fill_stats.size();
+ if (m_buffer_fill_stats.size() > 1) {
+ auto insertion_interval = time_now - m_time_last_buffer_notify;
+ auto total_length = insertion_interval * m_buffer_fill_stats.size();
- if (total_length > stats_keep_duration) {
- buffer_fill_stats.pop_front();
+ if (total_length > BUFFER_STATS_KEEP_DURATION) {
+ m_buffer_fill_stats.pop_front();
}
}
- time_last_buffer_notify = time_now;
+ m_time_last_buffer_notify = time_now;
}
void InputStat::notifyPeakLevels(int peak_left, int peak_right)
{
boost::mutex::scoped_lock lock(m_mutex);
- peaks_left.push_back(peak_left);
- peaks_right.push_back(peak_right);
+ m_peaks_left.push_back(peak_left);
+ m_peaks_right.push_back(peak_right);
using namespace std::chrono;
const auto time_now = steady_clock::now();
- if (peaks_left.size() > 1) {
- auto insertion_interval = time_now - time_last_peak_notify;
- auto peaks_total_length = insertion_interval * peaks_left.size();
+ if (m_peaks_left.size() > 1) {
+ auto insertion_interval = time_now - m_time_last_peak_notify;
+ auto peaks_total_length = insertion_interval * m_peaks_left.size();
- if (peaks_total_length > stats_keep_duration) {
- peaks_left.pop_front();
- peaks_right.pop_front();
+ if (peaks_total_length > PEAK_STATS_KEEP_DURATION) {
+ m_peaks_left.pop_front();
+ m_peaks_right.pop_front();
}
}
- time_last_peak_notify = time_now;
+ m_time_last_peak_notify = time_now;
- if (peaks_left.empty() or peaks_right.empty()) {
+ if (m_peaks_left.empty() or m_peaks_right.empty()) {
throw std::logic_error("Peak statistics empty!");
}
- const auto max_left = *max_element(peaks_left.begin(), peaks_left.end());
- const auto max_right = *max_element(peaks_right.begin(), peaks_right.end());
+ const auto max_left = *max_element(m_peaks_left.begin(), m_peaks_left.end());
+ const auto max_right = *max_element(m_peaks_right.begin(), m_peaks_right.end());
// State
@@ -350,13 +414,19 @@ void InputStat::notifyUnderrun(void)
boost::mutex::scoped_lock lock(m_mutex);
// Statistics
- num_underruns++;
+ m_num_underruns++;
// State
- m_time_last_event = time(NULL);
+ m_time_last_event = std::chrono::steady_clock::now();
if (m_glitch_counter < INPUT_UNSTABLE_THRESHOLD) {
m_glitch_counter++;
}
+ else {
+ // As we don't receive level notifications anymore, clear the
+ // audio level information
+ m_peaks_left.clear();
+ m_peaks_right.clear();
+ }
}
void InputStat::notifyOverrun(void)
@@ -364,10 +434,10 @@ void InputStat::notifyOverrun(void)
boost::mutex::scoped_lock lock(m_mutex);
// Statistics
- num_overruns++;
+ m_num_overruns++;
// State
- m_time_last_event = time(NULL);
+ m_time_last_event = std::chrono::steady_clock::now();
if (m_glitch_counter < INPUT_UNSTABLE_THRESHOLD) {
m_glitch_counter++;
}
@@ -384,9 +454,9 @@ std::string InputStat::encodeValuesJSON()
int peak_left = 0;
int peak_right = 0;
- if (not peaks_left.empty() and not peaks_right.empty()) {
- peak_left = *max_element(peaks_left.begin(), peaks_left.end());
- peak_right = *max_element(peaks_right.begin(), peaks_right.end());
+ if (not m_peaks_left.empty() and not m_peaks_right.empty()) {
+ peak_left = *max_element(m_peaks_left.begin(), m_peaks_left.end());
+ peak_right = *max_element(m_peaks_right.begin(), m_peaks_right.end());
}
/* convert to dB */
@@ -395,9 +465,9 @@ std::string InputStat::encodeValuesJSON()
long min_fill_buffer = MIN_FILL_BUFFER_UNDEF;
long max_fill_buffer = 0;
- if (not buffer_fill_stats.empty()) {
- auto buffer_min_max_fill = minmax_element(buffer_fill_stats.begin(),
- buffer_fill_stats.end());
+ if (not m_buffer_fill_stats.empty()) {
+ auto buffer_min_max_fill = minmax_element(m_buffer_fill_stats.begin(),
+ m_buffer_fill_stats.end());
min_fill_buffer = *buffer_min_max_fill.first;
max_fill_buffer = *buffer_min_max_fill.second;
}
@@ -408,8 +478,8 @@ std::string InputStat::encodeValuesJSON()
"\"max_fill\": " << max_fill_buffer << ", "
"\"peak_left\": " << dB_l << ", "
"\"peak_right\": " << dB_r << ", "
- "\"num_underruns\": " << num_underruns << ", "
- "\"num_overruns\": " << num_overruns << ", ";
+ "\"num_underruns\": " << m_num_underruns << ", "
+ "\"num_overruns\": " << m_num_overruns << ", ";
ss << "\"state\": ";
@@ -437,14 +507,14 @@ std::string InputStat::encodeValuesJSON()
input_state_t InputStat::determineState()
{
- time_t now = time(nullptr);
+ const auto now = std::chrono::steady_clock::now();
input_state_t state;
/* if the last event was more that INPUT_COUNTER_RESET_TIME
- * minutes ago, the timeout has expired. We can reset our
+ * ago, the timeout has expired. We can reset our
* glitch counter.
*/
- if (now - m_time_last_event > 60*INPUT_COUNTER_RESET_TIME) {
+ if (now - m_time_last_event > INPUT_COUNTER_RESET_TIME) {
m_glitch_counter = 0;
}
@@ -456,7 +526,7 @@ input_state_t InputStat::determineState()
* Consider an empty deque to be NoData too.
*/
if (std::all_of(
- buffer_fill_stats.begin(), buffer_fill_stats.end(),
+ m_buffer_fill_stats.begin(), m_buffer_fill_stats.end(),
[](long fill) { return fill == 0; }) ) {
state = NoData;
}
diff --git a/src/ManagementServer.h b/src/ManagementServer.h
index 1b432d5..885565a 100644
--- a/src/ManagementServer.h
+++ b/src/ManagementServer.h
@@ -57,14 +57,10 @@
#include <chrono>
#include <deque>
#include <boost/thread.hpp>
-#include <boost/bind.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
-#include <ctime>
#include <cmath>
-#define MIN_FILL_BUFFER_UNDEF (-1)
-
/*** State handing ***/
/* An input can be in one of the following three states:
*/
@@ -82,62 +78,6 @@ enum input_state_t
Streaming
};
-
-/* The delay after which the glitch counter is reset */
-#define INPUT_COUNTER_RESET_TIME 30 // minutes
-
-/* How many glitches we tolerate in Streaming state before
- * we consider the input Unstable */
-#define INPUT_UNSTABLE_THRESHOLD 3
-
-/* For how long the input buffers must be empty before we move an input to the
- * NoData state. */
-#define INPUT_NODATA_TIMEOUT 30 // seconds
-
-/* For silence detection, we count the number of occurrences the audio level
- * falls below a threshold.
- *
- * The counter is decreased for each frame that has good audio level.
- *
- * The counter saturates, and this value defines for how long the
- * input will be considered silent after a cut.
- *
- * If the count reaches a certain value, the input changes state
- * to Silence. */
-#define INPUT_AUDIO_LEVEL_THRESHOLD -50 // dB
-#define INPUT_AUDIO_LEVEL_SILENCE_COUNT 100 // superframes (120ms)
-#define INPUT_AUDIO_LEVEL_COUNT_SATURATION 500 // superframes (120ms)
-
-/* An example of how the state changes work.
- * The timeout is set to expire in 30 minutes
- * at each under-/overrun.
- *
- * The glitch counter is increased by one for each glitch (can be a
- * saturating counter), and set to zero when the counter timeout expires.
- *
- * The state is then simply depending on the glitch counter value.
- *
- * Graphical example:
-
- state STREAMING | UNSTABLE | STREAMING
- xruns U U U
- glitch
- counter 0 1 2 3 0
- reset
- timeout \ |\ |\ |\
- \ | \ | \ | \
- \ | \ | \ | \
- \| \ | \| \
- ` \| ` \
- ` \
- \
- \
- \
- \
- timeout expires ___________________\
- <--30min-->
- */
-
/* InputStat takes care of
* - saving the statistics for graphing
* - calculating the state of the input for monitoring
@@ -168,24 +108,24 @@ class InputStat
/************ STATISTICS ***********/
// Calculate minimum and maximum buffer fill from
// a FIFO of values from the last few seconds
- std::deque<long> buffer_fill_stats;
- std::chrono::time_point<std::chrono::steady_clock> time_last_buffer_notify;
+ std::deque<long> m_buffer_fill_stats;
+ std::chrono::time_point<std::chrono::steady_clock> m_time_last_buffer_notify;
// counter of number of overruns and underruns since startup
- uint32_t num_underruns;
- uint32_t num_overruns;
+ uint32_t m_num_underruns;
+ uint32_t m_num_overruns;
// Peak audio levels (linear 16-bit PCM) for the two channels.
// Keep a FIFO of values from the last few seconds
- std::deque<int> peaks_left;
- std::deque<int> peaks_right;
- std::chrono::time_point<std::chrono::steady_clock> time_last_peak_notify;
+ std::deque<int> m_peaks_left;
+ std::deque<int> m_peaks_right;
+ std::chrono::time_point<std::chrono::steady_clock> m_time_last_peak_notify;
/************* STATE ***************/
/* Variables used for determining the input state */
int m_glitch_counter; // saturating counter
int m_silence_counter; // saturating counter
- time_t m_time_last_event;
+ std::chrono::time_point<std::chrono::steady_clock> m_time_last_event;
// The mutex that has to be held during all notify and readout
mutable boost::mutex m_mutex;