mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-19 18:33:00 +08:00
[fix] correct video latency measurement and smooth connection RTT
This commit is contained in:
@@ -2925,17 +2925,25 @@ void GuiApplication::SyncStreamWindow() {
|
||||
static_cast<int>(std::lround(latency->average_ms))) +
|
||||
" ms"
|
||||
: "—"));
|
||||
const auto display_rtt = props->net_traffic_stats_.SmoothedRttMs(fps_now);
|
||||
const bool rtt_available = props->net_traffic_stats_button_pressed_ &&
|
||||
status == ConnectionStatus::Connected &&
|
||||
net.rtt_ms >= 0;
|
||||
display_rtt.has_value();
|
||||
std::string connection_latency = "—";
|
||||
if (rtt_available) {
|
||||
connection_latency =
|
||||
net.rtt_ms < 1
|
||||
*display_rtt < 1
|
||||
? "<1 ms"
|
||||
: std::to_string(static_cast<int>(std::lround(net.rtt_ms))) + " ms";
|
||||
: std::to_string(static_cast<int>(std::lround(*display_rtt))) + " ms";
|
||||
}
|
||||
(*ui_->stream)->set_stats_connection_latency(UiText(connection_latency));
|
||||
if (refresh_stats && status == ConnectionStatus::Connected) {
|
||||
LOG_DEBUG("Latency stats: remote={} video_ms={} video_samples={} "
|
||||
"rtt_raw_ms={} rtt_display_ms={}",
|
||||
props->remote_id_, latency ? latency->average_ms : -1,
|
||||
latency ? latency->samples : 0, net.rtt_ms,
|
||||
display_rtt.value_or(-1));
|
||||
}
|
||||
}
|
||||
|
||||
void GuiApplication::SyncStreamVideoFrame() {
|
||||
|
||||
@@ -26,6 +26,8 @@ struct VideoLatencyFrame {
|
||||
std::shared_ptr<VideoLatencyStats> stats;
|
||||
uint64_t id = 0;
|
||||
Clock::time_point capture_time{};
|
||||
void MarkPresented(Clock::time_point now = Clock::now()) const;
|
||||
// Fallback for renderers which expose submission but no presentation time.
|
||||
void MarkSubmitted(Clock::time_point now = Clock::now()) const;
|
||||
};
|
||||
|
||||
@@ -97,6 +99,10 @@ class VideoLatencyStats
|
||||
};
|
||||
|
||||
inline void VideoLatencyFrame::MarkSubmitted(Clock::time_point now) const {
|
||||
MarkPresented(now);
|
||||
}
|
||||
|
||||
inline void VideoLatencyFrame::MarkPresented(Clock::time_point now) const {
|
||||
if (stats) stats->Record(*this, now);
|
||||
}
|
||||
} // namespace crossdesk
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-09-26
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _CONNECTION_LATENCY_H_
|
||||
#define _CONNECTION_LATENCY_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
namespace crossdesk::gui_detail {
|
||||
|
||||
// Presentation-only smoothing. The transport's raw RTT remains available for
|
||||
// recovery, congestion control and diagnostics. A missing one-second report
|
||||
// does not flicker, but a disconnected/stalled peer cannot leave a stale value.
|
||||
class ConnectionLatencyStats {
|
||||
public:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
void Update(double rtt_ms, Clock::time_point now = Clock::now()) {
|
||||
if (!std::isfinite(rtt_ms) || rtt_ms < 0 || rtt_ms > 2000) return;
|
||||
std::lock_guard lock(mutex_);
|
||||
if (average_ms_ && now < updated_) return;
|
||||
if (!Fresh(now)) {
|
||||
average_ms_ = rtt_ms;
|
||||
} else {
|
||||
*average_ms_ += 0.25 * (rtt_ms - *average_ms_);
|
||||
}
|
||||
updated_ = now;
|
||||
}
|
||||
|
||||
std::optional<double> Get(Clock::time_point now = Clock::now()) const {
|
||||
std::lock_guard lock(mutex_);
|
||||
return Fresh(now) ? average_ms_ : std::nullopt;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
std::lock_guard lock(mutex_);
|
||||
average_ms_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
bool Fresh(Clock::time_point now) const {
|
||||
// The UI may snapshot `now` just before the report thread updates us.
|
||||
// A newer valid report must not cause a one-frame unavailable flicker.
|
||||
return average_ms_ && now - updated_ < std::chrono::seconds(3);
|
||||
}
|
||||
mutable std::mutex mutex_;
|
||||
std::optional<double> average_ms_;
|
||||
Clock::time_point updated_{};
|
||||
};
|
||||
|
||||
} // namespace crossdesk::gui_detail
|
||||
|
||||
#endif
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <atomic>
|
||||
|
||||
#include "minirtc.h"
|
||||
#include "runtime/connection_latency.h"
|
||||
|
||||
namespace crossdesk::gui_detail {
|
||||
|
||||
@@ -34,6 +35,7 @@ class NetTrafficStatsCache {
|
||||
total_outbound_stats_.Store(stats.total_outbound_stats);
|
||||
srtp_active_.store(stats.srtp_active, std::memory_order_relaxed);
|
||||
rtt_ms_.store(stats.rtt_ms, std::memory_order_relaxed);
|
||||
connection_latency_.Update(stats.rtt_ms);
|
||||
}
|
||||
|
||||
MiniRtcNetTrafficStats Load() const {
|
||||
@@ -55,7 +57,18 @@ class NetTrafficStatsCache {
|
||||
return data_outbound_stats_.bitrate.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void Reset() { Store(MiniRtcNetTrafficStats{}); }
|
||||
std::optional<double> SmoothedRttMs(
|
||||
ConnectionLatencyStats::Clock::time_point now =
|
||||
ConnectionLatencyStats::Clock::now()) const {
|
||||
return connection_latency_.Get(now);
|
||||
}
|
||||
|
||||
void ResetLatency() { connection_latency_.Reset(); }
|
||||
|
||||
void Reset() {
|
||||
Store(MiniRtcNetTrafficStats{});
|
||||
ResetLatency();
|
||||
}
|
||||
|
||||
private:
|
||||
struct InboundStats {
|
||||
@@ -101,6 +114,7 @@ class NetTrafficStatsCache {
|
||||
OutboundStats total_outbound_stats_;
|
||||
std::atomic<bool> srtp_active_{false};
|
||||
std::atomic<double> rtt_ms_{-1};
|
||||
ConnectionLatencyStats connection_latency_;
|
||||
};
|
||||
|
||||
} // namespace crossdesk::gui_detail
|
||||
|
||||
@@ -511,6 +511,7 @@ void PeerEventHandler::OnNetStatusReport(
|
||||
auto props = runtime->FindRemoteSession(remote_id);
|
||||
if (!props) return;
|
||||
if (props->traversal_mode_ != mode) {
|
||||
props->net_traffic_stats_.ResetLatency();
|
||||
props->traversal_mode_ = mode;
|
||||
LOG_INFO("Net mode: [{}]", int(props->traversal_mode_));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user