Skip to content
Closed
13 changes: 11 additions & 2 deletions cmake/compile_definitions/macos.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${CORE_VIDEO_LIBRARY}
${FOUNDATION_LIBRARY}
${IOKIT_LIBRARY}
${SCREEN_CAPTURE_KIT_LIBRARY}
${VIDEO_TOOLBOX_LIBRARY})

set(APPLE_PLIST_TEMPLATE "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/build/Info.plist.in")
Expand All @@ -46,8 +47,6 @@ set(PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/macos/av_audio.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/av_audio.mm"
"${CMAKE_SOURCE_DIR}/src/platform/macos/av_img_t.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/av_video.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/av_video.m"
"${CMAKE_SOURCE_DIR}/src/platform/macos/display.mm"
"${CMAKE_SOURCE_DIR}/src/platform/macos/input.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/macos/microphone.mm"
Expand All @@ -56,6 +55,16 @@ set(PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/macos/nv12_zero_device.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/macos/nv12_zero_device.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/publish.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/macos/sc_video.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/sc_video.m"
"${CMAKE_SOURCE_DIR}/third-party/TPCircularBuffer/TPCircularBuffer.c"
"${CMAKE_SOURCE_DIR}/third-party/TPCircularBuffer/TPCircularBuffer.h"
${APPLE_PLIST_FILE})

# sc_video.m is written against ARC for clarity (SCK APIs are async/
# block-heavy and benefit from ARC). The rest of the macOS Obj-C
# sources remain MRC; objects flowing across the boundary follow the
# standard +1-retain alloc/init convention so both modes interoperate.
set_source_files_properties(
"${CMAKE_SOURCE_DIR}/src/platform/macos/sc_video.m"
PROPERTIES COMPILE_FLAGS "-fobjc-arc")
8 changes: 8 additions & 0 deletions cmake/dependencies/macos.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ FIND_LIBRARY(CORE_VIDEO_LIBRARY CoreVideo)
FIND_LIBRARY(FOUNDATION_LIBRARY Foundation)
FIND_LIBRARY(IOKIT_LIBRARY IOKit)
FIND_LIBRARY(VIDEO_TOOLBOX_LIBRARY VideoToolbox)
# ScreenCaptureKit is the modern (macOS 12.3+) replacement for the
# deprecated AVCaptureScreenInput-based capture path. Sunshine's
# sc_video.{h,m} is unconditionally compiled into the macOS target;
# fail configure with a clear message rather than failing the build
# later on header lookup when the SDK doesn't ship the framework
# (e.g., when building with an Xcode older than 13.3 / SDK older than
# 12.3, which dropped out of routine compatibility long ago).
FIND_LIBRARY(SCREEN_CAPTURE_KIT_LIBRARY ScreenCaptureKit REQUIRED)
Comment on lines +15 to +21

if(SUNSHINE_ENABLE_TRAY)
FIND_LIBRARY(COCOA Cocoa REQUIRED)
Expand Down
41 changes: 0 additions & 41 deletions src/platform/macos/av_video.h

This file was deleted.

146 changes: 0 additions & 146 deletions src/platform/macos/av_video.m

This file was deleted.

30 changes: 21 additions & 9 deletions src/platform/macos/display.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/platform/macos/av_img_t.h"
#include "src/platform/macos/av_video.h"
#include "src/platform/macos/misc.h"
#include "src/platform/macos/nv12_zero_device.h"
#include "src/platform/macos/sc_video.h"

// Avoid conflict between AVFoundation and libavutil both defining AVMediaType
#define AVMediaType AVMediaType_FFmpeg
Expand Down Expand Up @@ -86,7 +86,7 @@
}

void log_display_diagnostic(CGDirectDisplayID display_id, const char *source) {
NSString *display_name = [AVVideo getDisplayName:display_id];
NSString *display_name = [SCVideo getDisplayName:display_id];
const char *display_name_utf8 = display_name ? display_name.UTF8String : "<unknown>";
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id);

Expand Down Expand Up @@ -211,7 +211,7 @@ void wake_displays_for_detection(const std::string &display_name) {
} // namespace

struct av_display_t: public display_t {
AVVideo *av_capture {};
SCVideo *av_capture {};
CGDirectDisplayID display_id {};
IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID};

