Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions cmake/compile_definitions/macos.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${CORE_MEDIA_LIBRARY}
${CORE_VIDEO_LIBRARY}
${FOUNDATION_LIBRARY}
${IO_KIT_LIBRARY}
${SCREEN_CAPTURE_KIT_LIBRARY}
${VIDEO_TOOLBOX_LIBRARY})

set(APPLE_PLIST_TEMPLATE "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/build/Info.plist.in")
Expand All @@ -45,16 +47,26 @@ 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/hid_gamepad.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/hid_gamepad.m"
"${CMAKE_SOURCE_DIR}/src/platform/macos/input.mm"
"${CMAKE_SOURCE_DIR}/src/platform/macos/microphone.mm"
"${CMAKE_SOURCE_DIR}/src/platform/macos/misc.mm"
"${CMAKE_SOURCE_DIR}/src/platform/macos/misc.h"
"${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")
55 changes: 55 additions & 0 deletions cmake/dependencies/ffmpeg.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,58 @@ else()
endif()

set(FFMPEG_INCLUDE_DIRS "${FFMPEG_PREPARED_BINARIES}/include")

# Sunshine's src/cbs.cpp uses libavcodec's INTERNAL headers (cbs_h264.h,
# cbs_h2645.h, h2645_parse.h, etc.) which FFmpeg's `make install` does
# not export. Stage the needed internal headers into the dist include
# tree alongside the public ones. Done at configure time so subsequent
# rebuilds don't pay the cost. Limited to a known-good list so we don't
# shadow system headers (FFmpeg has its own "thread.h", "internal.h",
# etc. that collide with libc++ when the entire source tree is on the
# include path).
get_filename_component(_FFMPEG_BINARY_PARENT "${FFMPEG_PREPARED_BINARIES}" DIRECTORY)
set(_FFMPEG_SOURCE_CANDIDATES
"${_FFMPEG_BINARY_PARENT}/FFmpeg/FFmpeg"
"${CMAKE_SOURCE_DIR}/third-party/build-deps/build-prores-vt/FFmpeg/FFmpeg"
)
foreach(_candidate ${_FFMPEG_SOURCE_CANDIDATES})
if(EXISTS "${_candidate}/libavcodec/h2645_parse.h")
set(_FFMPEG_INTERNAL_HEADERS
libavcodec/h2645_parse.h
libavcodec/h2645_sei.h
libavcodec/h264_sei.h
libavcodec/hevc/sei.h
libavcodec/sei.h
libavcodec/cbs.h
libavcodec/cbs_internal.h
libavcodec/cbs_sei.h
libavcodec/get_bits.h
libavcodec/golomb.h
libavcodec/mathops.h
libavcodec/mpegutils.h
libavcodec/vlc.h
libavutil/attributes_internal.h
libavutil/internal.h
libavutil/thread.h
libavutil/timer.h
libavutil/reverse.h
libavutil/libm.h
libavutil/cpu_internal.h
)
foreach(_hdr ${_FFMPEG_INTERNAL_HEADERS})
if(EXISTS "${_candidate}/${_hdr}" AND NOT EXISTS "${FFMPEG_PREPARED_BINARIES}/include/${_hdr}")
get_filename_component(_hdr_dir "${FFMPEG_PREPARED_BINARIES}/include/${_hdr}" DIRECTORY)
file(MAKE_DIRECTORY "${_hdr_dir}")
configure_file("${_candidate}/${_hdr}" "${FFMPEG_PREPARED_BINARIES}/include/${_hdr}" COPYONLY)
endif()
endforeach()
# ffbuild/config.h is needed by libavutil/internal.h. Stage it
# at the include-path root so the relative #include "config.h"
# from mathops.h finds it.
if(EXISTS "${_candidate}/ffbuild/config.h" AND NOT EXISTS "${FFMPEG_PREPARED_BINARIES}/include/config.h")
configure_file("${_candidate}/ffbuild/config.h" "${FFMPEG_PREPARED_BINARIES}/include/config.h" COPYONLY)
endif()
message(STATUS "Sunshine cbs.cpp: staged FFmpeg internal headers from ${_candidate}")
break()
endif()
endforeach()
14 changes: 14 additions & 0 deletions cmake/dependencies/macos.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,21 @@ FIND_LIBRARY(CORE_AUDIO_LIBRARY CoreAudio)
FIND_LIBRARY(CORE_MEDIA_LIBRARY CoreMedia)
FIND_LIBRARY(CORE_VIDEO_LIBRARY CoreVideo)
FIND_LIBRARY(FOUNDATION_LIBRARY Foundation)
# IOKit is needed for IOHIDUserDevice* (virtual gamepad device — hid_gamepad.m).
# Actually creating devices at runtime requires the user to disable AMFI via
# `nvram boot-args="amfi_get_out_of_my_way=1"`, but the symbols themselves
# are unconditionally present and the host alloc_gamepad path probes
# availability before relying on them.
FIND_LIBRARY(IO_KIT_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)

