diff --git a/src/internal_modules/roc_audio/jitter_meter.cpp b/src/internal_modules/roc_audio/jitter_meter.cpp new file mode 100644 index 000000000..ba21df3d7 --- /dev/null +++ b/src/internal_modules/roc_audio/jitter_meter.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024 Roc Streaming authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "roc_audio/jitter_meter.h" +#include "roc_core/panic.h" + +namespace roc { +namespace audio { + +bool JitterConfig::deduce_defaults(audio::LatencyTunerProfile latency_profile) { + if (jitter_window == 0) { + if (latency_profile == audio::LatencyTunerProfile_Responsive) { + jitter_window = 10000; + } else { + jitter_window = 30000; + } + } + + if (peak_quantile_window == 0) { + peak_quantile_window = jitter_window / 5; + } + + if (envelope_resistance_coeff == 0) { + if (latency_profile == audio::LatencyTunerProfile_Responsive) { + envelope_resistance_coeff = 0.05; + } else { + envelope_resistance_coeff = 0.1; + } + } + + return true; +} + +JitterMeter::JitterMeter(const JitterConfig& config, core::IArena& arena) + : config_(config) + , jitter_window_(arena, config.jitter_window) + , smooth_jitter_window_(arena, config.envelope_smoothing_window_len) + , envelope_window_(arena, config.peak_quantile_window, config.peak_quantile_coeff) + , peak_window_(arena, config.jitter_window) + , capacitor_charge_(0) + , capacitor_discharge_resistance_(0) + , capacitor_discharge_iteration_(0) { +} + +const JitterMetrics& JitterMeter::metrics() const { + return metrics_; +} + +void JitterMeter::update_jitter(const core::nanoseconds_t jitter) { + // Moving average of jitter. + jitter_window_.add(jitter); + + // Update current value of jitter envelope based on current value of jitter. + // Envelope is computed based on smoothed jitter + a peak detector with capacitor. + smooth_jitter_window_.add(jitter); + const core::nanoseconds_t jitter_envelope = + update_envelope_(smooth_jitter_window_.mov_max(), jitter_window_.mov_avg()); + + // Quantile of envelope. + envelope_window_.add(jitter_envelope); + // Moving maximum of quantile of envelope. + peak_window_.add(envelope_window_.mov_quantile()); + + metrics_.mean_jitter = jitter_window_.mov_avg(); + metrics_.peak_jitter = peak_window_.mov_max(); + metrics_.curr_jitter = jitter; + metrics_.curr_envelope = jitter_envelope; +} + +// This function calculates jitter envelope using a peak detector with capacitor. +// +// The quantile of jitter envelope is used as the value for `peak_jitter` metric. +// LatencyTuner selects target latency based on its value. We want find lowest +// possible peak jitter and target latency that are safe (don't cause disruptions). +// +// The function tries to achieve two goals: +// +// - The quantile of envelope (e.g. 90% of values) should be above regular repeating +// spikes, typical for wireless networks, and should ignore occasional exceptions +// if they're not too high and not too frequent. +// +// - The quantile of envelope should be however increased if occasional spike is +// really high, which is often a predictor of increasing network load +// (i.e. if spike is abnormally high, chances are that more high spikes follows). +// +// The role of the capacitor is to amplify the impact of jitter spikes of certain +// kinds. Without it, spikes would be too thin to be reliably detected by quantile. +// +// Typical jitter envelope before applying capacitor: +// +// ------------------------------------- maximum (too high) +// |╲ +// || |╲ |╲ +// --||----------||--------||----------- quantile (too low) +// __||______|╲__||__|╲____||__|╲____ +// +// And after applying capacitor: +// +// |╲_ +// --| |_-------|╲_-------|╲----------- quantile (good) +// | ╲ | ╲_ | ╲_ +// __| ╲_|╲__| ╲____| ╲____ +// +core::nanoseconds_t JitterMeter::update_envelope_(const core::nanoseconds_t cur_jitter, + const core::nanoseconds_t avg_jitter) { + // `capacitor_charge_` represents current envelope value. + // Each step we either instantly re-charge capacitor if we see a peak, or slowly + // discharge it until it reaches zero or we see next peek. + + if (capacitor_charge_ < cur_jitter) { + // If current jitter is higher than capacitor charge, instantly re-charge + // capacitor. The charge is set to the jitter value, and the resistance to + // discharging is proportional to the value of the jitter related to average. + // + // Peaks that are significantly higher than average cause very slow discharging, + // and hence have bigger impact on the envelope's quantile. + // + // Peaks that are not so high discharge quicker, but if they are frequent enough, + // capacitor value is constantly re-charged and keeps high. Hence, frequent peeks + // also have bigger impact on the envelope's quantile. + // + // Peaks that are neither high nor frequent have small impact on the quantile. + capacitor_charge_ = cur_jitter; + capacitor_discharge_resistance_ = std::pow((double)cur_jitter / avg_jitter, + config_.envelope_resistance_exponent) + * config_.envelope_resistance_coeff; + capacitor_discharge_iteration_ = 0; + } else if (capacitor_charge_ > 0) { + // No peak detected, continue discharging (exponentionally). + capacitor_charge_ = + core::nanoseconds_t(capacitor_charge_ + * std::exp(-capacitor_discharge_iteration_ + / capacitor_discharge_resistance_)); + capacitor_discharge_iteration_++; + } + + if (capacitor_charge_ < 0) { + // Fully discharged. Normally doesn't happen. + capacitor_charge_ = 0; + } + + return capacitor_charge_; +} + +} // namespace audio +} // namespace roc diff --git a/src/internal_modules/roc_audio/jitter_meter.h b/src/internal_modules/roc_audio/jitter_meter.h new file mode 100644 index 000000000..a7a16c439 --- /dev/null +++ b/src/internal_modules/roc_audio/jitter_meter.h @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2024 Roc Streaming authors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +//! @file roc_audio/jitter_meter.h +//! @brief Jitter metrics calculator. + +#ifndef ROC_AUDIO_JITTER_METER_H_ +#define ROC_AUDIO_JITTER_METER_H_ + +#include "roc_audio/latency_config.h" +#include "roc_core/iarena.h" +#include "roc_core/noncopyable.h" +#include "roc_core/time.h" +#include "roc_stat/mov_aggregate.h" +#include "roc_stat/mov_quantile.h" + +namespace roc { +namespace audio { + +//! Jitter meter parameters. +//! +//! Mean jitter is calculated as moving average of last `jitter_window` packets. +//! +//! Peak jitter calculation is performed in several steps: +//! +//! 1. Calculate jitter envelope - a curve that outlines jitter extremes. +//! Envelope calculation is based on a smoothing window +//! (`envelope_smoothing_window_len`) and a peak detector with capacitor +//! (`envelope_resistance_exponent`, `envelope_resistance_coeff`). +//! +//! 2. Calculate moving quantile of the envelope - a line above certain percentage +//! of the envelope values across moving window (`peak_quantile_coeff`, +//! `peak_quantile_window`). +//! +//! 3. Calculate moving maximum of the envelope's quantile across last `jitter_window` +//! samples. This is the resulting peak jitter. +struct JitterConfig { + //! Number of packets for calculating long-term jitter sliding statistics. + //! @remarks + //! Increase this value if you want slower and smoother reaction. + //! Peak jitter is not decreased until jitter envelope is low enough + //! during this window. + //! @note + //! Default value is about a few minutes. + size_t jitter_window; + + //! Number of packets in small smoothing window to calculate jitter envelope. + //! @remarks + //! The larger is this value, the rougher is jitter envelope. + //! @note + //! Default value is a few packets. + size_t envelope_smoothing_window_len; + + //! Exponent coefficient of capacitor resistance used in jitter envelope. + //! @note + //! Capacitor discharge speed is (peak ^ exp) * coeff, where `peak` is + //! the jitter peak size relative to the average jitter, `exp` is + //! `envelope_resistance_exponent`, and `coeff` is `envelope_resistance_coeff`. + //! @remarks + //! Increase this value to make impact to the peak jitter of high spikes much + //! stronger than impact of low spikes. + double envelope_resistance_exponent; + + //! Linear coefficient of capacitor resistance used in jitter envelope. + //! @note + //! Capacitor discharge speed is (peak ^ exp) * coeff, where `peak` is + //! the jitter peak size relative to the average jitter, `exp` is + //! `envelope_resistance_exponent`, and `coeff` is `envelope_resistance_coeff`. + //! @remarks + //! Increase this value to make impact to the peak jitter of frequent spikes + //! stronger than impact of rare spikes. + double envelope_resistance_coeff; + + //! Number of packets for calculating envelope quantile. + //! @remarks + //! This window size is used to calculate moving quantile of the envelope. + //! @note + //! This value is the compromise between reaction speed to the increased + //! jitter and ability to distinguish rare spikes from frequent ones. + //! If you increase this value, we can detect and cut out more spikes that + //! are harmless, but we react to the relevant spikes a bit slower. + size_t peak_quantile_window; + + //! Coefficient of envelope quantile from 0 to 1. + //! @remarks + //! Defines percentage of the envelope that we want to cut out. + //! @note + //! E.g. value 0.9 means that we want to draw a line that is above 90% + //! of all envelope values across the quantile window. + double peak_quantile_coeff; + + JitterConfig() + : jitter_window(0) + , envelope_smoothing_window_len(10) + , envelope_resistance_exponent(6) + , envelope_resistance_coeff(0) + , peak_quantile_window(0) + , peak_quantile_coeff(0.90) { + } + + //! Automatically fill missing settings. + ROC_ATTR_NODISCARD bool deduce_defaults(audio::LatencyTunerProfile latency_profile); +}; + +//! Jitter metrics. +struct JitterMetrics { + //! Moving average of the jitter. + core::nanoseconds_t mean_jitter; + + //! Moving peak value of the jitter. + //! @remarks + //! This metric is similar to moving maximum, but excludes short rate spikes + //! that are considered harmless. + core::nanoseconds_t peak_jitter; + + //! Last jitter value. + core::nanoseconds_t curr_jitter; + + //! Last jitter envelope value. + core::nanoseconds_t curr_envelope; + + JitterMetrics() + : mean_jitter(0) + , peak_jitter(0) + , curr_jitter(0) + , curr_envelope(0) { + } +}; + +//! Jitter metrics calculator. +class JitterMeter : public core::NonCopyable { +public: + //! Initialize. + JitterMeter(const JitterConfig& config, core::IArena& arena); + + //! Get updated jitter metrics. + const JitterMetrics& metrics() const; + + //! Update jitter metrics based on the jitter value for newly received packet. + void update_jitter(core::nanoseconds_t jitter); + +private: + core::nanoseconds_t update_envelope_(core::nanoseconds_t cur_jitter, + core::nanoseconds_t avg_jitter); + + const JitterConfig config_; + + JitterMetrics metrics_; + + stat::MovAggregate jitter_window_; + stat::MovAggregate smooth_jitter_window_; + stat::MovQuantile envelope_window_; + stat::MovAggregate peak_window_; + + core::nanoseconds_t capacitor_charge_; + double capacitor_discharge_resistance_; + double capacitor_discharge_iteration_; +}; + +} // namespace audio +} // namespace roc + +#endif // ROC_AUDIO_JITTER_METER_H_ diff --git a/src/internal_modules/roc_audio/latency_tuner.cpp b/src/internal_modules/roc_audio/latency_tuner.cpp index b6abf68f7..1d3975b30 100644 --- a/src/internal_modules/roc_audio/latency_tuner.cpp +++ b/src/internal_modules/roc_audio/latency_tuner.cpp @@ -198,7 +198,7 @@ bool LatencyTuner::update_stream() { roc_panic_if(init_status_ != status::StatusOK); if (enable_latency_adjustment_ && latency_is_adaptive_ && has_metrics_) { - update_target_latency_(link_metrics_.max_jitter, link_metrics_.jitter, + update_target_latency_(link_metrics_.peak_jitter, link_metrics_.mean_jitter, latency_metrics_.fec_block_duration); } @@ -350,7 +350,7 @@ void LatencyTuner::compute_scaling_(packet::stream_timestamp_diff_t actual_laten // NB: After the increasement the new latency target value must not be greater than // upper threshold in any circumstances. // -void LatencyTuner::update_target_latency_(const core::nanoseconds_t max_jitter_ns, +void LatencyTuner::update_target_latency_(const core::nanoseconds_t peak_jitter_ns, const core::nanoseconds_t mean_jitter_ns, const core::nanoseconds_t fec_block_ns) { // If there is no active timeout, check if evaluated target latency is @@ -360,7 +360,7 @@ void LatencyTuner::update_target_latency_(const core::nanoseconds_t max_jitter_n // jitter statistics. Later we'll use this value only for decision making if it // worth changing or we rather keep the current latency target untouched. const core::nanoseconds_t estimate = std::max( - std::max(core::nanoseconds_t(max_jitter_ns * max_jitter_overhead_), + std::max(core::nanoseconds_t(peak_jitter_ns * max_jitter_overhead_), core::nanoseconds_t(mean_jitter_ns * mean_jitter_overhead_)), fec_block_ns); @@ -527,7 +527,7 @@ void LatencyTuner::periodic_report_() { "latency tuner:" " e2e_latency=%ld(%.3fms) niq_latency=%ld(%.3fms) target_latency=%ld(%.3fms)" " stale=%ld(%.3fms)" - " packets(lost/exp)=%ld/%ld jitter(avg/min/max)=%.3fms/%.3fms/%.3fms" + " packets(lost/exp)=%ld/%ld jitter(mean/peak)=%.3fms/%.3fms" " fec_blk=%.3fms" " fe=%.6f eff_fe=%.6f fe_stbl=%s", // e2e_latency, niq_latency, target_latency @@ -540,9 +540,8 @@ void LatencyTuner::periodic_report_() { // packets (long)link_metrics_.lost_packets, (long)link_metrics_.expected_packets, // jitter - (double)link_metrics_.jitter / core::Millisecond, - (double)link_metrics_.min_jitter / core::Millisecond, - (double)link_metrics_.max_jitter / core::Millisecond, + (double)link_metrics_.mean_jitter / core::Millisecond, + (double)link_metrics_.peak_jitter / core::Millisecond, // fec_blk (double)latency_metrics_.fec_block_duration / core::Millisecond, // fe, eff_fe, fe_stbl diff --git a/src/internal_modules/roc_audio/latency_tuner.h b/src/internal_modules/roc_audio/latency_tuner.h index e17e65afc..2ebb4af99 100644 --- a/src/internal_modules/roc_audio/latency_tuner.h +++ b/src/internal_modules/roc_audio/latency_tuner.h @@ -90,7 +90,7 @@ class LatencyTuner : public core::NonCopyable<> { bool check_actual_latency_(packet::stream_timestamp_diff_t latency); void compute_scaling_(packet::stream_timestamp_diff_t latency); - void update_target_latency_(core::nanoseconds_t max_jitter_ns, + void update_target_latency_(core::nanoseconds_t peak_jitter_ns, core::nanoseconds_t mean_jitter_ns, core::nanoseconds_t fec_block_ns); void try_decrease_latency_(core::nanoseconds_t estimate, diff --git a/src/internal_modules/roc_packet/ilink_meter.h b/src/internal_modules/roc_packet/ilink_meter.h index b25ccb31a..69531776e 100644 --- a/src/internal_modules/roc_packet/ilink_meter.h +++ b/src/internal_modules/roc_packet/ilink_meter.h @@ -57,27 +57,21 @@ struct LinkMetrics { //! Calculated by rtp::LinkMeter on receiver, reported via RTCP to sender. int64_t lost_packets; - //! Estimated interarrival jitter. + //! Average interarrival jitter. //! An estimate of the statistical variance of the RTP data packet interarrival time. //! Calculated based on a sliding window. //! @note //! This value is calculated on sliding window on a receiver side and sender //! side gets this value via RTCP. - core::nanoseconds_t jitter; + core::nanoseconds_t mean_jitter; - //! Running max of jitter. + //! Peak interarrival jitter. + //! An estimate of the maximum jitter, excluding short small spikes. //! Calculated based on a sliding window. //! @note //! Available only on receiver. //! Calculated by rtp::LinkMeter. - core::nanoseconds_t max_jitter; - - //! Running min of jitter. - //! Calculated based on a sliding window. - //! @note - //! Available only on receiver. - //! Calculated by rtp::LinkMeter. - core::nanoseconds_t min_jitter; + core::nanoseconds_t peak_jitter; //! Estimated round-trip time between sender and receiver. //! Calculated based on NTP-like timestamp exchange implemented by RTCP protocol. @@ -91,9 +85,8 @@ struct LinkMetrics { , ext_last_seqnum(0) , expected_packets(0) , lost_packets(0) - , jitter(0) - , max_jitter(0) - , min_jitter(0) + , mean_jitter(0) + , peak_jitter(0) , rtt(0) { } }; diff --git a/src/internal_modules/roc_pipeline/config.cpp b/src/internal_modules/roc_pipeline/config.cpp index 9594cedae..112b3ec64 100644 --- a/src/internal_modules/roc_pipeline/config.cpp +++ b/src/internal_modules/roc_pipeline/config.cpp @@ -29,10 +29,6 @@ bool SenderSinkConfig::deduce_defaults(audio::ProcessorMap& processor_map) { return false; } - if (!link_meter.deduce_defaults(latency.tuner_profile)) { - return false; - } - if (!freq_est.deduce_defaults(latency.tuner_profile)) { return false; } @@ -82,7 +78,7 @@ bool ReceiverSessionConfig::deduce_defaults(audio::ProcessorMap& processor_map) return false; } - if (!link_meter.deduce_defaults(latency.tuner_profile)) { + if (!jitter.deduce_defaults(latency.tuner_profile)) { return false; } diff --git a/src/internal_modules/roc_pipeline/config.h b/src/internal_modules/roc_pipeline/config.h index bb79b32ee..688f078c4 100644 --- a/src/internal_modules/roc_pipeline/config.h +++ b/src/internal_modules/roc_pipeline/config.h @@ -14,6 +14,7 @@ #include "roc_address/protocol.h" #include "roc_audio/feedback_monitor.h" +#include "roc_audio/jitter_meter.h" #include "roc_audio/latency_config.h" #include "roc_audio/plc_config.h" #include "roc_audio/profiler.h" @@ -31,7 +32,6 @@ #include "roc_pipeline/pipeline_loop.h" #include "roc_rtcp/config.h" #include "roc_rtp/filter.h" -#include "roc_rtp/link_meter.h" namespace roc { namespace pipeline { @@ -84,9 +84,6 @@ struct SenderSinkConfig { //! Latency parameters. audio::LatencyConfig latency; - //! Link meter parameters. - rtp::LinkMeterConfig link_meter; - //! Freq estimator parameters. audio::FreqEstimatorConfig freq_est; @@ -180,8 +177,8 @@ struct ReceiverSessionConfig { //! Latency parameters. audio::LatencyConfig latency; - //! Link meter parameters. - rtp::LinkMeterConfig link_meter; + //! Jitter meter parameters. + audio::JitterConfig jitter; //! Freq estimator parameters. audio::FreqEstimatorConfig freq_est; diff --git a/src/internal_modules/roc_pipeline/receiver_session.cpp b/src/internal_modules/roc_pipeline/receiver_session.cpp index 3c887f9c7..bd19fa17d 100644 --- a/src/internal_modules/roc_pipeline/receiver_session.cpp +++ b/src/internal_modules/roc_pipeline/receiver_session.cpp @@ -54,7 +54,7 @@ ReceiverSession::ReceiverSession(const ReceiverSessionConfig& session_config, pkt_writer = source_queue_.get(); source_meter_.reset(new (source_meter_) rtp::LinkMeter( - *pkt_writer, session_config.link_meter, encoding_map, arena, dumper_)); + *pkt_writer, session_config.jitter, encoding_map, arena, dumper_)); if ((init_status_ = source_meter_->init_status()) != status::StatusOK) { return; } @@ -110,7 +110,7 @@ ReceiverSession::ReceiverSession(const ReceiverSessionConfig& session_config, repair_pkt_writer = repair_queue_.get(); repair_meter_.reset(new (repair_meter_) rtp::LinkMeter( - *repair_pkt_writer, session_config.link_meter, encoding_map, arena, dumper_)); + *repair_pkt_writer, session_config.jitter, encoding_map, arena, dumper_)); if ((init_status_ = repair_meter_->init_status()) != status::StatusOK) { return; } @@ -388,7 +388,7 @@ void ReceiverSession::generate_reports(const char* report_cname, report.ext_last_seqnum = link_metrics.ext_last_seqnum; report.packet_count = link_metrics.expected_packets; report.cum_loss = link_metrics.lost_packets; - report.jitter = link_metrics.jitter; + report.jitter = link_metrics.mean_jitter; report.niq_latency = latency_metrics.niq_latency; report.niq_stalling = latency_metrics.niq_stalling; report.e2e_latency = latency_metrics.e2e_latency; @@ -413,7 +413,7 @@ void ReceiverSession::generate_reports(const char* report_cname, report.ext_last_seqnum = link_metrics.ext_last_seqnum; report.packet_count = link_metrics.expected_packets; report.cum_loss = link_metrics.lost_packets; - report.jitter = link_metrics.jitter; + report.jitter = link_metrics.mean_jitter; reports++; n_reports--; diff --git a/src/internal_modules/roc_pipeline/sender_session.cpp b/src/internal_modules/roc_pipeline/sender_session.cpp index d59589b4f..02bae96db 100644 --- a/src/internal_modules/roc_pipeline/sender_session.cpp +++ b/src/internal_modules/roc_pipeline/sender_session.cpp @@ -406,7 +406,7 @@ SenderSession::notify_send_stream(packet::stream_source_t recv_source_id, link_metrics.ext_last_seqnum = recv_report.ext_last_seqnum; link_metrics.expected_packets = recv_report.packet_count; link_metrics.lost_packets = recv_report.cum_loss; - link_metrics.jitter = recv_report.jitter; + link_metrics.mean_jitter = recv_report.jitter; link_metrics.rtt = recv_report.rtt; feedback_monitor_->process_feedback(recv_source_id, latency_metrics, diff --git a/src/internal_modules/roc_rtp/link_meter.cpp b/src/internal_modules/roc_rtp/link_meter.cpp index 822980904..854e65eee 100644 --- a/src/internal_modules/roc_rtp/link_meter.cpp +++ b/src/internal_modules/roc_rtp/link_meter.cpp @@ -13,21 +13,8 @@ namespace roc { namespace rtp { -bool LinkMeterConfig::deduce_defaults(audio::LatencyTunerProfile latency_profile) { - if (sliding_window_length == 0) { - if (latency_profile == audio::LatencyTunerProfile_Responsive) { - // Responsive profile requires faster reactions to network changes. - sliding_window_length = 10000; - } else { - sliding_window_length = 30000; - } - } - - return true; -} - LinkMeter::LinkMeter(packet::IWriter& writer, - const LinkMeterConfig& config, + const audio::JitterConfig& jitter_config, const EncodingMap& encoding_map, core::IArena& arena, dbgio::CsvDumper* dumper) @@ -35,7 +22,6 @@ LinkMeter::LinkMeter(packet::IWriter& writer, , encoding_(NULL) , writer_(writer) , first_packet_(true) - , win_len_(config.sliding_window_length) , has_metrics_(false) , first_seqnum_(0) , last_seqnum_hi_(0) @@ -43,7 +29,7 @@ LinkMeter::LinkMeter(packet::IWriter& writer, , processed_packets_(0) , prev_queue_timestamp_(-1) , prev_stream_timestamp_(0) - , packet_jitter_stats_(arena, win_len_) + , jitter_meter_(jitter_config, arena) , dumper_(dumper) { } @@ -103,16 +89,23 @@ void LinkMeter::update_metrics_(const packet::Packet& packet) { if (!first_packet_) { update_jitter_(packet); - } else { - first_packet_ = false; } processed_packets_++; - prev_queue_timestamp_ = packet.udp()->queue_timestamp; - prev_stream_timestamp_ = packet.rtp()->stream_timestamp; + if (first_packet_ + || packet::stream_timestamp_gt(packet.rtp()->stream_timestamp, + prev_stream_timestamp_)) { + prev_queue_timestamp_ = packet.udp()->queue_timestamp; + prev_stream_timestamp_ = packet.rtp()->stream_timestamp; + } + first_packet_ = false; has_metrics_ = true; + + if (dumper_) { + dump_(packet); + } } void LinkMeter::update_seqnums_(const packet::Packet& packet) { @@ -159,35 +152,26 @@ void LinkMeter::update_jitter_(const packet::Packet& packet) { const core::nanoseconds_t d_s_ns = encoding_->sample_spec.stream_timestamp_delta_2_ns(d_s_ts); - packet_jitter_stats_.add(std::abs(d_enq_ns - d_s_ns)); - metrics_.jitter = (core::nanoseconds_t)packet_jitter_stats_.mov_avg(); - metrics_.max_jitter = (core::nanoseconds_t)packet_jitter_stats_.mov_max(); - metrics_.min_jitter = (core::nanoseconds_t)packet_jitter_stats_.mov_min(); + const core::nanoseconds_t jitter = std::abs(d_enq_ns - d_s_ns); + jitter_meter_.update_jitter(jitter); - if (dumper_) { - dump_(packet, d_enq_ns, d_s_ns); - } + const audio::JitterMetrics& jit_metrics = jitter_meter_.metrics(); + metrics_.mean_jitter = jit_metrics.mean_jitter; + metrics_.peak_jitter = jit_metrics.peak_jitter; } -core::nanoseconds_t rtp::LinkMeter::mean_jitter() const { - return (core::nanoseconds_t)packet_jitter_stats_.mov_avg(); -} +void LinkMeter::dump_(const packet::Packet& packet) { + const audio::JitterMetrics& jit_metrics = jitter_meter_.metrics(); -size_t LinkMeter::running_window_len() const { - return win_len_; -} - -void LinkMeter::dump_(const packet::Packet& packet, - const long d_enq_ns, - const long d_s_ns) { dbgio::CsvEntry e; e.type = 'm'; e.n_fields = 5; e.fields[0] = packet.udp()->queue_timestamp; e.fields[1] = packet.rtp()->stream_timestamp; - e.fields[2] = (double)std::abs(d_enq_ns - d_s_ns) / core::Millisecond; - e.fields[3] = packet_jitter_stats_.mov_max(); - e.fields[4] = packet_jitter_stats_.mov_min(); + e.fields[2] = (double)jit_metrics.curr_jitter / core::Millisecond; + e.fields[3] = jit_metrics.peak_jitter; + e.fields[4] = jit_metrics.curr_envelope; + dumper_->write(e); } diff --git a/src/internal_modules/roc_rtp/link_meter.h b/src/internal_modules/roc_rtp/link_meter.h index 34d3e91ce..71086a633 100644 --- a/src/internal_modules/roc_rtp/link_meter.h +++ b/src/internal_modules/roc_rtp/link_meter.h @@ -12,7 +12,7 @@ #ifndef ROC_RTP_LINK_METER_H_ #define ROC_RTP_LINK_METER_H_ -#include "roc_audio/latency_config.h" +#include "roc_audio/jitter_meter.h" #include "roc_audio/sample_spec.h" #include "roc_core/iarena.h" #include "roc_core/noncopyable.h" @@ -23,40 +23,24 @@ #include "roc_rtcp/reports.h" #include "roc_rtp/encoding.h" #include "roc_rtp/encoding_map.h" -#include "roc_stat/mov_aggregate.h" namespace roc { namespace rtp { -//! RTP link meter parameters. -struct LinkMeterConfig { - //! Number of packets we use to calculate sliding statistics. - //! @remarks - //! We calculate jitter statistics based on this last delivered packets. - size_t sliding_window_length; - - LinkMeterConfig() - : sliding_window_length(0) { - } - - //! Automatically fill missing settings. - ROC_ATTR_NODISCARD bool deduce_defaults(audio::LatencyTunerProfile latency_profile); -}; - //! RTP link meter. //! //! Computes various link metrics based on sequence of RTP packets. //! //! Inserted into pipeline as a writer, right after receiving packet, before storing //! packet in incoming queue, which allows to update metrics as soon as new packets -//! arrives, without waiting until it's read by depacketizer. +//! arrive, without waiting until it's requested by depacketizer. class LinkMeter : public packet::ILinkMeter, public packet::IWriter, public core::NonCopyable<> { public: //! Initialize. LinkMeter(packet::IWriter& writer, - const LinkMeterConfig& config, + const audio::JitterConfig& jitter_config, const EncodingMap& encoding_map, core::IArena& arena, dbgio::CsvDumper* dumper); @@ -88,19 +72,13 @@ class LinkMeter : public packet::ILinkMeter, //! Invoked early in pipeline right after the packet is received. virtual ROC_ATTR_NODISCARD status::StatusCode write(const packet::PacketPtr& packet); - //! Get recent average jitter over a running window. - core::nanoseconds_t mean_jitter() const; - - //! Window length to which metrics relate. - size_t running_window_len() const; - private: void update_metrics_(const packet::Packet& packet); void update_seqnums_(const packet::Packet& packet); void update_jitter_(const packet::Packet& packet); - void dump_(const packet::Packet& packet, const long d_enq_ns, const long d_s_ns); + void dump_(const packet::Packet& packet); const EncodingMap& encoding_map_; const Encoding* encoding_; @@ -109,9 +87,6 @@ class LinkMeter : public packet::ILinkMeter, bool first_packet_; - // Number of packets we use to calculate sliding statistics. - const size_t win_len_; - bool has_metrics_; packet::LinkMetrics metrics_; @@ -123,7 +98,7 @@ class LinkMeter : public packet::ILinkMeter, core::nanoseconds_t prev_queue_timestamp_; packet::stream_timestamp_t prev_stream_timestamp_; - stat::MovAggregate packet_jitter_stats_; + audio::JitterMeter jitter_meter_; dbgio::CsvDumper* dumper_; }; diff --git a/src/internal_modules/roc_stat/mov_aggregate.h b/src/internal_modules/roc_stat/mov_aggregate.h index e38992bc5..9318d3c4b 100644 --- a/src/internal_modules/roc_stat/mov_aggregate.h +++ b/src/internal_modules/roc_stat/mov_aggregate.h @@ -7,7 +7,7 @@ */ //! @file roc_stat/mov_aggregate.h -//! @brief Rolling window moving average, variance, minimum, and maximum. +//! @brief Rolling window average, variance, minimum, maximum. #ifndef ROC_STAT_MOV_AGGREGATE_H_ #define ROC_STAT_MOV_AGGREGATE_H_ @@ -20,7 +20,7 @@ namespace roc { namespace stat { -//! Rolling window moving average, variance, minimum, and maximum. +//! Rolling window average, variance, minimum, maximum. //! //! Efficiently implements moving average and variance based on Welford's method: //! - https://www.johndcook.com/blog/standard_deviation (incremental) diff --git a/src/internal_modules/roc_stat/mov_histogram.h b/src/internal_modules/roc_stat/mov_histogram.h index 0f6a03023..b31c3e110 100644 --- a/src/internal_modules/roc_stat/mov_histogram.h +++ b/src/internal_modules/roc_stat/mov_histogram.h @@ -7,7 +7,7 @@ */ //! @file roc_stat/mov_histogram.h -//! @brief Rolling window moving histogram. +//! @brief Rolling window histogram. #ifndef ROC_STAT_MOV_HISTOGRAM_H_ #define ROC_STAT_MOV_HISTOGRAM_H_ @@ -99,11 +99,11 @@ template class MovHistogram { T mov_quantile(const double quantile) const { roc_panic_if(!valid_); - T cap; + T cap = T(0); size_t count = 0; for (size_t bin_index = 0; bin_index < num_bins_; bin_index++) { - cap = value_range_min_ + bin_width_ * (bin_index + 1); + cap = value_range_min_ + T(bin_width_) * T(bin_index + 1); count += bins_[bin_index]; const double ratio = (double)count / ring_buffer_.size(); diff --git a/src/internal_modules/roc_stat/mov_quantile.h b/src/internal_modules/roc_stat/mov_quantile.h index 44e1129be..6ea8ffda0 100644 --- a/src/internal_modules/roc_stat/mov_quantile.h +++ b/src/internal_modules/roc_stat/mov_quantile.h @@ -7,7 +7,7 @@ */ //! @file roc_stat/mov_quantile.h -//! @brief Rolling window moving quantile. +//! @brief Rolling window quantile. #ifndef ROC_STAT_MOV_QUANTILE_H_ #define ROC_STAT_MOV_QUANTILE_H_ @@ -19,7 +19,7 @@ namespace roc { namespace stat { -//! Rolling window moving quantile. +//! Rolling window quantile. //! //! Efficiently implements moving quantile using partition heap based on approach //! described in https://aakinshin.net/posts/partitioning-heaps-quantile-estimator/. diff --git a/src/public_api/src/adapters.cpp b/src/public_api/src/adapters.cpp index 548bca242..1520c32e5 100644 --- a/src/public_api/src/adapters.cpp +++ b/src/public_api/src/adapters.cpp @@ -805,8 +805,8 @@ void link_metrics_to_user(roc_connection_metrics& out, const packet::LinkMetrics out.rtt = (unsigned long long)in.rtt; } - if (in.jitter > 0) { - out.jitter = (unsigned long long)in.jitter; + if (in.mean_jitter > 0) { + out.jitter = (unsigned long long)in.mean_jitter; } if (in.expected_packets > 0) { diff --git a/src/tests/roc_pipeline/test_helpers/control_writer.h b/src/tests/roc_pipeline/test_helpers/control_writer.h index 39eb06515..b3ddf7964 100644 --- a/src/tests/roc_pipeline/test_helpers/control_writer.h +++ b/src/tests/roc_pipeline/test_helpers/control_writer.h @@ -97,7 +97,7 @@ class ControlWriter : public core::NonCopyable<> { rr_blk.set_ssrc(remote_source_); rr_blk.set_cum_loss(link_metrics_.lost_packets); rr_blk.set_last_seqnum(link_metrics_.ext_last_seqnum); - rr_blk.set_jitter(sample_spec.ns_2_stream_timestamp(link_metrics_.jitter)); + rr_blk.set_jitter(sample_spec.ns_2_stream_timestamp(link_metrics_.mean_jitter)); rr_blk.set_last_sr(ntp_ts); rr_blk.set_delay_last_sr(0); diff --git a/src/tests/roc_pipeline/test_loopback_sink_2_source.cpp b/src/tests/roc_pipeline/test_loopback_sink_2_source.cpp index ecd030a3b..a35479b32 100644 --- a/src/tests/roc_pipeline/test_loopback_sink_2_source.cpp +++ b/src/tests/roc_pipeline/test_loopback_sink_2_source.cpp @@ -414,7 +414,7 @@ void check_metrics(ReceiverSlot& receiver, } else { CHECK(recv_party_metrics.link.lost_packets == 0); } - CHECK(recv_party_metrics.link.jitter > 0); + CHECK(recv_party_metrics.link.mean_jitter > 0); CHECK(recv_party_metrics.latency.niq_latency > 0); CHECK(recv_party_metrics.latency.niq_stalling >= 0); @@ -450,7 +450,8 @@ void check_metrics(ReceiverSlot& receiver, UNSIGNED_LONGS_EQUAL(recv_party_metrics.link.lost_packets, send_party_metrics.link.lost_packets); - CHECK(std::abs(recv_party_metrics.link.jitter - send_party_metrics.link.jitter) + CHECK(std::abs(recv_party_metrics.link.mean_jitter + - send_party_metrics.link.mean_jitter) < 1 * core::Millisecond); DOUBLES_EQUAL(recv_party_metrics.latency.niq_latency, diff --git a/src/tests/roc_pipeline/test_receiver_source.cpp b/src/tests/roc_pipeline/test_receiver_source.cpp index 5475ec21a..7ac140465 100644 --- a/src/tests/roc_pipeline/test_receiver_source.cpp +++ b/src/tests/roc_pipeline/test_receiver_source.cpp @@ -79,7 +79,7 @@ enum { ManyPackets = Latency / SamplesPerPacket * 10, ManyReports = 20, - LinkMeterWindow = ManyPackets * 10, + JitterMeterWindow = ManyPackets * 10, MaxSnJump = ManyPackets * 5, MaxTsJump = ManyPackets * 7 * SamplesPerPacket @@ -257,7 +257,7 @@ TEST_GROUP(receiver_source) { config.session_defaults.plc.backend = plc_backend; - config.session_defaults.link_meter.sliding_window_length = LinkMeterWindow; + config.session_defaults.jitter.jitter_window = JitterMeterWindow; config.common.rtcp.report_interval = ReportInterval * core::Second / SampleRate; config.common.rtcp.inactivity_timeout = ReportTimeout * core::Second / SampleRate; @@ -3402,7 +3402,7 @@ TEST(receiver_source, timestamp_mapping_remixing) { // Set high jitter, wait until latency increases and stabilizes. TEST(receiver_source, adaptive_latency_increase) { - const size_t stabilization_window = LinkMeterWindow * 5; + const size_t stabilization_window = JitterMeterWindow * 5; const core::nanoseconds_t tolerance = core::Millisecond * 5; const core::nanoseconds_t reaction = core::Second; @@ -3480,7 +3480,7 @@ TEST(receiver_source, adaptive_latency_increase) { // Set low jitter, wait until latency decreases and stabilizes. TEST(receiver_source, adaptive_latency_decrease) { - const size_t stabilization_window = LinkMeterWindow * 5; + const size_t stabilization_window = JitterMeterWindow * 5; const core::nanoseconds_t tolerance = core::Millisecond * 5; const core::nanoseconds_t reaction = core::Second; @@ -3558,7 +3558,7 @@ TEST(receiver_source, adaptive_latency_decrease) { // Adaptive latency should be bounded by max_target_latency TEST(receiver_source, adaptive_latency_upper_bound) { - const size_t stabilization_window = LinkMeterWindow * 5; + const size_t stabilization_window = JitterMeterWindow * 5; const core::nanoseconds_t tolerance = core::Millisecond * 5; const core::nanoseconds_t reaction = core::Second; @@ -3616,7 +3616,7 @@ TEST(receiver_source, adaptive_latency_upper_bound) { // Adaptive latency should be bounded by min_target_latency TEST(receiver_source, adaptive_latency_lower_bound) { - const size_t stabilization_window = LinkMeterWindow * 5; + const size_t stabilization_window = JitterMeterWindow * 5; const core::nanoseconds_t tolerance = core::Millisecond * 5; const core::nanoseconds_t reaction = core::Second; @@ -4009,7 +4009,7 @@ TEST(receiver_source, metrics_jitter) { // jitter 1 packet_writer.set_jitter(jitter1 - precision, jitter1 + precision); - for (size_t np = 0; np < LinkMeterWindow * 2; np++) { + for (size_t np = 0; np < JitterMeterWindow * 2; np++) { packet_writer.write_packets(1, SamplesPerPacket, packet_sample_spec); refresh_source(receiver, frame_reader.refresh_ts()); @@ -4027,7 +4027,7 @@ TEST(receiver_source, metrics_jitter) { UNSIGNED_LONGS_EQUAL(1, party_metrics_size); if (np > Latency / SamplesPerPacket) { - DOUBLES_EQUAL(jitter1, party_metrics.link.jitter, precision); + DOUBLES_EQUAL(jitter1, party_metrics.link.mean_jitter, precision); } } } @@ -4035,7 +4035,7 @@ TEST(receiver_source, metrics_jitter) { // jitter 2 packet_writer.set_jitter(jitter2 - precision, jitter2 + precision); - for (size_t np = 0; np < LinkMeterWindow * 2; np++) { + for (size_t np = 0; np < JitterMeterWindow * 2; np++) { packet_writer.write_packets(1, SamplesPerPacket, packet_sample_spec); refresh_source(receiver, frame_reader.refresh_ts()); @@ -4052,8 +4052,8 @@ TEST(receiver_source, metrics_jitter) { UNSIGNED_LONGS_EQUAL(1, slot_metrics.num_participants); UNSIGNED_LONGS_EQUAL(1, party_metrics_size); - if (np > LinkMeterWindow) { - DOUBLES_EQUAL(jitter2, party_metrics.link.jitter, precision); + if (np > JitterMeterWindow) { + DOUBLES_EQUAL(jitter2, party_metrics.link.mean_jitter, precision); } } } diff --git a/src/tests/roc_pipeline/test_sender_sink.cpp b/src/tests/roc_pipeline/test_sender_sink.cpp index a2b0c0fa8..43b06d3ed 100644 --- a/src/tests/roc_pipeline/test_sender_sink.cpp +++ b/src/tests/roc_pipeline/test_sender_sink.cpp @@ -650,7 +650,7 @@ TEST(sender_sink, metrics_feedback) { link_metrics.ext_last_seqnum = seed * 200; link_metrics.expected_packets = (seed * 200) - (seed * 100) + 1; link_metrics.lost_packets = (int)seed * 40; - link_metrics.jitter = (int)seed * core::Millisecond * 50; + link_metrics.mean_jitter = (int)seed * core::Millisecond * 50; audio::LatencyMetrics latency_metrics; latency_metrics.niq_latency = (int)seed * core::Millisecond * 50; @@ -688,8 +688,8 @@ TEST(sender_sink, metrics_feedback) { party_metrics[0].link.expected_packets); UNSIGNED_LONGS_EQUAL(link_metrics.lost_packets, party_metrics[0].link.lost_packets); - DOUBLES_EQUAL((double)link_metrics.jitter, - (double)party_metrics[0].link.jitter, core::Nanosecond); + DOUBLES_EQUAL((double)link_metrics.mean_jitter, + (double)party_metrics[0].link.mean_jitter, core::Nanosecond); DOUBLES_EQUAL((double)latency_metrics.niq_latency, (double)party_metrics[0].latency.niq_latency, diff --git a/src/tests/roc_rtp/test_link_meter.cpp b/src/tests/roc_rtp/test_link_meter.cpp index 756d1b789..4a9a0a654 100644 --- a/src/tests/roc_rtp/test_link_meter.cpp +++ b/src/tests/roc_rtp/test_link_meter.cpp @@ -27,7 +27,13 @@ namespace rtp { namespace { -enum { ChMask = 3, PacketSz = 100, SampleRate = 44100, Duration = 44 }; +enum { + ChMask = 3, + PacketSz = 100, + SampleRate = 44100, + Duration = 44, + RunningWindowLen = 1000 +}; core::HeapArena arena; packet::PacketFactory packet_factory(arena, PacketSz); @@ -62,9 +68,11 @@ packet::PacketPtr new_packet(packet::seqnum_t sn, return packet; } -LinkMeterConfig make_config() { - LinkMeterConfig config; - config.sliding_window_length = 10000; +audio::JitterConfig make_config() { + audio::JitterConfig config; + config.jitter_window = RunningWindowLen; + config.peak_quantile_window = RunningWindowLen / 5; + config.envelope_resistance_coeff = 0.1; return config; } @@ -161,7 +169,6 @@ TEST(link_meter, jitter_test) { packet::FifoQueue queue; LinkMeter meter(queue, make_config(), encoding_map, arena, NULL); - const ssize_t running_win_len = (ssize_t)meter.running_window_len(); const size_t num_packets = Duration * 100; core::nanoseconds_t ts_store[num_packets]; @@ -177,36 +184,34 @@ TEST(link_meter, jitter_test) { qts += qts_step + jitter_ns; sts += sts_step; - if (i > (size_t)running_win_len) { + if (i > RunningWindowLen) { // Check meter metrics running max in min jitter in last Duration number // of packets in ts_store. - core::nanoseconds_t min_jitter = core::Second; - core::nanoseconds_t max_jitter = 0; - for (size_t j = 0; j < (size_t)running_win_len; j++) { + core::nanoseconds_t peak_jitter = 0; + for (size_t j = 0; j < RunningWindowLen; j++) { core::nanoseconds_t jitter = std::abs(ts_store[i - j] - ts_store[i - j - 1] - qts_step); - min_jitter = std::min(min_jitter, jitter); - max_jitter = std::max(max_jitter, jitter); + peak_jitter = std::max(peak_jitter, jitter); } - UNSIGNED_LONGS_EQUAL(min_jitter, meter.metrics().min_jitter); - UNSIGNED_LONGS_EQUAL(max_jitter, meter.metrics().max_jitter); + DOUBLES_EQUAL(peak_jitter, meter.metrics().peak_jitter, + core::Millisecond * 3); // Reference average and variance of jitter from ts_store values. core::nanoseconds_t sum = 0; - for (size_t j = 0; j < (size_t)running_win_len; j++) { + for (size_t j = 0; j < RunningWindowLen; j++) { sum += std::abs(ts_store[i - j] - ts_store[i - j - 1] - qts_step); } - const core::nanoseconds_t mean = sum / running_win_len; + const core::nanoseconds_t mean = sum / RunningWindowLen; sum = 0; - for (size_t j = 0; j < (size_t)running_win_len; j++) { + for (size_t j = 0; j < RunningWindowLen; j++) { core::nanoseconds_t jitter = std::abs(ts_store[i - j] - ts_store[i - j - 1] - qts_step); sum += (jitter - mean) * (jitter - mean); } // Check the jitter value - DOUBLES_EQUAL(mean, meter.mean_jitter(), core::Microsecond * 1); + DOUBLES_EQUAL(mean, meter.metrics().mean_jitter, core::Microsecond * 1); } } } @@ -214,7 +219,6 @@ TEST(link_meter, jitter_test) { TEST(link_meter, ascending_test) { packet::FifoQueue queue; LinkMeter meter(queue, make_config(), encoding_map, arena, NULL); - const ssize_t running_win_len = (ssize_t)meter.running_window_len(); const size_t num_packets = Duration * 100; core::nanoseconds_t ts_store[num_packets]; @@ -231,19 +235,17 @@ TEST(link_meter, ascending_test) { qts += qts_step + (core::nanoseconds_t)i * core::Microsecond; sts += sts_step; - if (i > (size_t)running_win_len) { + if (i > RunningWindowLen) { // Check meter metrics running max in min jitter in last Duration number // of packets in ts_store. - core::nanoseconds_t min_jitter = core::Second; - core::nanoseconds_t max_jitter = 0; - for (size_t j = 0; j < (size_t)running_win_len; j++) { + core::nanoseconds_t peak_jitter = 0; + for (size_t j = 0; j < RunningWindowLen; j++) { core::nanoseconds_t jitter = std::abs(ts_store[i - j] - ts_store[i - j - 1] - qts_step); - min_jitter = std::min(min_jitter, jitter); - max_jitter = std::max(max_jitter, jitter); + peak_jitter = std::max(peak_jitter, jitter); } - UNSIGNED_LONGS_EQUAL(min_jitter, meter.metrics().min_jitter); - UNSIGNED_LONGS_EQUAL(max_jitter, meter.metrics().max_jitter); + DOUBLES_EQUAL(peak_jitter, meter.metrics().peak_jitter, + core::Millisecond * 3); } } } @@ -251,7 +253,6 @@ TEST(link_meter, ascending_test) { TEST(link_meter, descending_test) { packet::FifoQueue queue; LinkMeter meter(queue, make_config(), encoding_map, arena, NULL); - const ssize_t RunningWinLen = (ssize_t)meter.running_window_len(); const size_t num_packets = Duration * 100; core::nanoseconds_t ts_store[num_packets]; @@ -268,19 +269,17 @@ TEST(link_meter, descending_test) { qts += qts_step - (core::nanoseconds_t)i * core::Nanosecond * 10; sts += sts_step; - if (i > (size_t)RunningWinLen) { + if (i > RunningWindowLen) { // Check meter metrics running max in min jitter in last Duration number // of packets in ts_store. - core::nanoseconds_t min_jitter = core::Second; - core::nanoseconds_t max_jitter = 0; - for (size_t j = 0; j < (size_t)RunningWinLen; j++) { + core::nanoseconds_t peak_jitter = 0; + for (size_t j = 0; j < RunningWindowLen; j++) { core::nanoseconds_t jitter = std::abs(ts_store[i - j] - ts_store[i - j - 1] - qts_step); - min_jitter = std::min(min_jitter, jitter); - max_jitter = std::max(max_jitter, jitter); + peak_jitter = std::max(peak_jitter, jitter); } - UNSIGNED_LONGS_EQUAL(min_jitter, meter.metrics().min_jitter); - UNSIGNED_LONGS_EQUAL(max_jitter, meter.metrics().max_jitter); + DOUBLES_EQUAL(peak_jitter, meter.metrics().peak_jitter, + core::Millisecond * 3); } } } @@ -288,7 +287,6 @@ TEST(link_meter, descending_test) { TEST(link_meter, saw_test) { packet::FifoQueue queue; LinkMeter meter(queue, make_config(), encoding_map, arena, NULL); - const ssize_t running_win_len = (ssize_t)meter.running_window_len(); const size_t num_packets = Duration * 100; core::nanoseconds_t ts_store[num_packets]; @@ -305,23 +303,21 @@ TEST(link_meter, saw_test) { qts += step_ts_; sts += sts_step; step_ts_ += step_ts_inc; - if (i > 0 && i % (size_t)running_win_len == 0) { + if (i > 0 && i % RunningWindowLen == 0) { step_ts_inc = -step_ts_inc; } - if (i > (size_t)running_win_len) { + if (i > RunningWindowLen) { // Check meter metrics running max in min jitter in last Duration number // of packets in ts_store. - core::nanoseconds_t min_jitter = core::Second; - core::nanoseconds_t max_jitter = 0; - for (size_t j = 0; j < (size_t)running_win_len; j++) { + core::nanoseconds_t peak_jitter = 0; + for (size_t j = 0; j < RunningWindowLen; j++) { core::nanoseconds_t jitter = std::abs(ts_store[i - j] - ts_store[i - j - 1] - qts_step); - min_jitter = std::min(min_jitter, jitter); - max_jitter = std::max(max_jitter, jitter); + peak_jitter = std::max(peak_jitter, jitter); } - UNSIGNED_LONGS_EQUAL(min_jitter, meter.metrics().min_jitter); - UNSIGNED_LONGS_EQUAL(max_jitter, meter.metrics().max_jitter); + DOUBLES_EQUAL(peak_jitter, meter.metrics().peak_jitter, + core::Millisecond * 3); } } }