feat(macOS): libdispalydevice integration (#5338)

Co-authored-by: Dave Lane <42013603+ReenigneArcher@users.noreply.github.com>
This commit is contained in:
martona
2026-07-03 21:11:39 -04:00
committed by GitHub
parent 33aaefa8c1
commit fbafc49768
7 changed files with 214 additions and 299 deletions
+123 -7
View File
@@ -5,6 +5,13 @@
// header include // header include
#include "display_device.h" #include "display_device.h"
// standard includes
#include <algorithm>
#include <cctype>
#include <mutex>
#include <regex>
#include <string_view>
// lib includes // lib includes
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <display_device/audio_context_interface.h> #include <display_device/audio_context_interface.h>
@@ -12,8 +19,6 @@
#include <display_device/json.h> #include <display_device/json.h>
#include <display_device/retry_scheduler.h> #include <display_device/retry_scheduler.h>
#include <display_device/settings_manager_interface.h> #include <display_device/settings_manager_interface.h>
#include <mutex>
#include <regex>
// local includes // local includes
#include "audio.h" #include "audio.h"
@@ -27,6 +32,13 @@
#include <display_device/windows/win_display_device.h> #include <display_device/windows/win_display_device.h>
#endif #endif
#ifdef __APPLE__
#include <display_device/macos/display_power.h>
#include <display_device/macos/mac_api_layer.h>
#include <display_device/macos/mac_display_device.h>
#include <display_device/macos/settings_manager.h>
#endif
namespace display_device { namespace display_device {
namespace { namespace {
constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL {5000}; constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL {5000};
@@ -129,6 +141,37 @@ namespace display_device {
return (int) result; return (int) result;
} }
#ifdef __APPLE__
bool is_unsigned_integer(std::string_view value) {
return !value.empty() && std::ranges::all_of(value, [](unsigned char character) {
return std::isdigit(character);
});
}
#endif
std::string_view apply_result_name(SettingsManagerInterface::ApplyResult result) {
using enum SettingsManagerInterface::ApplyResult;
switch (result) {
case Ok:
return "Ok";
case ApiTemporarilyUnavailable:
return "ApiTemporarilyUnavailable";
case DevicePrepFailed:
return "DevicePrepFailed";
case PrimaryDevicePrepFailed:
return "PrimaryDevicePrepFailed";
case DisplayModePrepFailed:
return "DisplayModePrepFailed";
case HdrStatePrepFailed:
return "HdrStatePrepFailed";
case PersistenceSaveFailed:
return "PersistenceSaveFailed";
}
return "Unknown";
}
/** /**
* @brief Parse resolution value from the string. * @brief Parse resolution value from the string.
* @param input String to be parsed. * @param input String to be parsed.
@@ -624,6 +667,23 @@ namespace display_device {
.m_hdr_blank_delay = video_config.dd.wa.hdr_toggle_delay != std::chrono::milliseconds::zero() ? std::make_optional(video_config.dd.wa.hdr_toggle_delay) : std::nullopt .m_hdr_blank_delay = video_config.dd.wa.hdr_toggle_delay != std::chrono::milliseconds::zero() ? std::make_optional(video_config.dd.wa.hdr_toggle_delay) : std::nullopt
} }
); );
#elif defined(__APPLE__)
return std::make_unique<MacSettingsManager>(
std::make_shared<MacDisplayDevice>(std::make_shared<MacApiLayer>()),
std::make_shared<sunshine_audio_context_t>(),
std::make_unique<MacPersistentState>(
std::make_shared<FileSettingsPersistence>(persistence_filepath)
),
MacWorkarounds {}
);
#else
return nullptr;
#endif
}
std::unique_ptr<DisplayPowerInterface> make_display_power() {
#ifdef __APPLE__
return std::make_unique<MacDisplayPower>(std::make_shared<MacApiLayer>());
#else #else
return nullptr; return nullptr;
#endif #endif
@@ -745,6 +805,24 @@ namespace display_device {
return std::make_unique<deinit_t>(); return std::make_unique<deinit_t>();
} }
bool wake_display(const std::string &display_name, std::chrono::milliseconds timeout) {
const auto display_power {make_display_power()};
if (!display_power) {
return false;
}
return display_power->wakeDisplay(display_name, timeout);
}
std::unique_ptr<DisplayPowerGuardInterface> keep_display_awake(const std::string &reason) {
const auto display_power {make_display_power()};
if (!display_power) {
return nullptr;
}
return display_power->keepDisplayAwake(reason);
}
std::string map_output_name(const std::string &output_name) { std::string map_output_name(const std::string &output_name) {
std::lock_guard lock {DD_DATA.mutex}; std::lock_guard lock {DD_DATA.mutex};
if (!DD_DATA.sm_instance) { if (!DD_DATA.sm_instance) {
@@ -752,9 +830,17 @@ namespace display_device {
return output_name; return output_name;
} }
return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) { const auto mapped_name {DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) {
return settings_iface.getDisplayName(output_name); return settings_iface.getDisplayName(output_name);
}); })};
#ifdef __APPLE__
if (mapped_name.empty() && is_unsigned_integer(output_name)) {
return output_name;
}
#endif
return mapped_name;
} }
void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) { void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
@@ -765,11 +851,13 @@ namespace display_device {
} }
if (const auto *disabled {std::get_if<configuration_disabled_tag_t>(&result)}; disabled) { if (const auto *disabled {std::get_if<configuration_disabled_tag_t>(&result)}; disabled) {
BOOST_LOG(info) << "Display device configuration is disabled. Reverting any active display device configuration.";
revert_configuration(); revert_configuration();
return; return;
} }
// Error already logged for failed_to_parse_tag_t case, and we also don't BOOST_LOG(error) << "Failed to parse display device configuration. Display settings will not be changed.";
// Error details should already be logged for failed_to_parse_tag_t case, and we also don't
// want to revert active configuration in case we have any // want to revert active configuration in case we have any
} }
@@ -780,10 +868,24 @@ namespace display_device {
return; return;
} }
BOOST_LOG(info) << "Scheduling display device configuration:\n"
<< toJson(config);
DD_DATA.sm_instance->schedule([config](auto &settings_iface, auto &stop_token) { DD_DATA.sm_instance->schedule([config](auto &settings_iface, auto &stop_token) {
using enum SettingsManagerInterface::ApplyResult;
// We only want to keep retrying in case of a transient errors. // We only want to keep retrying in case of a transient errors.
// In other cases, when we either fail or succeed we just want to stop... // In other cases, when we either fail or succeed we just want to stop...
if (settings_iface.applySettings(config) != SettingsManagerInterface::ApplyResult::ApiTemporarilyUnavailable) { const auto result {settings_iface.applySettings(config)};
if (result == Ok) {
BOOST_LOG(info) << "Display device configuration applied successfully.";
} else if (result == ApiTemporarilyUnavailable) {
BOOST_LOG(warning) << "Display device configuration API is temporarily unavailable. Will retry.";
} else {
BOOST_LOG(error) << "Display device configuration failed with result: " << apply_result_name(result);
}
if (result != ApiTemporarilyUnavailable) {
stop_token.requestStop(); stop_token.requestStop();
} }
}, },
@@ -831,7 +933,21 @@ namespace display_device {
SingleDisplayConfiguration config; SingleDisplayConfiguration config;
config.m_device_id = video_config.output_name; config.m_device_id = video_config.output_name;
config.m_device_prep = *device_prep; config.m_device_prep = *device_prep;
config.m_hdr_state = parse_hdr_option(video_config, session);
const auto hdr_state {parse_hdr_option(video_config, session)};
#ifdef __APPLE__
if (hdr_state) {
BOOST_LOG(info) << "Ignoring HDR display device request on macOS because macOS HDR changes are not supported by libdisplaydevice.";
}
#else
config.m_hdr_state = hdr_state;
#endif
#ifdef __APPLE__
if (config.m_device_prep != SingleDisplayConfiguration::DevicePreparation::VerifyOnly) {
BOOST_LOG(warning) << "macOS libdisplaydevice currently supports only VerifyOnly display preparation. The requested preparation mode will fail.";
}
#endif
if (!parse_resolution_option(video_config, session, config)) { if (!parse_resolution_option(video_config, session, config)) {
// Error already logged // Error already logged
+18
View File
@@ -5,10 +5,13 @@
#pragma once #pragma once
// standard includes // standard includes
#include <chrono>
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <string>
// lib includes // lib includes
#include <display_device/display_power_interface.h>
#include <display_device/types.h> #include <display_device/types.h>
// forward declarations // forward declarations
@@ -50,6 +53,21 @@ namespace display_device {
*/ */
[[nodiscard]] std::string map_output_name(const std::string &output_name); [[nodiscard]] std::string map_output_name(const std::string &output_name);
/**
* @brief Ask the platform to wake displays before detection or capture.
* @param display_name Platform capture selector.
* @param timeout Maximum time to wait for platform-specific wake detection.
* @returns True if the display was already available or the wake request succeeded.
*/
[[nodiscard]] bool wake_display(const std::string &display_name, std::chrono::milliseconds timeout);
/**
* @brief Keep displays awake until the returned guard is destroyed.
* @param reason Short human-readable reason for the power assertion.
* @returns A guard owning the platform assertion, or nullptr if unsupported or unavailable.
*/
[[nodiscard]] std::unique_ptr<DisplayPowerGuardInterface> keep_display_awake(const std::string &reason);
/** /**
* @brief Configure the display device based on the user configuration and the session information. * @brief Configure the display device based on the user configuration and the session information.
* @note This is a convenience method for calling similar method of a different signature. * @note This is a convenience method for calling similar method of a different signature.
+1 -17
View File
@@ -5,8 +5,8 @@
#pragma once #pragma once
// platform includes // platform includes
#import <AppKit/AppKit.h>
#import <AVFoundation/AVFoundation.h> #import <AVFoundation/AVFoundation.h>
#import <CoreGraphics/CoreGraphics.h>
/** /**
* @brief macOS capture session and video output handles. * @brief macOS capture session and video output handles.
@@ -16,8 +16,6 @@ struct CaptureSession {
NSCondition *captureStopped; ///< Capture stopped. NSCondition *captureStopped; ///< Capture stopped.
}; };
static const int kMaxDisplays = 32;
/** /**
* @brief AVFoundation video capture controller used by the macOS backend. * @brief AVFoundation video capture controller used by the macOS backend.
*/ */
@@ -66,20 +64,6 @@ typedef bool (^FrameCallbackBlock)(CMSampleBufferRef);
*/ */
@property (nonatomic, assign) NSMapTable<AVCaptureConnection *, dispatch_semaphore_t> *captureSignals; @property (nonatomic, assign) NSMapTable<AVCaptureConnection *, dispatch_semaphore_t> *captureSignals;
/**
* @brief List display names accepted by the selected capture backend.
*
* @return Display names accepted by the selected capture backend.
*/
+ (NSArray<NSDictionary *> *)displayNames;
/**
* @brief Return the user-visible name for a CoreGraphics display.
*
* @param displayID Display ID.
* @return Display name for the supplied CoreGraphics display ID.
*/
+ (NSString *)getDisplayName:(CGDirectDisplayID)displayID;
/** /**
* @brief Initialize AVFoundation capture for a display and frame rate. * @brief Initialize AVFoundation capture for a display and frame rate.
* *
+4 -34
View File
@@ -7,44 +7,14 @@
@implementation AVVideo @implementation AVVideo
// XXX: Currently, this function only returns the screen IDs as names,
// which is not very helpful to the user. The API to retrieve names
// was deprecated with 10.9+.
// However, there is a solution with little external code that can be used:
// https://stackoverflow.com/questions/20025868/cgdisplayioserviceport-is-deprecated-in-os-x-10-9-how-to-replace
+ (NSArray<NSDictionary *> *)displayNames {
CGDirectDisplayID displays[kMaxDisplays];
uint32_t count;
if (CGGetActiveDisplayList(kMaxDisplays, displays, &count) != kCGErrorSuccess) {
return [NSArray array];
}
NSMutableArray *result = [NSMutableArray array];
for (uint32_t i = 0; i < count; i++) {
[result addObject:@{
@"id": [NSNumber numberWithUnsignedInt:displays[i]],
@"name": [NSString stringWithFormat:@"%d", displays[i]],
@"displayName": [self getDisplayName:displays[i]],
}];
}
return [NSArray arrayWithArray:result];
}
+ (NSString *)getDisplayName:(CGDirectDisplayID)displayID {
for (NSScreen *screen in [NSScreen screens]) {
if ([screen.deviceDescription[@"NSScreenNumber"] isEqualToNumber:[NSNumber numberWithUnsignedInt:displayID]]) {
return screen.localizedName;
}
}
return nil;
}
- (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate { - (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate {
self = [super init]; self = [super init];
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayID); CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayID);
if (!mode) {
[self release];
return nil;
}
self.displayID = displayID; self.displayID = displayID;
self.pixelFormat = kCVPixelFormatType_32BGRA; self.pixelFormat = kCVPixelFormatType_32BGRA;
+62 -240
View File
@@ -4,15 +4,14 @@
*/ */
// standard includes // standard includes
#include <cstdlib> #include <charconv>
#include <sstream> #include <chrono>
#include <thread> #include <optional>
#include <string_view>
// platform includes
#include <IOKit/pwr_mgt/IOPMLib.h>
// local includes // local includes
#include "src/config.h" #include "src/config.h"
#include "src/display_device.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/platform/macos/av_img_t.h" #include "src/platform/macos/av_img_t.h"
@@ -29,188 +28,29 @@
#include "src/video.h" #include "src/video.h"
#undef AVMediaType #undef AVMediaType
namespace fs = std::filesystem;
namespace platf { namespace platf {
using namespace std::literals; using namespace std::literals;
namespace { namespace {
const char *cg_error_name(CGError error) { std::optional<CGDirectDisplayID> parse_display_id(std::string_view display_name) {
switch (error) {
case kCGErrorSuccess:
return "success";
case kCGErrorFailure:
return "failure";
case kCGErrorIllegalArgument:
return "illegal argument";
case kCGErrorInvalidConnection:
return "invalid connection";
case kCGErrorInvalidContext:
return "invalid context";
case kCGErrorCannotComplete:
return "cannot complete";
case kCGErrorNotImplemented:
return "not implemented";
case kCGErrorRangeCheck:
return "range check";
case kCGErrorTypeCheck:
return "type check";
case kCGErrorInvalidOperation:
return "invalid operation";
case kCGErrorNoneAvailable:
return "none available";
default:
return "unknown";
}
}
std::string format_rect(CGRect rect) {
std::ostringstream formatted;
formatted << '(' << rect.origin.x << ',' << rect.origin.y << ") "
<< rect.size.width << 'x' << rect.size.height;
return formatted.str();
}
std::string display_mode_summary(CGDisplayModeRef mode) {
if (!mode) {
return "<none>";
}
std::ostringstream formatted;
formatted << CGDisplayModeGetWidth(mode) << 'x' << CGDisplayModeGetHeight(mode)
<< " points, " << CGDisplayModeGetPixelWidth(mode) << 'x'
<< CGDisplayModeGetPixelHeight(mode) << " pixels";
const auto refresh_rate = CGDisplayModeGetRefreshRate(mode);
if (refresh_rate > 0) {
formatted << ", " << refresh_rate << " Hz";
}
return formatted.str();
}
void log_display_diagnostic(CGDirectDisplayID display_id, const char *source) {
NSString *display_name = [AVVideo getDisplayName:display_id];
const char *display_name_utf8 = display_name ? display_name.UTF8String : "<unknown>";
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id);
BOOST_LOG(info) << "Display diagnostic ["sv << source << "]: id: "sv << display_id
<< ", name: "sv << display_name_utf8
<< ", main: "sv << (display_id == CGMainDisplayID())
<< ", active: "sv << CGDisplayIsActive(display_id)
<< ", online: "sv << CGDisplayIsOnline(display_id)
<< ", asleep: "sv << CGDisplayIsAsleep(display_id)
<< ", built-in: "sv << CGDisplayIsBuiltin(display_id)
<< ", bounds: "sv << format_rect(CGDisplayBounds(display_id))
<< ", framebuffer pixels: "sv << CGDisplayPixelsWide(display_id) << 'x' << CGDisplayPixelsHigh(display_id)
<< ", mode: "sv << display_mode_summary(mode);
if (mode) {
CFRelease(mode);
}
}
void log_nsscreen_diagnostics() {
NSArray<NSScreen *> *screens = [NSScreen screens];
BOOST_LOG(info) << "NSScreen diagnostics: count: "sv << [screens count];
for (NSScreen *screen in screens) {
NSNumber *display_id = screen.deviceDescription[@"NSScreenNumber"];
NSString *screen_name = screen.localizedName;
BOOST_LOG(info) << "NSScreen diagnostic: id: "sv << (display_id ? [display_id unsignedIntValue] : 0)
<< ", name: "sv << (screen_name ? screen_name.UTF8String : "<unknown>")
<< ", frame: "sv << format_rect(screen.frame)
<< ", backing scale: "sv << screen.backingScaleFactor;
}
}
void log_display_list_diagnostics(const char *list_name, CGError error, const CGDirectDisplayID *displays, uint32_t count) {
BOOST_LOG(info) << list_name << ": status: "sv << cg_error_name(error)
<< " ("sv << error << "), count: "sv << count;
if (error != kCGErrorSuccess) {
return;
}
for (uint32_t i = 0; i < count; ++i) {
log_display_diagnostic(displays[i], list_name);
}
}
void log_display_environment_diagnostics() {
CGDirectDisplayID active_displays[kMaxDisplays];
CGDirectDisplayID online_displays[kMaxDisplays];
uint32_t active_display_count = 0;
uint32_t online_display_count = 0;
const auto active_error = CGGetActiveDisplayList(kMaxDisplays, active_displays, &active_display_count);
const auto online_error = CGGetOnlineDisplayList(kMaxDisplays, online_displays, &online_display_count);
BOOST_LOG(info) << "Main display diagnostic: id: "sv << CGMainDisplayID();
log_display_list_diagnostics("CGGetActiveDisplayList", active_error, active_displays, active_display_count);
log_display_list_diagnostics("CGGetOnlineDisplayList", online_error, online_displays, online_display_count);
log_nsscreen_diagnostics();
}
bool has_required_active_display(const std::string &display_name) {
CGDirectDisplayID displays[kMaxDisplays];
uint32_t display_count = 0;
if (CGGetActiveDisplayList(kMaxDisplays, displays, &display_count) != kCGErrorSuccess) {
return false;
}
if (display_name.empty()) { if (display_name.empty()) {
return display_count > 0; return std::nullopt;
} }
char *end = nullptr; CGDirectDisplayID display_id {};
const auto selected_display_id = std::strtoul(display_name.c_str(), &end, 10); const auto *const begin {display_name.data()};
if (!end || *end != '\0') { const auto *const end {display_name.data() + display_name.size()};
return display_count > 0; const auto [ptr, ec] {std::from_chars(begin, end, display_id)};
if (ec != std::errc {} || ptr != end) {
return std::nullopt;
} }
for (uint32_t i = 0; i < display_count; ++i) { return display_id;
if (displays[i] == selected_display_id) {
return true;
}
} }
return false; OSType videotoolbox_pixel_format(const video::config_t &config) {
} const auto colorspace {video::colorspace_from_client_config(config, false)};
return colorspace.bit_depth == 10 ? kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
void wake_displays_for_detection(const std::string &display_name) {
IOPMAssertionID wake_assertion = kIOPMNullAssertionID;
const auto result = IOPMAssertionDeclareUserActivity(
CFSTR("Sunshine display detection"),
kIOPMUserActiveRemote,
&wake_assertion
);
if (result != kIOReturnSuccess) {
BOOST_LOG(warning) << "Unable to declare remote user activity to wake displays, IOReturn: "sv << result;
return;
}
BOOST_LOG(info) << "Declared remote user activity to wake displays, assertion id: "sv << wake_assertion;
for (int attempt = 0; attempt < 10 && !has_required_active_display(display_name); ++attempt) {
std::this_thread::sleep_for(100ms);
}
if (!has_required_active_display(display_name)) {
BOOST_LOG(warning) << "Display wake attempt did not expose the requested display ["sv
<< display_name << "] in the active display list."sv;
}
if (wake_assertion != kIOPMNullAssertionID) {
const auto release_result = IOPMAssertionRelease(wake_assertion);
if (release_result != kIOReturnSuccess) {
BOOST_LOG(warning) << "Unable to release display wake assertion, IOReturn: "sv << release_result;
}
}
} }
} // namespace } // namespace
@@ -220,41 +60,10 @@ namespace platf {
struct av_display_t: public display_t { struct av_display_t: public display_t {
AVVideo *av_capture {}; ///< AV capture. AVVideo *av_capture {}; ///< AV capture.
CGDirectDisplayID display_id {}; ///< Display ID. CGDirectDisplayID display_id {}; ///< Display ID.
IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID}; ///< Display sleep assertion. std::unique_ptr<display_device::DisplayPowerGuardInterface> display_power_guard; ///< Display power guard.
~av_display_t() override { ~av_display_t() override {
[av_capture release]; [av_capture release];
if (display_sleep_assertion != kIOPMNullAssertionID) {
const auto result = IOPMAssertionRelease(display_sleep_assertion);
if (result != kIOReturnSuccess) {
BOOST_LOG(warning) << "Unable to release display sleep assertion, IOReturn: "sv << result;
}
}
}
/**
* @brief Prevent macOS from sleeping the captured display while streaming.
*/
void prevent_display_sleep() {
if (display_sleep_assertion != kIOPMNullAssertionID) {
return;
}
const auto result = IOPMAssertionCreateWithName(
kIOPMAssertPreventUserIdleDisplaySleep,
kIOPMAssertionLevelOn,
CFSTR("Sunshine display capture"),
&display_sleep_assertion
);
if (result == kIOReturnSuccess) {
BOOST_LOG(info) << "Created display sleep prevention assertion, assertion id: "sv << display_sleep_assertion;
return;
}
display_sleep_assertion = kIOPMNullAssertionID;
BOOST_LOG(warning) << "Unable to create display sleep prevention assertion, IOReturn: "sv << result;
} }
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override { capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
@@ -412,37 +221,41 @@ namespace platf {
} }
auto display = std::make_shared<av_display_t>(); auto display = std::make_shared<av_display_t>();
display->prevent_display_sleep();
wake_displays_for_detection(display_name); BOOST_LOG(debug) << "Waking display for capture selector ["sv << display_name << ']';
if (!display_device::wake_display(display_name, 1s)) {
BOOST_LOG(debug) << "Display wake attempt did not expose the requested display ["sv << display_name << ']';
}
display->display_power_guard = display_device::keep_display_awake("Sunshine display capture");
if (display->display_power_guard) {
BOOST_LOG(debug) << "Keeping display awake for capture"sv;
} else {
BOOST_LOG(debug) << "Unable to create display sleep prevention assertion"sv;
}
// Default to main display // Default to main display
display->display_id = CGMainDisplayID(); display->display_id = CGMainDisplayID();
// Print all displays available with it's name and id if (const auto configured_display_id {parse_display_id(display_name)}) {
BOOST_LOG(info) << "Detecting displays"sv; display->display_id = *configured_display_id;
log_display_environment_diagnostics(); } else if (!display_name.empty()) {
auto display_array = [AVVideo displayNames];
bool matched_configured_display = display_name.empty();
for (NSDictionary *item in display_array) {
NSNumber *display_id = item[@"id"];
// We need show display's product name and corresponding display number given by user
NSString *name = item[@"displayName"];
// We are using CGGetActiveDisplayList that only returns active displays so hardcoded connected value in log to true
BOOST_LOG(info) << "Detected display: "sv << name.UTF8String << " (id: "sv << [NSString stringWithFormat:@"%@", display_id].UTF8String << ") connected: true"sv;
if (!display_name.empty() && std::atoi(display_name.c_str()) == [display_id unsignedIntValue]) {
display->display_id = [display_id unsignedIntValue];
matched_configured_display = true;
}
}
if (!matched_configured_display) {
BOOST_LOG(warning) << "Configured display ["sv << display_name BOOST_LOG(warning) << "Configured display ["sv << display_name
<< "] was not found in the active display list. Falling back to main display ["sv << "] is not a valid macOS capture display id. Falling back to main display ["sv
<< display->display_id << "]."sv; << display->display_id << "]."sv;
} }
log_display_diagnostic(display->display_id, "selected for AVFoundation capture"); // Print all displays available with their names and ids
BOOST_LOG(debug) << "Detecting displays"sv;
for (const auto &device : display_device::enumerate_devices()) {
if (device.m_display_name.empty()) {
continue;
}
BOOST_LOG(debug) << "Detected display: "sv << device.m_friendly_name
<< " (id: "sv << device.m_display_name << ") connected: true"sv;
}
BOOST_LOG(info) << "Configuring selected display ("sv << display->display_id << ") to stream"sv; 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]; display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:config.framerate];
@@ -458,19 +271,28 @@ namespace platf {
display->env_width = display->width; display->env_width = display->width;
display->env_height = display->height; display->env_height = display->height;
if (hwdevice_type == platf::mem_type_e::videotoolbox) {
const auto pixel_format {videotoolbox_pixel_format(config)};
[display->av_capture setFrameWidth:config.width frameHeight:config.height];
display->av_capture.pixelFormat = pixel_format;
}
return display; return display;
} }
std::vector<std::string> display_names(mem_type_e hwdevice_type) { std::vector<std::string> display_names(mem_type_e hwdevice_type) {
__block std::vector<std::string> display_names; std::vector<std::string> display_names;
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::videotoolbox) {
return display_names;
}
auto display_array = [AVVideo displayNames]; const auto devices {display_device::enumerate_devices()};
display_names.reserve(devices.size());
display_names.reserve([display_array count]); for (const auto &device : devices) {
[display_array enumerateObjectsUsingBlock:^(NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { if (!device.m_display_name.empty()) {
NSString *name = obj[@"name"]; display_names.emplace_back(device.m_display_name);
display_names.emplace_back(name.UTF8String); }
}]; }
return display_names; return display_names;
} }
+5
View File
@@ -102,8 +102,13 @@ INSTANTIATE_TEST_SUITE_P(
testing::Values( testing::Values(
std::make_pair(std::make_pair(hdr_option_e::disabled, client_wants_hdr_t {true}), std::nullopt), std::make_pair(std::make_pair(hdr_option_e::disabled, client_wants_hdr_t {true}), std::nullopt),
std::make_pair(std::make_pair(hdr_option_e::disabled, client_wants_hdr_t {false}), std::nullopt), std::make_pair(std::make_pair(hdr_option_e::disabled, client_wants_hdr_t {false}), std::nullopt),
#ifdef __APPLE__
std::make_pair(std::make_pair(hdr_option_e::automatic, client_wants_hdr_t {true}), std::nullopt),
std::make_pair(std::make_pair(hdr_option_e::automatic, client_wants_hdr_t {false}), std::nullopt)
#else
std::make_pair(std::make_pair(hdr_option_e::automatic, client_wants_hdr_t {true}), hdr_state_e::Enabled), std::make_pair(std::make_pair(hdr_option_e::automatic, client_wants_hdr_t {true}), hdr_state_e::Enabled),
std::make_pair(std::make_pair(hdr_option_e::automatic, client_wants_hdr_t {false}), hdr_state_e::Disabled) std::make_pair(std::make_pair(hdr_option_e::automatic, client_wants_hdr_t {false}), hdr_state_e::Disabled)
#endif
) )
); );