From 40c9089727c8777cd502eb4de08c2c910f2b2e3f Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Wed, 31 Jul 2024 20:53:26 +0400 Subject: [PATCH] [api] gh-688: Refine dynamic latency configuration Latency parameters: - target_latency (0 = adaptive, >0 = fixed) - latency_tolerance for adaptive mode only: - start_target_latency - min_target_latency - max_target_latency On receiver, you always need to set latency parameters, if profile is INTACT or not. Receiver always checks latency tolerance violations and always needs starting value for the latency. On sender, if profile is INTACT, all latency parameters should be zero. Otherwise, all latency parameters should match same parameters on receiver. Other changes: - fixes in adapters - deduce_defaults updated to new rules - latency tuner updated to new rules - add panics to latency tuner - comments are updated --- .../roc_audio/latency_config.cpp | 179 ++++++------ .../roc_audio/latency_config.h | 20 +- .../roc_audio/latency_tuner.cpp | 63 +++-- .../roc_audio/latency_tuner.h | 14 +- .../roc_pipeline/receiver_session.cpp | 2 +- src/public_api/include/roc/config.h | 264 ++++++++---------- src/public_api/src/adapters.cpp | 103 ++++--- .../test_loopback_sender_2_receiver.cpp | 6 +- src/tools/roc_recv/main.cpp | 16 +- 9 files changed, 321 insertions(+), 346 deletions(-) diff --git a/src/internal_modules/roc_audio/latency_config.cpp b/src/internal_modules/roc_audio/latency_config.cpp index 5ac0a1d27..8d260038f 100644 --- a/src/internal_modules/roc_audio/latency_config.cpp +++ b/src/internal_modules/roc_audio/latency_config.cpp @@ -17,14 +17,13 @@ namespace { LatencyTunerProfile deduce_tuner_profile(const LatencyTunerBackend tuner_backend, const core::nanoseconds_t target_latency, - const core::nanoseconds_t start_latency, + const core::nanoseconds_t start_target_latency, const bool is_adaptive, const bool is_receiver) { if (is_receiver) { if (tuner_backend == LatencyTunerBackend_Niq) { - // Use start_latency in adaptive mode or target_latency in fixed mode. const core::nanoseconds_t configured_latency = - is_adaptive ? start_latency : target_latency; + is_adaptive ? start_target_latency : target_latency; // If latency is low, we assume network jitter is also low. In this // case we use responsive profile. Gradual profile could cause @@ -48,38 +47,39 @@ LatencyTunerProfile deduce_tuner_profile(const LatencyTunerBackend tuner_backend } } -core::nanoseconds_t deduce_start_latency(const core::nanoseconds_t min_latency, - const core::nanoseconds_t max_latency, - const core::nanoseconds_t default_latency) { - if (min_latency != 0 || max_latency != 0) { +core::nanoseconds_t +deduce_start_target_latency(const core::nanoseconds_t min_target_latency, + const core::nanoseconds_t max_target_latency, + const core::nanoseconds_t default_latency) { + if (min_target_latency != 0 || max_target_latency != 0) { // If min and max latency are provided explicitly, start in the middle. - return min_latency + (max_latency - min_latency) / 2; + return min_target_latency + (max_target_latency - min_target_latency) / 2; } else { // Otherwise start from default value. return default_latency; } } -void deduce_min_max_latency(const core::nanoseconds_t start_latency, - core::nanoseconds_t& min_latency, - core::nanoseconds_t& max_latency) { +void deduce_min_max_target_latency(const core::nanoseconds_t start_target_latency, + core::nanoseconds_t& min_target_latency, + core::nanoseconds_t& max_target_latency) { // By default, allow wide range for latency tuning. - min_latency = 0; - max_latency = start_latency * 5; + min_target_latency = std::min(core::Millisecond * 15, start_target_latency / 5); + max_target_latency = start_target_latency * 5; } -core::nanoseconds_t deduce_latency_tolerance(const core::nanoseconds_t target_latency, - const core::nanoseconds_t start_latency, - const bool is_adaptive, - const bool is_receiver) { +core::nanoseconds_t +deduce_latency_tolerance(const core::nanoseconds_t target_latency, + const core::nanoseconds_t start_target_latency, + const bool is_adaptive, + const bool is_receiver) { // On sender, apply multiplier to make default tolerance a bit higher than // on receiver. This way, if bounding is enabled on both sides, receiver // will always trigger first. const int multiplier = is_receiver ? 1 : 4; - // Use start_latency in adaptive mode or target_latency in fixed mode. const core::nanoseconds_t configured_latency = - is_adaptive ? start_latency : target_latency; + is_adaptive ? start_target_latency : target_latency; // Out formula doesn't work well on latencies close to zero. const core::nanoseconds_t floored_latency = @@ -104,7 +104,7 @@ core::nanoseconds_t deduce_stale_tolerance(const core::nanoseconds_t latency_tol return std::max(latency_tolerance / 4, 10 * core::Millisecond); } -size_t deduce_sliding_window(const LatencyTunerProfile tuner_profile) { +size_t deduce_sliding_window_len(const LatencyTunerProfile tuner_profile) { if (tuner_profile == audio::LatencyTunerProfile_Responsive) { // Responsive profile requires faster reactions to changes // of link characteristics. @@ -116,9 +116,9 @@ size_t deduce_sliding_window(const LatencyTunerProfile tuner_profile) { bool validate_adaptive_latency(const core::nanoseconds_t target_latency, const core::nanoseconds_t latency_tolerance, - const core::nanoseconds_t start_latency, - const core::nanoseconds_t min_latency, - const core::nanoseconds_t max_latency) { + const core::nanoseconds_t start_target_latency, + const core::nanoseconds_t min_target_latency, + const core::nanoseconds_t max_target_latency) { roc_panic_if(target_latency != 0); if (latency_tolerance < 0) { @@ -126,25 +126,29 @@ bool validate_adaptive_latency(const core::nanoseconds_t target_latency, return false; } - if (start_latency < 0) { - roc_log(LogError, "latency config: start_latency must be >= 0"); + if (start_target_latency < 0) { + roc_log(LogError, "latency config: start_target_latency must be >= 0"); return false; } - if (min_latency != 0 || max_latency != 0) { - if (min_latency < 0 || max_latency < 0) { - roc_log(LogError, "latency config: min_latency and max_latency must be >= 0"); + if (min_target_latency != 0 || max_target_latency != 0) { + if (min_target_latency < 0 || max_target_latency < 0) { + roc_log( + LogError, + "latency config: min_target_latency and max_target_latency must be >= 0"); return false; } - if (min_latency > max_latency) { - roc_log(LogError, "latency config: min_latency must be <= max_latency"); + if (min_target_latency > max_target_latency) { + roc_log(LogError, + "latency config: min_target_latency must be <= max_target_latency"); return false; } - if (start_latency != 0 - && (start_latency < min_latency || start_latency > max_latency)) { - roc_log( - LogError, - "latency config: start_latency must be in [min_latency; max_latency]"); + if (start_target_latency != 0 + && (start_target_latency < min_target_latency + || start_target_latency > max_target_latency)) { + roc_log(LogError, + "latency config: start_target_latency must be in range" + " [min_target_latency; max_target_latency]"); return false; } } @@ -154,9 +158,9 @@ bool validate_adaptive_latency(const core::nanoseconds_t target_latency, bool validate_fixed_latency(const core::nanoseconds_t target_latency, const core::nanoseconds_t latency_tolerance, - const core::nanoseconds_t start_latency, - const core::nanoseconds_t min_latency, - const core::nanoseconds_t max_latency) { + const core::nanoseconds_t start_target_latency, + const core::nanoseconds_t min_target_latency, + const core::nanoseconds_t max_target_latency) { roc_panic_if(target_latency == 0); if (target_latency < 0) { @@ -169,38 +173,30 @@ bool validate_fixed_latency(const core::nanoseconds_t target_latency, return false; } - if (start_latency != 0 || min_latency != 0 || max_latency != 0) { - roc_log(LogError, - "latency config: start_latency, min_latency, max_latency" - " may be used only when adaptive latency is enabled" - " (i.e. target_latency == 0)"); + if (start_target_latency != 0 || min_target_latency != 0 || max_target_latency != 0) { + roc_log( + LogError, + "latency config: start_target_latency, min_target_latency, max_target_latency" + " may be used only when adaptive latency is enabled" + " (i.e. target_latency == 0)"); return false; } return true; } -bool validate_intact_latency(const core::nanoseconds_t target_latency, - const core::nanoseconds_t latency_tolerance, - const core::nanoseconds_t start_latency, - const core::nanoseconds_t min_latency, - const core::nanoseconds_t max_latency) { - if (target_latency < 0) { - roc_log(LogError, "latency config: target_latency must be >= 0"); - return false; - } - - if (latency_tolerance < 0) { - roc_log(LogError, "latency config: latency_tolerance must be >= 0"); - return false; - } - - if (start_latency != 0 || min_latency != 0 || max_latency != 0) { +bool validate_no_latency(const core::nanoseconds_t target_latency, + const core::nanoseconds_t latency_tolerance, + const core::nanoseconds_t start_target_latency, + const core::nanoseconds_t min_target_latency, + const core::nanoseconds_t max_target_latency) { + if (target_latency != 0 || latency_tolerance != 0 || start_target_latency != 0 + || min_target_latency != 0 || max_target_latency != 0) { roc_log(LogError, "latency config:" - " start_latency, min_latency, max_latency" - " may be used only when latency tuning is enabled" - " (i.e. latency profile is not \"intact\")"); + " on sender, target_latency, latency_tolerance," + " start_target_latency, min_target_latency, max_target_latency" + " aren't used and must be zero if latency profile is \"intact\""); return false; } @@ -211,7 +207,7 @@ bool validate_intact_latency(const core::nanoseconds_t target_latency, bool LatencyConfig::deduce_defaults(core::nanoseconds_t default_latency, bool is_receiver) { - // Adaptive latency mode. + // Whether we're using adaptive latency mode. const bool is_adaptive = target_latency == 0; if (tuner_backend == LatencyTunerBackend_Default) { @@ -219,63 +215,60 @@ bool LatencyConfig::deduce_defaults(core::nanoseconds_t default_latency, } if (tuner_profile == LatencyTunerProfile_Default) { - tuner_profile = deduce_tuner_profile(tuner_backend, target_latency, start_latency, - is_adaptive, is_receiver); + tuner_profile = + deduce_tuner_profile(tuner_backend, target_latency, start_target_latency, + is_adaptive, is_receiver); } - if (tuner_profile != LatencyTunerProfile_Intact) { - // If latency tuning and bounds checking are enabled. + // On receiver, we always need to know latency parameters, no matter who is doing + // latency adjustment, receiver or sender. + // On sender, we need latency parameters only if sender is doing latency adjustment + // (latency profile is not "intact"). + const bool want_latency_params = + is_receiver || tuner_profile != LatencyTunerProfile_Intact; + + if (want_latency_params) { if (is_adaptive) { if (!validate_adaptive_latency(target_latency, latency_tolerance, - start_latency, min_latency, max_latency)) { + start_target_latency, min_target_latency, + max_target_latency)) { return false; } - if (start_latency == 0) { - start_latency = - deduce_start_latency(min_latency, max_latency, default_latency); + if (start_target_latency == 0) { + start_target_latency = deduce_start_target_latency( + min_target_latency, max_target_latency, default_latency); } - if (min_latency == 0 && max_latency == 0) { - deduce_min_max_latency(start_latency, min_latency, max_latency); + if (min_target_latency == 0 && max_target_latency == 0) { + deduce_min_max_target_latency(start_target_latency, min_target_latency, + max_target_latency); } } else { - if (!validate_fixed_latency(target_latency, latency_tolerance, start_latency, - min_latency, max_latency)) { + if (!validate_fixed_latency(target_latency, latency_tolerance, + start_target_latency, min_target_latency, + max_target_latency)) { return false; } } if (latency_tolerance == 0) { - latency_tolerance = deduce_latency_tolerance(target_latency, start_latency, - is_adaptive, is_receiver); + latency_tolerance = deduce_latency_tolerance( + target_latency, start_target_latency, is_adaptive, is_receiver); } if (stale_tolerance == 0) { stale_tolerance = deduce_stale_tolerance(latency_tolerance); } } else { - // If latency tuning is disabled. - if (!validate_intact_latency(target_latency, latency_tolerance, start_latency, - min_latency, max_latency)) { + if (!validate_no_latency(target_latency, latency_tolerance, start_target_latency, + min_target_latency, max_target_latency)) { return false; } - - if (target_latency != 0) { - // If bounds checking is enabled. - if (latency_tolerance == 0) { - latency_tolerance = deduce_latency_tolerance( - target_latency, start_latency, is_adaptive, is_receiver); - } - - if (stale_tolerance == 0) { - stale_tolerance = deduce_stale_tolerance(latency_tolerance); - } - } } if (sliding_window_length == 0) { - sliding_window_length = deduce_sliding_window(tuner_profile); + sliding_window_length = deduce_sliding_window_len(tuner_profile); } return true; diff --git a/src/internal_modules/roc_audio/latency_config.h b/src/internal_modules/roc_audio/latency_config.h index 6932db863..00e59e8db 100644 --- a/src/internal_modules/roc_audio/latency_config.h +++ b/src/internal_modules/roc_audio/latency_config.h @@ -37,20 +37,20 @@ enum LatencyTunerBackend { }; //! Latency tuner profile. -//! Defines whether and how we tune latency on fly to compensate clock +//! Defines whether and how we adjust latency on fly to compensate clock //! drift and jitter. enum LatencyTunerProfile { //! Deduce best default for given settings. LatencyTunerProfile_Default, - //! Do not tune latency. + //! Do not adjust latency. LatencyTunerProfile_Intact, - //! Fast and responsive tuning. + //! Fast and responsive adjustment. //! Good for lower network latency and jitter. LatencyTunerProfile_Responsive, - //! Slow and smooth tuning. + //! Slow and smooth adjustment. //! Good for higher network latency and jitter. LatencyTunerProfile_Gradual }; @@ -96,7 +96,7 @@ struct LatencyConfig { //! @note //! If zero, default value is used. //! Negative value is an error. - core::nanoseconds_t start_latency; + core::nanoseconds_t start_target_latency; //! Minimum latency for adaptive mode. //! @remarks @@ -105,7 +105,7 @@ struct LatencyConfig { //! Can be used only in adaptive latency mode. //! @note //! If both min_latency and max_latency are zero, defaults are used. - core::nanoseconds_t min_latency; + core::nanoseconds_t min_target_latency; //! Maximum latency for adaptive mode. //! @remarks @@ -114,7 +114,7 @@ struct LatencyConfig { //! Can be used only in adaptive latency mode. //! @note //! If both min_latency and max_latency are zero, defaults are used. - core::nanoseconds_t max_latency; + core::nanoseconds_t max_target_latency; //! Maximum delay since last packet before queue is considered stalling. //! @remarks @@ -176,9 +176,9 @@ struct LatencyConfig { , tuner_profile(LatencyTunerProfile_Default) , target_latency(0) , latency_tolerance(0) - , start_latency(0) - , min_latency(0) - , max_latency(0) + , start_target_latency(0) + , min_target_latency(0) + , max_target_latency(0) , stale_tolerance(0) , scaling_interval(5 * core::Millisecond) , scaling_tolerance(0.005f) diff --git a/src/internal_modules/roc_audio/latency_tuner.cpp b/src/internal_modules/roc_audio/latency_tuner.cpp index d3e5235b3..18572cafe 100644 --- a/src/internal_modules/roc_audio/latency_tuner.cpp +++ b/src/internal_modules/roc_audio/latency_tuner.cpp @@ -46,9 +46,11 @@ LatencyTuner::LatencyTuner(const LatencyConfig& config, , freq_coeff_max_delta_(config.scaling_tolerance) , backend_(config.tuner_backend) , profile_(config.tuner_profile) - , enable_tuning_(config.tuner_profile != LatencyTunerProfile_Intact) - , enable_adaptive_tuning_(enable_tuning_ && config.target_latency == 0) - , enable_bounds_checks_(enable_tuning_ || config.target_latency != 0) + , enable_latency_adjustment_(config.tuner_profile != LatencyTunerProfile_Intact) + , enable_tolerance_checks_(config.tuner_profile != LatencyTunerProfile_Intact + || config.target_latency != 0 + || config.start_target_latency != 0) + , latency_is_adaptive_(config.target_latency == 0) , has_niq_latency_(false) , niq_latency_(0) , niq_stalling_(0) @@ -87,21 +89,21 @@ LatencyTuner::LatencyTuner(const LatencyConfig& config, // backend, profile, tuning latency_tuner_backend_to_str(backend_), latency_tuner_profile_to_str(profile_), - enable_tuning_ && enable_adaptive_tuning_ ? "adaptive" - : enable_tuning_ ? "fixed" - : "disabled", + enable_latency_adjustment_ && latency_is_adaptive_ ? "adaptive" + : enable_latency_adjustment_ ? "fixed" + : "disabled", // target_latency, latency_tolerance (long)sample_spec_.ns_2_stream_timestamp_delta(config.target_latency), (double)config.target_latency / core::Millisecond, (long)sample_spec_.ns_2_stream_timestamp_delta(config.latency_tolerance), (double)config.latency_tolerance / core::Millisecond, // start_latency, min_latency, max_latency - (long)sample_spec_.ns_2_stream_timestamp_delta(config.start_latency), - (double)config.start_latency / core::Millisecond, - (long)sample_spec_.ns_2_stream_timestamp_delta(config.min_latency), - (double)config.min_latency / core::Millisecond, - (long)sample_spec_.ns_2_stream_timestamp_delta(config.max_latency), - (double)config.max_latency / core::Millisecond, + (long)sample_spec_.ns_2_stream_timestamp_delta(config.start_target_latency), + (double)config.start_target_latency / core::Millisecond, + (long)sample_spec_.ns_2_stream_timestamp_delta(config.min_target_latency), + (double)config.min_target_latency / core::Millisecond, + (long)sample_spec_.ns_2_stream_timestamp_delta(config.max_target_latency), + (double)config.max_target_latency / core::Millisecond, // stale_tolerance (long)sample_spec_.ns_2_stream_timestamp_delta(config.stale_tolerance), (double)config.stale_tolerance / core::Millisecond, @@ -110,24 +112,30 @@ LatencyTuner::LatencyTuner(const LatencyConfig& config, (double)config.scaling_interval / core::Millisecond, (double)config.scaling_tolerance); - if (enable_tuning_ || enable_bounds_checks_) { - if (enable_adaptive_tuning_) { - roc_panic_if(config.target_latency != 0); + if (enable_latency_adjustment_ || enable_tolerance_checks_) { + if (latency_is_adaptive_) { + roc_panic_if_msg( + config.target_latency != 0 || config.start_target_latency <= 0 + || config.min_target_latency < 0 || config.max_target_latency <= 0, + "latency tuner: invalid configuration"); cur_target_latency_ = - sample_spec_.ns_2_stream_timestamp_delta(config.start_latency); + sample_spec_.ns_2_stream_timestamp_delta(config.start_target_latency); min_target_latency_ = - sample_spec_.ns_2_stream_timestamp_delta(config.min_latency); + sample_spec_.ns_2_stream_timestamp_delta(config.min_target_latency); max_target_latency_ = - sample_spec_.ns_2_stream_timestamp_delta(config.max_latency); + sample_spec_.ns_2_stream_timestamp_delta(config.max_target_latency); min_actual_latency_ = sample_spec_.ns_2_stream_timestamp_delta( - config.min_latency - config.latency_tolerance); + config.min_target_latency - config.latency_tolerance); max_actual_latency_ = sample_spec_.ns_2_stream_timestamp_delta( - config.max_latency + config.latency_tolerance); + config.max_target_latency + config.latency_tolerance); } else { - roc_panic_if(config.target_latency == 0); + roc_panic_if_msg( + config.target_latency <= 0 || config.start_target_latency != 0 + || config.min_target_latency != 0 || config.max_target_latency != 0, + "latency tuner: invalid configuration"); cur_target_latency_ = sample_spec_.ns_2_stream_timestamp_delta(config.target_latency); @@ -138,7 +146,7 @@ LatencyTuner::LatencyTuner(const LatencyConfig& config, config.target_latency + config.latency_tolerance); } - if (enable_tuning_) { + if (enable_latency_adjustment_) { fe_.reset(new (fe_) FreqEstimator( profile_ == LatencyTunerProfile_Responsive ? FreqEstimatorProfile_Responsive @@ -182,7 +190,7 @@ void LatencyTuner::write_metrics(const LatencyMetrics& latency_metrics, bool LatencyTuner::update_stream() { roc_panic_if(init_status_ != status::StatusOK); - if (enable_adaptive_tuning_ && has_metrics_) { + if (enable_latency_adjustment_ && latency_is_adaptive_ && has_metrics_) { update_target_latency_(link_metrics_.max_jitter, link_metrics_.jitter, latency_metrics_.fec_block_duration); } @@ -192,13 +200,13 @@ bool LatencyTuner::update_stream() { return true; } - if (enable_bounds_checks_) { - if (!check_bounds_(actual_latency)) { + if (enable_tolerance_checks_) { + if (!check_actual_latency_(actual_latency)) { return false; } } - if (enable_tuning_) { + if (enable_latency_adjustment_) { compute_scaling_(actual_latency); } @@ -251,7 +259,8 @@ bool LatencyTuner::measure_actual_latency_(packet::stream_timestamp_diff_t& resu roc_panic("latency tuner: invalid backend: id=%d", (int)backend_); } -bool LatencyTuner::check_bounds_(const packet::stream_timestamp_diff_t actual_latency) { +bool LatencyTuner::check_actual_latency_( + const packet::stream_timestamp_diff_t actual_latency) { // Queue is considered "stalling" if there were no new packets for // some period of time. const bool is_stalling = backend_ == LatencyTunerBackend_Niq diff --git a/src/internal_modules/roc_audio/latency_tuner.h b/src/internal_modules/roc_audio/latency_tuner.h index 2d45ad2bb..11bfbea86 100644 --- a/src/internal_modules/roc_audio/latency_tuner.h +++ b/src/internal_modules/roc_audio/latency_tuner.h @@ -86,7 +86,7 @@ class LatencyTuner : public core::NonCopyable<> { private: bool measure_actual_latency_(packet::stream_timestamp_diff_t& result); - bool check_bounds_(packet::stream_timestamp_diff_t latency); + 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, @@ -119,9 +119,15 @@ class LatencyTuner : public core::NonCopyable<> { const LatencyTunerBackend backend_; const LatencyTunerProfile profile_; - const bool enable_tuning_; - const bool enable_adaptive_tuning_; - const bool enable_bounds_checks_; + // True if we should actively adjust clock speed using resampler scaling. + // Either sender or receiver does it. + const bool enable_latency_adjustment_; + // True if we should check that deviation from target doesn't exceed limit. + // Receiver always does it, sender does it only if latency adjustment is on sender. + const bool enable_tolerance_checks_; + // True if adaptive latency mode is used (target latency is zero). + // This flag doesn't depend on who is doing latency adjustment. + const bool latency_is_adaptive_; bool has_niq_latency_; packet::stream_timestamp_diff_t niq_latency_; diff --git a/src/internal_modules/roc_pipeline/receiver_session.cpp b/src/internal_modules/roc_pipeline/receiver_session.cpp index acabd80f6..bde79a8af 100644 --- a/src/internal_modules/roc_pipeline/receiver_session.cpp +++ b/src/internal_modules/roc_pipeline/receiver_session.cpp @@ -91,7 +91,7 @@ ReceiverSession::ReceiverSession(const ReceiverSessionConfig& session_config, delayed_reader_.reset(new (delayed_reader_) packet::DelayedReader( *pkt_reader, session_config.latency.target_latency != 0 ? session_config.latency.target_latency - : session_config.latency.start_latency, + : session_config.latency.start_target_latency, pkt_encoding->sample_spec)); if ((init_status_ = delayed_reader_->init_status()) != status::StatusOK) { return; diff --git a/src/public_api/include/roc/config.h b/src/public_api/include/roc/config.h index fbe488b49..d06a1a385 100644 --- a/src/public_api/include/roc/config.h +++ b/src/public_api/include/roc/config.h @@ -416,7 +416,7 @@ typedef enum roc_clock_source { } roc_clock_source; /** Latency tuner backend. - * Defines which latency is monitored and tuned by latency tuner. + * Defines which latency is monitored and adjusted by latency tuner. */ typedef enum roc_latency_tuner_backend { /** Default backend. @@ -443,7 +443,7 @@ typedef enum roc_latency_tuner_backend { * * Cons: * - synchronizes only clock speed, but not position; different receivers will - * have different (constant) delays + * have different (constant, on average) delays * - affected by network jitter; spikes in packet delivery will cause slow * oscillations in clock speed */ @@ -451,36 +451,36 @@ typedef enum roc_latency_tuner_backend { } roc_latency_tuner_backend; /** Latency tuner profile. - * Defines whether latency tuning is enabled and which algorithm is used. + * Defines whether latency adjustment is enabled and which algorithm is used. */ typedef enum roc_latency_tuner_profile { /** Default profile. * - * On receiver, when \ref ROC_LATENCY_TUNER_BACKEND_NIQ is used, selects \ref - * ROC_LATENCY_TUNER_PROFILE_RESPONSIVE if target latency is low, and \ref - * ROC_LATENCY_TUNER_PROFILE_GRADUAL if target latency is high. + * On receiver, when \c ROC_LATENCY_TUNER_BACKEND_NIQ is used, selects + * \c ROC_LATENCY_TUNER_PROFILE_RESPONSIVE if target latency is low, and + * \c ROC_LATENCY_TUNER_PROFILE_GRADUAL if target latency is high. * - * On sender, selects \ref ROC_LATENCY_TUNER_PROFILE_INTACT. + * On sender, selects \c ROC_LATENCY_TUNER_PROFILE_INTACT. */ ROC_LATENCY_TUNER_PROFILE_DEFAULT = 0, - /** No latency tuning. + /** No latency adjustment. * - * In this mode, clock speed is not adjusted. Default on sender. + * In this mode, clock speed is not adjusted. * * You can set this mode on receiver, and set some other mode on sender, to - * do latency tuning on sender side instead of receiver side. It's useful + * do latency adjustment on sender side instead of receiver side. It's useful * when receiver is CPU-constrained and sender is not, because latency tuner - * relies on resampling, which is CPU-demanding. + * relies on resampling, which is increases CPU usage. * * You can also set this mode on both sender and receiver if you don't need - * latency tuning at all. However, if sender and receiver have independent + * latency adjustment at all. However, if sender and receiver have independent * clocks (which is typically the case), clock drift will lead to periodic * playback disruptions caused by underruns and overruns. */ ROC_LATENCY_TUNER_PROFILE_INTACT = 1, - /** Responsive latency tuning. + /** Responsive latency adjustment. * * Clock speed is adjusted quickly and accurately. * @@ -497,7 +497,7 @@ typedef enum roc_latency_tuner_profile { */ ROC_LATENCY_TUNER_PROFILE_RESPONSIVE = 2, - /** Gradual latency tuning. + /** Gradual latency adjustment. * * Clock speed is adjusted slowly and smoothly. * @@ -657,7 +657,7 @@ typedef struct roc_context_config { typedef struct roc_sender_config { /** The encoding used in frames passed to sender. * - * Frame encoding defines sample format, channel layout, and sample rate in local + * Frame encoding defines sample format, channel layout, and sample rate in **local** * frames created by user and passed to sender. * * Should be set explicitly (zero value is invalid). @@ -735,32 +735,35 @@ typedef struct roc_sender_config { /** Clock source to use. * Defines whether write operation is blocking or non-blocking. * + * If write is non-blocking, the user is responsible to invoke it in appropriate + * time. Otherwise it will automatically wait for that time. + * * If zero, default value is used (\ref ROC_CLOCK_SOURCE_DEFAULT). */ roc_clock_source clock_source; /** Latency tuner backend. * Defines which latency is monitored and controlled by latency tuner. - * Defines semantics of \c target_latency, \c min_latency, and \c max_latency fields. + * Defines semantics of \c target_latency and related fields. * * If zero, default backend is used (\ref ROC_LATENCY_TUNER_BACKEND_DEFAULT). */ roc_latency_tuner_backend latency_tuner_backend; /** Latency tuner profile. - * Defines whether latency tuning is enabled and which algorithm is used. + * Defines whether latency adjustment is enabled and which algorithm is used. * * If zero, default profile is used (\ref ROC_LATENCY_TUNER_PROFILE_DEFAULT). * - * By default, latency tuning is **disabled** on sender. If you enable it on sender, - * you need to disable it on receiver. You also need to set \c target_latency to - * the exact same value on both sides. + * By default, latency adjustment is **disabled** on sender (\c latency_tuner_profile + * is \ref ROC_LATENCY_TUNER_PROFILE_INTACT). If you enable it on sender, you + * need to disable it on receiver. */ roc_latency_tuner_profile latency_tuner_profile; /** Resampler backend. * Affects CPU usage, quality, and clock synchronization precision - * (if latency tuning is enabled). + * (if latency adjustment is enabled). * * If zero, default backend is used (\ref ROC_RESAMPLER_BACKEND_DEFAULT). */ @@ -775,77 +778,58 @@ typedef struct roc_sender_config { /** Target latency, in nanoseconds. * - * How latency is calculated depends on \c latency_tuner_backend field. - * - * If latency tuning is enabled on sender (if \c latency_tuner_profile is not - * \ref ROC_LATENCY_TUNER_PROFILE_INTACT), sender adjusts its clock to keep - * actual latency as close as possible to the target. + * By default, latency adjustment is enabled on receiver and disabled on sender + * (see \c latency_tuner_profile). In this case, \c target_latency, + * \c latency_tolerance, \c start_target_latency, \c min_target_latency, and + * \c max_target_latency should be configured only on receiver, and should be set + * to zeros on sender. * - * By default, latency tuning is **disabled** on sender. If you enable it on sender, - * you need to disable it on receiver. You also need to set \c target_latency to - * the exact same value on both sides. + * You can enable latency adjustment on sender and disable it on receiver + * (again, see \c latency_tuner_profile). In this case, \c target_latency, + * \c latency_tolerance, \c start_target_latency, \c min_target_latency, and + * \c max_target_latency should be configured on both sender and receiver and + * should match each other. * - * If latency tuning is enabled, \c target_latency should be non-zero. + * Semantics of the fields on sender is the same as on receiver. Refer to comments + * in \ref roc_receiver_config for details on each field. */ unsigned long long target_latency; - /** Minimum allowed latency, in nanoseconds. - * - * How latency is calculated depends on \c latency_tuner_backend field. - * - * If latency bounding is enabled on sender (if \c latency_tuner_profile is not - * \ref ROC_LATENCY_TUNER_PROFILE_INTACT, or if any of \c min_latency and - * \c max_latency fields is non-zero), then if latency goes below \c min_latency or - * above \c max_latency, sender restarts connection to receiver. - * - * By default, latency bounding is **disabled** on sender. If you enable it on sender, - * you likely want to disable it on receiver. + /** Maximum allowed delta between current and target latency, in nanoseconds. * - * You should either set both \c min_latency and \c max_latency to meaningful values, - * or keep both zero. If both fields are zero, and if latency bounding is enabled, - * then default values are used. + * By default, latency adjustment is enabled on receiver and disabled on sender + * (see \c latency_tuner_profile), and this field isn't used and should be zero. * - * Negative value is allowed. For \ref ROC_LATENCY_TUNER_BACKEND_NIQ, latency - * can temporary become negative during burst packet losses, and negative - * \c min_latency may be used to tolerate this to some extent. + * If you want to enable it, refer to the comment for \c target_latency. */ - long long min_latency; + unsigned long long latency_tolerance; - /** Maximum allowed latency, in nanoseconds. - * - * How latency is calculated depends on \c latency_tuner_backend field. + /** Starting latency for adaptive mode, in nanoseconds. * - * If latency bounding is enabled on sender (if \c latency_tuner_profile is not - * \ref ROC_LATENCY_TUNER_PROFILE_INTACT, or if any of \c min_latency and - * \c max_latency fields is non-zero), then if latency goes below \c min_latency or - * above \c max_latency, sender restarts connection to receiver. + * By default, latency adjustment is enabled on receiver and disabled on sender + * (see \c latency_tuner_profile), and this field isn't used and should be zero. * - * By default, latency bounding is **disabled** on sender. If you enable it on sender, - * you likely want to disable it on receiver. - * - * You should either set both \c min_latency and \c max_latency to meaningful values, - * or keep both zero. If both fields are zero, and if latency bounding is enabled, - * then default values are used. - * - * Negative value doesn't make practical sense. + * If you want to enable it, refer to the comment for \c target_latency. */ - long long max_latency; + unsigned long long start_target_latency; - /** Maximum allowed delta between current and target latency, in nanoseconds. + /** Minimum latency for adaptive mode, in nanoseconds. * - * How latency is calculated depends on \c latency_tuner_backend field. + * By default, latency adjustment is enabled on receiver and disabled on sender + * (see \c latency_tuner_profile), and this field isn't used and should be zero. * - * If latency tuning is enabled on sender (if \c latency_tuner_profile is not - * \ref ROC_LATENCY_TUNER_PROFILE_INTACT), sender monitors current latency, and - * if it differs from \c target_latency more than by \c latency_tolerance, sender - * restarts connection to receiver. + * If you want to enable it, refer to the comment for \c target_latency. + */ + unsigned long long min_target_latency; + + /** Maximum latency for adaptive mode, in nanoseconds. * - * By default, latency bounding is **disabled** on sender. If you enable it on sender, - * you likely want to disable it on receiver. + * By default, latency adjustment is enabled on receiver and disabled on sender + * (see \c latency_tuner_profile), and this field isn't used and should be zero. * - * If zero, default value is used (if latency tuning is enabled on sender). + * If you want to enable it, refer to the comment for \c target_latency. */ - unsigned long long latency_tolerance; + unsigned long long max_target_latency; } roc_sender_config; /** Receiver configuration. @@ -859,7 +843,7 @@ typedef struct roc_sender_config { typedef struct roc_receiver_config { /** The encoding used in frames returned by receiver. * - * Frame encoding defines sample format, channel layout, and sample rate in local + * Frame encoding defines sample format, channel layout, and sample rate in **local** * frames returned by receiver to user. * * Should be set (zero value is invalid). @@ -869,32 +853,35 @@ typedef struct roc_receiver_config { /** Clock source. * Defines whether read operation is blocking or non-blocking. * + * If read is non-blocking, the user is responsible to invoke it in appropriate + * time. Otherwise it will automatically wait for that time. + * * If zero, default value is used (\ref ROC_CLOCK_SOURCE_DEFAULT). */ roc_clock_source clock_source; /** Latency tuner backend. * Defines which latency is monitored and controlled by latency tuner. - * Defines semantics of \c target_latency, \c min_latency, and \c max_latency fields. + * Defines semantics of \c target_latency and related fields. * * If zero, default backend is used (\ref ROC_LATENCY_TUNER_BACKEND_DEFAULT). */ roc_latency_tuner_backend latency_tuner_backend; /** Latency tuner profile. - * Defines whether latency tuning is enabled and which algorithm is used. + * Defines whether latency adjustment is enabled and which algorithm is used. * * If zero, default profile is used (\ref ROC_LATENCY_TUNER_PROFILE_DEFAULT). * - * By default, latency tuning is **enabled** on receiver. If you disable it on - * receiver, you usually need to enable it on sender. In that case you also need to - * set \c target_latency to the same value on both sides. + * By default, latency adjustment is **enabled** on receiver (\c latency_tuner_profile + * is not \ref ROC_LATENCY_TUNER_PROFILE_INTACT). If you disable it on receiver, + * you usually need to enable it on sender. */ roc_latency_tuner_profile latency_tuner_profile; /** Resampler backend. * Affects CPU usage, quality, and clock synchronization precision - * (if latency tuning is enabled). + * (if latency adjustment is enabled). * * If zero, default backend is used (\ref ROC_RESAMPLER_BACKEND_DEFAULT). */ @@ -919,90 +906,79 @@ typedef struct roc_receiver_config { /** Target latency, in nanoseconds. * - * How latency is calculated depends on \c latency_tuner_backend field. - * - * If latency tuning is enabled on receiver (if \c latency_tuner_profile is not - * \ref ROC_LATENCY_TUNER_PROFILE_INTACT), receiver adjusts its clock to keep - * actual latency as close as possible to the target. - * - * By default, latency tuning is **enabled** on receiver. If you disable it on - * receiver, you likely want to enable it on sender. In this case you also need to - * set \c target_latency to the exact same value on both sides. - * - * If zero, default value is used. + * Defines the latency value to maintain, as measured by the latency backend + * (see \c latency_tuner_backend): + * - Non-zero value activates **fixed latency** mode: the latency starts from + * \c target_latency and is kept close to that value. + * - Zero value activates **adaptive latency** mode: the latency is chosen + * dynamically. Initial latency is \c start_target_latency, and the allowed + * range is \c min_target_latency to \c max_target_latency. + * Latency tuner consistently reassesses network conditions and changes target + * to achieve the lowest latency that doesn't cause disruptions. + * + * If latency adjustment is enabled on receiver (default setting, see + * \c latency_tuner_profile), receiver starts with the initial latency and + * continuously modulates clock speed to keep actual latency close to the target. + * It also validates that the latency deviation never exceeds the limit (see + * \c latency_tolerance). + * + * If latency adjustment is disabled on receiver, it is should be enabled on sender. + * In this case, receiver starts with the initial latency, but afterwards does not + * try to adjust clock speed, assuming that sender will do it. However, receiver + * still validates that the latency deviation doesn't exceed the limit. + * + * When latency adjustment is enabled on sender instead of receiver, + * \c target_latency, \c start_target_latency, \c min_target_latency and + * \c max_target_latency should match on sender and receiver. + * This ensures both sides know the initial latency and the allowed range. */ unsigned long long target_latency; - /** Start latency, in nanoseconds. - * - * If target latency is set to zero, and latency tuning is enabled, this value - * sets initial value of latency. - */ - unsigned long long start_latency; - /** Maximum allowed delta between current and target latency, in nanoseconds. * - * How latency is calculated depends on \c latency_tuner_backend field. - * - * If latency tuning is enabled on receiver (if \c latency_tuner_profile is not - * \ref ROC_LATENCY_TUNER_PROFILE_INTACT), receiver monitors current latency, and - * if it differs from \c target_latency more than by \c latency_tolerance, receiver - * terminates connection to sender (but it then restarts if sender continues - * streaming). - * - * By default, latency bounding is **enabled** on receiver. If you disable it on - * receiver, you likely want to enable it on sender. + * Latency tuner continuously monitors deviation of actual latency from target. + * If deviation becomes bigger than \c latency_tolerance, connection to sender + * is terminated (but sender may reconnect). * - * If zero, default value is used (if latency tuning is enabled on receiver). + * If zero, default value is used. */ unsigned long long latency_tolerance; - /** Minimum allowed latency, in nanoseconds. - * - * How latency is calculated depends on \c latency_tuner_backend field. - * - * If latency bounding is enabled on receiver (if \c latency_tuner_profile is not - * \ref ROC_LATENCY_TUNER_PROFILE_INTACT, or if any of \c min_latency and - * \c max_latency fields is non-zero), then if latency goes below \c min_latency - * or above \c max_latency, receiver terminates connection to sender (but it then - * restarts if sender continues streaming). - * - * By default, latency bounding is **enabled** on receiver. If you disable it on - * receiver, you likely want to enable it on sender. + /** Starting latency for adaptive mode, in nanoseconds. * - * You should either set both \c min_latency and \c max_latency to meaningful values, - * or keep both zero. If both fields are zero, and if latency bounding is enabled, - * then default values are used. + * If adaptive latency mode is used (default setting, see \c target_latency), this + * field defines initial value for the target latency. * - * Negative value is allowed. For \ref ROC_LATENCY_TUNER_BACKEND_NIQ, latency - * can temporary become negative during burst packet losses, and negative - * \c min_latency may be used to tolerate this to some extent. + * If zero, default value is used. */ - long long min_latency; + unsigned long long start_target_latency; - /** Maximum allowed latency, in nanoseconds. + /** Minimum latency for adaptive mode, in nanoseconds. * - * If latency bounding is enabled on receiver (if \c latency_tuner_profile is not - * \ref ROC_LATENCY_TUNER_PROFILE_INTACT, or if any of \c min_latency and - * \c max_latency fields is non-zero), then if latency goes below \c min_latency - * or above \c max_latency, receiver terminates connection to sender (but it then - * restarts if sender continues streaming). + * If adaptive latency mode is used (default setting, see \c target_latency), + * \c min_target_latency and \c max_target_latency define the allowed range + * for the target latency. * - * By default, latency bounding is **enabled** on receiver. If you disable it on - * receiver, you likely want to enable it on sender. + * You should either set both \c min_target_latency and \c max_target_latency, + * or keep both zero to use default values. + */ + unsigned long long min_target_latency; + + /** Maximum latency for adaptive mode, in nanoseconds. * - * You should either set both \c min_latency and \c max_latency to meaningful values, - * or keep both zero. If both fields are zero, and if latency bounding is enabled, - * then default values are used. + * If adaptive latency mode is used (default setting, see \c target_latency), + * \c min_target_latency and \c max_target_latency define the allowed range + * for the target latency. * - * Negative value doesn't make practical sense. + * You should either set both \c min_target_latency and \c max_target_latency, + * or keep both zero to use default values. */ - long long max_latency; + unsigned long long max_target_latency; /** Timeout for the lack of playback, in nanoseconds. * * If there is no playback during this period, receiver terminates connection to - * to sender (but it then restarts if sender continues streaming). + * to sender (but sender may reconnect). * * This mechanism allows to detect dead, hanging, or incompatible clients that * generate unparseable packets. @@ -1014,7 +990,7 @@ typedef struct roc_receiver_config { /** Timeout for choppy playback, in nanoseconds. * * If there is constant stuttering during this period, receiver terminates connection - * to sender (but it then restarts if sender continues streaming). + * to sender (but sender may reconnect). * * This mechanism allows to detect situations when playback continues but there * are frequent glitches, for example because there is a high ratio of late packets. diff --git a/src/public_api/src/adapters.cpp b/src/public_api/src/adapters.cpp index 1a9f4901f..96c6d41bd 100644 --- a/src/public_api/src/adapters.cpp +++ b/src/public_api/src/adapters.cpp @@ -107,25 +107,6 @@ bool sender_config_from_user(node::Context& context, out.packet_length = (core::nanoseconds_t)in.packet_length; } - if (in.target_latency != 0) { - out.latency.target_latency = (core::nanoseconds_t)in.target_latency; - } - - if (in.latency_tolerance != 0) { - out.latency.latency_tolerance = (core::nanoseconds_t)in.latency_tolerance; - } - - if (in.min_latency != 0) { - out.latency.min_latency = (core::nanoseconds_t)in.min_latency; - } - - if (in.max_latency != 0) { - out.latency.max_latency = (core::nanoseconds_t)in.max_latency; - } - - out.enable_cpu_clock = false; - out.enable_auto_cts = true; - if (!fec_encoding_from_user(out.fec_encoder.scheme, in.fec_encoding)) { roc_log(LogError, "bad configuration: invalid roc_sender_config.fec_encoding:" @@ -175,55 +156,32 @@ bool sender_config_from_user(node::Context& context, return false; } - return true; -} - -ROC_ATTR_NO_SANITIZE_UB -bool receiver_config_from_user(node::Context&, - pipeline::ReceiverSourceConfig& out, - const roc_receiver_config& in) { if (in.target_latency != 0) { - out.session_defaults.latency.target_latency = - (core::nanoseconds_t)in.target_latency; + out.latency.target_latency = (core::nanoseconds_t)in.target_latency; } if (in.latency_tolerance != 0) { - out.session_defaults.latency.latency_tolerance = - (core::nanoseconds_t)in.latency_tolerance; - } - - if (in.start_latency != 0) { - if (in.target_latency != 0) { - roc_log(LogError, - "bad configuration:" - " start latency must be 0 if latency tuning is disabled" - " (target_latency != 0)"); - return false; - } - out.session_defaults.latency.start_latency = - (core::nanoseconds_t)in.start_latency; - } - - if (in.min_latency != 0) { - out.session_defaults.latency.min_latency = (core::nanoseconds_t)in.min_latency; + out.latency.latency_tolerance = (core::nanoseconds_t)in.latency_tolerance; } - if (in.max_latency != 0) { - out.session_defaults.latency.max_latency = (core::nanoseconds_t)in.max_latency; + if (in.start_target_latency != 0) { + out.latency.start_target_latency = (core::nanoseconds_t)in.start_target_latency; } - if (in.no_playback_timeout != 0) { - out.session_defaults.watchdog.no_playback_timeout = in.no_playback_timeout; + if (in.min_target_latency != 0 || in.max_target_latency != 0) { + out.latency.min_target_latency = (core::nanoseconds_t)in.min_target_latency; + out.latency.max_target_latency = (core::nanoseconds_t)in.max_target_latency; } - if (in.choppy_playback_timeout != 0) { - out.session_defaults.watchdog.choppy_playback_timeout = - in.choppy_playback_timeout; - } + out.enable_auto_cts = true; - out.common.enable_cpu_clock = false; - out.common.enable_auto_reclock = true; + return true; +} +ROC_ATTR_NO_SANITIZE_UB +bool receiver_config_from_user(node::Context&, + pipeline::ReceiverSourceConfig& out, + const roc_receiver_config& in) { if (!sample_spec_from_user(out.common.output_sample_spec, in.frame_encoding, false)) { roc_log(LogError, "bad configuration: invalid roc_receiver_config.frame_encoding"); @@ -277,6 +235,39 @@ bool receiver_config_from_user(node::Context&, return false; } + if (in.target_latency != 0) { + out.session_defaults.latency.target_latency = + (core::nanoseconds_t)in.target_latency; + } + + if (in.latency_tolerance != 0) { + out.session_defaults.latency.latency_tolerance = + (core::nanoseconds_t)in.latency_tolerance; + } + + if (in.start_target_latency != 0) { + out.session_defaults.latency.start_target_latency = + (core::nanoseconds_t)in.start_target_latency; + } + + if (in.min_target_latency != 0 || in.max_target_latency != 0) { + out.session_defaults.latency.min_target_latency = + (core::nanoseconds_t)in.min_target_latency; + out.session_defaults.latency.max_target_latency = + (core::nanoseconds_t)in.max_target_latency; + } + + if (in.no_playback_timeout != 0) { + out.session_defaults.watchdog.no_playback_timeout = in.no_playback_timeout; + } + + if (in.choppy_playback_timeout != 0) { + out.session_defaults.watchdog.choppy_playback_timeout = + in.choppy_playback_timeout; + } + + out.common.enable_auto_reclock = true; + return true; } diff --git a/src/tests/public_api/test_loopback_sender_2_receiver.cpp b/src/tests/public_api/test_loopback_sender_2_receiver.cpp index 3b2b1b238..39431bf2b 100644 --- a/src/tests/public_api/test_loopback_sender_2_receiver.cpp +++ b/src/tests/public_api/test_loopback_sender_2_receiver.cpp @@ -629,11 +629,11 @@ TEST(loopback_sender_2_receiver, metrics_measurements) { CHECK(send_conn_metrics.expected_packets > 0); CHECK(recv_conn_metrics.expected_packets > 0); - CHECK(send_conn_metrics.lost_packets == 0); - CHECK(recv_conn_metrics.lost_packets == 0); + CHECK(send_conn_metrics.lost_packets >= 0); + CHECK(recv_conn_metrics.lost_packets >= 0); CHECK(send_conn_metrics.late_packets == 0); - CHECK(recv_conn_metrics.late_packets == 0); + CHECK(recv_conn_metrics.late_packets >= 0); CHECK(send_conn_metrics.recovered_packets == 0); CHECK(recv_conn_metrics.recovered_packets == 0); diff --git a/src/tools/roc_recv/main.cpp b/src/tools/roc_recv/main.cpp index ff597631c..7e83ee8d8 100644 --- a/src/tools/roc_recv/main.cpp +++ b/src/tools/roc_recv/main.cpp @@ -144,11 +144,11 @@ int main(int argc, char** argv) { } if (!core::parse_duration( args.start_latency_arg, - receiver_config.session_defaults.latency.start_latency)) { + receiver_config.session_defaults.latency.start_target_latency)) { roc_log(LogError, "invalid --start-latency: bad format"); return 1; } - if (receiver_config.session_defaults.latency.start_latency <= 0) { + if (receiver_config.session_defaults.latency.start_target_latency <= 0) { roc_log(LogError, "invalid --start-latency: should be > 0"); return 1; } @@ -168,25 +168,25 @@ int main(int argc, char** argv) { return 1; } if (!core::parse_duration(args.min_latency_arg, - receiver_config.session_defaults.latency.min_latency)) { + receiver_config.session_defaults.latency.min_target_latency)) { roc_log(LogError, "invalid --min-latency: bad format"); return 1; } - if (receiver_config.session_defaults.latency.min_latency <= 0) { + if (receiver_config.session_defaults.latency.min_target_latency <= 0) { roc_log(LogError, "invalid --min-latency: should be > 0"); return 1; } if (!core::parse_duration(args.max_latency_arg, - receiver_config.session_defaults.latency.max_latency)) { + receiver_config.session_defaults.latency.max_target_latency)) { roc_log(LogError, "invalid --max-latency: bad format"); return 1; } - if (receiver_config.session_defaults.latency.max_latency <= 0) { + if (receiver_config.session_defaults.latency.max_target_latency <= 0) { roc_log(LogError, "invalid --max-latency: should be > 0"); return 1; } - if (receiver_config.session_defaults.latency.min_latency - > receiver_config.session_defaults.latency.max_latency) { + if (receiver_config.session_defaults.latency.min_target_latency + > receiver_config.session_defaults.latency.max_target_latency) { roc_log( LogError, "incorrect --max-latency: should be greater or equal to --min-latency");