2023-07-03 18:26:53 +00:00
|
|
|
//
|
|
|
|
// plugin-properties.m
|
|
|
|
// mac-avcapture
|
|
|
|
//
|
|
|
|
// Created by Patrick Heyer on 2023-03-07.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "OBSAVCapture.h"
|
|
|
|
#import "plugin-properties.h"
|
|
|
|
|
|
|
|
extern const char *av_capture_get_text(const char *text_id);
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
void configure_property(obs_property_t *property, bool enable, bool visible, void *callback, OBSAVCapture *capture)
|
2023-07-03 18:26:53 +00:00
|
|
|
{
|
|
|
|
if (property) {
|
|
|
|
obs_property_set_enabled(property, enable);
|
|
|
|
obs_property_set_visible(property, visible);
|
|
|
|
|
|
|
|
if (callback) {
|
2024-02-20 19:15:19 +00:00
|
|
|
obs_property_set_modified_callback2(property, callback, (__bridge void *) (capture));
|
2023-07-03 18:26:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
bool properties_changed(OBSAVCapture *capture, obs_properties_t *properties, obs_property_t *property __unused,
|
2023-07-03 18:26:53 +00:00
|
|
|
obs_data_t *settings)
|
|
|
|
{
|
2024-02-20 19:15:19 +00:00
|
|
|
OBSAVCaptureInfo *captureInfo = capture.captureInfo;
|
|
|
|
|
2023-07-03 18:26:53 +00:00
|
|
|
obs_property_t *prop_use_preset = obs_properties_get(properties, "use_preset");
|
|
|
|
obs_property_t *prop_device = obs_properties_get(properties, "device");
|
|
|
|
obs_property_t *prop_presets = obs_properties_get(properties, "preset");
|
|
|
|
|
|
|
|
obs_property_set_enabled(prop_use_preset, !captureInfo->isFastPath);
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
if (captureInfo && settings) {
|
|
|
|
properties_update_device(capture, prop_device, settings);
|
2023-07-03 18:26:53 +00:00
|
|
|
|
|
|
|
bool use_preset = (settings ? obs_data_get_bool(settings, "use_preset") : true);
|
|
|
|
|
|
|
|
if (use_preset) {
|
2024-02-20 19:15:19 +00:00
|
|
|
properties_update_preset(capture, prop_presets, settings);
|
2023-07-03 18:26:53 +00:00
|
|
|
} else {
|
2024-02-20 19:15:19 +00:00
|
|
|
properties_update_config(capture, properties, settings);
|
2023-07-03 18:26:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
bool properties_changed_preset(OBSAVCapture *capture, obs_properties_t *properties __unused, obs_property_t *property,
|
|
|
|
obs_data_t *settings)
|
2023-07-03 18:26:53 +00:00
|
|
|
{
|
|
|
|
bool use_preset = obs_data_get_bool(settings, "use_preset");
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
if (capture && settings && use_preset) {
|
2023-07-03 18:26:53 +00:00
|
|
|
NSArray *presetKeys =
|
|
|
|
[capture.presetList keysSortedByValueUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
|
|
|
|
NSNumber *obj1Resolution;
|
|
|
|
NSNumber *obj2Resolution;
|
|
|
|
if ([obj1 isEqualToString:@"High"]) {
|
|
|
|
obj1Resolution = @3;
|
|
|
|
} else if ([obj1 isEqualToString:@"Medium"]) {
|
|
|
|
obj1Resolution = @2;
|
|
|
|
} else if ([obj1 isEqualToString:@"Low"]) {
|
|
|
|
obj1Resolution = @1;
|
|
|
|
} else {
|
|
|
|
NSArray<NSString *> *obj1Dimensions = [obj1 componentsSeparatedByString:@"x"];
|
|
|
|
obj1Resolution = [NSNumber numberWithInt:([[obj1Dimensions objectAtIndex:0] intValue] *
|
|
|
|
[[obj1Dimensions objectAtIndex:1] intValue])];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([obj2 isEqualToString:@"High"]) {
|
|
|
|
obj2Resolution = @3;
|
|
|
|
} else if ([obj2 isEqualToString:@"Medium"]) {
|
|
|
|
obj2Resolution = @2;
|
|
|
|
} else if ([obj2 isEqualToString:@"Low"]) {
|
|
|
|
obj2Resolution = @1;
|
|
|
|
} else {
|
|
|
|
NSArray<NSString *> *obj2Dimensions = [obj2 componentsSeparatedByString:@"x"];
|
|
|
|
obj2Resolution = [NSNumber numberWithInt:([[obj2Dimensions objectAtIndex:0] intValue] *
|
|
|
|
[[obj2Dimensions objectAtIndex:1] intValue])];
|
|
|
|
}
|
|
|
|
|
|
|
|
NSComparisonResult result = [obj1Resolution compare:obj2Resolution];
|
|
|
|
|
|
|
|
if (result == NSOrderedAscending) {
|
|
|
|
return (NSComparisonResult) NSOrderedDescending;
|
|
|
|
} else if (result == NSOrderedDescending) {
|
|
|
|
return (NSComparisonResult) NSOrderedAscending;
|
|
|
|
} else {
|
|
|
|
return (NSComparisonResult) NSOrderedSame;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
|
|
|
|
NSString *UUID = [OBSAVCapture stringFromSettings:settings withSetting:@"device"];
|
|
|
|
AVCaptureDevice *device = [AVCaptureDevice deviceWithUniqueID:UUID];
|
|
|
|
NSString *currentPreset = [OBSAVCapture stringFromSettings:settings withSetting:@"preset"];
|
|
|
|
|
|
|
|
obs_property_list_clear(property);
|
|
|
|
|
|
|
|
if (device) {
|
|
|
|
for (NSString *presetName in presetKeys) {
|
|
|
|
NSString *presetDescription = capture.presetList[presetName];
|
|
|
|
|
|
|
|
if ([device supportsAVCaptureSessionPreset:presetName]) {
|
|
|
|
obs_property_list_add_string(property, presetDescription.UTF8String, presetName.UTF8String);
|
|
|
|
} else if ([currentPreset isEqualToString:presetName]) {
|
|
|
|
size_t index =
|
|
|
|
obs_property_list_add_string(property, presetDescription.UTF8String, presetName.UTF8String);
|
|
|
|
obs_property_list_item_disable(property, index, true);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} else if (UUID.length) {
|
|
|
|
size_t index = obs_property_list_add_string(property, capture.presetList[currentPreset].UTF8String,
|
|
|
|
currentPreset.UTF8String);
|
|
|
|
obs_property_list_item_disable(property, index, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
} else {
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
bool properties_changed_use_preset(OBSAVCapture *capture, obs_properties_t *properties,
|
2023-07-03 18:26:53 +00:00
|
|
|
obs_property_t *property __unused, obs_data_t *settings)
|
|
|
|
{
|
|
|
|
bool use_preset = obs_data_get_bool(settings, "use_preset");
|
|
|
|
obs_property_t *preset_list = obs_properties_get(properties, "preset");
|
|
|
|
|
|
|
|
obs_property_set_visible(preset_list, use_preset);
|
|
|
|
|
|
|
|
if (use_preset) {
|
2024-02-20 19:15:19 +00:00
|
|
|
properties_changed_preset(capture, properties, preset_list, settings);
|
2023-07-03 18:26:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *update_properties[5] = {"resolution", "frame_rate", "color_space", "video_range", "input_format"};
|
|
|
|
|
|
|
|
size_t number_of_properties = sizeof(update_properties) / sizeof(update_properties[0]);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < number_of_properties; i++) {
|
|
|
|
obs_property_t *update_property = obs_properties_get(properties, update_properties[i]);
|
|
|
|
|
|
|
|
if (update_property) {
|
|
|
|
obs_property_set_visible(update_property, !use_preset);
|
|
|
|
obs_property_set_enabled(update_property, !use_preset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
bool properties_update_preset(OBSAVCapture *capture, obs_property_t *property, obs_data_t *settings)
|
2023-07-03 18:26:53 +00:00
|
|
|
{
|
2024-02-20 19:15:19 +00:00
|
|
|
NSArray *presetKeys =
|
|
|
|
[capture.presetList keysSortedByValueUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
|
2023-07-03 18:26:53 +00:00
|
|
|
NSNumber *obj1Resolution;
|
|
|
|
NSNumber *obj2Resolution;
|
|
|
|
if ([obj1 isEqualToString:@"High"]) {
|
|
|
|
obj1Resolution = @3;
|
|
|
|
} else if ([obj1 isEqualToString:@"Medium"]) {
|
|
|
|
obj1Resolution = @2;
|
|
|
|
} else if ([obj1 isEqualToString:@"Low"]) {
|
|
|
|
obj1Resolution = @1;
|
|
|
|
} else {
|
|
|
|
NSArray<NSString *> *obj1Dimensions = [obj1 componentsSeparatedByString:@"x"];
|
|
|
|
obj1Resolution = [NSNumber numberWithInt:([[obj1Dimensions objectAtIndex:0] intValue] *
|
|
|
|
[[obj1Dimensions objectAtIndex:1] intValue])];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([obj2 isEqualToString:@"High"]) {
|
|
|
|
obj2Resolution = @3;
|
|
|
|
} else if ([obj2 isEqualToString:@"Medium"]) {
|
|
|
|
obj2Resolution = @2;
|
|
|
|
} else if ([obj2 isEqualToString:@"Low"]) {
|
|
|
|
obj2Resolution = @1;
|
|
|
|
} else {
|
|
|
|
NSArray<NSString *> *obj2Dimensions = [obj2 componentsSeparatedByString:@"x"];
|
|
|
|
obj2Resolution = [NSNumber numberWithInt:([[obj2Dimensions objectAtIndex:0] intValue] *
|
|
|
|
[[obj2Dimensions objectAtIndex:1] intValue])];
|
|
|
|
}
|
|
|
|
|
|
|
|
NSComparisonResult result = [obj1Resolution compare:obj2Resolution];
|
|
|
|
|
|
|
|
if (result == NSOrderedAscending) {
|
|
|
|
return (NSComparisonResult) NSOrderedDescending;
|
|
|
|
} else if (result == NSOrderedDescending) {
|
|
|
|
return (NSComparisonResult) NSOrderedAscending;
|
|
|
|
} else {
|
|
|
|
return (NSComparisonResult) NSOrderedSame;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
|
|
|
|
NSString *deviceUUID = [OBSAVCapture stringFromSettings:settings withSetting:@"device"];
|
|
|
|
AVCaptureDevice *device = [AVCaptureDevice deviceWithUniqueID:deviceUUID];
|
|
|
|
NSString *currentPreset = [OBSAVCapture stringFromSettings:settings withSetting:@"preset"];
|
|
|
|
|
|
|
|
obs_property_list_clear(property);
|
|
|
|
|
|
|
|
if (device) {
|
|
|
|
for (NSString *presetName in presetKeys) {
|
2024-02-20 19:15:19 +00:00
|
|
|
NSString *presetDescription = capture.presetList[presetName];
|
2023-07-03 18:26:53 +00:00
|
|
|
|
|
|
|
if ([device supportsAVCaptureSessionPreset:presetName]) {
|
|
|
|
obs_property_list_add_string(property, presetDescription.UTF8String, presetName.UTF8String);
|
|
|
|
} else if ([currentPreset isEqualToString:presetName]) {
|
|
|
|
size_t index =
|
|
|
|
obs_property_list_add_string(property, presetDescription.UTF8String, presetName.UTF8String);
|
|
|
|
obs_property_list_item_disable(property, index, true);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} else if (deviceUUID.length) {
|
2024-02-20 19:15:19 +00:00
|
|
|
size_t index = obs_property_list_add_string(property, capture.presetList[currentPreset].UTF8String,
|
2023-07-03 18:26:53 +00:00
|
|
|
currentPreset.UTF8String);
|
|
|
|
obs_property_list_item_disable(property, index, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
bool properties_update_device(OBSAVCapture *capture __unused, obs_property_t *property, obs_data_t *settings)
|
2023-07-03 18:26:53 +00:00
|
|
|
{
|
|
|
|
obs_property_list_clear(property);
|
|
|
|
|
|
|
|
NSString *currentDeviceUUID = [OBSAVCapture stringFromSettings:settings withSetting:@"device"];
|
|
|
|
NSString *currentDeviceName = [OBSAVCapture stringFromSettings:settings withSetting:@"device_name"];
|
|
|
|
BOOL isDeviceFound = NO;
|
|
|
|
|
|
|
|
obs_property_list_add_string(property, "", "");
|
|
|
|
|
|
|
|
NSArray *deviceTypes;
|
|
|
|
if (@available(macOS 13, *)) {
|
|
|
|
deviceTypes = @[
|
|
|
|
AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeExternalUnknown,
|
|
|
|
AVCaptureDeviceTypeDeskViewCamera
|
|
|
|
];
|
|
|
|
} else {
|
|
|
|
deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeExternalUnknown];
|
|
|
|
}
|
|
|
|
|
|
|
|
AVCaptureDeviceDiscoverySession *videoDiscoverySession =
|
|
|
|
[AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo
|
|
|
|
position:AVCaptureDevicePositionUnspecified];
|
|
|
|
AVCaptureDeviceDiscoverySession *muxedDiscoverySession =
|
|
|
|
[AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeMuxed
|
|
|
|
position:AVCaptureDevicePositionUnspecified];
|
|
|
|
|
|
|
|
for (AVCaptureDevice *device in [videoDiscoverySession devices]) {
|
|
|
|
obs_property_list_add_string(property, device.localizedName.UTF8String, device.uniqueID.UTF8String);
|
|
|
|
|
|
|
|
if (!isDeviceFound && [currentDeviceUUID isEqualToString:device.uniqueID]) {
|
|
|
|
isDeviceFound = YES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (AVCaptureDevice *device in [muxedDiscoverySession devices]) {
|
|
|
|
obs_property_list_add_string(property, device.localizedName.UTF8String, device.uniqueID.UTF8String);
|
|
|
|
|
|
|
|
if (!isDeviceFound && [currentDeviceUUID isEqualToString:device.uniqueID]) {
|
|
|
|
isDeviceFound = YES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isDeviceFound && currentDeviceUUID.length > 0) {
|
|
|
|
size_t index =
|
|
|
|
obs_property_list_add_string(property, currentDeviceName.UTF8String, currentDeviceUUID.UTF8String);
|
|
|
|
obs_property_list_item_disable(property, index, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
bool properties_update_config(OBSAVCapture *capture, obs_properties_t *properties, obs_data_t *settings)
|
2023-07-03 18:26:53 +00:00
|
|
|
{
|
|
|
|
AVCaptureDevice *device = [AVCaptureDevice deviceWithUniqueID:[OBSAVCapture stringFromSettings:settings
|
|
|
|
withSetting:@"device"]];
|
|
|
|
|
|
|
|
obs_property_t *prop_resolution = obs_properties_get(properties, "resolution");
|
|
|
|
obs_property_t *prop_framerate = obs_properties_get(properties, "frame_rate");
|
|
|
|
|
|
|
|
obs_property_list_clear(prop_resolution);
|
|
|
|
obs_property_frame_rate_clear(prop_framerate);
|
|
|
|
|
|
|
|
obs_property_t *prop_input_format = NULL;
|
|
|
|
obs_property_t *prop_color_space = NULL;
|
|
|
|
obs_property_t *prop_video_range = NULL;
|
|
|
|
|
2024-02-15 21:06:21 +00:00
|
|
|
prop_input_format = obs_properties_get(properties, "input_format");
|
|
|
|
obs_property_list_clear(prop_input_format);
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
if (!capture.isFastPath) {
|
2023-07-03 18:26:53 +00:00
|
|
|
prop_color_space = obs_properties_get(properties, "color_space");
|
|
|
|
prop_video_range = obs_properties_get(properties, "video_range");
|
|
|
|
|
|
|
|
obs_property_list_clear(prop_video_range);
|
|
|
|
obs_property_list_clear(prop_color_space);
|
|
|
|
}
|
|
|
|
|
|
|
|
CMVideoDimensions resolution = [OBSAVCapture dimensionsFromSettings:settings];
|
|
|
|
|
|
|
|
if (resolution.width == 0 || resolution.height == 0) {
|
2024-02-20 19:15:19 +00:00
|
|
|
[capture AVCaptureLog:LOG_DEBUG withFormat:@"No valid resolution found in settings"];
|
2023-07-03 18:26:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct media_frames_per_second fps;
|
|
|
|
if (!obs_data_get_frames_per_second(settings, "frame_rate", &fps, NULL)) {
|
2024-02-20 19:15:19 +00:00
|
|
|
[capture AVCaptureLog:LOG_DEBUG withFormat:@"No valid framerate found in settings"];
|
2023-07-03 18:26:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CMTime time = {.value = fps.denominator, .timescale = fps.numerator, .flags = 1};
|
|
|
|
|
|
|
|
int input_format = 0;
|
|
|
|
int color_space = 0;
|
|
|
|
int video_range = 0;
|
|
|
|
|
|
|
|
NSMutableArray *inputFormats = NULL;
|
|
|
|
NSMutableArray *colorSpaces = NULL;
|
|
|
|
NSMutableArray *videoRanges = NULL;
|
|
|
|
|
2024-02-15 21:06:21 +00:00
|
|
|
input_format = (int) obs_data_get_int(settings, "input_format");
|
|
|
|
inputFormats = [[NSMutableArray alloc] init];
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
if (!capture.isFastPath) {
|
2023-07-03 18:26:53 +00:00
|
|
|
color_space = (int) obs_data_get_int(settings, "color_space");
|
|
|
|
video_range = (int) obs_data_get_int(settings, "video_range");
|
|
|
|
|
|
|
|
colorSpaces = [[NSMutableArray alloc] init];
|
|
|
|
videoRanges = [[NSMutableArray alloc] init];
|
|
|
|
}
|
|
|
|
|
|
|
|
NSMutableArray *resolutions = [[NSMutableArray alloc] init];
|
|
|
|
NSMutableArray *frameRates = [[NSMutableArray alloc] init];
|
|
|
|
|
|
|
|
BOOL hasFoundResolution = NO;
|
|
|
|
BOOL hasFoundFramerate = NO;
|
2024-02-15 21:06:21 +00:00
|
|
|
BOOL hasFoundInputFormat = NO;
|
2024-02-20 19:15:19 +00:00
|
|
|
BOOL hasFoundColorSpace = capture.isFastPath;
|
|
|
|
BOOL hasFoundVideoRange = capture.isFastPath;
|
2023-07-03 18:26:53 +00:00
|
|
|
|
2024-02-24 02:33:04 +00:00
|
|
|
CFPropertyListRef priorColorPrimary = @"";
|
|
|
|
|
2023-07-03 18:26:53 +00:00
|
|
|
if (device) {
|
|
|
|
// Iterate over all formats reported by the device and gather them for property lists
|
|
|
|
for (AVCaptureDeviceFormat *format in device.formats) {
|
2024-02-20 19:15:19 +00:00
|
|
|
if (!capture.isFastPath) {
|
2023-07-03 18:26:53 +00:00
|
|
|
FourCharCode formatSubType = CMFormatDescriptionGetMediaSubType(format.formatDescription);
|
|
|
|
|
|
|
|
NSString *formatDescription = [OBSAVCapture stringFromSubType:formatSubType];
|
|
|
|
int device_format = [OBSAVCapture formatFromSubtype:formatSubType];
|
|
|
|
int device_range;
|
|
|
|
const char *range_description;
|
|
|
|
|
|
|
|
if ([OBSAVCapture isFullRangeFormat:formatSubType]) {
|
|
|
|
device_range = VIDEO_RANGE_FULL;
|
|
|
|
range_description = av_capture_get_text("VideoRange.Full");
|
|
|
|
} else {
|
|
|
|
device_range = VIDEO_RANGE_PARTIAL;
|
|
|
|
range_description = av_capture_get_text("VideoRange.Partial");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hasFoundInputFormat && input_format == device_format) {
|
|
|
|
hasFoundInputFormat = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hasFoundVideoRange && video_range == device_range) {
|
|
|
|
hasFoundVideoRange = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (![inputFormats containsObject:@(formatSubType)]) {
|
|
|
|
obs_property_list_add_int(prop_input_format, formatDescription.UTF8String, device_format);
|
|
|
|
[inputFormats addObject:@(formatSubType)];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (![videoRanges containsObject:@(range_description)]) {
|
|
|
|
obs_property_list_add_int(prop_video_range, range_description, device_range);
|
|
|
|
[videoRanges addObject:@(range_description)];
|
|
|
|
}
|
|
|
|
|
|
|
|
int device_color_space = [OBSAVCapture colorspaceFromDescription:format.formatDescription];
|
|
|
|
|
|
|
|
if (![colorSpaces containsObject:@(device_color_space)]) {
|
|
|
|
obs_property_list_add_int(prop_color_space,
|
|
|
|
[OBSAVCapture stringFromColorspace:device_color_space].UTF8String,
|
|
|
|
device_color_space);
|
|
|
|
[colorSpaces addObject:@(device_color_space)];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hasFoundColorSpace && device_color_space == color_space) {
|
|
|
|
hasFoundColorSpace = YES;
|
|
|
|
}
|
2024-02-15 21:06:21 +00:00
|
|
|
} else {
|
|
|
|
FourCharCode formatSubType = CMFormatDescriptionGetMediaSubType(format.formatDescription);
|
|
|
|
|
|
|
|
NSString *formatDescription = [OBSAVCapture stringFromSubType:formatSubType];
|
|
|
|
int device_format = [OBSAVCapture formatFromSubtype:formatSubType];
|
|
|
|
|
|
|
|
if (!hasFoundInputFormat && input_format == device_format) {
|
|
|
|
hasFoundInputFormat = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (![inputFormats containsObject:@(formatSubType)]) {
|
|
|
|
obs_property_list_add_int(prop_input_format, formatDescription.UTF8String, device_format);
|
|
|
|
[inputFormats addObject:@(formatSubType)];
|
|
|
|
}
|
2023-07-03 18:26:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CMVideoDimensions formatDimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription);
|
|
|
|
|
|
|
|
NSDictionary *resolutionData =
|
|
|
|
@{@"width": @(formatDimensions.width),
|
|
|
|
@"height": @(formatDimensions.height)};
|
|
|
|
|
|
|
|
if (![resolutions containsObject:resolutionData]) {
|
|
|
|
[resolutions addObject:resolutionData];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hasFoundResolution && formatDimensions.width == resolution.width &&
|
|
|
|
formatDimensions.height == resolution.height) {
|
|
|
|
hasFoundResolution = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only iterate over available framerates if input format, color space, and resolution are matching
|
2024-02-24 02:33:04 +00:00
|
|
|
if (hasFoundInputFormat && hasFoundColorSpace && hasFoundResolution) {
|
2024-03-08 18:37:59 +00:00
|
|
|
CFComparisonResult isColorPrimaryMatch = kCFCompareEqualTo;
|
|
|
|
|
2024-02-24 02:33:04 +00:00
|
|
|
CFPropertyListRef colorPrimary = CMFormatDescriptionGetExtension(
|
|
|
|
format.formatDescription, kCMFormatDescriptionExtension_ColorPrimaries);
|
|
|
|
|
2024-03-08 18:37:59 +00:00
|
|
|
if (colorPrimary) {
|
|
|
|
isColorPrimaryMatch = CFStringCompare(colorPrimary, priorColorPrimary, 0);
|
|
|
|
}
|
2024-02-24 02:33:04 +00:00
|
|
|
|
|
|
|
if (isColorPrimaryMatch != kCFCompareEqualTo || !hasFoundFramerate) {
|
|
|
|
for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges.reverseObjectEnumerator) {
|
|
|
|
FourCharCode formatSubType = CMFormatDescriptionGetMediaSubType(format.formatDescription);
|
|
|
|
int device_format = [OBSAVCapture formatFromSubtype:formatSubType];
|
|
|
|
|
|
|
|
if (input_format == device_format) {
|
|
|
|
struct media_frames_per_second min_fps = {
|
|
|
|
.numerator = (uint32_t) clamp_Uint(range.maxFrameDuration.timescale, 0, UINT32_MAX),
|
|
|
|
.denominator = (uint32_t) clamp_Uint(range.maxFrameDuration.value, 0, UINT32_MAX)};
|
|
|
|
struct media_frames_per_second max_fps = {
|
|
|
|
.numerator = (uint32_t) clamp_Uint(range.minFrameDuration.timescale, 0, UINT32_MAX),
|
|
|
|
.denominator = (uint32_t) clamp_Uint(range.minFrameDuration.value, 0, UINT32_MAX)};
|
|
|
|
|
|
|
|
if (![frameRates containsObject:range]) {
|
|
|
|
obs_property_frame_rate_fps_range_add(prop_framerate, min_fps, max_fps);
|
|
|
|
[frameRates addObject:range];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hasFoundFramerate && CMTimeCompare(range.maxFrameDuration, time) >= 0 &&
|
|
|
|
CMTimeCompare(range.minFrameDuration, time) <= 0) {
|
|
|
|
hasFoundFramerate = YES;
|
|
|
|
}
|
2024-02-15 21:06:21 +00:00
|
|
|
}
|
2023-07-03 18:26:53 +00:00
|
|
|
}
|
2024-02-24 02:33:04 +00:00
|
|
|
|
|
|
|
priorColorPrimary = colorPrimary;
|
2023-07-03 18:26:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add resolutions in reverse order (formats reported by macOS are sorted with lowest resolution first)
|
|
|
|
for (NSDictionary *resolutionData in resolutions.reverseObjectEnumerator) {
|
|
|
|
NSError *error;
|
|
|
|
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:resolutionData options:0 error:&error];
|
|
|
|
|
|
|
|
int width = [[resolutionData objectForKey:@"width"] intValue];
|
|
|
|
int height = [[resolutionData objectForKey:@"height"] intValue];
|
|
|
|
|
|
|
|
obs_property_list_add_string(
|
|
|
|
prop_resolution, [NSString stringWithFormat:@"%dx%d", width, height].UTF8String,
|
|
|
|
[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding].UTF8String);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add currently selected values in disabled state if they are not supported by the device
|
|
|
|
size_t index;
|
|
|
|
|
2024-02-15 21:06:21 +00:00
|
|
|
FourCharCode formatSubType = [OBSAVCapture fourCharCodeFromFormat:input_format withRange:video_range];
|
|
|
|
if (!hasFoundInputFormat) {
|
|
|
|
NSString *formatDescription = [OBSAVCapture stringFromSubType:formatSubType];
|
2023-07-03 18:26:53 +00:00
|
|
|
|
2024-02-15 21:06:21 +00:00
|
|
|
index = obs_property_list_add_int(prop_input_format, formatDescription.UTF8String, input_format);
|
|
|
|
obs_property_list_item_disable(prop_input_format, index, true);
|
|
|
|
}
|
|
|
|
|
2024-02-20 19:15:19 +00:00
|
|
|
if (!capture.isFastPath) {
|
2023-07-03 18:26:53 +00:00
|
|
|
if (!hasFoundVideoRange) {
|
|
|
|
int device_range;
|
|
|
|
const char *range_description;
|
|
|
|
|
|
|
|
if ([OBSAVCapture isFullRangeFormat:formatSubType]) {
|
|
|
|
device_range = VIDEO_RANGE_FULL;
|
|
|
|
range_description = av_capture_get_text("VideoRange.Full");
|
|
|
|
} else {
|
|
|
|
device_range = VIDEO_RANGE_PARTIAL;
|
|
|
|
range_description = av_capture_get_text("VideoRange.Partial");
|
|
|
|
}
|
|
|
|
|
|
|
|
index = obs_property_list_add_int(prop_video_range, range_description, device_range);
|
|
|
|
obs_property_list_item_disable(prop_video_range, index, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hasFoundColorSpace) {
|
|
|
|
index = obs_property_list_add_int(
|
|
|
|
prop_color_space, [OBSAVCapture stringFromColorspace:color_space].UTF8String, color_space);
|
|
|
|
obs_property_list_item_disable(prop_color_space, index, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hasFoundResolution) {
|
|
|
|
NSDictionary *resolutionData = @{@"width": @(resolution.width), @"height": @(resolution.height)};
|
|
|
|
|
|
|
|
NSError *error;
|
|
|
|
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:resolutionData options:0 error:&error];
|
|
|
|
|
|
|
|
index = obs_property_list_add_string(
|
|
|
|
prop_resolution, [NSString stringWithFormat:@"%dx%d", resolution.width, resolution.height].UTF8String,
|
|
|
|
[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding].UTF8String);
|
|
|
|
obs_property_list_item_disable(prop_resolution, index, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|