[feat] add video latency to network statistics

This commit is contained in:
dijunkun
2026-09-26 00:39:05 +08:00
parent 704938a157
commit 7ac8163931
17 changed files with 246 additions and 50 deletions
@@ -87,6 +87,7 @@ struct SlintVideoPresenter::Impl {
VideoRenderSize gl_image_size;
VideoRenderSize gl_pending_size;
bool gl_pending_dirty = false;
VideoLatencyFrame gl_pending_timing;
#endif
SurfaceState CurrentState() const {
@@ -192,6 +193,7 @@ struct SlintVideoPresenter::Impl {
glPixelStorei(GL_UNPACK_ALIGNMENT, previous_unpack_alignment);
glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(previous_texture));
gl_pending_dirty = false;
gl_pending_timing.MarkSubmitted();
return;
}
@@ -322,7 +324,7 @@ SlintVideoPresenter::PresentResult SlintVideoPresenter::Present(
impl_->seeded_frame_sequence[frame.remote_id] != frame.sequence) {
const auto submit_result = impl_->renderer.SubmitCachedNv12(
frame.remote_id, frame.nv12->data(), frame.nv12->size(), frame.width,
frame.height);
frame.height, frame.timing);
if (submit_result == VideoRenderer::SubmitResult::submitted ||
submit_result == VideoRenderer::SubmitResult::dropped) {
impl_->seeded_frame_sequence[frame.remote_id] = frame.sequence;
@@ -440,6 +442,7 @@ SlintVideoPresenter::PresentResult SlintVideoPresenter::Present(
impl_->gl_pending_frame.swap(impl_->gl_conversion_frame);
impl_->gl_pending_size = {output_width, output_height};
impl_->gl_pending_dirty = true;
impl_->gl_pending_timing = frame.timing;
}
const VideoRenderSize output_size{output_width, output_height};
if (impl_->gl_image_size != output_size) {
@@ -469,6 +472,7 @@ SlintVideoPresenter::PresentResult SlintVideoPresenter::Present(
}
(*impl_->stream)->set_frame(slint::Image(std::move(pixels)));
frame.timing.MarkSubmitted();
(*impl_->stream)->set_has_frame(true);
(*impl_->stream)->set_receiving_text("");
result.width = frame.width;
@@ -14,6 +14,8 @@
#include <string>
#include <vector>
#include "rendering/video_latency.h"
namespace slint {
template <typename T> class ComponentHandle;
}
@@ -42,6 +44,7 @@ public:
int width = 0;
int height = 0;
uint64_t sequence = 0;
VideoLatencyFrame timing;
};
struct PresentResult {
@@ -0,0 +1,104 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-09-26
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _VIDEO_LATENCY_H_
#define _VIDEO_LATENCY_H_
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <optional>
namespace crossdesk {
class VideoLatencyStats;
// Travels with the exact queued pixels, including native frames. Its clock is
// translated once at the receive callback; rendering needs no live PeerPtr.
struct VideoLatencyFrame {
using Clock = std::chrono::steady_clock;
std::shared_ptr<VideoLatencyStats> stats;
uint64_t id = 0;
Clock::time_point capture_time{};
void MarkSubmitted(Clock::time_point now = Clock::now()) const;
};
class VideoLatencyStats
: public std::enable_shared_from_this<VideoLatencyStats> {
public:
using Clock = VideoLatencyFrame::Clock;
struct Snapshot {
double average_ms;
size_t samples;
};
VideoLatencyFrame Frame(uint64_t captured_us, int64_t local_now_us,
Clock::time_point now = Clock::now()) {
if (captured_us == 0 || local_now_us <= 0 ||
captured_us > uint64_t(local_now_us) ||
uint64_t(local_now_us) - captured_us > 5'000'000)
return {};
std::lock_guard lock(mutex_);
return {shared_from_this(), ++issued_id_,
now - std::chrono::microseconds(local_now_us - captured_us)};
}
void Record(const VideoLatencyFrame& frame, Clock::time_point now) {
std::lock_guard lock(mutex_);
// Redraws, cached snapshots and late completions must not count twice.
if (frame.id <= last_id_) return;
last_id_ = frame.id;
const double ms =
std::chrono::duration<double, std::milli>(now - frame.capture_time)
.count();
if (ms < 0 || ms > 5000) return;
samples_.push_back({now, ms});
Prune(now);
}
std::optional<Snapshot> Get(Clock::time_point now = Clock::now()) {
std::lock_guard lock(mutex_);
Prune(now);
if (samples_.empty()) return std::nullopt;
double sum = 0;
for (const auto& sample : samples_) {
sum += sample.ms;
}
return Snapshot{sum / samples_.size(), samples_.size()};
}
void Reset() {
std::lock_guard lock(mutex_);
samples_.clear();
last_id_ = issued_id_; // Invalidate frames still held by a renderer.
}
private:
void Prune(Clock::time_point now) {
while (!samples_.empty() &&
(now - samples_.front().time > std::chrono::seconds(1) ||
samples_.size() > 240))
samples_.pop_front();
}
struct Sample {
Clock::time_point time;
double ms;
};
std::mutex mutex_;
uint64_t issued_id_ = 0;
uint64_t last_id_ = 0;
std::deque<Sample> samples_;
};
inline void VideoLatencyFrame::MarkSubmitted(Clock::time_point now) const {
if (stats) stats->Record(*this, now);
}
} // namespace crossdesk
#endif