mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-07 14:49:09 +08:00
[feat] add video latency to network statistics
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user