plugin-properties.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. //
  2. // plugin-properties.m
  3. // mac-avcapture
  4. //
  5. // Created by Patrick Heyer on 2023-03-07.
  6. //
  7. #import "OBSAVCapture.h"
  8. #import "plugin-properties.h"
  9. #import "AVCaptureDeviceFormat+OBSListable.h"
  10. extern const char *av_capture_get_text(const char *text_id);
  11. void configure_property(obs_property_t *property, bool enable, bool visible, void *callback, OBSAVCapture *capture)
  12. {
  13. if (property) {
  14. obs_property_set_enabled(property, enable);
  15. obs_property_set_visible(property, visible);
  16. if (callback) {
  17. obs_property_set_modified_callback2(property, callback, (__bridge void *) (capture));
  18. }
  19. }
  20. }
  21. bool properties_changed(OBSAVCapture *capture, obs_properties_t *properties, obs_property_t *property __unused,
  22. obs_data_t *settings)
  23. {
  24. OBSAVCaptureInfo *captureInfo = capture.captureInfo;
  25. obs_property_t *prop_use_preset = obs_properties_get(properties, "use_preset");
  26. obs_property_t *prop_device = obs_properties_get(properties, "device");
  27. obs_property_t *prop_presets = obs_properties_get(properties, "preset");
  28. obs_property_set_enabled(prop_use_preset, !captureInfo->isFastPath);
  29. if (captureInfo && settings) {
  30. properties_update_device(capture, prop_device, settings);
  31. bool use_preset = (settings ? obs_data_get_bool(settings, "use_preset") : true);
  32. if (use_preset) {
  33. properties_update_preset(capture, prop_presets, settings);
  34. } else {
  35. properties_update_config(capture, properties, settings);
  36. }
  37. }
  38. return true;
  39. }
  40. bool properties_changed_preset(OBSAVCapture *capture, obs_properties_t *properties __unused, obs_property_t *property,
  41. obs_data_t *settings)
  42. {
  43. bool use_preset = obs_data_get_bool(settings, "use_preset");
  44. if (capture && settings && use_preset) {
  45. NSArray *presetKeys =
  46. [capture.presetList keysSortedByValueUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
  47. NSNumber *obj1Resolution;
  48. NSNumber *obj2Resolution;
  49. if ([obj1 isEqualToString:@"High"]) {
  50. obj1Resolution = @3;
  51. } else if ([obj1 isEqualToString:@"Medium"]) {
  52. obj1Resolution = @2;
  53. } else if ([obj1 isEqualToString:@"Low"]) {
  54. obj1Resolution = @1;
  55. } else {
  56. NSArray<NSString *> *obj1Dimensions = [obj1 componentsSeparatedByString:@"x"];
  57. obj1Resolution = [NSNumber numberWithInt:([[obj1Dimensions objectAtIndex:0] intValue] *
  58. [[obj1Dimensions objectAtIndex:1] intValue])];
  59. }
  60. if ([obj2 isEqualToString:@"High"]) {
  61. obj2Resolution = @3;
  62. } else if ([obj2 isEqualToString:@"Medium"]) {
  63. obj2Resolution = @2;
  64. } else if ([obj2 isEqualToString:@"Low"]) {
  65. obj2Resolution = @1;
  66. } else {
  67. NSArray<NSString *> *obj2Dimensions = [obj2 componentsSeparatedByString:@"x"];
  68. obj2Resolution = [NSNumber numberWithInt:([[obj2Dimensions objectAtIndex:0] intValue] *
  69. [[obj2Dimensions objectAtIndex:1] intValue])];
  70. }
  71. NSComparisonResult result = [obj1Resolution compare:obj2Resolution];
  72. if (result == NSOrderedAscending) {
  73. return (NSComparisonResult) NSOrderedDescending;
  74. } else if (result == NSOrderedDescending) {
  75. return (NSComparisonResult) NSOrderedAscending;
  76. } else {
  77. return (NSComparisonResult) NSOrderedSame;
  78. }
  79. }];
  80. NSString *UUID = [OBSAVCapture stringFromSettings:settings withSetting:@"device"];
  81. AVCaptureDevice *device = [AVCaptureDevice deviceWithUniqueID:UUID];
  82. NSString *currentPreset = [OBSAVCapture stringFromSettings:settings withSetting:@"preset"];
  83. obs_property_list_clear(property);
  84. if (device) {
  85. for (NSString *presetName in presetKeys) {
  86. NSString *presetDescription = capture.presetList[presetName];
  87. if ([device supportsAVCaptureSessionPreset:presetName]) {
  88. obs_property_list_add_string(property, presetDescription.UTF8String, presetName.UTF8String);
  89. } else if ([currentPreset isEqualToString:presetName]) {
  90. size_t index =
  91. obs_property_list_add_string(property, presetDescription.UTF8String, presetName.UTF8String);
  92. obs_property_list_item_disable(property, index, true);
  93. }
  94. };
  95. } else if (UUID.length) {
  96. size_t index = obs_property_list_add_string(property, capture.presetList[currentPreset].UTF8String,
  97. currentPreset.UTF8String);
  98. obs_property_list_item_disable(property, index, true);
  99. }
  100. return YES;
  101. } else {
  102. return NO;
  103. }
  104. }
  105. bool properties_changed_use_preset(OBSAVCapture *capture, obs_properties_t *properties,
  106. obs_property_t *property __unused, obs_data_t *settings)
  107. {
  108. bool use_preset = obs_data_get_bool(settings, "use_preset");
  109. obs_property_t *preset_list = obs_properties_get(properties, "preset");
  110. obs_property_set_visible(preset_list, use_preset);
  111. if (use_preset) {
  112. properties_changed_preset(capture, properties, preset_list, settings);
  113. }
  114. const char *update_properties[2] = {"frame_rate", "supported_format"};
  115. size_t number_of_properties = sizeof(update_properties) / sizeof(update_properties[0]);
  116. for (size_t i = 0; i < number_of_properties; i++) {
  117. obs_property_t *update_property = obs_properties_get(properties, update_properties[i]);
  118. if (update_property) {
  119. obs_property_set_visible(update_property, !use_preset);
  120. obs_property_set_enabled(update_property, !use_preset);
  121. }
  122. }
  123. return true;
  124. }
  125. bool properties_update_preset(OBSAVCapture *capture, obs_property_t *property, obs_data_t *settings)
  126. {
  127. NSArray *presetKeys =
  128. [capture.presetList keysSortedByValueUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
  129. NSNumber *obj1Resolution;
  130. NSNumber *obj2Resolution;
  131. if ([obj1 isEqualToString:@"High"]) {
  132. obj1Resolution = @3;
  133. } else if ([obj1 isEqualToString:@"Medium"]) {
  134. obj1Resolution = @2;
  135. } else if ([obj1 isEqualToString:@"Low"]) {
  136. obj1Resolution = @1;
  137. } else {
  138. NSArray<NSString *> *obj1Dimensions = [obj1 componentsSeparatedByString:@"x"];
  139. obj1Resolution = [NSNumber numberWithInt:([[obj1Dimensions objectAtIndex:0] intValue] *
  140. [[obj1Dimensions objectAtIndex:1] intValue])];
  141. }
  142. if ([obj2 isEqualToString:@"High"]) {
  143. obj2Resolution = @3;
  144. } else if ([obj2 isEqualToString:@"Medium"]) {
  145. obj2Resolution = @2;
  146. } else if ([obj2 isEqualToString:@"Low"]) {
  147. obj2Resolution = @1;
  148. } else {
  149. NSArray<NSString *> *obj2Dimensions = [obj2 componentsSeparatedByString:@"x"];
  150. obj2Resolution = [NSNumber numberWithInt:([[obj2Dimensions objectAtIndex:0] intValue] *
  151. [[obj2Dimensions objectAtIndex:1] intValue])];
  152. }
  153. NSComparisonResult result = [obj1Resolution compare:obj2Resolution];
  154. if (result == NSOrderedAscending) {
  155. return (NSComparisonResult) NSOrderedDescending;
  156. } else if (result == NSOrderedDescending) {
  157. return (NSComparisonResult) NSOrderedAscending;
  158. } else {
  159. return (NSComparisonResult) NSOrderedSame;
  160. }
  161. }];
  162. NSString *deviceUUID = [OBSAVCapture stringFromSettings:settings withSetting:@"device"];
  163. AVCaptureDevice *device = [AVCaptureDevice deviceWithUniqueID:deviceUUID];
  164. NSString *currentPreset = [OBSAVCapture stringFromSettings:settings withSetting:@"preset"];
  165. obs_property_list_clear(property);
  166. if (device) {
  167. for (NSString *presetName in presetKeys) {
  168. NSString *presetDescription = capture.presetList[presetName];
  169. if ([device supportsAVCaptureSessionPreset:presetName]) {
  170. obs_property_list_add_string(property, presetDescription.UTF8String, presetName.UTF8String);
  171. } else if ([currentPreset isEqualToString:presetName]) {
  172. size_t index =
  173. obs_property_list_add_string(property, presetDescription.UTF8String, presetName.UTF8String);
  174. obs_property_list_item_disable(property, index, true);
  175. }
  176. };
  177. } else if (deviceUUID.length) {
  178. size_t index = obs_property_list_add_string(property, capture.presetList[currentPreset].UTF8String,
  179. currentPreset.UTF8String);
  180. obs_property_list_item_disable(property, index, true);
  181. }
  182. return true;
  183. }
  184. bool properties_update_device(OBSAVCapture *capture __unused, obs_property_t *property, obs_data_t *settings)
  185. {
  186. obs_property_list_clear(property);
  187. NSString *currentDeviceUUID = [OBSAVCapture stringFromSettings:settings withSetting:@"device"];
  188. NSString *currentDeviceName = [OBSAVCapture stringFromSettings:settings withSetting:@"device_name"];
  189. BOOL isDeviceFound = NO;
  190. obs_property_list_add_string(property, "", "");
  191. NSArray *deviceTypes;
  192. if (@available(macOS 13, *)) {
  193. deviceTypes = @[
  194. AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeExternalUnknown,
  195. AVCaptureDeviceTypeDeskViewCamera
  196. ];
  197. } else {
  198. deviceTypes = @[AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeExternalUnknown];
  199. }
  200. AVCaptureDeviceDiscoverySession *videoDiscoverySession =
  201. [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeVideo
  202. position:AVCaptureDevicePositionUnspecified];
  203. AVCaptureDeviceDiscoverySession *muxedDiscoverySession =
  204. [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:deviceTypes mediaType:AVMediaTypeMuxed
  205. position:AVCaptureDevicePositionUnspecified];
  206. for (AVCaptureDevice *device in [videoDiscoverySession devices]) {
  207. obs_property_list_add_string(property, device.localizedName.UTF8String, device.uniqueID.UTF8String);
  208. if (!isDeviceFound && [currentDeviceUUID isEqualToString:device.uniqueID]) {
  209. isDeviceFound = YES;
  210. }
  211. }
  212. for (AVCaptureDevice *device in [muxedDiscoverySession devices]) {
  213. obs_property_list_add_string(property, device.localizedName.UTF8String, device.uniqueID.UTF8String);
  214. if (!isDeviceFound && [currentDeviceUUID isEqualToString:device.uniqueID]) {
  215. isDeviceFound = YES;
  216. }
  217. }
  218. if (!isDeviceFound && currentDeviceUUID.length > 0) {
  219. size_t index =
  220. obs_property_list_add_string(property, currentDeviceName.UTF8String, currentDeviceUUID.UTF8String);
  221. obs_property_list_item_disable(property, index, true);
  222. }
  223. return true;
  224. }
  225. bool properties_update_config(OBSAVCapture *capture, obs_properties_t *properties, obs_data_t *settings)
  226. {
  227. AVCaptureDevice *device = [AVCaptureDevice deviceWithUniqueID:[OBSAVCapture stringFromSettings:settings
  228. withSetting:@"device"]];
  229. obs_property_t *prop_framerate = obs_properties_get(properties, "frame_rate");
  230. obs_property_t *prop_format = obs_properties_get(properties, "supported_format");
  231. obs_property_t *prop_effects_warning = obs_properties_get(properties, "effects_warning");
  232. obs_property_frame_rate_clear(prop_framerate);
  233. obs_property_list_clear(prop_format);
  234. obs_property_list_clear(prop_effects_warning);
  235. struct media_frames_per_second fps;
  236. if (!obs_data_get_frames_per_second(settings, "frame_rate", &fps, NULL)) {
  237. [capture AVCaptureLog:LOG_DEBUG withFormat:@"No valid framerate found in settings"];
  238. }
  239. const char *selectedFormatData = obs_data_get_string(settings, "supported_format");
  240. NSString *selectedFormatString = [NSString stringWithCString:selectedFormatData encoding:NSUTF8StringEncoding];
  241. NSMutableArray *frameRates = [[NSMutableArray alloc] init];
  242. if (device) {
  243. NSSortDescriptor *aspectSort = [[NSSortDescriptor alloc] initWithKey:@"aspectRatioComparisonValue"
  244. ascending:false];
  245. NSSortDescriptor *pixelBandwidthSort = [[NSSortDescriptor alloc] initWithKey:@"pixelBandwidthComparisonValue"
  246. ascending:false];
  247. NSSortDescriptor *bppSort = [[NSSortDescriptor alloc] initWithKey:@"bitsPerPixel" ascending:true];
  248. NSArray<NSSortDescriptor *> *sortArray =
  249. [NSArray arrayWithObjects:aspectSort, pixelBandwidthSort, bppSort, nil];
  250. for (AVCaptureDeviceFormat *format in [device.formats sortedArrayUsingDescriptors:sortArray]) {
  251. NSString *enumeratedFormatString = format.obsPropertyListDescription;
  252. NSString *internalRepresentation = format.obsPropertyListInternalRepresentation;
  253. obs_property_list_add_string(prop_format, enumeratedFormatString.UTF8String,
  254. internalRepresentation.UTF8String);
  255. if ([selectedFormatString isEqualToString:internalRepresentation]) {
  256. for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) {
  257. struct media_frames_per_second min_fps = {
  258. .numerator = (uint32_t) clamp_Uint(range.maxFrameDuration.timescale, 0, UINT32_MAX),
  259. .denominator = (uint32_t) clamp_Uint(range.maxFrameDuration.value, 0, UINT32_MAX)};
  260. struct media_frames_per_second max_fps = {
  261. .numerator = (uint32_t) clamp_Uint(range.minFrameDuration.timescale, 0, UINT32_MAX),
  262. .denominator = (uint32_t) clamp_Uint(range.minFrameDuration.value, 0, UINT32_MAX)};
  263. if (![frameRates containsObject:range]) {
  264. obs_property_frame_rate_fps_range_add(prop_framerate, min_fps, max_fps);
  265. [frameRates addObject:range];
  266. }
  267. }
  268. }
  269. }
  270. }
  271. return true;
  272. }