[fix] harden macOS screen capture lifecycle against late callbacks

This commit is contained in:
dijunkun
2026-05-28 04:21:59 +08:00
parent 895e297771
commit 53edf3d57e
2 changed files with 137 additions and 55 deletions
@@ -16,7 +16,11 @@ int ScreenCapturerSck::Init(const int fps, cb_desktop_data cb) {
} }
screen_capturer_sck_impl_ = CreateScreenCapturerSck(); screen_capturer_sck_impl_ = CreateScreenCapturerSck();
screen_capturer_sck_impl_->Init(fps, on_data_); const int ret = screen_capturer_sck_impl_->Init(fps, on_data_);
if (ret != 0) {
screen_capturer_sck_impl_.reset();
return ret;
}
return 0; return 0;
} }
@@ -29,8 +33,11 @@ int ScreenCapturerSck::Destroy() {
} }
int ScreenCapturerSck::Start(bool show_cursor) { int ScreenCapturerSck::Start(bool show_cursor) {
screen_capturer_sck_impl_->Start(show_cursor); if (!screen_capturer_sck_impl_) {
return 0; return -1;
}
return screen_capturer_sck_impl_->Start(show_cursor);
} }
int ScreenCapturerSck::Stop() { int ScreenCapturerSck::Stop() {
@@ -80,4 +87,4 @@ std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() {
void ScreenCapturerSck::OnFrame() {} void ScreenCapturerSck::OnFrame() {}
void ScreenCapturerSck::CleanUp() {} void ScreenCapturerSck::CleanUp() {}
} // namespace crossdesk } // namespace crossdesk
@@ -16,7 +16,12 @@
#include <IOKit/graphics/IOGraphicsLib.h> #include <IOKit/graphics/IOGraphicsLib.h>
#include <IOSurface/IOSurface.h> #include <IOSurface/IOSurface.h>
#include <ScreenCaptureKit/ScreenCaptureKit.h> #include <ScreenCaptureKit/ScreenCaptureKit.h>
#include <algorithm>
#include <atomic> #include <atomic>
#include <cctype>
#include <cstring>
#include <limits>
#include <map>
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include "display_info.h" #include "display_info.h"
@@ -28,6 +33,15 @@ class ScreenCapturerSckImpl;
static const int kFullDesktopScreenId = -1; static const int kFullDesktopScreenId = -1;
static std::string NSErrorToString(NSError *error) {
if (!error) {
return "";
}
const char *description = [error.localizedDescription UTF8String];
return description ? description : "";
}
// 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
@@ -78,6 +92,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
std::map<CGDirectDisplayID, int> display_id_map_reverse_; std::map<CGDirectDisplayID, int> display_id_map_reverse_;
std::map<CGDirectDisplayID, std::string> display_id_name_map_; std::map<CGDirectDisplayID, std::string> display_id_name_map_;
unsigned char *nv12_frame_ = nullptr; unsigned char *nv12_frame_ = nullptr;
size_t nv12_frame_size_ = 0;
int width_ = 0; int width_ = 0;
int height_ = 0; int height_ = 0;
int fps_ = 60; int fps_ = 60;
@@ -100,7 +115,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
// Helper object to receive Objective-C callbacks from ScreenCaptureKit and call into this C++ // Helper object to receive Objective-C callbacks from ScreenCaptureKit and call into this C++
// 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_; SckHelper *__strong helper_ = nil;
// Callback for returning captured frames, or errors, to the caller. Only used on the caller's // Callback for returning captured frames, or errors, to the caller. Only used on the caller's
// thread. // thread.
cb_desktop_data _on_data = nullptr; cb_desktop_data _on_data = nullptr;
@@ -110,7 +125,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
// Guards some variables that may be accessed on different threads. // Guards some variables that may be accessed on different threads.
std::mutex lock_; std::mutex lock_;
// Provides captured desktop frames. // Provides captured desktop frames.
SCStream *__strong stream_; SCStream *__strong stream_ = nil;
// Currently selected display, or 0 if the full desktop is selected. This capturer does not // Currently selected display, or 0 if the full desktop is selected. This capturer does not
// support full-desktop capture, and will fall back to the first display. // support full-desktop capture, and will fall back to the first display.
CGDirectDisplayID current_display_ = 0; CGDirectDisplayID current_display_ = 0;
@@ -182,6 +197,19 @@ ScreenCapturerSckImpl::ScreenCapturerSckImpl() {
} }
ScreenCapturerSckImpl::~ScreenCapturerSckImpl() { ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
SckHelper *helper_to_release = nil;
{
std::lock_guard<std::mutex> lock(lock_);
if (stream_) {
[stream_ stopCaptureWithCompletionHandler:nil];
stream_ = nil;
}
_on_data = nullptr;
helper_to_release = helper_;
helper_ = nil;
}
[helper_to_release releaseCapturer];
display_info_list_.clear(); display_info_list_.clear();
display_id_map_.clear(); display_id_map_.clear();
display_id_map_reverse_.clear(); display_id_map_reverse_.clear();
@@ -190,15 +218,22 @@ ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
if (nv12_frame_) { if (nv12_frame_) {
delete[] nv12_frame_; delete[] nv12_frame_;
nv12_frame_ = nullptr; nv12_frame_ = nullptr;
nv12_frame_size_ = 0;
} }
[stream_ stopCaptureWithCompletionHandler:nil];
[helper_ releaseCapturer];
} }
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) { int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
if (!cb) {
LOG_ERROR("Screen capturer callback is null");
return -1;
}
_on_data = cb; _on_data = cb;
fps_ = fps; fps_ = fps > 0 ? fps : 60;
display_info_list_.clear();
display_id_map_.clear();
display_id_map_reverse_.clear();
display_id_name_map_.clear();
if (@available(macOS 10.15, *)) { if (@available(macOS 10.15, *)) {
bool has_permission = CGPreflightScreenCaptureAccess(); bool has_permission = CGPreflightScreenCaptureAccess();
@@ -216,8 +251,7 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) { getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) {
if (error) { if (error) {
capture_error = error; capture_error = error;
LOG_ERROR("Failed to get shareable content: {}", LOG_ERROR("Failed to get shareable content: {}", NSErrorToString(error));
std::string([error.localizedDescription UTF8String]));
} else { } else {
content = result; content = result;
} }
@@ -227,7 +261,7 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
if (capture_error || !content || content.displays.count == 0) { if (capture_error || !content || content.displays.count == 0) {
LOG_ERROR("Failed to get display info, error: {}", LOG_ERROR("Failed to get display info, error: {}",
std::string([capture_error.localizedDescription UTF8String])); NSErrorToString(capture_error));
return -1; return -1;
} }
@@ -284,51 +318,58 @@ int ScreenCapturerSckImpl::Start(bool show_cursor) {
} }
int ScreenCapturerSckImpl::SwitchTo(int monitor_index) { int ScreenCapturerSckImpl::SwitchTo(int monitor_index) {
if (stream_) { auto display_it = display_id_map_.find(monitor_index);
[stream_ stopCaptureWithCompletionHandler:^(NSError *error) { if (display_it == display_id_map_.end()) {
std::lock_guard<std::mutex> lock(lock_); LOG_WARN("SwitchTo skipped, invalid monitor_index={}, displays={}",
stream_ = nil; monitor_index, display_id_map_.size());
current_display_ = display_id_map_[monitor_index]; return -1;
StartOrReconfigureCapturer();
}];
} else {
current_display_ = display_id_map_[monitor_index];
StartOrReconfigureCapturer();
} }
const CGDirectDisplayID target_display = display_it->second;
{
std::lock_guard<std::mutex> lock(lock_);
current_display_ = target_display;
}
StartOrReconfigureCapturer();
return 0; return 0;
} }
int ScreenCapturerSckImpl::ResetToInitialMonitor() { int ScreenCapturerSckImpl::ResetToInitialMonitor() {
int target = initial_monitor_index_; int target = initial_monitor_index_;
if (display_info_list_.empty()) return -1; if (display_info_list_.empty()) return -1;
CGDirectDisplayID target_display = display_id_map_[target]; auto display_it = display_id_map_.find(target);
if (current_display_ == target_display) return 0; if (display_it == display_id_map_.end()) {
if (stream_) { LOG_WARN("ResetToInitialMonitor skipped, invalid monitor_index={}", target);
[stream_ stopCaptureWithCompletionHandler:^(NSError *error) { return -1;
std::lock_guard<std::mutex> lock(lock_);
stream_ = nil;
current_display_ = target_display;
StartOrReconfigureCapturer();
}];
} else {
current_display_ = target_display;
StartOrReconfigureCapturer();
} }
CGDirectDisplayID target_display = display_it->second;
if (current_display_ == target_display) return 0;
{
std::lock_guard<std::mutex> lock(lock_);
current_display_ = target_display;
}
StartOrReconfigureCapturer();
return 0; return 0;
} }
int ScreenCapturerSckImpl::Destroy() { int ScreenCapturerSckImpl::Destroy() {
std::lock_guard<std::mutex> lock(lock_); SckHelper *helper_to_release = nil;
if (stream_) { {
LOG_INFO("Destroying stream"); std::lock_guard<std::mutex> lock(lock_);
[stream_ stopCaptureWithCompletionHandler:nil]; if (stream_) {
stream_ = nil; LOG_INFO("Destroying stream");
[stream_ stopCaptureWithCompletionHandler:nil];
stream_ = nil;
}
current_display_ = 0;
permanent_error_ = false;
_on_data = nullptr;
helper_to_release = helper_;
helper_ = nil;
} }
current_display_ = 0;
permanent_error_ = false; [helper_to_release releaseCapturer];
_on_data = nullptr;
[helper_ releaseCapturer];
helper_ = nil;
return 0; return 0;
} }
@@ -416,7 +457,7 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
// TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for // TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for
// best performance. // best performance.
NSError *add_stream_output_error; NSError *add_stream_output_error = nil;
dispatch_queue_t queue = dispatch_queue_create("ScreenCaptureKit.Queue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue = dispatch_queue_create("ScreenCaptureKit.Queue", DISPATCH_QUEUE_SERIAL);
bool add_stream_output_result = [stream_ addStreamOutput:helper_ bool add_stream_output_result = [stream_ addStreamOutput:helper_
type:SCStreamOutputTypeScreen type:SCStreamOutputTypeScreen
@@ -425,7 +466,7 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
if (!add_stream_output_result) { if (!add_stream_output_result) {
stream_ = nil; stream_ = nil;
LOG_ERROR("addStreamOutput failed"); LOG_ERROR("addStreamOutput failed: {}", NSErrorToString(add_stream_output_error));
permanent_error_ = true; permanent_error_ = true;
return; return;
} }
@@ -436,7 +477,7 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
// calls stopCaptureWithCompletionHandler on the stream, which cancels // calls stopCaptureWithCompletionHandler on the stream, which cancels
// this handler. // this handler.
permanent_error_ = true; permanent_error_ = true;
LOG_ERROR("startCaptureWithCompletionHandler failed"); LOG_ERROR("startCaptureWithCompletionHandler failed: {}", NSErrorToString(error));
} else { } else {
LOG_INFO("Capture started"); LOG_INFO("Capture started");
} }
@@ -448,8 +489,18 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer, void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
CFDictionaryRef attachment) { CFDictionaryRef attachment) {
(void)attachment;
if (!pixelBuffer) {
return;
}
size_t width = CVPixelBufferGetWidth(pixelBuffer); size_t width = CVPixelBufferGetWidth(pixelBuffer);
size_t height = CVPixelBufferGetHeight(pixelBuffer); size_t height = CVPixelBufferGetHeight(pixelBuffer);
if (width == 0 || height == 0 || CVPixelBufferGetPlaneCount(pixelBuffer) < 2) {
LOG_ERROR("Invalid CVPixelBuffer: width={}, height={}, planes={}", width, height,
CVPixelBufferGetPlaneCount(pixelBuffer));
return;
}
CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); CVReturn status = CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
if (status != kCVReturnSuccess) { if (status != kCVReturnSuccess) {
@@ -458,18 +509,37 @@ void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
} }
size_t required_size = width * height * 3 / 2; size_t required_size = width * height * 3 / 2;
if (!nv12_frame_ || (width_ * height_ * 3 / 2 < required_size)) { if (required_size > static_cast<size_t>((std::numeric_limits<int>::max)())) {
LOG_ERROR("Captured frame is too large: {} bytes", required_size);
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
return;
}
std::lock_guard<std::mutex> lock(lock_);
if (!_on_data) {
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
return;
}
if (!nv12_frame_ || nv12_frame_size_ < required_size) {
delete[] nv12_frame_; delete[] nv12_frame_;
nv12_frame_ = new unsigned char[required_size]; nv12_frame_ = new unsigned char[required_size];
width_ = width; nv12_frame_size_ = required_size;
height_ = height;
} }
width_ = static_cast<int>(width);
height_ = static_cast<int>(height);
void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); void *base_y = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); size_t stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); void *base_uv = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
size_t stride_uv = CVPixelBufferGetBytesPerRowOfPlane(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_; unsigned char *dst_y = nv12_frame_;
for (size_t row = 0; row < height; ++row) { for (size_t row = 0; row < height; ++row) {
@@ -481,7 +551,8 @@ void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width); memcpy(dst_uv + row * width, static_cast<unsigned char *>(base_uv) + row * stride_uv, width);
} }
_on_data(nv12_frame_, width * height * 3 / 2, width, height, _on_data(nv12_frame_, static_cast<int>(required_size), static_cast<int>(width),
static_cast<int>(height),
display_id_name_map_[current_display_].c_str()); display_id_name_map_[current_display_].c_str());
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
@@ -503,10 +574,14 @@ void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
} }
SckHelper *local_helper = helper_; SckHelper *local_helper = helper_;
if (!local_helper) {
LOG_ERROR("Cannot reconfigure capturer: helper is null");
return;
}
auto handler = ^(SCShareableContent *content, NSError *error) { auto handler = ^(SCShareableContent *content, NSError *error) {
if (error) { if (error) {
LOG_ERROR("getShareableContent failed: {}", LOG_ERROR("getShareableContent failed: {}", NSErrorToString(error));
std::string([error.localizedDescription UTF8String]));
[local_helper onShareableContentCreated:nil]; [local_helper onShareableContentCreated:nil];
return; return;
} }
@@ -576,4 +651,4 @@ void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() { std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
return std::make_unique<ScreenCapturerSckImpl>(); return std::make_unique<ScreenCapturerSckImpl>();
} }