mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-27 04:35:34 +08:00
[feat] support new screen capture method by using ScreenCaptureKit on MacOSX
This commit is contained in:
@@ -152,7 +152,7 @@ int ScreenCapturerAvf::Destroy() {
|
||||
}
|
||||
|
||||
int ScreenCapturerAvf::Start() {
|
||||
if (_running) {
|
||||
if (running_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ class ScreenCapturerAvf : public ScreenCapturer {
|
||||
void CleanUp();
|
||||
|
||||
private:
|
||||
std::atomic_bool _running;
|
||||
std::atomic_bool _paused;
|
||||
std::atomic_bool _inited;
|
||||
|
||||
104
src/screen_capturer/macosx/core_graphics/screen_capturer_cg.cpp
Normal file
104
src/screen_capturer/macosx/core_graphics/screen_capturer_cg.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include <IOSurface/IOSurface.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "rd_log.h"
|
||||
#include "screen_capturer_cgd.h"
|
||||
|
||||
ScreenCapturerCg::ScreenCapturerCg() {}
|
||||
|
||||
ScreenCapturerCg::~ScreenCapturerCg() {}
|
||||
|
||||
int ScreenCapturerCg::Init(const int fps, cb_desktop_data cb) {
|
||||
if (cb) {
|
||||
_on_data = cb;
|
||||
}
|
||||
|
||||
size_t pixel_width = 1280;
|
||||
size_t pixel_height = 720;
|
||||
CGDirectDisplayID display_id = 0;
|
||||
|
||||
CGDisplayStreamFrameAvailableHandler handler =
|
||||
^(CGDisplayStreamFrameStatus status, uint64_t display_time,
|
||||
IOSurfaceRef frame_surface, CGDisplayStreamUpdateRef updateRef) {
|
||||
if (status == kCGDisplayStreamFrameStatusStopped) return;
|
||||
// Only pay attention to frame updates.
|
||||
if (status != kCGDisplayStreamFrameStatusFrameComplete) return;
|
||||
|
||||
// size_t count = 0;
|
||||
// const CGRect* rects = CGDisplayStreamUpdateGetRects(
|
||||
// updateRef, kCGDisplayStreamUpdateDirtyRects, &count);
|
||||
|
||||
// 获取帧数据
|
||||
void* frameData = IOSurfaceGetBaseAddressOfPlane(frame_surface, 0);
|
||||
size_t width = IOSurfaceGetWidthOfPlane(frame_surface, 0);
|
||||
size_t height = IOSurfaceGetHeightOfPlane(frame_surface, 0);
|
||||
};
|
||||
|
||||
CFDictionaryRef properties_dictionary = CFDictionaryCreate(
|
||||
kCFAllocatorDefault, (const void*[]){kCGDisplayStreamShowCursor},
|
||||
(const void*[]){kCFBooleanFalse}, 1, &kCFTypeDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
CGDisplayStreamRef display_stream =
|
||||
CGDisplayStreamCreate(display_id, pixel_width, pixel_height, 'BGRA',
|
||||
properties_dictionary, handler);
|
||||
|
||||
if (display_stream) {
|
||||
CGError error = CGDisplayStreamStart(display_stream);
|
||||
if (error != kCGErrorSuccess) return -1;
|
||||
|
||||
CFRunLoopSourceRef source = CGDisplayStreamGetRunLoopSource(display_stream);
|
||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
|
||||
display_streams_.push_back(display_stream);
|
||||
}
|
||||
|
||||
CFRelease(properties_dictionary);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerCg::Destroy() {
|
||||
running_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerCg::Start() {
|
||||
if (_running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
running_ = true;
|
||||
capture_thread_ = std::thread([this]() {
|
||||
while (running_) {
|
||||
CFRunLoopRun();
|
||||
}
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerCg::Stop() {
|
||||
running_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerCg::Pause() { return 0; }
|
||||
|
||||
int ScreenCapturerCg::Resume() { return 0; }
|
||||
|
||||
void ScreenCapturerCg::OnFrame() {}
|
||||
|
||||
void ScreenCapturerCg::CleanUp() {}
|
||||
|
||||
//
|
||||
|
||||
void ScreenCapturerCg::UnregisterRefreshAndMoveHandlers() {
|
||||
for (CGDisplayStreamRef stream : display_streams_) {
|
||||
CFRunLoopSourceRef source = CGDisplayStreamGetRunLoopSource(stream);
|
||||
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
|
||||
CGDisplayStreamStop(stream);
|
||||
CFRelease(stream);
|
||||
}
|
||||
display_streams_.clear();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-10-16
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_CGD_H_
|
||||
#define _SCREEN_CAPTURER_CGD_H_
|
||||
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "screen_capturer.h"
|
||||
|
||||
class ScreenCapturerCg : public ScreenCapturer {
|
||||
public:
|
||||
ScreenCapturerCg();
|
||||
~ScreenCapturerCg();
|
||||
|
||||
public:
|
||||
virtual int Init(const int fps, cb_desktop_data cb);
|
||||
|
||||
virtual int Destroy();
|
||||
|
||||
virtual int Start();
|
||||
|
||||
virtual int Stop();
|
||||
|
||||
int Pause();
|
||||
|
||||
int Resume();
|
||||
|
||||
void OnFrame();
|
||||
|
||||
protected:
|
||||
void CleanUp();
|
||||
|
||||
private:
|
||||
int _fps;
|
||||
cb_desktop_data _on_data;
|
||||
|
||||
// thread
|
||||
std::thread capture_thread_;
|
||||
std::atomic_bool running_;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "screen_capturer_sck.h"
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
ScreenCapturerSck::ScreenCapturerSck() {}
|
||||
ScreenCapturerSck::~ScreenCapturerSck() {
|
||||
// if (inited_ && capture_thread_.joinable()) {
|
||||
// capture_thread_.join();
|
||||
// inited_ = false;
|
||||
// }
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Init(const int fps, cb_desktop_data cb) {
|
||||
if (cb) {
|
||||
on_data_ = cb;
|
||||
} else {
|
||||
LOG_ERROR("cb is null");
|
||||
return -1;
|
||||
}
|
||||
|
||||
screen_capturer_sck_impl_ = CreateScreenCapturerSck();
|
||||
screen_capturer_sck_impl_->Init(fps, on_data_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Destroy() { return 0; }
|
||||
|
||||
int ScreenCapturerSck::Start() {
|
||||
// if (running_) {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// running_ = true;
|
||||
// capture_thread_ = std::thread([this]() {
|
||||
// while (running_) {
|
||||
// }
|
||||
// });
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ScreenCapturerSck::Stop() { return 0; }
|
||||
|
||||
int ScreenCapturerSck::Pause() { return 0; }
|
||||
|
||||
int ScreenCapturerSck::Resume() { return 0; }
|
||||
|
||||
void ScreenCapturerSck::OnFrame() {}
|
||||
|
||||
void ScreenCapturerSck::CleanUp() {}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2024-10-17
|
||||
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _SCREEN_CAPTURER_SCK_H_
|
||||
#define _SCREEN_CAPTURER_SCK_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "screen_capturer.h"
|
||||
|
||||
class ScreenCapturerSck : public ScreenCapturer {
|
||||
public:
|
||||
ScreenCapturerSck();
|
||||
~ScreenCapturerSck();
|
||||
|
||||
public:
|
||||
virtual int Init(const int fps, cb_desktop_data cb);
|
||||
|
||||
virtual int Destroy();
|
||||
|
||||
virtual int Start();
|
||||
|
||||
virtual int Stop();
|
||||
|
||||
int Pause();
|
||||
|
||||
int Resume();
|
||||
|
||||
void OnFrame();
|
||||
|
||||
protected:
|
||||
void CleanUp();
|
||||
|
||||
private:
|
||||
std::unique_ptr<ScreenCapturer> CreateScreenCapturerSck();
|
||||
|
||||
private:
|
||||
int _fps;
|
||||
cb_desktop_data on_data_;
|
||||
unsigned char* nv12_frame_ = nullptr;
|
||||
bool inited_ = false;
|
||||
|
||||
// thread
|
||||
std::thread capture_thread_;
|
||||
std::atomic_bool running_;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ScreenCapturer> screen_capturer_sck_impl_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "screen_capturer_sck.h"
|
||||
|
||||
#include "rd_log.h"
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <IOSurface/IOSurface.h>
|
||||
#include <ScreenCaptureKit/ScreenCaptureKit.h>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
class ScreenCapturerSckImpl;
|
||||
|
||||
// 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.
|
||||
// Also, the `SCContentFilter` fields `contentRect` and `pointPixelScale` were
|
||||
// introduced in macOS 14.
|
||||
API_AVAILABLE(macos(14.0))
|
||||
@interface SckHelper : NSObject <SCStreamDelegate, SCStreamOutput>
|
||||
|
||||
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer;
|
||||
|
||||
- (void)onShareableContentCreated:(SCShareableContent *)content;
|
||||
|
||||
// Called just before the capturer is destroyed. This avoids a dangling pointer,
|
||||
// and prevents any new calls into a deleted capturer. If any method-call on the
|
||||
// capturer is currently running on a different thread, this blocks until it
|
||||
// completes.
|
||||
- (void)releaseCapturer;
|
||||
@end
|
||||
|
||||
class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
||||
public:
|
||||
explicit ScreenCapturerSckImpl();
|
||||
|
||||
ScreenCapturerSckImpl(const ScreenCapturerSckImpl &) = delete;
|
||||
ScreenCapturerSckImpl &operator=(const ScreenCapturerSckImpl &) = delete;
|
||||
~ScreenCapturerSckImpl();
|
||||
|
||||
public:
|
||||
int Init(const int fps, cb_desktop_data cb);
|
||||
void OnReceiveContent(SCShareableContent *content);
|
||||
void OnNewIOSurface(IOSurfaceRef io_surface, CFDictionaryRef attachment);
|
||||
|
||||
virtual int Destroy() { return 0; }
|
||||
|
||||
virtual int Start() { return 0; }
|
||||
|
||||
virtual int Stop() { return 0; }
|
||||
|
||||
private:
|
||||
SckHelper *__strong helper_;
|
||||
SCStream *__strong stream_;
|
||||
|
||||
cb_desktop_data _on_data;
|
||||
unsigned char *nv12_frame_ = nullptr;
|
||||
bool permanent_error_ = false;
|
||||
CGDirectDisplayID current_display_ = -1;
|
||||
std::mutex mtx_;
|
||||
};
|
||||
|
||||
@implementation SckHelper {
|
||||
// This lock is to prevent the capturer being destroyed while an instance
|
||||
// method is still running on another thread.
|
||||
std::mutex helper_mtx_;
|
||||
ScreenCapturerSckImpl *_capturer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCapturer:(ScreenCapturerSckImpl *)capturer {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_capturer = capturer;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onShareableContentCreated:(SCShareableContent *)content {
|
||||
std::lock_guard<std::mutex> lock(helper_mtx_);
|
||||
if (_capturer) {
|
||||
_capturer->OnReceiveContent(content);
|
||||
} else {
|
||||
LOG_ERROR("Invalid capturer");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stream:(SCStream *)stream
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
ofType:(SCStreamOutputType)type {
|
||||
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
if (!pixelBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
IOSurfaceRef ioSurface = CVPixelBufferGetIOSurface(pixelBuffer);
|
||||
if (!ioSurface) {
|
||||
return;
|
||||
}
|
||||
|
||||
CFArrayRef attachmentsArray = CMSampleBufferGetSampleAttachmentsArray(
|
||||
sampleBuffer, /*createIfNecessary=*/false);
|
||||
if (!attachmentsArray || CFArrayGetCount(attachmentsArray) <= 0) {
|
||||
LOG_ERROR("Discarding frame with no attachments");
|
||||
return;
|
||||
}
|
||||
|
||||
CFDictionaryRef attachment =
|
||||
static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachmentsArray, 0));
|
||||
|
||||
std::lock_guard<std::mutex> lock(helper_mtx_);
|
||||
if (_capturer) {
|
||||
_capturer->OnNewIOSurface(ioSurface, attachment);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)releaseCapturer {
|
||||
std::lock_guard<std::mutex> lock(helper_mtx_);
|
||||
_capturer = nullptr;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
ScreenCapturerSckImpl::ScreenCapturerSckImpl() {
|
||||
helper_ = [[SckHelper alloc] initWithCapturer:this];
|
||||
}
|
||||
|
||||
ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
|
||||
[stream_ stopCaptureWithCompletionHandler:nil];
|
||||
[helper_ releaseCapturer];
|
||||
}
|
||||
|
||||
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
||||
_on_data = cb;
|
||||
|
||||
SckHelper *local_helper = helper_;
|
||||
auto handler = ^(SCShareableContent *content, NSError *error) {
|
||||
[local_helper onShareableContentCreated:content];
|
||||
};
|
||||
|
||||
[SCShareableContent getShareableContentWithCompletionHandler:handler];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ScreenCapturerSckImpl::OnReceiveContent(SCShareableContent *content) {
|
||||
if (!content) {
|
||||
LOG_ERROR("getShareableContent failed");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content.displays.count) {
|
||||
LOG_ERROR("getShareableContent returned no displays");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SCDisplay *captured_display;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
for (SCDisplay *display in content.displays) {
|
||||
if (current_display_ == display.displayID) {
|
||||
captured_display = display;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!captured_display) {
|
||||
if (-1 == current_display_) {
|
||||
LOG_ERROR("Full screen capture is not supported, falling back to first "
|
||||
"display");
|
||||
} else {
|
||||
LOG_ERROR("Display [{}] not found, falling back to first display",
|
||||
current_display_);
|
||||
}
|
||||
captured_display = content.displays.firstObject;
|
||||
}
|
||||
}
|
||||
|
||||
SCContentFilter *filter =
|
||||
[[SCContentFilter alloc] initWithDisplay:captured_display
|
||||
excludingWindows:@[]];
|
||||
SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init];
|
||||
config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
|
||||
config.showsCursor = false;
|
||||
config.width = filter.contentRect.size.width * filter.pointPixelScale;
|
||||
config.height = filter.contentRect.size.height * filter.pointPixelScale;
|
||||
config.captureResolution = SCCaptureResolutionNominal;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
|
||||
if (stream_) {
|
||||
LOG_INFO("Updating stream configuration");
|
||||
[stream_ updateContentFilter:filter completionHandler:nil];
|
||||
[stream_ updateConfiguration:config completionHandler:nil];
|
||||
} else {
|
||||
stream_ = [[SCStream alloc] initWithFilter:filter
|
||||
configuration:config
|
||||
delegate:helper_];
|
||||
|
||||
// TODO: crbug.com/327458809 - Choose an appropriate sampleHandlerQueue for
|
||||
// best performance.
|
||||
NSError *add_stream_output_error;
|
||||
bool add_stream_output_result =
|
||||
[stream_ addStreamOutput:helper_
|
||||
type:SCStreamOutputTypeScreen
|
||||
sampleHandlerQueue:nil
|
||||
error:&add_stream_output_error];
|
||||
if (!add_stream_output_result) {
|
||||
stream_ = nil;
|
||||
LOG_ERROR("addStreamOutput failed");
|
||||
permanent_error_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto handler = ^(NSError *error) {
|
||||
if (error) {
|
||||
// It should be safe to access `this` here, because the C++ destructor
|
||||
// calls stopCaptureWithCompletionHandler on the stream, which cancels
|
||||
// this handler.
|
||||
permanent_error_ = true;
|
||||
LOG_ERROR("startCaptureWithCompletionHandler failed");
|
||||
} else {
|
||||
LOG_INFO("Capture started");
|
||||
}
|
||||
};
|
||||
|
||||
[stream_ startCaptureWithCompletionHandler:handler];
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenCapturerSckImpl::OnNewIOSurface(IOSurfaceRef io_surface,
|
||||
CFDictionaryRef attachment) {
|
||||
size_t width = IOSurfaceGetWidth(io_surface);
|
||||
size_t height = IOSurfaceGetHeight(io_surface);
|
||||
|
||||
uint32_t aseed;
|
||||
IOSurfaceLock(io_surface, kIOSurfaceLockReadOnly, &aseed);
|
||||
|
||||
nv12_frame_ =
|
||||
static_cast<unsigned char *>(IOSurfaceGetBaseAddress(io_surface));
|
||||
|
||||
_on_data(nv12_frame_, width * height * 3 / 2, width, height);
|
||||
|
||||
IOSurfaceUnlock(io_surface, kIOSurfaceLockReadOnly, &aseed);
|
||||
}
|
||||
|
||||
std::unique_ptr<ScreenCapturer> ScreenCapturerSck::CreateScreenCapturerSck() {
|
||||
return std::make_unique<ScreenCapturerSckImpl>();
|
||||
}
|
||||
@@ -12,7 +12,8 @@
|
||||
#elif __linux__
|
||||
#include "screen_capturer_x11.h"
|
||||
#elif __APPLE__
|
||||
#include "screen_capturer_avf.h"
|
||||
// #include "screen_capturer_avf.h"
|
||||
#include "screen_capturer_sck.h"
|
||||
#endif
|
||||
|
||||
class ScreenCapturerFactory {
|
||||
@@ -26,7 +27,8 @@ class ScreenCapturerFactory {
|
||||
#elif __linux__
|
||||
return new ScreenCapturerX11();
|
||||
#elif __APPLE__
|
||||
return new ScreenCapturerAvf();
|
||||
// return new ScreenCapturerAvf();
|
||||
return new ScreenCapturerSck();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user