From eff41c50e52d6ce7ef1d3d7be8072a6c27875df4 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Mon, 19 Feb 2018 11:24:26 +0100 Subject: ManagementServer: make audio_levels statistic more responsive --- src/ManagementServer.cpp | 164 +++++++++++++++++++++++++++++++++-------------- src/ManagementServer.h | 76 +++------------------- 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 #include #include -#include #include #include #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 #include #include -#include #include #include -#include #include -#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 buffer_fill_stats; - std::chrono::time_point time_last_buffer_notify; + std::deque m_buffer_fill_stats; + std::chrono::time_point 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 peaks_left; - std::deque peaks_right; - std::chrono::time_point time_last_peak_notify; + std::deque m_peaks_left; + std::deque m_peaks_right; + std::chrono::time_point 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 m_time_last_event; // The mutex that has to be held during all notify and readout mutable boost::mutex m_mutex; -- cgit v1.2.3