mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-24 21:57:08 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16348444f0 |
@@ -1,11 +1,5 @@
|
|||||||
/*
|
#ifndef CROSSDESK_GUI_APPLICATION_STATE_H_
|
||||||
* @Author: DI JUNKUN
|
#define CROSSDESK_GUI_APPLICATION_STATE_H_
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _APPLICATION_STATE_H_
|
|
||||||
#define _APPLICATION_STATE_H_
|
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -198,4 +192,4 @@ struct ApplicationState : MainWindowState,
|
|||||||
|
|
||||||
} // namespace crossdesk::gui_detail
|
} // namespace crossdesk::gui_detail
|
||||||
|
|
||||||
#endif
|
#endif // CROSSDESK_GUI_APPLICATION_STATE_H_
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
* @Author: DI JUNKUN
|
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _GUI_APPLICATION_H_
|
#ifndef _GUI_APPLICATION_H_
|
||||||
#define _GUI_APPLICATION_H_
|
#define _GUI_APPLICATION_H_
|
||||||
|
|
||||||
@@ -100,4 +94,4 @@ private:
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
/*
|
#pragma once
|
||||||
* @Author: DI JUNKUN
|
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _SERVER_WINDOW_STATE_H_
|
|
||||||
#define _SERVER_WINDOW_STATE_H_
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
@@ -31,6 +24,5 @@ inline int ReconcileSelectedController(const std::vector<std::string>& ids,
|
|||||||
}
|
}
|
||||||
return static_cast<int>(std::distance(ids.begin(), selected));
|
return static_cast<int>(std::distance(ids.begin(), selected));
|
||||||
}
|
}
|
||||||
} // namespace crossdesk::server_window_state
|
|
||||||
|
|
||||||
#endif
|
} // namespace crossdesk::server_window_state
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
/*
|
#pragma once
|
||||||
* @Author: DI JUNKUN
|
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _WINDOW_GEOMETRY_H_
|
|
||||||
#define _WINDOW_GEOMETRY_H_
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@@ -45,5 +38,3 @@ constexpr PhysicalPosition BottomRightPosition(const PhysicalRect& bounds,
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace crossdesk::window_geometry
|
} // namespace crossdesk::window_geometry
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -74,8 +74,7 @@ int SessionDeviceManager::InitializeScreenCapturer() {
|
|||||||
|
|
||||||
const int init_ret = screen_capturer_->Init(
|
const int init_ret = screen_capturer_->Init(
|
||||||
fps, [this, fps](unsigned char *data, int size, int width, int height,
|
fps, [this, fps](unsigned char *data, int size, int width, int height,
|
||||||
const char *display_name,
|
const char *display_name) {
|
||||||
const XNativeVideoFrame *native_frame) {
|
|
||||||
const auto now_time = std::chrono::steady_clock::now();
|
const auto now_time = std::chrono::steady_clock::now();
|
||||||
if (!ShouldSendCapturedFrame(now_time, fps)) {
|
if (!ShouldSendCapturedFrame(now_time, fps)) {
|
||||||
return;
|
return;
|
||||||
@@ -143,7 +142,6 @@ int SessionDeviceManager::InitializeScreenCapturer() {
|
|||||||
frame.width = width;
|
frame.width = width;
|
||||||
frame.height = height;
|
frame.height = height;
|
||||||
frame.captured_timestamp = GetSystemTimeMicros(owner_.peer_);
|
frame.captured_timestamp = GetSystemTimeMicros(owner_.peer_);
|
||||||
frame.native_frame = native_frame;
|
|
||||||
for (const std::string &remote_id : connected_remote_ids) {
|
for (const std::string &remote_id : connected_remote_ids) {
|
||||||
SendVideoFrameToPeer(owner_.peer_, &frame, stream_id.c_str(),
|
SendVideoFrameToPeer(owner_.peer_, &frame, stream_id.c_str(),
|
||||||
remote_id.data(), remote_id.size());
|
remote_id.data(), remote_id.size());
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
/*
|
#ifndef CROSSDESK_GUI_RENDER_H_
|
||||||
* @Author: DI JUNKUN
|
#define CROSSDESK_GUI_RENDER_H_
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _RENDER_H_
|
|
||||||
#define _RENDER_H_
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -31,4 +25,4 @@ private:
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif // CROSSDESK_GUI_RENDER_H_
|
||||||
|
|||||||
@@ -119,9 +119,21 @@ struct SlintVideoPresenter::Impl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard lock(gl_mutex);
|
|
||||||
auto* opengl_renderer =
|
auto* opengl_renderer =
|
||||||
dynamic_cast<OpenGlVideoRenderer*>(&renderer);
|
dynamic_cast<OpenGlVideoRenderer*>(&renderer);
|
||||||
|
if (state == slint::RenderingState::AfterRendering && stream &&
|
||||||
|
opengl_renderer &&
|
||||||
|
(*stream)->get_native_video_enabled() &&
|
||||||
|
opengl_renderer->ShouldContinueRendering()) {
|
||||||
|
// Queue the next frame directly from the display callback. A
|
||||||
|
// 16/17 ms polling timer can drift across vsync and make Windows
|
||||||
|
// coalesce every second request, effectively capping video near
|
||||||
|
// 30 fps even when decode and submission stay near 60 fps.
|
||||||
|
(*stream)->window().request_redraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock(gl_mutex);
|
||||||
if (state == slint::RenderingState::RenderingSetup) {
|
if (state == slint::RenderingState::RenderingSetup) {
|
||||||
GLuint texture = 0;
|
GLuint texture = 0;
|
||||||
glGenTextures(1, &texture);
|
glGenTextures(1, &texture);
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
/*
|
#ifndef CROSSDESK_GUI_RENDERING_SLINT_VIDEO_PRESENTER_H_
|
||||||
* @Author: DI JUNKUN
|
#define CROSSDESK_GUI_RENDERING_SLINT_VIDEO_PRESENTER_H_
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _SLINT_VIDEO_PRESENTER_H_
|
|
||||||
#define _SLINT_VIDEO_PRESENTER_H_
|
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -78,4 +72,4 @@ private:
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif // CROSSDESK_GUI_RENDERING_SLINT_VIDEO_PRESENTER_H_
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
* @Author: DI JUNKUN
|
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _CURSOR_POSITION_H_
|
#ifndef _CURSOR_POSITION_H_
|
||||||
#define _CURSOR_POSITION_H_
|
#define _CURSOR_POSITION_H_
|
||||||
|
|
||||||
@@ -70,4 +64,4 @@ inline bool NormalizeCursorPosition(
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
* @Author: DI JUNKUN
|
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _CURSOR_STATE_PROVIDER_H_
|
#ifndef _CURSOR_STATE_PROVIDER_H_
|
||||||
#define _CURSOR_STATE_PROVIDER_H_
|
#define _CURSOR_STATE_PROVIDER_H_
|
||||||
|
|
||||||
@@ -36,4 +30,4 @@ class CursorStateProvider {
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: DI JUNKUN
|
* @Author: DI JUNKUN
|
||||||
* @Date: 2026-09-04
|
* @Date: 2026-02-28
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _DEVICE_PRESENCE_CACHE_H_
|
#ifndef CROSSDESK_GUI_DEVICE_PRESENCE_CACHE_H_
|
||||||
#define _DEVICE_PRESENCE_CACHE_H_
|
#define CROSSDESK_GUI_DEVICE_PRESENCE_CACHE_H_
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -38,4 +38,4 @@ private:
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif // CROSSDESK_GUI_DEVICE_PRESENCE_CACHE_H_
|
||||||
|
|||||||
@@ -141,10 +141,9 @@ int GuiRuntime::CreateConnectionPeer() {
|
|||||||
sizeof(params_.log_path) - 1);
|
sizeof(params_.log_path) - 1);
|
||||||
params_.log_path[sizeof(params_.log_path) - 1] = '\0';
|
params_.log_path[sizeof(params_.log_path) - 1] = '\0';
|
||||||
params_.hardware_acceleration = config_center_->IsHardwareVideoCodec();
|
params_.hardware_acceleration = config_center_->IsHardwareVideoCodec();
|
||||||
#if defined(_WIN32) || defined(__APPLE__)
|
#if defined(_WIN32)
|
||||||
// Windows renderers retain pooled CPU NV12 frames or CUDA device frames;
|
// Windows accepts ref-counted CPU NV12 and CUDA device NV12. The OpenGL
|
||||||
// macOS retains VideoToolbox CVPixelBuffers for direct Metal sampling. Both
|
// renderer retains the decoded frame until the UI thread has uploaded it.
|
||||||
// platforms fall back to a packed CPU copy when native upload is unavailable.
|
|
||||||
params_.native_video_output = true;
|
params_.native_video_output = true;
|
||||||
#else
|
#else
|
||||||
params_.native_video_output = false;
|
params_.native_video_output = false;
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
/*
|
#ifndef CROSSDESK_GUI_RUNTIME_H_
|
||||||
* @Author: DI JUNKUN
|
#define CROSSDESK_GUI_RUNTIME_H_
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _GUI_RUNTIME_H_
|
|
||||||
#define _GUI_RUNTIME_H_
|
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -104,4 +98,4 @@ class GuiRuntime : protected gui_detail::GuiState {
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif // CROSSDESK_GUI_RUNTIME_H_
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: DI JUNKUN
|
* Aggregate state visible to GuiRuntime and GuiApplication.
|
||||||
* @Date: 2026-09-04
|
*
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
* Definitions live beside their owners: SDL/window state in application and
|
||||||
|
* connection/session state in runtime. This header is intentionally only the
|
||||||
|
* composition point.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _GUI_STATE_H_
|
#ifndef CROSSDESK_GUI_STATE_H_
|
||||||
#define _GUI_STATE_H_
|
#define CROSSDESK_GUI_STATE_H_
|
||||||
|
|
||||||
#include "application/application_state.h"
|
#include "application/application_state.h"
|
||||||
#include "runtime/runtime_state.h"
|
#include "runtime/runtime_state.h"
|
||||||
@@ -16,4 +18,4 @@ struct GuiState : ApplicationState, RuntimeState {};
|
|||||||
|
|
||||||
} // namespace crossdesk::gui_detail
|
} // namespace crossdesk::gui_detail
|
||||||
|
|
||||||
#endif
|
#endif // CROSSDESK_GUI_STATE_H_
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
/*
|
#ifndef CROSSDESK_GUI_PEER_EVENT_HANDLER_H_
|
||||||
* @Author: DI JUNKUN
|
#define CROSSDESK_GUI_PEER_EVENT_HANDLER_H_
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _PEER_EVENT_HANDLER_H_
|
|
||||||
#define _PEER_EVENT_HANDLER_H_
|
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -53,4 +47,4 @@ private:
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif // CROSSDESK_GUI_PEER_EVENT_HANDLER_H_
|
||||||
|
|||||||
@@ -14,17 +14,22 @@ namespace {
|
|||||||
|
|
||||||
constexpr auto kBackgroundSnapshotInterval = std::chrono::seconds(1);
|
constexpr auto kBackgroundSnapshotInterval = std::chrono::seconds(1);
|
||||||
|
|
||||||
const XNativeVideoFrame* GetNativeVideoFrame(const XVideoFrame* frame) {
|
#if defined(_WIN32)
|
||||||
if (!frame || !frame->native_frame) {
|
const XWindowsVideoFrame* GetWindowsNativeFrame(const XVideoFrame* frame) {
|
||||||
|
if (!frame ||
|
||||||
|
frame->native_handle_type != XVideoFrameNativeHandleWindowsNv12 ||
|
||||||
|
!frame->native_handle) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
const auto* native = frame->native_frame;
|
const auto* native =
|
||||||
|
static_cast<const XWindowsVideoFrame*>(frame->native_handle);
|
||||||
return native->struct_size >=
|
return native->struct_size >=
|
||||||
static_cast<uint32_t>(sizeof(XNativeVideoFrame)) &&
|
static_cast<uint32_t>(sizeof(XWindowsVideoFrame)) &&
|
||||||
native->owner && native->copy_to_nv12
|
native->owner && native->copy_to_cpu
|
||||||
? native
|
? native
|
||||||
: nullptr;
|
: nullptr;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -52,10 +57,12 @@ void PeerEventHandler::OnReceiveVideoBuffer(
|
|||||||
if (native_renderer && native_renderer->IsActive()) {
|
if (native_renderer && native_renderer->IsActive()) {
|
||||||
VideoRenderer::SubmitResult submit_result =
|
VideoRenderer::SubmitResult submit_result =
|
||||||
VideoRenderer::SubmitResult::failed;
|
VideoRenderer::SubmitResult::failed;
|
||||||
if (GetNativeVideoFrame(video_frame)) {
|
#if defined(_WIN32)
|
||||||
submit_result = native_renderer->SubmitNativeFrame(
|
if (GetWindowsNativeFrame(video_frame)) {
|
||||||
remote_id, *video_frame->native_frame);
|
submit_result =
|
||||||
|
native_renderer->SubmitNativeFrame(remote_id, *video_frame);
|
||||||
} else
|
} else
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
submit_result = native_renderer->SubmitNv12(
|
submit_result = native_renderer->SubmitNv12(
|
||||||
remote_id, reinterpret_cast<const uint8_t*>(video_frame->data),
|
remote_id, reinterpret_cast<const uint8_t*>(video_frame->data),
|
||||||
@@ -109,12 +116,14 @@ void PeerEventHandler::OnReceiveVideoBuffer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t frame_size = video_frame->size;
|
size_t frame_size = video_frame->size;
|
||||||
const XNativeVideoFrame* native_frame =
|
#if defined(_WIN32)
|
||||||
GetNativeVideoFrame(video_frame);
|
const XWindowsVideoFrame* windows_native_frame =
|
||||||
if (native_frame) {
|
GetWindowsNativeFrame(video_frame);
|
||||||
frame_size = static_cast<size_t>(native_frame->width) *
|
if (windows_native_frame) {
|
||||||
native_frame->height * 3U / 2U;
|
frame_size = static_cast<size_t>(windows_native_frame->width) *
|
||||||
|
windows_native_frame->height * 3U / 2U;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (frame_size == 0) {
|
if (frame_size == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -128,13 +137,15 @@ void PeerEventHandler::OnReceiveVideoBuffer(
|
|||||||
props->back_frame_->resize(frame_size);
|
props->back_frame_->resize(frame_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (native_frame) {
|
#if defined(_WIN32)
|
||||||
if (native_frame->copy_to_nv12(
|
if (windows_native_frame) {
|
||||||
native_frame->owner, props->back_frame_->data(),
|
if (windows_native_frame->copy_to_cpu(
|
||||||
|
windows_native_frame->owner, props->back_frame_->data(),
|
||||||
props->back_frame_->size()) != 0) {
|
props->back_frame_->size()) != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
if (!video_frame->data || video_frame->size < frame_size) {
|
if (!video_frame->data || video_frame->size < frame_size) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
/*
|
#ifndef CROSSDESK_GUI_REMOTE_ACTION_CODEC_H_
|
||||||
* @Author: DI JUNKUN
|
#define CROSSDESK_GUI_REMOTE_ACTION_CODEC_H_
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _REMOTE_ACTION_CODEC_H_
|
|
||||||
#define _REMOTE_ACTION_CODEC_H_
|
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -20,4 +14,4 @@ void Free(RemoteAction &action);
|
|||||||
|
|
||||||
} // namespace crossdesk::remote_action_codec
|
} // namespace crossdesk::remote_action_codec
|
||||||
|
|
||||||
#endif
|
#endif // CROSSDESK_GUI_REMOTE_ACTION_CODEC_H_
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
* @Author: DI JUNKUN
|
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _REMOTE_SESSION_H_
|
#ifndef _REMOTE_SESSION_H_
|
||||||
#define _REMOTE_SESSION_H_
|
#define _REMOTE_SESSION_H_
|
||||||
|
|
||||||
@@ -180,4 +174,4 @@ using RemoteSessionPtr = std::shared_ptr<RemoteSession>;
|
|||||||
|
|
||||||
} // namespace crossdesk::gui_detail
|
} // namespace crossdesk::gui_detail
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
* @Author: DI JUNKUN
|
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _RUNTIME_STATE_H_
|
#ifndef _RUNTIME_STATE_H_
|
||||||
#define _RUNTIME_STATE_H_
|
#define _RUNTIME_STATE_H_
|
||||||
|
|
||||||
@@ -218,4 +212,4 @@ struct RuntimeState : InfrastructureState,
|
|||||||
|
|
||||||
} // namespace crossdesk::gui_detail
|
} // namespace crossdesk::gui_detail
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
/*
|
#ifndef CROSSDESK_GUI_UI_UI_LOCALIZATION_H_
|
||||||
* @Author: DI JUNKUN
|
#define CROSSDESK_GUI_UI_UI_LOCALIZATION_H_
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _UI_LOCALIZATION_H_
|
|
||||||
#define _UI_LOCALIZATION_H_
|
|
||||||
|
|
||||||
#include <slint.h>
|
#include <slint.h>
|
||||||
|
|
||||||
@@ -168,5 +162,4 @@ inline int ApplyStreamWindowStrings(
|
|||||||
|
|
||||||
} // namespace crossdesk::ui_localization
|
} // namespace crossdesk::ui_localization
|
||||||
|
|
||||||
|
#endif // CROSSDESK_GUI_UI_UI_LOCALIZATION_H_
|
||||||
#endif
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,10 +13,11 @@
|
|||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
// NV12 underlay for Slint's FemtoVG OpenGL renderer on Windows and Linux.
|
// NV12 underlay for Slint's FemtoVG OpenGL renderer on Windows and Linux.
|
||||||
// MiniRTC submits cross-platform native NV12 descriptors or tightly packed CPU
|
// MiniRTC submits either ref-counted Windows native NV12 frames or packed CPU
|
||||||
// fallback frames from its decode callback thread. CUDA frames rotate through
|
// NV12 fallback frames from its decode callback thread. CUDA-backed frames use
|
||||||
// fenced OpenGL upload resources; CPU descriptors expose their planes without
|
// CUDA/OpenGL interop; CPU-backed frames are retained without an intermediate
|
||||||
// an intermediate queue copy. The UI thread always consumes the newest frame.
|
// queue copy. The Slint UI thread updates Y and UV textures and performs color
|
||||||
|
// conversion and scaling before Slint draws its overlay.
|
||||||
class OpenGlVideoRenderer final : public VideoRenderer {
|
class OpenGlVideoRenderer final : public VideoRenderer {
|
||||||
public:
|
public:
|
||||||
OpenGlVideoRenderer();
|
OpenGlVideoRenderer();
|
||||||
@@ -32,6 +33,11 @@ public:
|
|||||||
bool IsReady() const override;
|
bool IsReady() const override;
|
||||||
bool IsActive() const override;
|
bool IsActive() const override;
|
||||||
|
|
||||||
|
// Keeps Slint's native surface on a display-driven redraw loop while decoded
|
||||||
|
// frames are arriving. This avoids timer/vsync phase drift that otherwise
|
||||||
|
// causes every second redraw request to be coalesced on Windows.
|
||||||
|
bool ShouldContinueRendering() const;
|
||||||
|
|
||||||
// Only the selected stream is queued for upload. Frames for other tabs are
|
// Only the selected stream is queued for upload. Frames for other tabs are
|
||||||
// retained by GuiRuntime at a throttled rate for thumbnail/tab restoration.
|
// retained by GuiRuntime at a throttled rate for thumbnail/tab restoration.
|
||||||
bool SetSelectedStream(std::string remote_id) override;
|
bool SetSelectedStream(std::string remote_id) override;
|
||||||
@@ -43,7 +49,7 @@ public:
|
|||||||
SubmitResult SubmitCachedNv12(std::string_view remote_id, const uint8_t *data,
|
SubmitResult SubmitCachedNv12(std::string_view remote_id, const uint8_t *data,
|
||||||
size_t size, int width, int height) override;
|
size_t size, int width, int height) override;
|
||||||
SubmitResult SubmitNativeFrame(std::string_view remote_id,
|
SubmitResult SubmitNativeFrame(std::string_view remote_id,
|
||||||
const XNativeVideoFrame &frame) override;
|
const XVideoFrame &frame) override;
|
||||||
|
|
||||||
// Draws below Slint while its OpenGL context is current. All dimensions are
|
// Draws below Slint while its OpenGL context is current. All dimensions are
|
||||||
// physical pixels in the window client area. corner_radius_pixels clips the
|
// physical pixels in the window client area. corner_radius_pixels clips the
|
||||||
|
|||||||
@@ -429,7 +429,7 @@ bool ScreenCapturerDrm::CaptureOutputFrame(const DrmOutput& output,
|
|||||||
|
|
||||||
if (emit_callback && callback_) {
|
if (emit_callback && callback_) {
|
||||||
callback_(nv12.data(), static_cast<int>(nv12.size()), capture_width,
|
callback_(nv12.data(), static_cast<int>(nv12.size()), capture_width,
|
||||||
capture_height, output.stream_id.c_str(), nullptr);
|
capture_height, output.stream_id.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
UnmapFramebuffer(mapped_ptr, mapped_size, prime_fd);
|
UnmapFramebuffer(mapped_ptr, mapped_size, prime_fd);
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ int ScreenCapturerLinux::Init(const int fps, cb_desktop_data cb) {
|
|||||||
fps_ = fps;
|
fps_ = fps;
|
||||||
callback_orig_ = std::move(cb);
|
callback_orig_ = std::move(cb);
|
||||||
callback_ = [this](unsigned char* data, int size, int width, int height,
|
callback_ = [this](unsigned char* data, int size, int width, int height,
|
||||||
const char* reported_stream_id,
|
const char* reported_stream_id) {
|
||||||
const XNativeVideoFrame* native_frame) {
|
|
||||||
const std::string mapped_stream_id = MapStreamId(reported_stream_id);
|
const std::string mapped_stream_id = MapStreamId(reported_stream_id);
|
||||||
if (mapped_stream_id.empty()) {
|
if (mapped_stream_id.empty()) {
|
||||||
if (!invalid_stream_id_logged_.exchange(true,
|
if (!invalid_stream_id_logged_.exchange(true,
|
||||||
@@ -65,8 +64,7 @@ int ScreenCapturerLinux::Init(const int fps, cb_desktop_data cb) {
|
|||||||
}
|
}
|
||||||
invalid_stream_id_logged_.store(false, std::memory_order_relaxed);
|
invalid_stream_id_logged_.store(false, std::memory_order_relaxed);
|
||||||
if (callback_orig_) {
|
if (callback_orig_) {
|
||||||
callback_orig_(data, size, width, height, mapped_stream_id.c_str(),
|
callback_orig_(data, size, width, height, mapped_stream_id.c_str());
|
||||||
native_frame);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1047,7 +1047,7 @@ void ScreenCapturerWayland::HandlePipeWireBuffer() {
|
|||||||
if (callback_) {
|
if (callback_) {
|
||||||
const std::string stream_id = MakeDisplayStreamId(0);
|
const std::string stream_id = MakeDisplayStreamId(0);
|
||||||
callback_(nv12.data(), static_cast<int>(nv12.size()), even_width,
|
callback_(nv12.data(), static_cast<int>(nv12.size()), even_width,
|
||||||
even_height, stream_id.c_str(), nullptr);
|
even_height, stream_id.c_str());
|
||||||
}
|
}
|
||||||
pipewire_last_frame_ms_.store(NowMs());
|
pipewire_last_frame_ms_.store(NowMs());
|
||||||
|
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ void ScreenCapturerX11::OnFrame() {
|
|||||||
if (callback_) {
|
if (callback_) {
|
||||||
const std::string stream_id = MakeDisplayStreamId(monitor_index);
|
const std::string stream_id = MakeDisplayStreamId(monitor_index);
|
||||||
callback_(nv12.data(), width_ * height_ * 3 / 2, width_, height_,
|
callback_(nv12.data(), width_ * height_ * 3 / 2, width_, height_,
|
||||||
stream_id.c_str(), nullptr);
|
stream_id.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
XDestroyImage(image);
|
XDestroyImage(image);
|
||||||
|
|||||||
@@ -12,11 +12,11 @@
|
|||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
// macOS video compositor used by CrossDesk only. VideoToolbox CVPixelBuffers
|
// macOS video compositor used by CrossDesk only. MiniRTC continues to expose
|
||||||
// are sampled directly through CVMetalTextureCache; packed CPU NV12 remains a
|
// tightly packed CPU NV12 frames; this renderer performs the single required
|
||||||
// fallback for software decoding and cached snapshots. AppKit attachment and
|
// copy into shared Metal buffers and converts NV12 to RGB in the fragment
|
||||||
// rendering are main-thread operations, while frame submission is safe from
|
// shader. AppKit attachment and rendering are main-thread operations, while
|
||||||
// MiniRTC's decode callback thread.
|
// SubmitNv12() is safe to call from MiniRTC's decode callback thread.
|
||||||
class MacMetalVideoRenderer final : public VideoRenderer {
|
class MacMetalVideoRenderer final : public VideoRenderer {
|
||||||
public:
|
public:
|
||||||
MacMetalVideoRenderer();
|
MacMetalVideoRenderer();
|
||||||
@@ -37,11 +37,6 @@ class MacMetalVideoRenderer final : public VideoRenderer {
|
|||||||
SubmitResult SubmitNv12(std::string_view remote_id, const uint8_t* data,
|
SubmitResult SubmitNv12(std::string_view remote_id, const uint8_t* data,
|
||||||
size_t size, int width, int height) override;
|
size_t size, int width, int height) override;
|
||||||
|
|
||||||
// Retains an IOSurface-backed VideoToolbox CVPixelBuffer until the Metal
|
|
||||||
// command buffer using it has completed.
|
|
||||||
SubmitResult SubmitNativeFrame(std::string_view remote_id,
|
|
||||||
const XNativeVideoFrame& frame) override;
|
|
||||||
|
|
||||||
// Seeds a newly selected stream from its retained CPU snapshot without
|
// Seeds a newly selected stream from its retained CPU snapshot without
|
||||||
// replacing a newer decoded frame that is already queued or rendering.
|
// replacing a newer decoded frame that is already queued or rendering.
|
||||||
SubmitResult SubmitCachedNv12(std::string_view remote_id,
|
SubmitResult SubmitCachedNv12(std::string_view remote_id,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "platform/macos/gui/metal_video_renderer.h"
|
#include "platform/macos/gui/metal_video_renderer.h"
|
||||||
|
|
||||||
#import <AppKit/AppKit.h>
|
#import <AppKit/AppKit.h>
|
||||||
#import <CoreVideo/CVMetalTextureCache.h>
|
|
||||||
#import <Metal/Metal.h>
|
#import <Metal/Metal.h>
|
||||||
#import <QuartzCore/CAMetalLayer.h>
|
#import <QuartzCore/CAMetalLayer.h>
|
||||||
|
|
||||||
@@ -195,74 +194,6 @@ namespace {
|
|||||||
|
|
||||||
constexpr size_t kFrameSlotCount = 3;
|
constexpr size_t kFrameSlotCount = 3;
|
||||||
|
|
||||||
class NativeVideoFrameRef {
|
|
||||||
public:
|
|
||||||
NativeVideoFrameRef() = default;
|
|
||||||
|
|
||||||
explicit NativeVideoFrameRef(const XNativeVideoFrame* frame) {
|
|
||||||
if (frame && frame->owner && frame->retain && frame->release) {
|
|
||||||
frame_ = *frame;
|
|
||||||
frame_.struct_size = sizeof(frame_);
|
|
||||||
frame_.retain(frame_.owner);
|
|
||||||
valid_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeVideoFrameRef(const NativeVideoFrameRef& other)
|
|
||||||
: NativeVideoFrameRef(other.Get()) {}
|
|
||||||
|
|
||||||
NativeVideoFrameRef(NativeVideoFrameRef&& other) noexcept
|
|
||||||
: frame_(other.frame_), valid_(std::exchange(other.valid_, false)) {
|
|
||||||
other.frame_ = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeVideoFrameRef& operator=(NativeVideoFrameRef other) noexcept {
|
|
||||||
Swap(other);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
~NativeVideoFrameRef() { Reset(); }
|
|
||||||
|
|
||||||
void Reset() {
|
|
||||||
if (valid_ && frame_.owner && frame_.release) {
|
|
||||||
frame_.release(frame_.owner);
|
|
||||||
}
|
|
||||||
frame_ = {};
|
|
||||||
valid_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Swap(NativeVideoFrameRef& other) noexcept {
|
|
||||||
std::swap(frame_, other.frame_);
|
|
||||||
std::swap(valid_, other.valid_);
|
|
||||||
}
|
|
||||||
|
|
||||||
const XNativeVideoFrame* Get() const {
|
|
||||||
return valid_ ? &frame_ : nullptr;
|
|
||||||
}
|
|
||||||
explicit operator bool() const { return valid_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
XNativeVideoFrame frame_{};
|
|
||||||
bool valid_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const XNativeVideoFrame* GetMacNativeFrame(
|
|
||||||
const XNativeVideoFrame& frame) {
|
|
||||||
const XNativeVideoFrame* native = &frame;
|
|
||||||
if (native->struct_size < static_cast<uint32_t>(sizeof(*native)) ||
|
|
||||||
native->type != XNativeVideoFrameCVPixelBuffer ||
|
|
||||||
!native->payload.cv_pixel_buffer || !native->owner || !native->retain ||
|
|
||||||
!native->release || !native->copy_to_nv12 || native->width == 0 ||
|
|
||||||
native->height == 0 || (native->width & 1U) != 0 ||
|
|
||||||
(native->height & 1U) != 0 ||
|
|
||||||
native->width > static_cast<uint32_t>(std::numeric_limits<int>::max()) ||
|
|
||||||
native->height >
|
|
||||||
static_cast<uint32_t>(std::numeric_limits<int>::max())) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return native;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slint's winit backend creates AppKit windows with transparent backing so
|
// Slint's winit backend creates AppKit windows with transparent backing so
|
||||||
// transparent Slint items can be composited. Keep the content out of the
|
// transparent Slint items can be composited. Keep the content out of the
|
||||||
// titlebar and choose whether AppKit's material or the semantic window color
|
// titlebar and choose whether AppKit's material or the semantic window color
|
||||||
@@ -331,9 +262,6 @@ struct FrameSlot {
|
|||||||
id<MTLBuffer> uv_buffer = nil;
|
id<MTLBuffer> uv_buffer = nil;
|
||||||
id<MTLTexture> y_texture = nil;
|
id<MTLTexture> y_texture = nil;
|
||||||
id<MTLTexture> uv_texture = nil;
|
id<MTLTexture> uv_texture = nil;
|
||||||
CVMetalTextureRef y_cv_texture = nullptr;
|
|
||||||
CVMetalTextureRef uv_cv_texture = nullptr;
|
|
||||||
NativeVideoFrameRef native_frame;
|
|
||||||
size_t y_stride = 0;
|
size_t y_stride = 0;
|
||||||
size_t uv_stride = 0;
|
size_t uv_stride = 0;
|
||||||
int width = 0;
|
int width = 0;
|
||||||
@@ -346,15 +274,6 @@ struct FrameSlot {
|
|||||||
void ReleaseResources() {
|
void ReleaseResources() {
|
||||||
y_texture = nil;
|
y_texture = nil;
|
||||||
uv_texture = nil;
|
uv_texture = nil;
|
||||||
if (y_cv_texture) {
|
|
||||||
CFRelease(y_cv_texture);
|
|
||||||
y_cv_texture = nullptr;
|
|
||||||
}
|
|
||||||
if (uv_cv_texture) {
|
|
||||||
CFRelease(uv_cv_texture);
|
|
||||||
uv_cv_texture = nullptr;
|
|
||||||
}
|
|
||||||
native_frame.Reset();
|
|
||||||
y_buffer = nil;
|
y_buffer = nil;
|
||||||
uv_buffer = nil;
|
uv_buffer = nil;
|
||||||
y_stride = 0;
|
y_stride = 0;
|
||||||
@@ -487,7 +406,6 @@ struct MacMetalVideoRenderer::Impl {
|
|||||||
id<MTLDevice> device = nil;
|
id<MTLDevice> device = nil;
|
||||||
id<MTLCommandQueue> command_queue = nil;
|
id<MTLCommandQueue> command_queue = nil;
|
||||||
id<MTLRenderPipelineState> pipeline = nil;
|
id<MTLRenderPipelineState> pipeline = nil;
|
||||||
CVMetalTextureCacheRef texture_cache = nullptr;
|
|
||||||
std::shared_ptr<SharedFrameState> frames =
|
std::shared_ptr<SharedFrameState> frames =
|
||||||
std::make_shared<SharedFrameState>();
|
std::make_shared<SharedFrameState>();
|
||||||
NSView* slint_view = nil;
|
NSView* slint_view = nil;
|
||||||
@@ -499,8 +417,6 @@ struct MacMetalVideoRenderer::Impl {
|
|||||||
uint64_t rendered_content_generation = 0;
|
uint64_t rendered_content_generation = 0;
|
||||||
std::shared_ptr<std::atomic<bool>> native_surface_needs_redraw =
|
std::shared_ptr<std::atomic<bool>> native_surface_needs_redraw =
|
||||||
std::make_shared<std::atomic<bool>>(true);
|
std::make_shared<std::atomic<bool>>(true);
|
||||||
std::atomic<bool> native_path_logged{false};
|
|
||||||
std::atomic<bool> native_fallback_logged{false};
|
|
||||||
|
|
||||||
Impl() {
|
Impl() {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
@@ -514,12 +430,6 @@ struct MacMetalVideoRenderer::Impl {
|
|||||||
LOG_ERROR("Metal video renderer unavailable: command queue creation failed");
|
LOG_ERROR("Metal video renderer unavailable: command queue creation failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const CVReturn cache_status = CVMetalTextureCacheCreate(
|
|
||||||
kCFAllocatorDefault, nullptr, device, nullptr, &texture_cache);
|
|
||||||
if (cache_status != kCVReturnSuccess || !texture_cache) {
|
|
||||||
LOG_WARN("Metal video renderer could not create CV texture cache: {}",
|
|
||||||
cache_status);
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* library_error = nil;
|
NSError* library_error = nil;
|
||||||
id<MTLLibrary> library =
|
id<MTLLibrary> library =
|
||||||
@@ -556,11 +466,6 @@ struct MacMetalVideoRenderer::Impl {
|
|||||||
~Impl() {
|
~Impl() {
|
||||||
DetachViews();
|
DetachViews();
|
||||||
frames.reset();
|
frames.reset();
|
||||||
if (texture_cache) {
|
|
||||||
CVMetalTextureCacheFlush(texture_cache, 0);
|
|
||||||
CFRelease(texture_cache);
|
|
||||||
texture_cache = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Ready() const {
|
bool Ready() const {
|
||||||
@@ -750,142 +655,6 @@ MacMetalVideoRenderer::SubmitResult MacMetalVideoRenderer::SubmitNv12(
|
|||||||
return SubmitNv12Internal(remote_id, data, size, width, height, true);
|
return SubmitNv12Internal(remote_id, data, size, width, height, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
MacMetalVideoRenderer::SubmitResult MacMetalVideoRenderer::SubmitNativeFrame(
|
|
||||||
std::string_view remote_id, const XNativeVideoFrame& frame) {
|
|
||||||
if (!IsReady()) {
|
|
||||||
return SubmitResult::failed;
|
|
||||||
}
|
|
||||||
const XNativeVideoFrame* native = GetMacNativeFrame(frame);
|
|
||||||
if (!native) {
|
|
||||||
return SubmitResult::failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard lock(impl_->frames->mutex);
|
|
||||||
if (impl_->frames->selected_stream != remote_id) {
|
|
||||||
return SubmitResult::not_selected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const int width = static_cast<int>(native->width);
|
|
||||||
const int height = static_cast<int>(native->height);
|
|
||||||
const auto submit_cpu_fallback = [&]() {
|
|
||||||
if (!impl_->native_fallback_logged.exchange(true,
|
|
||||||
std::memory_order_acq_rel)) {
|
|
||||||
LOG_WARN("macOS native CVPixelBuffer upload unavailable; using CPU NV12 "
|
|
||||||
"fallback");
|
|
||||||
}
|
|
||||||
const size_t y_size = static_cast<size_t>(width) * height;
|
|
||||||
std::vector<unsigned char> packed_nv12(y_size + y_size / 2);
|
|
||||||
if (native->copy_to_nv12(native->owner, packed_nv12.data(),
|
|
||||||
packed_nv12.size()) != 0) {
|
|
||||||
return SubmitResult::failed;
|
|
||||||
}
|
|
||||||
return SubmitNv12(remote_id, packed_nv12.data(), packed_nv12.size(), width,
|
|
||||||
height);
|
|
||||||
};
|
|
||||||
|
|
||||||
CVPixelBufferRef pixel_buffer =
|
|
||||||
static_cast<CVPixelBufferRef>(native->payload.cv_pixel_buffer);
|
|
||||||
const OSType pixel_format = CVPixelBufferGetPixelFormatType(pixel_buffer);
|
|
||||||
if (!CVPixelBufferIsPlanar(pixel_buffer) ||
|
|
||||||
CVPixelBufferGetPlaneCount(pixel_buffer) < 2 ||
|
|
||||||
CVPixelBufferGetWidth(pixel_buffer) != native->width ||
|
|
||||||
CVPixelBufferGetHeight(pixel_buffer) != native->height ||
|
|
||||||
(pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange &&
|
|
||||||
pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)) {
|
|
||||||
return submit_cpu_fallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeVideoFrameRef retained(native);
|
|
||||||
if (!retained || !impl_->texture_cache) {
|
|
||||||
return submit_cpu_fallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
CVMetalTextureRef y_cv_texture = nullptr;
|
|
||||||
CVReturn y_status = CVMetalTextureCacheCreateTextureFromImage(
|
|
||||||
kCFAllocatorDefault, impl_->texture_cache, pixel_buffer, nullptr,
|
|
||||||
MTLPixelFormatR8Unorm, native->width, native->height, 0,
|
|
||||||
&y_cv_texture);
|
|
||||||
if (y_status != kCVReturnSuccess || !y_cv_texture) {
|
|
||||||
return submit_cpu_fallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
CVMetalTextureRef uv_cv_texture = nullptr;
|
|
||||||
CVReturn uv_status = CVMetalTextureCacheCreateTextureFromImage(
|
|
||||||
kCFAllocatorDefault, impl_->texture_cache, pixel_buffer, nullptr,
|
|
||||||
MTLPixelFormatRG8Unorm, native->width / 2U, native->height / 2U, 1,
|
|
||||||
&uv_cv_texture);
|
|
||||||
if (uv_status != kCVReturnSuccess || !uv_cv_texture) {
|
|
||||||
CFRelease(y_cv_texture);
|
|
||||||
return submit_cpu_fallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
id<MTLTexture> y_texture = CVMetalTextureGetTexture(y_cv_texture);
|
|
||||||
id<MTLTexture> uv_texture = CVMetalTextureGetTexture(uv_cv_texture);
|
|
||||||
if (y_texture == nil || uv_texture == nil) {
|
|
||||||
CFRelease(y_cv_texture);
|
|
||||||
CFRelease(uv_cv_texture);
|
|
||||||
return submit_cpu_fallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& frames = *impl_->frames;
|
|
||||||
std::lock_guard lock(frames.mutex);
|
|
||||||
if (frames.selected_stream != remote_id) {
|
|
||||||
CFRelease(y_cv_texture);
|
|
||||||
CFRelease(uv_cv_texture);
|
|
||||||
return SubmitResult::not_selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Latest-frame-only queue: replace an unconsumed decoded frame to bound
|
|
||||||
// latency, while never recycling a frame still referenced by the GPU.
|
|
||||||
FrameSlot* target = nullptr;
|
|
||||||
for (auto& slot : frames.slots) {
|
|
||||||
if (slot.use == SlotUse::pending) {
|
|
||||||
target = &slot;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!target) {
|
|
||||||
uint64_t oldest_sequence = std::numeric_limits<uint64_t>::max();
|
|
||||||
for (auto& slot : frames.slots) {
|
|
||||||
if (slot.use != SlotUse::available) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!slot.valid) {
|
|
||||||
target = &slot;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (slot.sequence < oldest_sequence) {
|
|
||||||
oldest_sequence = slot.sequence;
|
|
||||||
target = &slot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!target) {
|
|
||||||
CFRelease(y_cv_texture);
|
|
||||||
CFRelease(uv_cv_texture);
|
|
||||||
return SubmitResult::dropped;
|
|
||||||
}
|
|
||||||
|
|
||||||
target->ReleaseResources();
|
|
||||||
target->y_cv_texture = y_cv_texture;
|
|
||||||
target->uv_cv_texture = uv_cv_texture;
|
|
||||||
target->y_texture = y_texture;
|
|
||||||
target->uv_texture = uv_texture;
|
|
||||||
target->native_frame = std::move(retained);
|
|
||||||
target->width = width;
|
|
||||||
target->height = height;
|
|
||||||
target->remote_id.assign(remote_id);
|
|
||||||
target->sequence = frames.next_sequence++;
|
|
||||||
target->use = SlotUse::pending;
|
|
||||||
target->valid = true;
|
|
||||||
if (!impl_->native_path_logged.exchange(true, std::memory_order_acq_rel)) {
|
|
||||||
LOG_INFO("macOS VideoToolbox-to-Metal native NV12 path enabled");
|
|
||||||
}
|
|
||||||
return SubmitResult::submitted;
|
|
||||||
}
|
|
||||||
|
|
||||||
MacMetalVideoRenderer::SubmitResult MacMetalVideoRenderer::SubmitCachedNv12(
|
MacMetalVideoRenderer::SubmitResult MacMetalVideoRenderer::SubmitCachedNv12(
|
||||||
std::string_view remote_id, const uint8_t* data, size_t size, int width,
|
std::string_view remote_id, const uint8_t* data, size_t size, int width,
|
||||||
int height) {
|
int height) {
|
||||||
@@ -1276,57 +1045,40 @@ bool MacMetalVideoRenderer::CopyLatestNv12(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeVideoFrameRef native_frame;
|
std::lock_guard lock(impl_->frames->mutex);
|
||||||
{
|
const FrameSlot* newest = nullptr;
|
||||||
std::lock_guard lock(impl_->frames->mutex);
|
for (const auto& slot : impl_->frames->slots) {
|
||||||
const FrameSlot* newest = nullptr;
|
if (!slot.valid || slot.remote_id != remote_id || slot.y_buffer == nil ||
|
||||||
for (const auto& slot : impl_->frames->slots) {
|
slot.uv_buffer == nil || slot.sequence == 0) {
|
||||||
if (!slot.valid || slot.remote_id != remote_id || slot.sequence == 0 ||
|
continue;
|
||||||
(!slot.native_frame &&
|
|
||||||
(slot.y_buffer == nil || slot.uv_buffer == nil))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (newest == nullptr || slot.sequence > newest->sequence) {
|
|
||||||
newest = &slot;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (newest == nullptr || newest->width <= 0 || newest->height <= 0) {
|
if (newest == nullptr || slot.sequence > newest->sequence) {
|
||||||
return false;
|
newest = &slot;
|
||||||
}
|
|
||||||
|
|
||||||
*width = newest->width;
|
|
||||||
*height = newest->height;
|
|
||||||
native_frame = newest->native_frame;
|
|
||||||
if (!native_frame) {
|
|
||||||
const size_t y_size =
|
|
||||||
static_cast<size_t>(newest->width) * newest->height;
|
|
||||||
output->resize(y_size + y_size / 2);
|
|
||||||
auto* y_source =
|
|
||||||
static_cast<const uint8_t*>(newest->y_buffer.contents);
|
|
||||||
auto* uv_source =
|
|
||||||
static_cast<const uint8_t*>(newest->uv_buffer.contents);
|
|
||||||
for (int row = 0; row < newest->height; ++row) {
|
|
||||||
std::memcpy(output->data() +
|
|
||||||
static_cast<size_t>(row) * newest->width,
|
|
||||||
y_source + static_cast<size_t>(row) * newest->y_stride,
|
|
||||||
newest->width);
|
|
||||||
}
|
|
||||||
uint8_t* uv_destination = output->data() + y_size;
|
|
||||||
for (int row = 0; row < newest->height / 2; ++row) {
|
|
||||||
std::memcpy(uv_destination +
|
|
||||||
static_cast<size_t>(row) * newest->width,
|
|
||||||
uv_source + static_cast<size_t>(row) * newest->uv_stride,
|
|
||||||
newest->width);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (newest == nullptr || newest->width <= 0 || newest->height <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const XNativeVideoFrame* native = native_frame.Get();
|
const size_t y_size =
|
||||||
const size_t y_size = static_cast<size_t>(native->width) * native->height;
|
static_cast<size_t>(newest->width) * newest->height;
|
||||||
output->resize(y_size + y_size / 2);
|
output->resize(y_size + y_size / 2);
|
||||||
return native->copy_to_nv12(native->owner, output->data(), output->size()) ==
|
auto* y_source = static_cast<const uint8_t*>(newest->y_buffer.contents);
|
||||||
0;
|
auto* uv_source = static_cast<const uint8_t*>(newest->uv_buffer.contents);
|
||||||
|
for (int row = 0; row < newest->height; ++row) {
|
||||||
|
std::memcpy(output->data() + static_cast<size_t>(row) * newest->width,
|
||||||
|
y_source + static_cast<size_t>(row) * newest->y_stride,
|
||||||
|
newest->width);
|
||||||
|
}
|
||||||
|
uint8_t* uv_destination = output->data() + y_size;
|
||||||
|
for (int row = 0; row < newest->height / 2; ++row) {
|
||||||
|
std::memcpy(uv_destination + static_cast<size_t>(row) * newest->width,
|
||||||
|
uv_source + static_cast<size_t>(row) * newest->uv_stride,
|
||||||
|
newest->width);
|
||||||
|
}
|
||||||
|
*width = newest->width;
|
||||||
|
*height = newest->height;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|||||||
@@ -42,65 +42,6 @@ static std::string NSErrorToString(NSError *error) {
|
|||||||
return description ? description : "";
|
return description ? description : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
static void RetainCapturedPixelBuffer(void *owner) {
|
|
||||||
if (owner) {
|
|
||||||
CVPixelBufferRetain(static_cast<CVPixelBufferRef>(owner));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ReleaseCapturedPixelBuffer(void *owner) {
|
|
||||||
if (owner) {
|
|
||||||
CVPixelBufferRelease(static_cast<CVPixelBufferRef>(owner));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int CopyCapturedPixelBufferToNv12(void *owner, uint8_t *destination,
|
|
||||||
size_t destination_size) {
|
|
||||||
auto pixel_buffer = static_cast<CVPixelBufferRef>(owner);
|
|
||||||
if (!pixel_buffer || !destination ||
|
|
||||||
!CVPixelBufferIsPlanar(pixel_buffer) ||
|
|
||||||
CVPixelBufferGetPlaneCount(pixel_buffer) < 2) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
const OSType pixel_format = CVPixelBufferGetPixelFormatType(pixel_buffer);
|
|
||||||
if (pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange &&
|
|
||||||
pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t width = CVPixelBufferGetWidth(pixel_buffer);
|
|
||||||
const size_t height = CVPixelBufferGetHeight(pixel_buffer);
|
|
||||||
const size_t required_size = width * height * 3U / 2U;
|
|
||||||
if (destination_size < required_size ||
|
|
||||||
CVPixelBufferLockBaseAddress(pixel_buffer,
|
|
||||||
kCVPixelBufferLock_ReadOnly) !=
|
|
||||||
kCVReturnSuccess) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto *y_plane = static_cast<const uint8_t *>(
|
|
||||||
CVPixelBufferGetBaseAddressOfPlane(pixel_buffer, 0));
|
|
||||||
const auto *uv_plane = static_cast<const uint8_t *>(
|
|
||||||
CVPixelBufferGetBaseAddressOfPlane(pixel_buffer, 1));
|
|
||||||
const size_t y_stride = CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 0);
|
|
||||||
const size_t uv_stride = CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 1);
|
|
||||||
int result = 0;
|
|
||||||
if (!y_plane || !uv_plane || y_stride < width || uv_stride < width) {
|
|
||||||
result = -1;
|
|
||||||
} else {
|
|
||||||
for (size_t row = 0; row < height; ++row) {
|
|
||||||
std::memcpy(destination + row * width, y_plane + row * y_stride, width);
|
|
||||||
}
|
|
||||||
uint8_t *uv_destination = destination + width * height;
|
|
||||||
for (size_t row = 0; row < height / 2U; ++row) {
|
|
||||||
std::memcpy(uv_destination + row * width,
|
|
||||||
uv_plane + row * uv_stride, width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CVPixelBufferUnlockBaseAddress(pixel_buffer, kCVPixelBufferLock_ReadOnly);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture
|
// The ScreenCaptureKit API was available in macOS 12.3, but full-screen capture
|
||||||
// was reported to be broken before macOS 13 - see http://crbug.com/40234870.
|
// was reported to be broken before macOS 13 - see http://crbug.com/40234870.
|
||||||
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
|
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
|
||||||
@@ -151,11 +92,14 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
|||||||
private:
|
private:
|
||||||
std::vector<DisplayInfo> display_info_list_;
|
std::vector<DisplayInfo> display_info_list_;
|
||||||
std::map<int, CGDirectDisplayID> display_id_map_;
|
std::map<int, CGDirectDisplayID> display_id_map_;
|
||||||
|
unsigned char *nv12_frame_ = nullptr;
|
||||||
|
size_t nv12_frame_size_ = 0;
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
int fps_ = 60;
|
int fps_ = 60;
|
||||||
bool show_cursor_ = false;
|
bool show_cursor_ = false;
|
||||||
bool capture_requested_ = false;
|
bool capture_requested_ = false;
|
||||||
bool invalid_stream_id_logged_ = false;
|
bool invalid_stream_id_logged_ = false;
|
||||||
bool native_output_logged_ = false;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be
|
// Called by SckHelper when shareable content is returned by ScreenCaptureKit. `content` will be
|
||||||
@@ -175,8 +119,8 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
|||||||
// object. The helper may outlive this C++ instance, if a completion-handler is passed to
|
// object. The helper may outlive this C++ instance, if a completion-handler is passed to
|
||||||
// ScreenCaptureKit APIs and the C++ object is deleted before the handler executes.
|
// ScreenCaptureKit APIs and the C++ object is deleted before the handler executes.
|
||||||
SckHelper *__strong helper_ = nil;
|
SckHelper *__strong helper_ = nil;
|
||||||
// Callback for returning captured frames to the caller. ScreenCaptureKit may
|
// Callback for returning captured frames, or errors, to the caller. Only used on the caller's
|
||||||
// invoke it on its sample-handler queue.
|
// thread.
|
||||||
cb_desktop_data _on_data = nullptr;
|
cb_desktop_data _on_data = nullptr;
|
||||||
// Signals that a permanent error occurred. This may be set on any thread, and is read by
|
// Signals that a permanent error occurred. This may be set on any thread, and is read by
|
||||||
// CaptureFrame() which runs on the caller's thread.
|
// CaptureFrame() which runs on the caller's thread.
|
||||||
@@ -300,6 +244,12 @@ ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
|
|||||||
|
|
||||||
display_info_list_.clear();
|
display_info_list_.clear();
|
||||||
display_id_map_.clear();
|
display_id_map_.clear();
|
||||||
|
|
||||||
|
if (nv12_frame_) {
|
||||||
|
delete[] nv12_frame_;
|
||||||
|
nv12_frame_ = nullptr;
|
||||||
|
nv12_frame_size_ = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
||||||
@@ -312,7 +262,6 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
|||||||
fps_ = fps > 0 ? fps : 60;
|
fps_ = fps > 0 ? fps : 60;
|
||||||
display_info_list_.clear();
|
display_info_list_.clear();
|
||||||
display_id_map_.clear();
|
display_id_map_.clear();
|
||||||
native_output_logged_ = false;
|
|
||||||
|
|
||||||
if (@available(macOS 10.15, *)) {
|
if (@available(macOS 10.15, *)) {
|
||||||
bool has_permission = CGPreflightScreenCaptureAccess();
|
bool has_permission = CGPreflightScreenCaptureAccess();
|
||||||
@@ -647,69 +596,81 @@ void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t width = CVPixelBufferGetWidth(pixelBuffer);
|
size_t width = CVPixelBufferGetWidth(pixelBuffer);
|
||||||
const size_t height = CVPixelBufferGetHeight(pixelBuffer);
|
size_t height = CVPixelBufferGetHeight(pixelBuffer);
|
||||||
const OSType pixel_format = CVPixelBufferGetPixelFormatType(pixelBuffer);
|
if (width == 0 || height == 0 || CVPixelBufferGetPlaneCount(pixelBuffer) < 2) {
|
||||||
if (width == 0 || height == 0 || (width & 1U) != 0 ||
|
LOG_ERROR("Invalid CVPixelBuffer: width={}, height={}, planes={}", width, height,
|
||||||
(height & 1U) != 0 || !CVPixelBufferIsPlanar(pixelBuffer) ||
|
|
||||||
CVPixelBufferGetPlaneCount(pixelBuffer) < 2 ||
|
|
||||||
(pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange &&
|
|
||||||
pixel_format != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)) {
|
|
||||||
LOG_ERROR("Invalid CVPixelBuffer: width={}, height={}, format={}, planes={}",
|
|
||||||
width, height, pixel_format,
|
|
||||||
CVPixelBufferGetPlaneCount(pixelBuffer));
|
CVPixelBufferGetPlaneCount(pixelBuffer));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t required_size = width * height * 3U / 2U;
|
CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
if (required_size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
|
if (status != kCVReturnSuccess) {
|
||||||
LOG_ERROR("Captured frame is too large: {} bytes", required_size);
|
LOG_ERROR("Failed to lock CVPixelBuffer base address: %d", status);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cb_desktop_data callback;
|
size_t required_size = width * height * 3 / 2;
|
||||||
std::string stream_id;
|
if (required_size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
|
||||||
bool log_native_output = false;
|
LOG_ERROR("Captured frame is too large: {} bytes", required_size);
|
||||||
{
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
std::lock_guard<std::mutex> lock(lock_);
|
return;
|
||||||
if (!_on_data || !capture_requested_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
stream_id = ResolveDisplayStreamId(current_stream_id_.c_str(),
|
|
||||||
display_info_list_.size(),
|
|
||||||
current_monitor_index_);
|
|
||||||
if (stream_id.empty()) {
|
|
||||||
if (!invalid_stream_id_logged_) {
|
|
||||||
LOG_ERROR("Dropping macOS frames without a registered stream id, "
|
|
||||||
"display_id={}",
|
|
||||||
current_display_);
|
|
||||||
invalid_stream_id_logged_ = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
invalid_stream_id_logged_ = false;
|
|
||||||
callback = _on_data;
|
|
||||||
if (!native_output_logged_) {
|
|
||||||
native_output_logged_ = true;
|
|
||||||
log_native_output = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XNativeVideoFrame native_frame{};
|
std::lock_guard<std::mutex> lock(lock_);
|
||||||
native_frame.struct_size = sizeof(native_frame);
|
if (!_on_data || !capture_requested_) {
|
||||||
native_frame.type = XNativeVideoFrameCVPixelBuffer;
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
native_frame.width = static_cast<uint32_t>(width);
|
return;
|
||||||
native_frame.height = static_cast<uint32_t>(height);
|
|
||||||
native_frame.payload.cv_pixel_buffer = pixelBuffer;
|
|
||||||
native_frame.owner = pixelBuffer;
|
|
||||||
native_frame.retain = &RetainCapturedPixelBuffer;
|
|
||||||
native_frame.release = &ReleaseCapturedPixelBuffer;
|
|
||||||
native_frame.copy_to_nv12 = &CopyCapturedPixelBufferToNv12;
|
|
||||||
if (log_native_output) {
|
|
||||||
LOG_INFO("macOS ScreenCaptureKit native CVPixelBuffer output enabled");
|
|
||||||
}
|
}
|
||||||
callback(nullptr, static_cast<int>(required_size), static_cast<int>(width),
|
|
||||||
static_cast<int>(height), stream_id.c_str(), &native_frame);
|
if (!nv12_frame_ || nv12_frame_size_ < required_size) {
|
||||||
|
delete[] nv12_frame_;
|
||||||
|
nv12_frame_ = new unsigned char[required_size];
|
||||||
|
nv12_frame_size_ = required_size;
|
||||||
|
}
|
||||||
|
width_ = static_cast<int>(width);
|
||||||
|
height_ = static_cast<int>(height);
|
||||||
|
|
||||||
|
void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
|
||||||
|
size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
|
||||||
|
|
||||||
|
void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
|
||||||
|
size_t stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
|
||||||
|
if (!base_y || !base_uv || stride_y < width || stride_uv < width) {
|
||||||
|
LOG_ERROR("Invalid CVPixelBuffer planes: base_y={}, base_uv={}, stride_y={}, stride_uv={}",
|
||||||
|
base_y != nullptr, base_uv != nullptr, stride_y, stride_uv);
|
||||||
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char *dst_y = nv12_frame_;
|
||||||
|
for (size_t row = 0; row < height; ++row) {
|
||||||
|
memcpy(dst_y + row * width, static_cast<unsigned char *>(base_y) + row * stride_y, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char *dst_uv = nv12_frame_ + width * height;
|
||||||
|
for (size_t row = 0; row < height / 2; ++row) {
|
||||||
|
memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string stream_id = ResolveDisplayStreamId(
|
||||||
|
current_stream_id_.c_str(), display_info_list_.size(),
|
||||||
|
current_monitor_index_);
|
||||||
|
if (stream_id.empty()) {
|
||||||
|
if (!invalid_stream_id_logged_) {
|
||||||
|
LOG_ERROR("Dropping macOS frames without a registered stream id, "
|
||||||
|
"display_id={}",
|
||||||
|
current_display_);
|
||||||
|
invalid_stream_id_logged_ = true;
|
||||||
|
}
|
||||||
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
invalid_stream_id_logged_ = false;
|
||||||
|
_on_data(nv12_frame_, static_cast<int>(required_size), static_cast<int>(width),
|
||||||
|
static_cast<int>(height), stream_id.c_str());
|
||||||
|
|
||||||
|
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
|
void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
/*
|
|
||||||
* @Author: DI JUNKUN
|
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _SYSTEM_INFO_H_
|
|
||||||
#define _SYSTEM_INFO_H_
|
|
||||||
|
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
|
||||||
#include <ifaddrs.h>
|
#include <ifaddrs.h>
|
||||||
@@ -62,6 +53,3 @@ std::string GetHostName() {
|
|||||||
bool IsWaylandSession() { return false; }
|
bool IsWaylandSession() { return false; }
|
||||||
|
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -58,10 +58,8 @@ public:
|
|||||||
virtual SubmitResult SubmitCachedNv12(std::string_view remote_id,
|
virtual SubmitResult SubmitCachedNv12(std::string_view remote_id,
|
||||||
const uint8_t* data, size_t size,
|
const uint8_t* data, size_t size,
|
||||||
int width, int height) = 0;
|
int width, int height) = 0;
|
||||||
// Retains a platform-native decoded frame when the backend supports its
|
|
||||||
// storage and handle types. Unsupported backends keep the default fallback.
|
|
||||||
virtual SubmitResult SubmitNativeFrame(std::string_view,
|
virtual SubmitResult SubmitNativeFrame(std::string_view,
|
||||||
const XNativeVideoFrame&) {
|
const XVideoFrame&) {
|
||||||
return SubmitResult::failed;
|
return SubmitResult::failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ void ScreenCapturerDxgi::CaptureLoop() {
|
|||||||
if (idx >= 0 && idx < static_cast<int>(display_info_list_.size())) {
|
if (idx >= 0 && idx < static_cast<int>(display_info_list_.size())) {
|
||||||
const std::string stream_id = MakeDisplayStreamId(idx);
|
const std::string stream_id = MakeDisplayStreamId(idx);
|
||||||
callback_(nv12_frame_, nv12_size, even_width, even_height,
|
callback_(nv12_frame_, nv12_size, even_width, even_height,
|
||||||
stream_id.c_str(), nullptr);
|
stream_id.c_str());
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR("DXGI: CaptureLoop invalid monitor_index {} (list size {})",
|
LOG_ERROR("DXGI: CaptureLoop invalid monitor_index {} (list size {})",
|
||||||
idx, display_info_list_.size());
|
idx, display_info_list_.size());
|
||||||
|
|||||||
@@ -212,8 +212,7 @@ void ScreenCapturerGdi::CaptureLoop() {
|
|||||||
|
|
||||||
if (callback_) {
|
if (callback_) {
|
||||||
const std::string stream_id = MakeDisplayStreamId(idx);
|
const std::string stream_id = MakeDisplayStreamId(idx);
|
||||||
callback_(nv12_frame_, nv12_size, width, height, stream_id.c_str(),
|
callback_(nv12_frame_, nv12_size, width, height, stream_id.c_str());
|
||||||
nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectObject(mem_dc, old);
|
SelectObject(mem_dc, old);
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
|
|||||||
|
|
||||||
const std::string stream_id = MakeDisplayStreamId(id);
|
const std::string stream_id = MakeDisplayStreamId(id);
|
||||||
on_data_(nv12_frame_, nv12_size, even_width, even_height,
|
on_data_(nv12_frame_, nv12_size, even_width, even_height,
|
||||||
stream_id.c_str(), nullptr);
|
stream_id.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <display_stream_id.h>
|
#include <display_stream_id.h>
|
||||||
#include "captured_nv12_frame.h"
|
|
||||||
#include "interactive_state.h"
|
#include "interactive_state.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "screen_capturer_dxgi.h"
|
#include "screen_capturer_dxgi.h"
|
||||||
@@ -404,17 +403,8 @@ ScreenCapturerWin::~ScreenCapturerWin() { Destroy(); }
|
|||||||
int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
|
int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
|
||||||
fps_ = fps;
|
fps_ = fps;
|
||||||
cb_orig_ = cb;
|
cb_orig_ = cb;
|
||||||
native_output_logged_.store(false, std::memory_order_relaxed);
|
|
||||||
native_output_error_logged_.store(false, std::memory_order_relaxed);
|
|
||||||
try {
|
|
||||||
native_frame_pool_ = CapturedNv12FramePool::Create();
|
|
||||||
} catch (...) {
|
|
||||||
LOG_ERROR("Windows capturer: failed to create native NV12 frame pool");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
cb_ = [this](unsigned char* data, int size, int w, int h,
|
cb_ = [this](unsigned char* data, int size, int w, int h,
|
||||||
const char* reported_stream_id,
|
const char* reported_stream_id) {
|
||||||
const XNativeVideoFrame* native_frame) {
|
|
||||||
if (secure_desktop_capture_active_.load(std::memory_order_relaxed)) {
|
if (secure_desktop_capture_active_.load(std::memory_order_relaxed)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -460,8 +450,7 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
|
|||||||
"elapsed_ms={}",
|
"elapsed_ms={}",
|
||||||
raw_stream_id, mapped_stream_id, w, h, size, elapsed_ms);
|
raw_stream_id, mapped_stream_id, w, h, size, elapsed_ms);
|
||||||
}
|
}
|
||||||
EmitCapturedFrame(data, size, w, h, mapped_stream_id.c_str(),
|
if (cb_orig_) cb_orig_(data, size, w, h, mapped_stream_id.c_str());
|
||||||
native_frame);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
@@ -524,52 +513,9 @@ int ScreenCapturerWin::Destroy() {
|
|||||||
stream_id_alias_.clear();
|
stream_id_alias_.clear();
|
||||||
handle_to_canonical_index_.clear();
|
handle_to_canonical_index_.clear();
|
||||||
}
|
}
|
||||||
native_frame_pool_.reset();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScreenCapturerWin::EmitCapturedFrame(
|
|
||||||
unsigned char* data, int size, int width, int height,
|
|
||||||
const char* stream_id, const XNativeVideoFrame* native_frame) {
|
|
||||||
if (!cb_orig_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (native_frame) {
|
|
||||||
if (!native_output_logged_.exchange(true, std::memory_order_relaxed)) {
|
|
||||||
LOG_INFO("Windows capturer native frame output enabled (type={})",
|
|
||||||
static_cast<uint32_t>(native_frame->type));
|
|
||||||
}
|
|
||||||
cb_orig_(data, size, width, height, stream_id, native_frame);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CapturedNv12Frame* owned_frame = nullptr;
|
|
||||||
if (native_frame_pool_ && data && size > 0 && width > 0 && height > 0) {
|
|
||||||
owned_frame = native_frame_pool_->CopyFrom(
|
|
||||||
data, static_cast<size_t>(size), static_cast<uint32_t>(width),
|
|
||||||
static_cast<uint32_t>(height));
|
|
||||||
}
|
|
||||||
if (!owned_frame) {
|
|
||||||
if (!native_output_error_logged_.exchange(true,
|
|
||||||
std::memory_order_relaxed)) {
|
|
||||||
LOG_WARN(
|
|
||||||
"Windows capturer could not retain a native NV12 frame; falling "
|
|
||||||
"back to copied CPU input (size={}x{}, bytes={})",
|
|
||||||
width, height, size);
|
|
||||||
}
|
|
||||||
cb_orig_(data, size, width, height, stream_id, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!native_output_logged_.exchange(true, std::memory_order_relaxed)) {
|
|
||||||
LOG_INFO("Windows capturer native NV12 output enabled");
|
|
||||||
}
|
|
||||||
cb_orig_(nullptr, static_cast<int>(owned_frame->Size()), width, height,
|
|
||||||
stream_id, owned_frame->Descriptor());
|
|
||||||
owned_frame->Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
int ScreenCapturerWin::Start(bool show_cursor) {
|
int ScreenCapturerWin::Start(bool show_cursor) {
|
||||||
if (!impl_) return -1;
|
if (!impl_) return -1;
|
||||||
if (running_.load(std::memory_order_relaxed)) {
|
if (running_.load(std::memory_order_relaxed)) {
|
||||||
@@ -1315,11 +1261,9 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
|
|||||||
ReadSecureDesktopSharedFrame(
|
ReadSecureDesktopSharedFrame(
|
||||||
static_cast<DWORD>(frame_interval_ms + 20), &secure_frame,
|
static_cast<DWORD>(frame_interval_ms + 20), &secure_frame,
|
||||||
&captured_width, &captured_height, &error_message)) {
|
&captured_width, &captured_height, &error_message)) {
|
||||||
if (!secure_frame.empty()) {
|
if (cb_orig_ && !secure_frame.empty()) {
|
||||||
EmitCapturedFrame(secure_frame.data(),
|
cb_orig_(secure_frame.data(), static_cast<int>(secure_frame.size()),
|
||||||
static_cast<int>(secure_frame.size()),
|
captured_width, captured_height, display_name.c_str());
|
||||||
captured_width, captured_height,
|
|
||||||
display_name.c_str());
|
|
||||||
}
|
}
|
||||||
frame_delivered = true;
|
frame_delivered = true;
|
||||||
}
|
}
|
||||||
@@ -1331,11 +1275,9 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
|
|||||||
status.interactive_desktop,
|
status.interactive_desktop,
|
||||||
&secure_frame, &captured_width,
|
&secure_frame, &captured_width,
|
||||||
&captured_height, &error_message)) {
|
&captured_height, &error_message)) {
|
||||||
if (!secure_frame.empty()) {
|
if (cb_orig_ && !secure_frame.empty()) {
|
||||||
EmitCapturedFrame(secure_frame.data(),
|
cb_orig_(secure_frame.data(), static_cast<int>(secure_frame.size()),
|
||||||
static_cast<int>(secure_frame.size()),
|
captured_width, captured_height, display_name.c_str());
|
||||||
captured_width, captured_height,
|
|
||||||
display_name.c_str());
|
|
||||||
}
|
}
|
||||||
frame_delivered = true;
|
frame_delivered = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -23,8 +22,6 @@
|
|||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
class CapturedNv12FramePool;
|
|
||||||
|
|
||||||
class ScreenCapturerWin : public ScreenCapturer {
|
class ScreenCapturerWin : public ScreenCapturer {
|
||||||
public:
|
public:
|
||||||
ScreenCapturerWin();
|
ScreenCapturerWin();
|
||||||
@@ -56,8 +53,6 @@ class ScreenCapturerWin : public ScreenCapturer {
|
|||||||
std::mutex alias_mutex_;
|
std::mutex alias_mutex_;
|
||||||
std::vector<DisplayInfo> canonical_displays_;
|
std::vector<DisplayInfo> canonical_displays_;
|
||||||
std::atomic<bool> invalid_stream_id_logged_{false};
|
std::atomic<bool> invalid_stream_id_logged_{false};
|
||||||
std::atomic<bool> native_output_logged_{false};
|
|
||||||
std::atomic<bool> native_output_error_logged_{false};
|
|
||||||
std::atomic<bool> running_{false};
|
std::atomic<bool> running_{false};
|
||||||
std::atomic<bool> paused_{false};
|
std::atomic<bool> paused_{false};
|
||||||
std::atomic<bool> show_cursor_{true};
|
std::atomic<bool> show_cursor_{true};
|
||||||
@@ -82,13 +77,9 @@ class ScreenCapturerWin : public ScreenCapturer {
|
|||||||
std::string secure_shared_stage_;
|
std::string secure_shared_stage_;
|
||||||
std::string secure_shared_desktop_;
|
std::string secure_shared_desktop_;
|
||||||
bool secure_shared_capture_started_ = false;
|
bool secure_shared_capture_started_ = false;
|
||||||
std::shared_ptr<CapturedNv12FramePool> native_frame_pool_;
|
|
||||||
|
|
||||||
void BuildCanonicalFromImpl();
|
void BuildCanonicalFromImpl();
|
||||||
void RebuildAliasesFromImpl();
|
void RebuildAliasesFromImpl();
|
||||||
void EmitCapturedFrame(unsigned char* data, int size, int width, int height,
|
|
||||||
const char* stream_id,
|
|
||||||
const XNativeVideoFrame* native_frame = nullptr);
|
|
||||||
void StopSecureCaptureThread();
|
void StopSecureCaptureThread();
|
||||||
bool RestartCaptureBackendAfterSecureDesktop();
|
bool RestartCaptureBackendAfterSecureDesktop();
|
||||||
void SecureDesktopCaptureLoop();
|
void SecureDesktopCaptureLoop();
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Author: DI JUNKUN
|
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "captured_nv12_frame.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
#include <limits>
|
|
||||||
#include <new>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace crossdesk {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
bool GetNv12Size(uint32_t width, uint32_t height, size_t* size) {
|
|
||||||
if (!size || width == 0 || height == 0 || (width & 1U) != 0 ||
|
|
||||||
(height & 1U) != 0 ||
|
|
||||||
static_cast<size_t>(width) >
|
|
||||||
std::numeric_limits<size_t>::max() / height) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const size_t pixels = static_cast<size_t>(width) * height;
|
|
||||||
if (pixels > std::numeric_limits<size_t>::max() / 3U * 2U) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*size = pixels * 3U / 2U;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
std::shared_ptr<CapturedNv12FramePool> CapturedNv12FramePool::Create(
|
|
||||||
size_t max_cached_buffers) {
|
|
||||||
return std::shared_ptr<CapturedNv12FramePool>(
|
|
||||||
new CapturedNv12FramePool(max_cached_buffers));
|
|
||||||
}
|
|
||||||
|
|
||||||
CapturedNv12FramePool::CapturedNv12FramePool(size_t max_cached_buffers)
|
|
||||||
: max_cached_buffers_(max_cached_buffers) {}
|
|
||||||
|
|
||||||
CapturedNv12Frame* CapturedNv12FramePool::CopyFrom(const uint8_t* data,
|
|
||||||
size_t size,
|
|
||||||
uint32_t width,
|
|
||||||
uint32_t height) {
|
|
||||||
size_t required_size = 0;
|
|
||||||
if (!data || !GetNv12Size(width, height, &required_size) ||
|
|
||||||
size < required_size) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> storage;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
auto best = free_buffers_.end();
|
|
||||||
for (auto it = free_buffers_.begin(); it != free_buffers_.end(); ++it) {
|
|
||||||
if (it->capacity() < required_size) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (best == free_buffers_.end() || it->capacity() < best->capacity()) {
|
|
||||||
best = it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (best == free_buffers_.end() && !free_buffers_.empty()) {
|
|
||||||
best = std::max_element(
|
|
||||||
free_buffers_.begin(), free_buffers_.end(),
|
|
||||||
[](const auto& lhs, const auto& rhs) {
|
|
||||||
return lhs.capacity() < rhs.capacity();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (best != free_buffers_.end()) {
|
|
||||||
storage = std::move(*best);
|
|
||||||
free_buffers_.erase(best);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
storage.resize(required_size);
|
|
||||||
std::memcpy(storage.data(), data, required_size);
|
|
||||||
return new CapturedNv12Frame(shared_from_this(), std::move(storage), width,
|
|
||||||
height);
|
|
||||||
} catch (...) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CapturedNv12FramePool::Recycle(std::vector<uint8_t> storage) {
|
|
||||||
if (max_cached_buffers_ == 0 || storage.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
if (free_buffers_.size() < max_cached_buffers_) {
|
|
||||||
free_buffers_.push_back(std::move(storage));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto smallest = std::min_element(
|
|
||||||
free_buffers_.begin(), free_buffers_.end(),
|
|
||||||
[](const auto& lhs, const auto& rhs) {
|
|
||||||
return lhs.capacity() < rhs.capacity();
|
|
||||||
});
|
|
||||||
if (smallest != free_buffers_.end() &&
|
|
||||||
smallest->capacity() < storage.capacity()) {
|
|
||||||
*smallest = std::move(storage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CapturedNv12Frame::CapturedNv12Frame(
|
|
||||||
std::shared_ptr<CapturedNv12FramePool> pool,
|
|
||||||
std::vector<uint8_t> storage, uint32_t width, uint32_t height)
|
|
||||||
: pool_(std::move(pool)),
|
|
||||||
storage_(std::move(storage)),
|
|
||||||
width_(width),
|
|
||||||
height_(height) {
|
|
||||||
descriptor_.struct_size = sizeof(descriptor_);
|
|
||||||
descriptor_.type = XNativeVideoFrameCpuNv12;
|
|
||||||
descriptor_.width = width_;
|
|
||||||
descriptor_.height = height_;
|
|
||||||
descriptor_.payload.cpu_nv12 = {
|
|
||||||
storage_.data(), storage_.data() + static_cast<size_t>(width_) * height_,
|
|
||||||
width_, width_};
|
|
||||||
descriptor_.owner = this;
|
|
||||||
descriptor_.retain = &RetainOwner;
|
|
||||||
descriptor_.release = &ReleaseOwner;
|
|
||||||
descriptor_.copy_to_nv12 = &CopyToNv12;
|
|
||||||
}
|
|
||||||
|
|
||||||
CapturedNv12Frame::~CapturedNv12Frame() {
|
|
||||||
if (pool_) {
|
|
||||||
pool_->Recycle(std::move(storage_));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CapturedNv12Frame::AddRef() {
|
|
||||||
references_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CapturedNv12Frame::Release() {
|
|
||||||
if (references_.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
|
||||||
delete this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CapturedNv12Frame::RetainOwner(void* owner) {
|
|
||||||
static_cast<CapturedNv12Frame*>(owner)->AddRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CapturedNv12Frame::ReleaseOwner(void* owner) {
|
|
||||||
static_cast<CapturedNv12Frame*>(owner)->Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
int CapturedNv12Frame::CopyToNv12(void* owner, uint8_t* destination,
|
|
||||||
size_t destination_size) {
|
|
||||||
auto* frame = static_cast<CapturedNv12Frame*>(owner);
|
|
||||||
if (!frame || !destination || destination_size < frame->Size()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
std::memcpy(destination, frame->storage_.data(), frame->Size());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace crossdesk
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Author: DI JUNKUN
|
|
||||||
* @Date: 2026-09-04
|
|
||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _CAPTURED_NV12_FRAME_H_
|
|
||||||
#define _CAPTURED_NV12_FRAME_H_
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "minirtc.h"
|
|
||||||
|
|
||||||
namespace crossdesk {
|
|
||||||
|
|
||||||
class CapturedNv12Frame;
|
|
||||||
|
|
||||||
// Owns the pixels behind captured native frames. A frame may outlive the
|
|
||||||
// capture callback while MiniRTC waits for its asynchronous encoder, so capture
|
|
||||||
// buffers cannot be borrowed directly. Released allocations are cached to keep
|
|
||||||
// that ownership transfer inexpensive after the pipeline is warm.
|
|
||||||
class CapturedNv12FramePool final
|
|
||||||
: public std::enable_shared_from_this<CapturedNv12FramePool> {
|
|
||||||
public:
|
|
||||||
static std::shared_ptr<CapturedNv12FramePool> Create(
|
|
||||||
size_t max_cached_buffers = 4);
|
|
||||||
|
|
||||||
CapturedNv12Frame* CopyFrom(const uint8_t* data, size_t size,
|
|
||||||
uint32_t width, uint32_t height);
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit CapturedNv12FramePool(size_t max_cached_buffers);
|
|
||||||
void Recycle(std::vector<uint8_t> storage);
|
|
||||||
|
|
||||||
friend class CapturedNv12Frame;
|
|
||||||
|
|
||||||
const size_t max_cached_buffers_;
|
|
||||||
std::mutex mutex_;
|
|
||||||
std::vector<std::vector<uint8_t>> free_buffers_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CapturedNv12Frame final {
|
|
||||||
public:
|
|
||||||
const XNativeVideoFrame* Descriptor() const { return &descriptor_; }
|
|
||||||
size_t Size() const { return storage_.size(); }
|
|
||||||
|
|
||||||
void AddRef();
|
|
||||||
void Release();
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class CapturedNv12FramePool;
|
|
||||||
|
|
||||||
CapturedNv12Frame(std::shared_ptr<CapturedNv12FramePool> pool,
|
|
||||||
std::vector<uint8_t> storage, uint32_t width,
|
|
||||||
uint32_t height);
|
|
||||||
~CapturedNv12Frame();
|
|
||||||
|
|
||||||
static void RetainOwner(void* owner);
|
|
||||||
static void ReleaseOwner(void* owner);
|
|
||||||
static int CopyToNv12(void* owner, uint8_t* destination,
|
|
||||||
size_t destination_size);
|
|
||||||
|
|
||||||
std::atomic<uint32_t> references_{1};
|
|
||||||
std::shared_ptr<CapturedNv12FramePool> pool_;
|
|
||||||
std::vector<uint8_t> storage_;
|
|
||||||
uint32_t width_ = 0;
|
|
||||||
uint32_t height_ = 0;
|
|
||||||
XNativeVideoFrame descriptor_{};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace crossdesk
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -10,18 +10,14 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "display_info.h"
|
#include "display_info.h"
|
||||||
#include "minirtc.h"
|
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
class ScreenCapturer {
|
class ScreenCapturer {
|
||||||
public:
|
public:
|
||||||
// |stream_id| is a logical MiniRTC stream ID (DisplayN), not a platform
|
// The final callback argument is a logical MiniRTC stream ID (DisplayN),
|
||||||
// display name or physical handle. |native_frame| is borrowed for the
|
// not a platform display name or physical handle.
|
||||||
// duration of the callback; retain its owner before using it asynchronously.
|
typedef std::function<void(unsigned char*, int, int, int, const char*)>
|
||||||
typedef std::function<void(unsigned char* data, int size, int width,
|
|
||||||
int height, const char* stream_id,
|
|
||||||
const XNativeVideoFrame* native_frame)>
|
|
||||||
cb_desktop_data;
|
cb_desktop_data;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "video_receive_scheduler.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::unique_ptr<minirtc::ReceivedFrame>
|
||||||
|
MakeFrame(uint32_t rtp_timestamp, int64_t received_us, bool key_frame = false) {
|
||||||
|
const uint8_t payload = key_frame ? 0x65 : 0x41;
|
||||||
|
auto frame =
|
||||||
|
std::make_unique<minirtc::ReceivedFrame>(&payload, sizeof(payload));
|
||||||
|
frame->SetRtpTimestamp(rtp_timestamp);
|
||||||
|
frame->SetReceivedTimestamp(received_us);
|
||||||
|
frame->SetCapturedTimestamp(received_us - 5000);
|
||||||
|
frame->SetFrameType(key_frame ? minirtc::kVideoFrameKey
|
||||||
|
: minirtc::kVideoFrameDelta);
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Expect(bool condition, const char *message) {
|
||||||
|
if (!condition) {
|
||||||
|
std::cerr << message << '\n';
|
||||||
|
}
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestOneFramePerPlaybackCycle() {
|
||||||
|
minirtc::VideoReceiveScheduler scheduler(60);
|
||||||
|
scheduler.Enqueue(MakeFrame(90000, 1000000), 1000000);
|
||||||
|
scheduler.Enqueue(MakeFrame(91500, 1016667), 1016667);
|
||||||
|
|
||||||
|
auto first = scheduler.Poll(1016667, false);
|
||||||
|
bool ok = Expect(first.frame != nullptr, "first frame was not released");
|
||||||
|
ok &= Expect(first.frame && first.frame->RtpTimestamp() == 90000,
|
||||||
|
"scheduler did not preserve RTP order");
|
||||||
|
|
||||||
|
auto same_cycle = scheduler.Poll(1016667, false);
|
||||||
|
ok &= Expect(!same_cycle.frame,
|
||||||
|
"scheduler released more than one frame in a cycle");
|
||||||
|
ok &= Expect(same_cycle.next_delay_us > 0,
|
||||||
|
"scheduler did not schedule the next playback cycle");
|
||||||
|
|
||||||
|
auto second = scheduler.Poll(1033334, false);
|
||||||
|
ok &= Expect(second.frame != nullptr,
|
||||||
|
"second frame was not released on the next cycle");
|
||||||
|
ok &= Expect(second.frame && second.frame->RtpTimestamp() == 91500,
|
||||||
|
"second RTP timestamp was incorrect");
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestSevereLagJumpsToNewestKeyFrame() {
|
||||||
|
minirtc::VideoReceiveScheduler scheduler(60);
|
||||||
|
for (uint32_t i = 0; i < 10; ++i) {
|
||||||
|
scheduler.Enqueue(MakeFrame(90000 + i * 1500, 1000000 + i * 16667, i == 8),
|
||||||
|
1000000 + i * 16667);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = scheduler.Poll(1200000, false);
|
||||||
|
const auto metrics = scheduler.GetMetrics(1200000);
|
||||||
|
bool ok = Expect(result.frame && result.frame->IsKeyFrame(),
|
||||||
|
"catch-up did not jump to the newest key frame");
|
||||||
|
ok &= Expect(result.dropped_frames == 8,
|
||||||
|
"catch-up dropped an unexpected number of frames");
|
||||||
|
ok &= Expect(metrics.catch_up_count == 1,
|
||||||
|
"catch-up metric was not incremented");
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestSevereLagRequestsKeyFrameWhenNoSyncPointExists() {
|
||||||
|
minirtc::VideoReceiveScheduler scheduler(60);
|
||||||
|
for (uint32_t i = 0; i < 10; ++i) {
|
||||||
|
scheduler.Enqueue(MakeFrame(180000 + i * 1500, 2000000 + i * 16667),
|
||||||
|
2000000 + i * 16667);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = scheduler.Poll(2200000, false);
|
||||||
|
const auto metrics = scheduler.GetMetrics(2200000);
|
||||||
|
bool ok = Expect(!result.frame,
|
||||||
|
"undecodable delta frame was released during catch-up");
|
||||||
|
ok &=
|
||||||
|
Expect(result.request_key_frame, "catch-up did not request a key frame");
|
||||||
|
ok &= Expect(metrics.buffered_frames == 0 && metrics.awaiting_key_frame,
|
||||||
|
"scheduler did not enter key-frame wait state");
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestAdaptiveBufferStaysWithinOneToThreeFrames() {
|
||||||
|
minirtc::VideoReceiveScheduler scheduler(60);
|
||||||
|
int64_t arrival_us = 3000000;
|
||||||
|
for (uint32_t i = 0; i < 40; ++i) {
|
||||||
|
arrival_us += (i % 2 == 0) ? 5000 : 30000;
|
||||||
|
scheduler.Enqueue(MakeFrame(270000 + i * 1500, arrival_us), arrival_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto metrics = scheduler.GetMetrics(arrival_us);
|
||||||
|
bool ok = Expect(metrics.target_buffer_frames >= 1 &&
|
||||||
|
metrics.target_buffer_frames <= 3,
|
||||||
|
"adaptive target left the 1-3 frame range");
|
||||||
|
ok &= Expect(metrics.target_buffer_frames > 1,
|
||||||
|
"arrival jitter did not increase the target buffer");
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestLegacyMicrosecondTimestampFallsBackToArrivalCadence() {
|
||||||
|
minirtc::VideoReceiveScheduler scheduler(60);
|
||||||
|
int64_t arrival_us = 4000000;
|
||||||
|
for (uint32_t i = 0; i < 20; ++i) {
|
||||||
|
scheduler.Enqueue(MakeFrame(500000 + i * 16667, arrival_us), arrival_us);
|
||||||
|
scheduler.Poll(arrival_us, false);
|
||||||
|
arrival_us += 16667;
|
||||||
|
}
|
||||||
|
scheduler.Poll(arrival_us, false);
|
||||||
|
|
||||||
|
const auto metrics = scheduler.GetMetrics(arrival_us);
|
||||||
|
bool ok = Expect(metrics.estimated_frame_interval_us >= 15000 &&
|
||||||
|
metrics.estimated_frame_interval_us <= 20000,
|
||||||
|
"legacy RTP units slowed the playback interval");
|
||||||
|
ok &= Expect(metrics.timestamp_fallbacks > 0,
|
||||||
|
"legacy RTP units did not activate timestamp fallback");
|
||||||
|
ok &= Expect(metrics.released_frames == 20 && metrics.dropped_frames == 0,
|
||||||
|
"legacy RTP units caused scheduler frame loss");
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestSixtyFpsCadenceDoesNotTriggerCatchUp() {
|
||||||
|
minirtc::VideoReceiveScheduler scheduler(60);
|
||||||
|
constexpr int64_t kStartUs = 6000000;
|
||||||
|
constexpr int64_t kFrameIntervalUs = 16667;
|
||||||
|
constexpr uint32_t kStartRtpTimestamp = 700000;
|
||||||
|
constexpr uint32_t kRtpFrameInterval = 1500;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < 60; ++i) {
|
||||||
|
const int64_t now_us = kStartUs + static_cast<int64_t>(i) *
|
||||||
|
kFrameIntervalUs;
|
||||||
|
scheduler.Enqueue(
|
||||||
|
MakeFrame(kStartRtpTimestamp + i * kRtpFrameInterval, now_us),
|
||||||
|
now_us);
|
||||||
|
scheduler.Poll(now_us, false);
|
||||||
|
}
|
||||||
|
scheduler.Poll(kStartUs + 60 * kFrameIntervalUs, false);
|
||||||
|
|
||||||
|
const auto metrics =
|
||||||
|
scheduler.GetMetrics(kStartUs + 60 * kFrameIntervalUs);
|
||||||
|
bool ok = Expect(metrics.released_frames == 60,
|
||||||
|
"60 fps input was not released at 60 fps cadence");
|
||||||
|
ok &= Expect(metrics.dropped_frames == 0 && metrics.catch_up_count == 0,
|
||||||
|
"steady 60 fps input incorrectly triggered catch-up");
|
||||||
|
ok &= Expect(metrics.estimated_frame_interval_us >= 15000 &&
|
||||||
|
metrics.estimated_frame_interval_us <= 18000,
|
||||||
|
"steady 60 fps interval estimate drifted");
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestKeyFrameRequestCooldownKeepsDecodableProgress() {
|
||||||
|
minirtc::VideoReceiveScheduler scheduler(60);
|
||||||
|
for (uint32_t i = 0; i < 10; ++i) {
|
||||||
|
scheduler.Enqueue(MakeFrame(600000 + i * 1500,
|
||||||
|
5000000 + static_cast<int64_t>(i) * 16667),
|
||||||
|
5000000 + static_cast<int64_t>(i) * 16667);
|
||||||
|
}
|
||||||
|
auto first_catch_up = scheduler.Poll(5200000, false);
|
||||||
|
bool ok = Expect(first_catch_up.request_key_frame,
|
||||||
|
"initial severe lag did not request a key frame");
|
||||||
|
|
||||||
|
scheduler.Enqueue(MakeFrame(615000, 5220000, true), 5220000);
|
||||||
|
auto key_frame = scheduler.Poll(5240000, false);
|
||||||
|
ok &= Expect(key_frame.frame && key_frame.frame->IsKeyFrame(),
|
||||||
|
"requested key frame was not released");
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < 10; ++i) {
|
||||||
|
scheduler.Enqueue(MakeFrame(616500 + i * 1500,
|
||||||
|
5250000 + static_cast<int64_t>(i) * 16667),
|
||||||
|
5250000 + static_cast<int64_t>(i) * 16667);
|
||||||
|
}
|
||||||
|
auto cooldown_catch_up = scheduler.Poll(5450000, false);
|
||||||
|
const auto metrics = scheduler.GetMetrics(5450000);
|
||||||
|
ok &= Expect(!cooldown_catch_up.request_key_frame,
|
||||||
|
"key frame request cooldown was ignored");
|
||||||
|
ok &= Expect(cooldown_catch_up.frame != nullptr,
|
||||||
|
"cooldown prevented forward playback progress");
|
||||||
|
ok &= Expect(metrics.key_frame_requests == 1,
|
||||||
|
"key frame request metric ignored cooldown");
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
bool ok = true;
|
||||||
|
ok &= TestOneFramePerPlaybackCycle();
|
||||||
|
ok &= TestSevereLagJumpsToNewestKeyFrame();
|
||||||
|
ok &= TestSevereLagRequestsKeyFrameWhenNoSyncPointExists();
|
||||||
|
ok &= TestAdaptiveBufferStaysWithinOneToThreeFrames();
|
||||||
|
ok &= TestLegacyMicrosecondTimestampFallsBackToArrivalCadence();
|
||||||
|
ok &= TestSixtyFpsCadenceDoesNotTriggerCatchUp();
|
||||||
|
ok &= TestKeyFrameRequestCooldownKeepsDecodableProgress();
|
||||||
|
return ok ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "rtp_defines.h"
|
||||||
|
#include "rtp_packetizer_av1.h"
|
||||||
|
#include "rtp_packetizer_h264.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool Expect(bool condition, const char *message) {
|
||||||
|
if (!condition) {
|
||||||
|
std::cerr << message << '\n';
|
||||||
|
}
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AllPacketsUseTimestamp(
|
||||||
|
const std::vector<std::unique_ptr<minirtc::RtpPacket>> &packets,
|
||||||
|
uint32_t expected_timestamp) {
|
||||||
|
if (packets.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto &packet : packets) {
|
||||||
|
if (!packet || packet->Timestamp() != expected_timestamp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestTimestampConversion() {
|
||||||
|
bool ok = Expect(minirtc::rtp::VideoTimestampFromMicroseconds(1000000) ==
|
||||||
|
90000,
|
||||||
|
"one second did not convert to 90 kHz");
|
||||||
|
ok &= Expect(minirtc::rtp::VideoTimestampFromMicroseconds(16667) == 1500,
|
||||||
|
"60 fps interval did not convert to 1500 RTP ticks");
|
||||||
|
ok &= Expect(minirtc::rtp::VideoTimestampToMicroseconds(90000) == 1000000,
|
||||||
|
"90 kHz timestamp did not convert to one second");
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestH264PacketizerPreservesTimestamp() {
|
||||||
|
constexpr uint32_t kTimestamp = 0x89abcdef;
|
||||||
|
minirtc::RtpPacketizerH264 packetizer(1234);
|
||||||
|
std::vector<uint8_t> small_nalu = {0x65, 0x01, 0x02};
|
||||||
|
auto single_packet =
|
||||||
|
packetizer.Build(small_nalu.data(), small_nalu.size(), kTimestamp, true);
|
||||||
|
bool ok = Expect(AllPacketsUseTimestamp(single_packet, kTimestamp),
|
||||||
|
"H.264 single-NAL packetizer changed RTP timestamp");
|
||||||
|
|
||||||
|
std::vector<uint8_t> large_nalu(MAX_NALU_LEN + 100, 0x55);
|
||||||
|
large_nalu.front() = 0x65;
|
||||||
|
auto fragmented =
|
||||||
|
packetizer.Build(large_nalu.data(), large_nalu.size(), kTimestamp, true);
|
||||||
|
ok &= Expect(AllPacketsUseTimestamp(fragmented, kTimestamp),
|
||||||
|
"H.264 FU-A packetizer changed RTP timestamp");
|
||||||
|
|
||||||
|
auto padding = packetizer.BuildPadding(2000, kTimestamp, true);
|
||||||
|
ok &= Expect(AllPacketsUseTimestamp(padding, kTimestamp),
|
||||||
|
"H.264 padding packetizer changed RTP timestamp");
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TestAv1PacketizerPreservesTimestamp() {
|
||||||
|
constexpr uint32_t kTimestamp = 0x12345678;
|
||||||
|
minirtc::RtpPacketizerAv1 packetizer(5678);
|
||||||
|
// One frame OBU with a one-byte payload.
|
||||||
|
std::vector<uint8_t> obu = {0x32, 0x01, 0x00};
|
||||||
|
auto packets = packetizer.Build(obu.data(), obu.size(), kTimestamp, true);
|
||||||
|
return Expect(AllPacketsUseTimestamp(packets, kTimestamp),
|
||||||
|
"AV1 packetizer changed RTP timestamp");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
bool ok = true;
|
||||||
|
ok &= TestTimestampConversion();
|
||||||
|
ok &= TestH264PacketizerPreservesTimestamp();
|
||||||
|
ok &= TestAv1PacketizerPreservesTimestamp();
|
||||||
|
return ok ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -106,6 +106,18 @@ function setup_targets()
|
|||||||
set_default(false)
|
set_default(false)
|
||||||
add_files("apps/desktop/tests/video_callback_lifetime_test.cpp")
|
add_files("apps/desktop/tests/video_callback_lifetime_test.cpp")
|
||||||
|
|
||||||
|
target("video_receive_scheduler_test")
|
||||||
|
set_kind("binary")
|
||||||
|
set_default(false)
|
||||||
|
add_deps("minirtc")
|
||||||
|
add_files("apps/desktop/tests/video_receive_scheduler_test.cpp")
|
||||||
|
|
||||||
|
target("video_rtp_timestamp_test")
|
||||||
|
set_kind("binary")
|
||||||
|
set_default(false)
|
||||||
|
add_deps("minirtc")
|
||||||
|
add_files("apps/desktop/tests/video_rtp_timestamp_test.cpp")
|
||||||
|
|
||||||
target("windows_manifest_resource_test")
|
target("windows_manifest_resource_test")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
set_default(false)
|
set_default(false)
|
||||||
@@ -159,10 +171,8 @@ function setup_targets()
|
|||||||
set_kind("object")
|
set_kind("object")
|
||||||
add_deps("rd_log", "common", "crossdesk_wire")
|
add_deps("rd_log", "common", "crossdesk_wire")
|
||||||
add_includedirs("apps/desktop/src/screen_capturer", {public = true})
|
add_includedirs("apps/desktop/src/screen_capturer", {public = true})
|
||||||
add_includedirs("deps/submodules/minirtc/src/api", {public = true})
|
|
||||||
if is_os("windows") then
|
if is_os("windows") then
|
||||||
add_packages("libyuv")
|
add_packages("libyuv")
|
||||||
add_files("apps/desktop/src/screen_capturer/captured_nv12_frame.cpp")
|
|
||||||
add_files("apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp",
|
add_files("apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp",
|
||||||
"apps/desktop/src/platform/windows/screen_capturer/screen_capturer_gdi.cpp",
|
"apps/desktop/src/platform/windows/screen_capturer/screen_capturer_gdi.cpp",
|
||||||
"apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp",
|
"apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp",
|
||||||
@@ -369,8 +379,7 @@ function setup_targets()
|
|||||||
"apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_entry.cpp")
|
"apps/desktop/src/platform/windows/screen_capturer/wgc_plugin_entry.cpp")
|
||||||
add_files("apps/desktop/resources/windows/wgc_plugin.rc")
|
add_files("apps/desktop/resources/windows/wgc_plugin.rc")
|
||||||
add_includedirs("apps/desktop/src/common", "apps/desktop/src/screen_capturer",
|
add_includedirs("apps/desktop/src/common", "apps/desktop/src/screen_capturer",
|
||||||
"apps/desktop/src/platform/windows/screen_capturer",
|
"apps/desktop/src/platform/windows/screen_capturer")
|
||||||
"deps/submodules/minirtc/src/api")
|
|
||||||
|
|
||||||
target("crossdesk_service")
|
target("crossdesk_service")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
|
|||||||
@@ -1124,17 +1124,12 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
|||||||
static_cast<size_t>(_selectedDisplay.load()));
|
static_cast<size_t>(_selectedDisplay.load()));
|
||||||
if (source != expected) return;
|
if (source != expected) return;
|
||||||
}
|
}
|
||||||
const XNativeVideoFrame *native_frame = frame->native_frame;
|
|
||||||
const bool has_native_pixel_buffer =
|
const bool has_native_pixel_buffer =
|
||||||
native_frame &&
|
frame->native_handle &&
|
||||||
native_frame->struct_size >=
|
frame->native_handle_type == XVideoFrameNativeHandleCVPixelBuffer;
|
||||||
static_cast<uint32_t>(sizeof(XNativeVideoFrame)) &&
|
|
||||||
native_frame->type == XNativeVideoFrameCVPixelBuffer &&
|
|
||||||
native_frame->payload.cv_pixel_buffer;
|
|
||||||
if (has_native_pixel_buffer) {
|
if (has_native_pixel_buffer) {
|
||||||
CVPixelBufferRef pixel_buffer =
|
CVPixelBufferRef pixel_buffer =
|
||||||
static_cast<CVPixelBufferRef>(
|
static_cast<CVPixelBufferRef>(frame->native_handle);
|
||||||
native_frame->payload.cv_pixel_buffer);
|
|
||||||
[self enqueueVideoPixelBuffer:pixel_buffer
|
[self enqueueVideoPixelBuffer:pixel_buffer
|
||||||
width:frame->width
|
width:frame->width
|
||||||
height:frame->height];
|
height:frame->height];
|
||||||
|
|||||||
Vendored
+1
-1
Submodule deps/submodules/minirtc updated: 454a0c544a...6738542d75
Reference in New Issue
Block a user