Expand Down Expand Up @@ -304,7 +304,7 @@ capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const
} else if (pix_fmt == pix_fmt_e::nv12 || pix_fmt == pix_fmt_e::p010) {
auto device = std::make_unique<nv12_zero_device>();

device->init(static_cast<void *>(av_capture), pix_fmt, setResolution, setPixelFormat);
device->init((void *) av_capture, pix_fmt, setResolution, setPixelFormat);

return device;
} else {
Expand Down Expand Up @@ -361,11 +361,11 @@ int dummy_img(img_t *img) override {
* height --> the intended capture height
*/
static void setResolution(void *display, int width, int height) {
[static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height];
[(SCVideo *) display setFrameWidth:width frameHeight:height];
}

static void setPixelFormat(void *display, OSType pixelFormat) {
static_cast<AVVideo *>(display).pixelFormat = pixelFormat;
((SCVideo *) display).pixelFormat = pixelFormat;
}
};

Expand All @@ -386,7 +386,7 @@ static void setPixelFormat(void *display, OSType pixelFormat) {
BOOST_LOG(info) << "Detecting displays"sv;
log_display_environment_diagnostics();

auto display_array = [AVVideo displayNames];
auto display_array = [SCVideo displayNames];
bool matched_configured_display = display_name.empty();
for (NSDictionary *item in display_array) {
NSNumber *display_id = item[@"id"];
Expand All @@ -409,7 +409,19 @@ static void setPixelFormat(void *display, OSType pixelFormat) {
log_display_diagnostic(display->display_id, "selected for AVFoundation capture");
BOOST_LOG(info) << "Configuring selected display ("sv << display->display_id << ") to stream"sv;

display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:config.framerate];
// ScreenCaptureKit is the only capture backend Sunshine ships on macOS;
// the deployment target (14.2) is well above SCK's minimum (12.3) so
// there is no @available branch and no legacy AVCaptureScreenInput
// fallback to maintain.
//
// hdrAllowed reflects the negotiated `enable_hdr` for this session
// (rtsp.cpp maps `x-nv-video[0].dynamicRangeMode` into config.dynamicRange).
// SCK uses this together with the chosen pixel format depth to decide
// whether to flip captureDynamicRange to HDRLocalDisplay; neither
// condition alone is sufficient. See sc_video.m::applyDynamicRangeForPixelFormat:.
const BOOL hdr_allowed = config.dynamicRange ? YES : NO;
BOOST_LOG(info) << "Using ScreenCaptureKit capture backend (HDR "sv << (hdr_allowed ? "allowed" : "blocked") << ")"sv;
display->av_capture = [[SCVideo alloc] initWithDisplay:display->display_id frameRate:config.framerate hdrAllowed:hdr_allowed];

if (!display->av_capture) {
BOOST_LOG(error) << "Video setup failed."sv;
Expand All @@ -428,7 +440,7 @@ static void setPixelFormat(void *display, OSType pixelFormat) {
std::vector<std::string> display_names(mem_type_e hwdevice_type) {
__block std::vector<std::string> display_names;

auto display_array = [AVVideo displayNames];
auto display_array = [SCVideo displayNames];

display_names.reserve([display_array count]);
[display_array enumerateObjectsUsingBlock:^(NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
Expand Down
47 changes: 47 additions & 0 deletions src/platform/macos/sc_video.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @file src/platform/macos/sc_video.h
* @brief Declarations for ScreenCaptureKit-based video capture on macOS.
*
* SCVideo is now Sunshine's only macOS capture backend. The deployment
* target (MACOSX_DEPLOYMENT_TARGET=14.2) is well above the macOS 12.3
* minimum where ScreenCaptureKit became available, so the legacy
* AVCaptureScreenInput-based AVVideo path has been removed entirely.
*/
#pragma once

#import <AppKit/AppKit.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>

// Block signature used to deliver captured sample buffers back to the
// platform-agnostic capture loop. Returning NO from the block stops
// further deliveries on this capture session.
typedef bool (^FrameCallbackBlock)(CMSampleBufferRef);

@interface SCVideo : NSObject

@property (nonatomic, assign) CGDirectDisplayID displayID;
@property (nonatomic, assign) CMTime minFrameDuration;
@property (nonatomic, assign) OSType pixelFormat;
@property (nonatomic, assign) int frameWidth;
@property (nonatomic, assign) int frameHeight;

// YES iff the negotiated streaming session enabled HDR (Moonlight's
// hdrMode flag). Required (in combination with a 10-bit pixel format)
// before SCK is allowed to flip captureDynamicRange to HDRLocalDisplay
// on macOS 14+. Defaults to NO; the SDR capture path is always safe.
@property (nonatomic, assign) BOOL hdrAllowed;

- (instancetype)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate;
- (instancetype)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate hdrAllowed:(BOOL)hdrAllowed;

- (void)setFrameWidth:(int)frameWidth frameHeight:(int)frameHeight;
- (dispatch_semaphore_t)capture:(FrameCallbackBlock)frameCallback;

// Enumerate the currently-active CGDisplays as an array of dictionaries
// with keys @"id" (NSNumber, the CGDirectDisplayID), @"name" (NSString,
// the numeric id as a string for legacy callers), and @"displayName"
// (NSString, the user-facing name from NSScreen.localizedName).
+ (NSArray<NSDictionary *> *)displayNames;

@end
Loading