if(SUNSHINE_ENABLE_TRAY)
FIND_LIBRARY(COCOA Cocoa REQUIRED)
Expand Down
6 changes: 6 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

- Added disabled-by-default experimental macOS ProRes VideoToolbox encoder
plumbing for custom clients. This is Sunshine-side protocol and encoder
support only and does not add stock Moonlight ProRes decoder compatibility.

@htmlonly
<script type="module" src="https://md-block.verou.me/md-block.js"></script>
<md-block
Expand Down
87 changes: 87 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2124,6 +2124,44 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>

### prores_mode

<table>
<tr>
<td>Description</td>
<td colspan="2">
Allows custom clients to request experimental macOS ProRes VideoToolbox video streams.
@warning{This does not add stock Moonlight client decoder support and should remain disabled unless
a custom client is explicitly being tested.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
0
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
prores_mode = 1
@endcode</td>
</tr>
<tr>
<td rowspan="3">Choices</td>
<td>0</td>
<td>disabled</td>
</tr>
<tr>
<td>1</td>
<td>accept an explicit ProRes request from a custom client</td>
</tr>
<tr>
<td>2</td>
<td>force ProRes for local development sessions</td>
</tr>
</table>

### capture

<table>
Expand Down Expand Up @@ -3004,6 +3042,55 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>

### prores_profile

<table>
<tr>
<td>Description</td>
<td colspan="2">
Sets the FFmpeg `prores_videotoolbox` profile when experimental ProRes is enabled.
@note{This option only applies when using macOS.}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
lt
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
prores_profile = hq
@endcode</td>
</tr>
<tr>
<td rowspan="6">Choices</td>
<td>proxy</td>
<td>ProRes 422 Proxy</td>
</tr>
<tr>
<td>lt</td>
<td>ProRes 422 LT</td>
</tr>
<tr>
<td>standard</td>
<td>ProRes 422</td>
</tr>
<tr>
<td>hq</td>
<td>ProRes 422 HQ</td>
</tr>
<tr>
<td>4444</td>
<td>ProRes 4444</td>
</tr>
<tr>
<td>xq</td>
<td>ProRes 4444 XQ</td>
</tr>
</table>

## VA-API Encoder

### vaapi_strict_rc_buffer
Expand Down
23 changes: 23 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,25 @@ namespace config {

} // namespace vt

namespace prores {

std::string profile_from_view(const std::string_view profile) {
#define _CONVERT_(x) \
if (profile == #x##sv) \
return #x
_CONVERT_(proxy);
_CONVERT_(lt);
_CONVERT_(standard);
_CONVERT_(hq);
_CONVERT_(4444);
_CONVERT_(xq);
#undef _CONVERT_
BOOST_LOG(warning) << "config: unknown prores_profile value: " << profile;
return "lt";
}

} // namespace prores

namespace sw {
int svtav1_preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x, y) \
Expand Down Expand Up @@ -454,6 +473,8 @@ namespace config {

0, // hevc_mode
0, // av1_mode
0, // prores_mode
"lt"s, // prores_profile

2, // min_threads
{
Expand Down Expand Up @@ -1101,6 +1122,8 @@ namespace config {
int_f(vars, "qp", video.qp);
int_between_f(vars, "hevc_mode", video.hevc_mode, {0, 3});
int_between_f(vars, "av1_mode", video.av1_mode, {0, 3});
int_between_f(vars, "prores_mode", video.prores_mode, {0, 2});
generic_f(vars, "prores_profile", video.prores_profile, prores::profile_from_view);
int_f(vars, "min_threads", video.min_threads);
string_f(vars, "sw_preset", video.sw.sw_preset);
if (!video.sw.sw_preset.empty()) {
Expand Down
3 changes: 3 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@
};

void log_config_settings(const std::unordered_map<std::string, std::string> &vars, bool save);
void apply_config(std::unordered_map<std::string, std::string> &&vars);

Check warning on line 34 in src/config.h

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use the transparent equality "std::equal_to<>" and a custom transparent heterogeneous hasher with this associative string container.

See more on https://sonarcloud.io/project/issues?id=LizardByte_Sunshine&issues=AZ5obtt0S-oXNeKBksOu&open=AZ5obtt0S-oXNeKBksOu&pullRequest=5202

struct video_t {
// ffmpeg params
int qp; // higher == more compression and less quality

int hevc_mode;
int av1_mode;
int prores_mode;
std::string prores_profile;

int min_threads; // Minimum number of threads/slices for CPU encoding

Expand Down
9 changes: 5 additions & 4 deletions src/nvenc/nvenc_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,11 @@
}

{
auto video_format_string = client_config.videoFormat == 0 ? "H.264 " :
client_config.videoFormat == 1 ? "HEVC " :
client_config.videoFormat == 2 ? "AV1 " :
" ";
auto video_format_string = client_config.videoFormat == video::SUNSHINE_FORMAT_H264 ? "H.264 " :
client_config.videoFormat == video::SUNSHINE_FORMAT_HEVC ? "HEVC " :
client_config.videoFormat == video::SUNSHINE_FORMAT_AV1 ? "AV1 " :
client_config.videoFormat == video::SUNSHINE_FORMAT_PRORES ? "ProRes " :
" ";

Check warning on line 460 in src/nvenc/nvenc_base.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested conditional operator into an independent statement.

See more on https://sonarcloud.io/project/issues?id=LizardByte_Sunshine&issues=AZ5obttWS-oXNeKBksOt&open=AZ5obttWS-oXNeKBksOt&pullRequest=5202
std::string extra;
if (init_params.enableEncodeAsync) {
extra += " async";
Expand Down
15 changes: 10 additions & 5 deletions src/nvhttp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -736,34 +736,39 @@ namespace nvhttp {
}

uint32_t codec_mode_flags = SCM_H264;
if (video::last_encoder_probe_supported_yuv444_for_codec[0]) {
if (video::last_encoder_probe_supported_yuv444_for_codec[video::SUNSHINE_FORMAT_H264]) {
codec_mode_flags |= SCM_H264_HIGH8_444;
}
if (video::active_hevc_mode >= 2) {
codec_mode_flags |= SCM_HEVC;
if (video::last_encoder_probe_supported_yuv444_for_codec[1]) {
if (video::last_encoder_probe_supported_yuv444_for_codec[video::SUNSHINE_FORMAT_HEVC]) {
codec_mode_flags |= SCM_HEVC_REXT8_444;
}
}
if (video::active_hevc_mode >= 3) {
codec_mode_flags |= SCM_HEVC_MAIN10;
if (video::last_encoder_probe_supported_yuv444_for_codec[1]) {
if (video::last_encoder_probe_supported_yuv444_for_codec[video::SUNSHINE_FORMAT_HEVC]) {
codec_mode_flags |= SCM_HEVC_REXT10_444;
}
}
if (video::active_av1_mode >= 2) {
codec_mode_flags |= SCM_AV1_MAIN8;
if (video::last_encoder_probe_supported_yuv444_for_codec[2]) {
if (video::last_encoder_probe_supported_yuv444_for_codec[video::SUNSHINE_FORMAT_AV1]) {
codec_mode_flags |= SCM_AV1_HIGH8_444;
}
}
if (video::active_av1_mode >= 3) {
codec_mode_flags |= SCM_AV1_MAIN10;
if (video::last_encoder_probe_supported_yuv444_for_codec[2]) {
if (video::last_encoder_probe_supported_yuv444_for_codec[video::SUNSHINE_FORMAT_AV1]) {
codec_mode_flags |= SCM_AV1_HIGH10_444;
}
}
tree.put("root.ServerCodecModeSupport", codec_mode_flags);
if (video::active_prores_mode > 0) {
tree.put("root.SunshineExperimentalProRes", "1");
tree.put("root.SunshineExperimentalProResVideoFormat", video::SUNSHINE_FORMAT_PRORES);
tree.put("root.SunshineExperimentalProResProfile", config::video.prores_profile);
}

if (!config::nvhttp.external_ip.empty()) {
tree.put("root.ExternalIP", config::nvhttp.external_ip);
Expand Down
4 changes: 4 additions & 0 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ namespace platf {
yuv420p10, ///< YUV 4:2:0 10-bit
nv12, ///< NV12
p010, ///< P010
nv24, ///< NV24 (YUV 4:4:4 8-bit BiPlanar)
p410, ///< P410 (YUV 4:4:4 10-bit BiPlanar)
ayuv, ///< AYUV
yuv444p16, ///< Planar 10-bit (shifted to 16-bit) YUV 4:4:4
y410, ///< Y410
Expand All @@ -257,6 +259,8 @@ namespace platf {
_CONVERT(yuv420p10);
_CONVERT(nv12);
_CONVERT(p010);
_CONVERT(nv24);
_CONVERT(p410);
_CONVERT(ayuv);
_CONVERT(yuv444p16);
_CONVERT(y410);
Expand Down
Loading