From 7e5367bd6df7fbdb70dd3bd02d033d7bc26417d7 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 15 Feb 2018 16:32:29 +0100 Subject: Prepare audio level stats for clear-on-read removal --- src/ManagementServer.cpp | 143 +++++++++++++++++++++++++++++++++++++++++- src/ManagementServer.h | 158 +++++++---------------------------------------- 2 files changed, 164 insertions(+), 137 deletions(-) (limited to 'src') diff --git a/src/ManagementServer.cpp b/src/ManagementServer.cpp index 3300c89..10ba396 100644 --- a/src/ManagementServer.cpp +++ b/src/ManagementServer.cpp @@ -2,7 +2,7 @@ Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org @@ -40,6 +40,8 @@ #include "ManagementServer.h" #include "Log.h" +static constexpr auto stats_keep_duration = std::chrono::seconds(30); + ManagementServer& get_mgmt_server() { static ManagementServer mgmt_server; @@ -244,9 +246,22 @@ void ManagementServer::update_ptree(const boost::property_tree::ptree& pt) /************************************************/ -void InputStat::registerAtServer() +InputStat::InputStat(const std::string& name) + : m_name(name) { - get_mgmt_server().registerInput(this); + /* Statistics */ + num_underruns = 0; + num_overruns = 0; + + /* State handling */ + time_t now = time(NULL); + m_time_last_event = now; + m_time_last_buffer_nonempty = 0; + m_buffer_empty = true; + m_glitch_counter = 0; + m_silence_counter = 0; + + reset(); } InputStat::~InputStat() @@ -254,6 +269,120 @@ InputStat::~InputStat() get_mgmt_server().unregisterInput(m_name); } +void InputStat::registerAtServer() +{ + get_mgmt_server().registerInput(this); +} + +void InputStat::reset() +{ + min_fill_buffer = MIN_FILL_BUFFER_UNDEF; + max_fill_buffer = 0; + + peaks_left.clear(); + peaks_right.clear(); +} + +void InputStat::notifyBuffer(long bufsize) +{ + boost::mutex::scoped_lock lock(m_mutex); + + // Statistics + if (bufsize > max_fill_buffer) { + max_fill_buffer = bufsize; + } + + if (bufsize < min_fill_buffer || + min_fill_buffer == MIN_FILL_BUFFER_UNDEF) { + min_fill_buffer = bufsize; + } + + // State + m_buffer_empty = (bufsize == 0); + if (!m_buffer_empty) { + m_time_last_buffer_nonempty = time(NULL); + } +} + +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); + + 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 (peaks_total_length > stats_keep_duration) { + peaks_left.pop_front(); + peaks_right.pop_front(); + } + } + time_last_peak_notify = time_now; + + if (peaks_left.empty() or 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()); + + // State + + // using the lower of the two channels allows us to detect if only one + // channel is silent. + const int lower_peak = max_left < max_right ? max_left : max_right; + + const int16_t int16_max = std::numeric_limits::max(); + int peak_dB = lower_peak ? + round(20*log10((double)lower_peak / int16_max)) : + -90; + + if (peak_dB < INPUT_AUDIO_LEVEL_THRESHOLD) { + if (m_silence_counter < INPUT_AUDIO_LEVEL_COUNT_SATURATION) { + m_silence_counter++; + } + } + else { + if (m_silence_counter > 0) { + m_silence_counter--; + } + } +} + +void InputStat::notifyUnderrun(void) +{ + boost::mutex::scoped_lock lock(m_mutex); + + // Statistics + num_underruns++; + + // State + m_time_last_event = time(NULL); + if (m_glitch_counter < INPUT_UNSTABLE_THRESHOLD) { + m_glitch_counter++; + } +} + +void InputStat::notifyOverrun(void) +{ + boost::mutex::scoped_lock lock(m_mutex); + + // Statistics + num_overruns++; + + // State + m_time_last_event = time(NULL); + if (m_glitch_counter < INPUT_UNSTABLE_THRESHOLD) { + m_glitch_counter++; + } +} + std::string InputStat::encodeValuesJSON() { std::ostringstream ss; @@ -262,6 +391,14 @@ std::string InputStat::encodeValuesJSON() boost::mutex::scoped_lock lock(m_mutex); + 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()); + } + /* convert to dB */ int dB_l = peak_left ? round(20*log10((double)peak_left / int16_max)) : -90; int dB_r = peak_right ? round(20*log10((double)peak_right / int16_max)) : -90; diff --git a/src/ManagementServer.h b/src/ManagementServer.h index e75aed3..fcdbc16 100644 --- a/src/ManagementServer.h +++ b/src/ManagementServer.h @@ -2,7 +2,7 @@ Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org @@ -54,12 +54,14 @@ #include #include #include +#include +#include #include #include #include #include #include -#include +#include #define MIN_FILL_BUFFER_UNDEF (-1) @@ -70,32 +72,26 @@ enum input_state_t { /* The input is waiting for data, all buffers are empty */ NoData, - /* The input is running, but has seen many underruns or overruns recently */ Unstable, - /* The input is running, but the audio level is too low, or has * been too low recently */ Silence, - /* The input is running stable */ Streaming }; -/* The delay after which the glitch counter is reset - */ +/* 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 - */ + * 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. - */ + * NoData state. */ #define INPUT_NODATA_TIMEOUT 30 // seconds /* For silence detection, we count the number of occurrences the audio level @@ -107,8 +103,7 @@ enum input_state_t * input will be considered silent after a cut. * * If the count reaches a certain value, the input changes state - * to Silence. - */ + * 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) @@ -150,131 +145,25 @@ enum input_state_t class InputStat { public: - InputStat(std::string name) : m_name(name) - { - /* Statistics */ - num_underruns = 0; - num_overruns = 0; - - /* State handling */ - time_t now = time(NULL); - m_time_last_event = now; - m_time_last_buffer_nonempty = 0; - m_buffer_empty = true; - m_glitch_counter = 0; - m_silence_counter = 0; - - reset(); - } - - void registerAtServer(void); - + InputStat(const std::string& name); + InputStat(const InputStat& other) = delete; + InputStat& operator=(const InputStat& other) = delete; ~InputStat(); + void registerAtServer(void); // Gets called each time the statistics are transmitted, // and resets the counters to zero - void reset(void) - { - min_fill_buffer = MIN_FILL_BUFFER_UNDEF; - max_fill_buffer = 0; + void reset(void); - peak_left = 0; - peak_right = 0; - } - - std::string& get_name(void) { return m_name; } + std::string get_name(void) const { return m_name; } /* This function is called for every frame read by - * the multiplexer - */ - void notifyBuffer(long bufsize) - { - boost::mutex::scoped_lock lock(m_mutex); - - // Statistics - if (bufsize > max_fill_buffer) { - max_fill_buffer = bufsize; - } - - if (bufsize < min_fill_buffer || - min_fill_buffer == MIN_FILL_BUFFER_UNDEF) { - min_fill_buffer = bufsize; - } - - // State - m_buffer_empty = (bufsize == 0); - if (!m_buffer_empty) { - m_time_last_buffer_nonempty = time(NULL); - } - } - - void notifyPeakLevels(int peak_left, int peak_right) - { - boost::mutex::scoped_lock lock(m_mutex); - - // Statistics - if (peak_left > this->peak_left) { - this->peak_left = peak_left; - } - - if (peak_right > this->peak_right) { - this->peak_right = peak_right; - } - - // State - - // using the smallest of the two channels - // allows us to detect if only one channel - // is silent. - int minpeak = peak_left < peak_right ? peak_left : peak_right; - - const int16_t int16_max = std::numeric_limits::max(); - int peak_dB = minpeak ? - round(20*log10((double)minpeak / int16_max)) : - -90; - - if (peak_dB < INPUT_AUDIO_LEVEL_THRESHOLD) { - if (m_silence_counter < INPUT_AUDIO_LEVEL_COUNT_SATURATION) { - m_silence_counter++; - } - } - else { - if (m_silence_counter > 0) { - m_silence_counter--; - } - } - } - - void notifyUnderrun(void) - { - boost::mutex::scoped_lock lock(m_mutex); - - // Statistics - num_underruns++; - - // State - m_time_last_event = time(NULL); - if (m_glitch_counter < INPUT_UNSTABLE_THRESHOLD) { - m_glitch_counter++; - } - } - - void notifyOverrun(void) - { - boost::mutex::scoped_lock lock(m_mutex); - - // Statistics - num_overruns++; - - // State - m_time_last_event = time(NULL); - if (m_glitch_counter < INPUT_UNSTABLE_THRESHOLD) { - m_glitch_counter++; - } - } - + * the multiplexer */ + void notifyBuffer(long bufsize); + void notifyPeakLevels(int peak_left, int peak_right); + void notifyUnderrun(void); + void notifyOverrun(void); std::string encodeValuesJSON(void); - input_state_t determineState(void); private: @@ -289,9 +178,11 @@ class InputStat uint32_t num_underruns; uint32_t num_overruns; - // peak audio levels (linear 16-bit PCM) for the two channels - int peak_left; - int peak_right; + // 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; /************* STATE ***************/ /* Variables used for determining the input state */ @@ -303,7 +194,6 @@ class InputStat // The mutex that has to be held during all notify and readout mutable boost::mutex m_mutex; - }; class ManagementServer -- cgit v1.2.3