diff --git a/apps/desktop/src/gui/features/devices/session_device_manager.cpp b/apps/desktop/src/gui/features/devices/session_device_manager.cpp index ecca12c0..395a8c91 100644 --- a/apps/desktop/src/gui/features/devices/session_device_manager.cpp +++ b/apps/desktop/src/gui/features/devices/session_device_manager.cpp @@ -1,11 +1,12 @@ #include "features/devices/session_device_manager.h" +#include #include +#include #include #include -#include #include "platform.h" #include "rd_log.h" #include "runtime/gui_runtime.h" @@ -59,6 +60,53 @@ bool SessionDeviceManager::ShouldSendCapturedFrame( return true; } +void SessionDeviceManager::RecordCaptureCadence( + std::chrono::steady_clock::time_point now, int fps, + bool from_secure_desktop) { + std::lock_guard lock(capture_metrics_mutex_); + if (capture_metrics_started_ == std::chrono::steady_clock::time_point{}) + capture_metrics_started_ = now; + const auto elapsed_us = std::chrono::duration_cast( + now - capture_metrics_started_) + .count(); + if (elapsed_us >= 2000000) { + if (capture_forwarded_) { + const char* source = capture_secure_callbacks_ == 0 ? "normal_desktop" + : capture_secure_callbacks_ == capture_callbacks_ + ? "secure_desktop" + : "mixed"; + LOG_INFO( + "Capture delivery: source={} normal_callbacks={} secure_callbacks={} " + "target_fps={} input_fps={:.2f} " + "forwarded_fps={:.2f} callbacks={} rate_dropped={} " + "forwarded={} send_errors={} max_callback_gap_us={} " + "send_avg_us={} send_max_us={} window_ms={}", + source, capture_callbacks_ - capture_secure_callbacks_, + capture_secure_callbacks_, fps, + capture_callbacks_ * 1000000.0 / elapsed_us, + capture_forwarded_ * 1000000.0 / elapsed_us, capture_callbacks_, + capture_rate_drops_, capture_forwarded_, capture_send_errors_, + capture_max_gap_us_, capture_send_us_ / capture_forwarded_, + capture_max_send_us_, elapsed_us / 1000); + } + capture_metrics_started_ = now; + capture_callbacks_ = capture_rate_drops_ = capture_forwarded_ = 0; + capture_secure_callbacks_ = 0; + capture_send_errors_ = 0; + capture_max_gap_us_ = capture_send_us_ = capture_max_send_us_ = 0; + } + if (last_capture_callback_ != std::chrono::steady_clock::time_point{}) { + capture_max_gap_us_ = + std::max(capture_max_gap_us_, + std::chrono::duration_cast( + now - last_capture_callback_) + .count()); + } + last_capture_callback_ = std::max(last_capture_callback_, now); + ++capture_callbacks_; + if (from_secure_desktop) ++capture_secure_callbacks_; +} + void SessionDeviceManager::Initialize() { InitializeAudioOutput(); screen_capturer_factory_ = new ScreenCapturerFactory(); @@ -88,6 +136,11 @@ int SessionDeviceManager::InitializeScreenCapturer() { last_frame_time_ = {}; next_frame_deadline_ = {}; + capture_metrics_started_ = last_capture_callback_ = {}; + capture_callbacks_ = capture_rate_drops_ = capture_forwarded_ = 0; + capture_secure_callbacks_ = 0; + capture_send_errors_ = 0; + capture_max_gap_us_ = capture_send_us_ = capture_max_send_us_ = 0; const int fps = owner_.config_center_->GetVideoFrameRate() == ConfigCenter::VIDEO_FRAME_RATE::FPS_30 ? 30 @@ -103,7 +156,15 @@ int SessionDeviceManager::InitializeScreenCapturer() { const char *display_name, const MiniRtcNativeVideoFrame *native_frame) { const auto now_time = std::chrono::steady_clock::now(); + bool from_secure_desktop = false; +#ifdef _WIN32 + from_secure_desktop = + ScreenCapturerWin::CurrentFrameIsFromSecureDesktop(); +#endif + RecordCaptureCadence(now_time, fps, from_secure_desktop); if (!ShouldSendCapturedFrame(now_time, fps)) { + std::lock_guard lock(capture_metrics_mutex_); + ++capture_rate_drops_; return; } @@ -170,9 +231,24 @@ int SessionDeviceManager::InitializeScreenCapturer() { frame.height = height; frame.captured_timestamp = GetSystemTimeMicros(owner_.peer_); frame.native_frame = native_frame; + const auto send_started = std::chrono::steady_clock::now(); + uint64_t send_errors = 0; for (const std::string &remote_id : connected_remote_ids) { - SendVideoFrameToPeer(owner_.peer_, &frame, stream_id.c_str(), - remote_id.data(), remote_id.size()); + if (SendVideoFrameToPeer(owner_.peer_, &frame, stream_id.c_str(), + remote_id.data(), remote_id.size()) != 0) + ++send_errors; + } + const auto send_us = + std::chrono::duration_cast( + std::chrono::steady_clock::now() - send_started) + .count(); + { + std::lock_guard lock(capture_metrics_mutex_); + ++capture_forwarded_; + capture_send_errors_ += send_errors; + capture_send_us_ += send_us; + capture_max_send_us_ = + std::max(capture_max_send_us_, send_us); } last_video_frame_stream_id_ = stream_id; last_frame_time_ = now_time; diff --git a/apps/desktop/src/gui/features/devices/session_device_manager.h b/apps/desktop/src/gui/features/devices/session_device_manager.h index ca84b8c3..532dcf5e 100644 --- a/apps/desktop/src/gui/features/devices/session_device_manager.h +++ b/apps/desktop/src/gui/features/devices/session_device_manager.h @@ -84,6 +84,8 @@ private: void ClearCapturedKeyboardInput(); bool ShouldSendCapturedFrame(std::chrono::steady_clock::time_point now, int fps); + void RecordCaptureCadence(std::chrono::steady_clock::time_point now, int fps, + bool from_secure_desktop); GuiRuntime &owner_; SDL_AudioStream *output_stream_ = nullptr; @@ -99,6 +101,14 @@ private: std::mutex captured_keyboard_inputs_mutex_; std::chrono::steady_clock::time_point last_frame_time_{}; std::chrono::steady_clock::time_point next_frame_deadline_{}; + std::mutex capture_metrics_mutex_; + std::chrono::steady_clock::time_point capture_metrics_started_{}; + std::chrono::steady_clock::time_point last_capture_callback_{}; + uint64_t capture_callbacks_ = 0, capture_rate_drops_ = 0; + uint64_t capture_secure_callbacks_ = 0; + uint64_t capture_forwarded_ = 0, capture_send_errors_ = 0; + int64_t capture_max_gap_us_ = 0; + int64_t capture_send_us_ = 0, capture_max_send_us_ = 0; std::string last_video_frame_stream_id_; bool invalid_video_stream_id_logged_ = false; }; diff --git a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp index e727324d..80fd2a77 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp @@ -33,6 +33,8 @@ namespace { using Json = nlohmann::json; +thread_local bool current_frame_from_secure_desktop = false; + constexpr DWORD kSecureDesktopStatusIntervalMs = 250; constexpr DWORD kSecureDesktopStatusPipeTimeoutMs = 500; constexpr DWORD kSecureDesktopHelperPipeTimeoutMs = 120; @@ -460,6 +462,10 @@ int ScreenCapturerWin::Destroy() { return 0; } +bool ScreenCapturerWin::CurrentFrameIsFromSecureDesktop() { + return current_frame_from_secure_desktop; +} + void ScreenCapturerWin::EmitCapturedFrame( unsigned char* data, int size, int width, int height, const char* stream_id, const MiniRtcNativeVideoFrame* native_frame, @@ -473,6 +479,14 @@ void ScreenCapturerWin::EmitCapturedFrame( return; } + // Normal and helper callbacks can originate on different threads during + // desktop transitions. Keep diagnostics tied to this callback's source. + struct RestoreFrameSource { + bool previous; + ~RestoreFrameSource() { current_frame_from_secure_desktop = previous; } + } restore_source{ + std::exchange(current_frame_from_secure_desktop, from_secure_desktop)}; + if (native_frame) { if (!native_output_logged_.exchange(true, std::memory_order_relaxed)) { LOG_INFO("Windows capturer native frame output enabled (type={})", diff --git a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.h b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.h index ee916f69..7480ed88 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.h +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.h @@ -48,6 +48,9 @@ class ScreenCapturerWin : public ScreenCapturer { void SetPrivacyController(PrivacyController* privacy) override { privacy_ = privacy; } // Set before Init(), while capture is stopped. void SetCaptureMethod(ScreenCaptureMethod method) { capture_method_ = method; } + // Query synchronously inside the frame callback, on the delivering thread. + // This describes the actual frame, independently of service status polling. + static bool CurrentFrameIsFromSecureDesktop(); private: std::unique_ptr impl_;