mac-screen-capture.m 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341
  1. #include <AvailabilityMacros.h>
  2. #include <Cocoa/Cocoa.h>
  3. bool is_screen_capture_available(void)
  4. {
  5. if (@available(macOS 12.5, *)) {
  6. return true;
  7. } else {
  8. return false;
  9. }
  10. }
  11. #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 120300 // __MAC_12_3
  12. #pragma clang diagnostic push
  13. #pragma clang diagnostic ignored "-Wunguarded-availability-new"
  14. #include <stdlib.h>
  15. #include <obs-module.h>
  16. #include <util/threading.h>
  17. #include <pthread.h>
  18. #include <IOSurface/IOSurface.h>
  19. #include <ScreenCaptureKit/ScreenCaptureKit.h>
  20. #include <CoreMedia/CMSampleBuffer.h>
  21. #include <CoreVideo/CVPixelBuffer.h>
  22. #define MACCAP_LOG(level, msg, ...) \
  23. blog(level, "[ mac-screencapture ]: " msg, ##__VA_ARGS__)
  24. #define MACCAP_ERR(msg, ...) MACCAP_LOG(LOG_ERROR, msg, ##__VA_ARGS__)
  25. typedef enum {
  26. ScreenCaptureDisplayStream = 0,
  27. ScreenCaptureWindowStream = 1,
  28. ScreenCaptureApplicationStream = 2,
  29. } ScreenCaptureStreamType;
  30. @interface ScreenCaptureDelegate : NSObject <SCStreamOutput>
  31. @property struct screen_capture *sc;
  32. @end
  33. struct screen_capture {
  34. obs_source_t *source;
  35. gs_effect_t *effect;
  36. gs_texture_t *tex;
  37. NSRect frame;
  38. bool hide_cursor;
  39. bool hide_obs;
  40. bool show_hidden_windows;
  41. bool show_empty_names;
  42. SCStream *disp;
  43. SCStreamConfiguration *stream_properties;
  44. SCShareableContent *shareable_content;
  45. ScreenCaptureDelegate *capture_delegate;
  46. os_event_t *disp_finished;
  47. os_event_t *stream_start_completed;
  48. os_sem_t *shareable_content_available;
  49. IOSurfaceRef current, prev;
  50. pthread_mutex_t mutex;
  51. ScreenCaptureStreamType capture_type;
  52. CGDirectDisplayID display;
  53. CGWindowID window;
  54. NSString *application_id;
  55. };
  56. #pragma mark -
  57. static void destroy_screen_stream(struct screen_capture *sc)
  58. {
  59. if (sc->disp) {
  60. [sc->disp stopCaptureWithCompletionHandler:^(
  61. NSError *_Nullable error) {
  62. if (error && error.code != 3808) {
  63. MACCAP_ERR(
  64. "destroy_screen_stream: Failed to stop stream with error %s\n",
  65. [[error localizedFailureReason]
  66. cStringUsingEncoding:
  67. NSUTF8StringEncoding]);
  68. }
  69. os_event_signal(sc->disp_finished);
  70. }];
  71. os_event_wait(sc->disp_finished);
  72. }
  73. if (sc->stream_properties) {
  74. [sc->stream_properties release];
  75. sc->stream_properties = NULL;
  76. }
  77. if (sc->tex) {
  78. gs_texture_destroy(sc->tex);
  79. sc->tex = NULL;
  80. }
  81. if (sc->current) {
  82. IOSurfaceDecrementUseCount(sc->current);
  83. CFRelease(sc->current);
  84. sc->current = NULL;
  85. }
  86. if (sc->prev) {
  87. IOSurfaceDecrementUseCount(sc->prev);
  88. CFRelease(sc->prev);
  89. sc->prev = NULL;
  90. }
  91. if (sc->disp) {
  92. [sc->disp release];
  93. sc->disp = NULL;
  94. }
  95. os_event_destroy(sc->disp_finished);
  96. os_event_destroy(sc->stream_start_completed);
  97. }
  98. static void screen_capture_destroy(void *data)
  99. {
  100. struct screen_capture *sc = data;
  101. if (!sc)
  102. return;
  103. obs_enter_graphics();
  104. destroy_screen_stream(sc);
  105. obs_leave_graphics();
  106. if (sc->shareable_content) {
  107. os_sem_wait(sc->shareable_content_available);
  108. [sc->shareable_content release];
  109. os_sem_destroy(sc->shareable_content_available);
  110. sc->shareable_content_available = NULL;
  111. }
  112. if (sc->capture_delegate) {
  113. [sc->capture_delegate release];
  114. }
  115. [sc->application_id release];
  116. pthread_mutex_destroy(&sc->mutex);
  117. bfree(sc);
  118. }
  119. static inline void screen_stream_video_update(struct screen_capture *sc,
  120. CMSampleBufferRef sample_buffer)
  121. {
  122. bool frame_detail_errored = false;
  123. float scale_factor = 1.0f;
  124. CGRect window_rect = {};
  125. CFArrayRef attachments_array =
  126. CMSampleBufferGetSampleAttachmentsArray(sample_buffer, false);
  127. if (sc->capture_type == ScreenCaptureWindowStream &&
  128. attachments_array != NULL &&
  129. CFArrayGetCount(attachments_array) > 0) {
  130. CFDictionaryRef attachments_dict =
  131. CFArrayGetValueAtIndex(attachments_array, 0);
  132. if (attachments_dict != NULL) {
  133. CFTypeRef frame_scale_factor = CFDictionaryGetValue(
  134. attachments_dict, SCStreamFrameInfoScaleFactor);
  135. if (frame_scale_factor != NULL) {
  136. Boolean result = CFNumberGetValue(
  137. (CFNumberRef)frame_scale_factor,
  138. kCFNumberFloatType, &scale_factor);
  139. if (result == false) {
  140. scale_factor = 1.0f;
  141. frame_detail_errored = true;
  142. }
  143. }
  144. CFTypeRef content_rect_dict = CFDictionaryGetValue(
  145. attachments_dict, SCStreamFrameInfoContentRect);
  146. CFTypeRef content_scale_factor = CFDictionaryGetValue(
  147. attachments_dict,
  148. SCStreamFrameInfoContentScale);
  149. if ((content_rect_dict != NULL) &&
  150. (content_scale_factor != NULL)) {
  151. CGRect content_rect = {};
  152. float points_to_pixels = 0.0f;
  153. Boolean result =
  154. CGRectMakeWithDictionaryRepresentation(
  155. (__bridge CFDictionaryRef)
  156. content_rect_dict,
  157. &content_rect);
  158. if (result == false) {
  159. content_rect = CGRectZero;
  160. frame_detail_errored = true;
  161. }
  162. result = CFNumberGetValue(
  163. (CFNumberRef)content_scale_factor,
  164. kCFNumberFloatType, &points_to_pixels);
  165. if (result == false) {
  166. points_to_pixels = 1.0f;
  167. frame_detail_errored = true;
  168. }
  169. window_rect.origin = content_rect.origin;
  170. window_rect.size.width =
  171. content_rect.size.width /
  172. points_to_pixels * scale_factor;
  173. window_rect.size.height =
  174. content_rect.size.height /
  175. points_to_pixels * scale_factor;
  176. }
  177. }
  178. }
  179. CVImageBufferRef image_buffer =
  180. CMSampleBufferGetImageBuffer(sample_buffer);
  181. CVPixelBufferLockBaseAddress(image_buffer, 0);
  182. IOSurfaceRef frame_surface = CVPixelBufferGetIOSurface(image_buffer);
  183. CVPixelBufferUnlockBaseAddress(image_buffer, 0);
  184. IOSurfaceRef prev_current = NULL;
  185. if (frame_surface && !pthread_mutex_lock(&sc->mutex)) {
  186. bool needs_to_update_properties = false;
  187. if (!frame_detail_errored) {
  188. if (sc->capture_type == ScreenCaptureWindowStream) {
  189. if ((sc->frame.size.width !=
  190. window_rect.size.width) ||
  191. (sc->frame.size.height !=
  192. window_rect.size.height)) {
  193. sc->frame.size.width =
  194. window_rect.size.width;
  195. sc->frame.size.height =
  196. window_rect.size.height;
  197. needs_to_update_properties = true;
  198. }
  199. } else {
  200. size_t width =
  201. CVPixelBufferGetWidth(image_buffer);
  202. size_t height =
  203. CVPixelBufferGetHeight(image_buffer);
  204. if ((sc->frame.size.width != width) ||
  205. (sc->frame.size.height != height)) {
  206. sc->frame.size.width = width;
  207. sc->frame.size.height = height;
  208. needs_to_update_properties = true;
  209. }
  210. }
  211. }
  212. if (needs_to_update_properties) {
  213. [sc->stream_properties
  214. setWidth:(size_t)sc->frame.size.width];
  215. [sc->stream_properties
  216. setHeight:(size_t)sc->frame.size.height];
  217. [sc->disp
  218. updateConfiguration:sc->stream_properties
  219. completionHandler:^(
  220. NSError *_Nullable error) {
  221. if (error) {
  222. MACCAP_ERR(
  223. "screen_stream_video_update: Failed to update stream properties with error %s\n",
  224. [[error localizedFailureReason]
  225. cStringUsingEncoding:
  226. NSUTF8StringEncoding]);
  227. }
  228. }];
  229. }
  230. prev_current = sc->current;
  231. sc->current = frame_surface;
  232. CFRetain(sc->current);
  233. IOSurfaceIncrementUseCount(sc->current);
  234. pthread_mutex_unlock(&sc->mutex);
  235. }
  236. if (prev_current) {
  237. IOSurfaceDecrementUseCount(prev_current);
  238. CFRelease(prev_current);
  239. }
  240. }
  241. static inline void screen_stream_audio_update(struct screen_capture *sc,
  242. CMSampleBufferRef sample_buffer)
  243. {
  244. CMFormatDescriptionRef format_description =
  245. CMSampleBufferGetFormatDescription(sample_buffer);
  246. const AudioStreamBasicDescription *audio_description =
  247. CMAudioFormatDescriptionGetStreamBasicDescription(
  248. format_description);
  249. if (audio_description->mChannelsPerFrame < 1) {
  250. MACCAP_ERR(
  251. "screen_stream_audio_update: Received sample buffer has less than 1 channel per frame (mChannelsPerFrame set to '%d')\n",
  252. audio_description->mChannelsPerFrame);
  253. return;
  254. }
  255. char *_Nullable bytes = NULL;
  256. CMBlockBufferRef data_buffer =
  257. CMSampleBufferGetDataBuffer(sample_buffer);
  258. size_t data_buffer_length = CMBlockBufferGetDataLength(data_buffer);
  259. CMBlockBufferGetDataPointer(data_buffer, 0, &data_buffer_length, NULL,
  260. &bytes);
  261. CMTime presentation_time =
  262. CMSampleBufferGetOutputPresentationTimeStamp(sample_buffer);
  263. struct obs_source_audio audio_data = {};
  264. for (uint32_t channel_idx = 0;
  265. channel_idx < audio_description->mChannelsPerFrame;
  266. ++channel_idx) {
  267. uint32_t offset =
  268. (uint32_t)(data_buffer_length /
  269. audio_description->mChannelsPerFrame) *
  270. channel_idx;
  271. audio_data.data[channel_idx] = (uint8_t *)bytes + offset;
  272. }
  273. audio_data.frames = (uint32_t)(data_buffer_length /
  274. audio_description->mBytesPerFrame /
  275. audio_description->mChannelsPerFrame);
  276. audio_data.speakers = audio_description->mChannelsPerFrame;
  277. audio_data.samples_per_sec = (uint32_t)audio_description->mSampleRate;
  278. audio_data.timestamp =
  279. (uint64_t)(CMTimeGetSeconds(presentation_time) * NSEC_PER_SEC);
  280. audio_data.format = AUDIO_FORMAT_FLOAT_PLANAR;
  281. obs_source_output_audio(sc->source, &audio_data);
  282. }
  283. static bool init_screen_stream(struct screen_capture *sc)
  284. {
  285. SCContentFilter *content_filter;
  286. sc->frame = CGRectZero;
  287. sc->stream_properties = [[SCStreamConfiguration alloc] init];
  288. os_sem_wait(sc->shareable_content_available);
  289. SCDisplay * (^get_target_display)() = ^SCDisplay *()
  290. {
  291. __block SCDisplay *target_display = nil;
  292. [sc->shareable_content.displays
  293. indexOfObjectPassingTest:^BOOL(
  294. SCDisplay *_Nonnull display, NSUInteger idx,
  295. BOOL *_Nonnull stop) {
  296. if (display.displayID == sc->display) {
  297. target_display = sc->shareable_content
  298. .displays[idx];
  299. *stop = TRUE;
  300. }
  301. return *stop;
  302. }];
  303. return target_display;
  304. };
  305. void (^set_display_mode)(struct screen_capture *, SCDisplay *) = ^void(
  306. struct screen_capture *capture_data,
  307. SCDisplay *target_display) {
  308. CGDisplayModeRef display_mode =
  309. CGDisplayCopyDisplayMode(target_display.displayID);
  310. [capture_data->stream_properties
  311. setWidth:CGDisplayModeGetPixelWidth(display_mode)];
  312. [capture_data->stream_properties
  313. setHeight:CGDisplayModeGetPixelHeight(display_mode)];
  314. CGDisplayModeRelease(display_mode);
  315. };
  316. switch (sc->capture_type) {
  317. case ScreenCaptureDisplayStream: {
  318. SCDisplay *target_display = get_target_display();
  319. if (sc->hide_obs) {
  320. __block SCRunningApplication *obsApp = nil;
  321. [sc->shareable_content
  322. .applications indexOfObjectPassingTest:^BOOL(
  323. SCRunningApplication
  324. *_Nonnull app,
  325. NSUInteger idx
  326. __unused,
  327. BOOL *_Nonnull stop) {
  328. if ([app.bundleIdentifier
  329. isEqualToString:
  330. [[NSBundle mainBundle]
  331. bundleIdentifier]]) {
  332. obsApp = app;
  333. *stop = TRUE;
  334. }
  335. return *stop;
  336. }];
  337. NSArray *exclusions =
  338. [[NSArray alloc] initWithObjects:obsApp, nil];
  339. NSArray *empty = [[NSArray alloc] init];
  340. content_filter = [[SCContentFilter alloc]
  341. initWithDisplay:target_display
  342. excludingApplications:exclusions
  343. exceptingWindows:empty];
  344. [empty release];
  345. [exclusions release];
  346. } else {
  347. NSArray *empty = [[NSArray alloc] init];
  348. content_filter = [[SCContentFilter alloc]
  349. initWithDisplay:target_display
  350. excludingWindows:empty];
  351. [empty release];
  352. }
  353. set_display_mode(sc, target_display);
  354. } break;
  355. case ScreenCaptureWindowStream: {
  356. __block SCWindow *target_window = nil;
  357. if (sc->window != 0) {
  358. [sc->shareable_content.windows
  359. indexOfObjectPassingTest:^BOOL(
  360. SCWindow *_Nonnull window,
  361. NSUInteger idx, BOOL *_Nonnull stop) {
  362. if (window.windowID == sc->window) {
  363. target_window =
  364. sc->shareable_content
  365. .windows[idx];
  366. *stop = TRUE;
  367. }
  368. return *stop;
  369. }];
  370. } else {
  371. target_window =
  372. [sc->shareable_content.windows objectAtIndex:0];
  373. sc->window = target_window.windowID;
  374. }
  375. content_filter = [[SCContentFilter alloc]
  376. initWithDesktopIndependentWindow:target_window];
  377. if (target_window) {
  378. [sc->stream_properties
  379. setWidth:(size_t)target_window.frame.size.width];
  380. [sc->stream_properties
  381. setHeight:(size_t)target_window.frame.size
  382. .height];
  383. }
  384. } break;
  385. case ScreenCaptureApplicationStream: {
  386. SCDisplay *target_display = get_target_display();
  387. __block SCRunningApplication *target_application = nil;
  388. {
  389. [sc->shareable_content.applications
  390. indexOfObjectPassingTest:^BOOL(
  391. SCRunningApplication
  392. *_Nonnull application,
  393. NSUInteger idx, BOOL *_Nonnull stop) {
  394. if ([application.bundleIdentifier
  395. isEqualToString:
  396. sc->
  397. application_id]) {
  398. target_application =
  399. sc->shareable_content
  400. .applications
  401. [idx];
  402. *stop = TRUE;
  403. }
  404. return *stop;
  405. }];
  406. }
  407. NSArray *target_application_array = [[NSArray alloc]
  408. initWithObjects:target_application, nil];
  409. NSArray *empty_array = [[NSArray alloc] init];
  410. content_filter = [[SCContentFilter alloc]
  411. initWithDisplay:target_display
  412. includingApplications:target_application_array
  413. exceptingWindows:empty_array];
  414. [target_application_array release];
  415. [empty_array release];
  416. set_display_mode(sc, target_display);
  417. } break;
  418. }
  419. os_sem_post(sc->shareable_content_available);
  420. CGColorRef background = CGColorGetConstantColor(kCGColorClear);
  421. [sc->stream_properties setQueueDepth:8];
  422. [sc->stream_properties setShowsCursor:!sc->hide_cursor];
  423. [sc->stream_properties setColorSpaceName:kCGColorSpaceDisplayP3];
  424. [sc->stream_properties setBackgroundColor:background];
  425. FourCharCode l10r_type = 0;
  426. l10r_type = ('l' << 24) | ('1' << 16) | ('0' << 8) | 'r';
  427. [sc->stream_properties setPixelFormat:l10r_type];
  428. if (@available(macOS 13.0, *)) {
  429. #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000
  430. [sc->stream_properties setCapturesAudio:TRUE];
  431. [sc->stream_properties setExcludesCurrentProcessAudio:TRUE];
  432. [sc->stream_properties setChannelCount:2];
  433. #endif
  434. } else {
  435. if (sc->capture_type != ScreenCaptureWindowStream) {
  436. sc->disp = NULL;
  437. [content_filter release];
  438. os_event_init(&sc->disp_finished, OS_EVENT_TYPE_MANUAL);
  439. os_event_init(&sc->stream_start_completed,
  440. OS_EVENT_TYPE_MANUAL);
  441. return true;
  442. }
  443. }
  444. sc->disp = [[SCStream alloc] initWithFilter:content_filter
  445. configuration:sc->stream_properties
  446. delegate:nil];
  447. [content_filter release];
  448. NSError *addStreamOutputError = nil;
  449. BOOL did_add_output = [sc->disp addStreamOutput:sc->capture_delegate
  450. type:SCStreamOutputTypeScreen
  451. sampleHandlerQueue:nil
  452. error:&addStreamOutputError];
  453. if (!did_add_output) {
  454. MACCAP_ERR(
  455. "init_screen_stream: Failed to add stream output with error %s\n",
  456. [[addStreamOutputError localizedFailureReason]
  457. cStringUsingEncoding:NSUTF8StringEncoding]);
  458. [addStreamOutputError release];
  459. return !did_add_output;
  460. }
  461. #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000
  462. if (@available(macOS 13.0, *)) {
  463. did_add_output = [sc->disp
  464. addStreamOutput:sc->capture_delegate
  465. type:SCStreamOutputTypeAudio
  466. sampleHandlerQueue:nil
  467. error:&addStreamOutputError];
  468. if (!did_add_output) {
  469. MACCAP_ERR(
  470. "init_screen_stream: Failed to add audio stream output with error %s\n",
  471. [[addStreamOutputError localizedFailureReason]
  472. cStringUsingEncoding:
  473. NSUTF8StringEncoding]);
  474. [addStreamOutputError release];
  475. return !did_add_output;
  476. }
  477. }
  478. #endif
  479. os_event_init(&sc->disp_finished, OS_EVENT_TYPE_MANUAL);
  480. os_event_init(&sc->stream_start_completed, OS_EVENT_TYPE_MANUAL);
  481. __block BOOL did_stream_start = false;
  482. [sc->disp startCaptureWithCompletionHandler:^(
  483. NSError *_Nullable error) {
  484. did_stream_start = (BOOL)(error == nil);
  485. if (!did_stream_start) {
  486. MACCAP_ERR(
  487. "init_screen_stream: Failed to start capture with error %s\n",
  488. [[error localizedFailureReason]
  489. cStringUsingEncoding:
  490. NSUTF8StringEncoding]);
  491. // Clean up disp so it isn't stopped
  492. [sc->disp release];
  493. sc->disp = NULL;
  494. }
  495. os_event_signal(sc->stream_start_completed);
  496. }];
  497. os_event_wait(sc->stream_start_completed);
  498. return did_stream_start;
  499. }
  500. static void screen_capture_build_content_list(struct screen_capture *sc,
  501. bool display_capture)
  502. {
  503. typedef void (^shareable_content_callback)(SCShareableContent *,
  504. NSError *);
  505. shareable_content_callback new_content_received = ^void(
  506. SCShareableContent *shareable_content, NSError *error) {
  507. if (error == nil && sc->shareable_content_available != NULL) {
  508. sc->shareable_content = [shareable_content retain];
  509. } else {
  510. #ifdef DEBUG
  511. MACCAP_ERR(
  512. "screen_capture_properties: Failed to get shareable content with error %s\n",
  513. [[error localizedFailureReason]
  514. cStringUsingEncoding:
  515. NSUTF8StringEncoding]);
  516. #endif
  517. MACCAP_LOG(
  518. LOG_WARNING,
  519. "Unable to get list of available applications or windows. "
  520. "Please check if OBS has necessary screen capture permissions.");
  521. }
  522. os_sem_post(sc->shareable_content_available);
  523. };
  524. os_sem_wait(sc->shareable_content_available);
  525. [sc->shareable_content release];
  526. [SCShareableContent
  527. getShareableContentExcludingDesktopWindows:TRUE
  528. onScreenWindowsOnly:
  529. (display_capture
  530. ? FALSE
  531. : !sc->show_hidden_windows)
  532. completionHandler:new_content_received];
  533. }
  534. static void *screen_capture_create(obs_data_t *settings, obs_source_t *source)
  535. {
  536. struct screen_capture *sc = bzalloc(sizeof(struct screen_capture));
  537. sc->source = source;
  538. sc->hide_cursor = !obs_data_get_bool(settings, "show_cursor");
  539. sc->hide_obs = obs_data_get_bool(settings, "hide_obs");
  540. sc->show_empty_names = obs_data_get_bool(settings, "show_empty_names");
  541. sc->show_hidden_windows =
  542. obs_data_get_bool(settings, "show_hidden_windows");
  543. sc->window = (CGWindowID)obs_data_get_int(settings, "window");
  544. sc->capture_type = (unsigned int)obs_data_get_int(settings, "type");
  545. os_sem_init(&sc->shareable_content_available, 1);
  546. screen_capture_build_content_list(
  547. sc, sc->capture_type == ScreenCaptureDisplayStream);
  548. sc->capture_delegate = [[ScreenCaptureDelegate alloc] init];
  549. sc->capture_delegate.sc = sc;
  550. sc->effect = obs_get_base_effect(OBS_EFFECT_DEFAULT_RECT);
  551. if (!sc->effect)
  552. goto fail;
  553. bool legacy_display_id = obs_data_has_user_value(settings, "display");
  554. if (legacy_display_id) {
  555. CGDirectDisplayID display_id =
  556. (CGDirectDisplayID)obs_data_get_int(settings,
  557. "display");
  558. CFUUIDRef display_uuid =
  559. CGDisplayCreateUUIDFromDisplayID(display_id);
  560. CFStringRef uuid_string =
  561. CFUUIDCreateString(kCFAllocatorDefault, display_uuid);
  562. obs_data_set_string(
  563. settings, "display_uuid",
  564. CFStringGetCStringPtr(uuid_string,
  565. kCFStringEncodingUTF8));
  566. obs_data_erase(settings, "display");
  567. CFRelease(uuid_string);
  568. CFRelease(display_uuid);
  569. }
  570. const char *display_uuid =
  571. obs_data_get_string(settings, "display_uuid");
  572. if (display_uuid) {
  573. CFStringRef uuid_string = CFStringCreateWithCString(
  574. kCFAllocatorDefault, display_uuid,
  575. kCFStringEncodingUTF8);
  576. CFUUIDRef uuid_ref = CFUUIDCreateFromString(kCFAllocatorDefault,
  577. uuid_string);
  578. sc->display = CGDisplayGetDisplayIDFromUUID(uuid_ref);
  579. CFRelease(uuid_string);
  580. CFRelease(uuid_ref);
  581. } else {
  582. sc->display = CGMainDisplayID();
  583. }
  584. sc->application_id = [[NSString alloc]
  585. initWithUTF8String:obs_data_get_string(settings,
  586. "application")];
  587. pthread_mutex_init(&sc->mutex, NULL);
  588. if (!init_screen_stream(sc))
  589. goto fail;
  590. return sc;
  591. fail:
  592. obs_leave_graphics();
  593. screen_capture_destroy(sc);
  594. return NULL;
  595. }
  596. static void screen_capture_video_tick(void *data, float seconds __unused)
  597. {
  598. struct screen_capture *sc = data;
  599. if (!sc->current)
  600. return;
  601. if (!obs_source_showing(sc->source))
  602. return;
  603. IOSurfaceRef prev_prev = sc->prev;
  604. if (pthread_mutex_lock(&sc->mutex))
  605. return;
  606. sc->prev = sc->current;
  607. sc->current = NULL;
  608. pthread_mutex_unlock(&sc->mutex);
  609. if (prev_prev == sc->prev)
  610. return;
  611. obs_enter_graphics();
  612. if (sc->tex)
  613. gs_texture_rebind_iosurface(sc->tex, sc->prev);
  614. else
  615. sc->tex = gs_texture_create_from_iosurface(sc->prev);
  616. obs_leave_graphics();
  617. if (prev_prev) {
  618. IOSurfaceDecrementUseCount(prev_prev);
  619. CFRelease(prev_prev);
  620. }
  621. }
  622. static void screen_capture_video_render(void *data,
  623. gs_effect_t *effect __unused)
  624. {
  625. struct screen_capture *sc = data;
  626. if (!sc->tex)
  627. return;
  628. const bool previous = gs_framebuffer_srgb_enabled();
  629. gs_enable_framebuffer_srgb(true);
  630. gs_eparam_t *param = gs_effect_get_param_by_name(sc->effect, "image");
  631. gs_effect_set_texture(param, sc->tex);
  632. while (gs_effect_loop(sc->effect, "DrawD65P3"))
  633. gs_draw_sprite(sc->tex, 0, 0, 0);
  634. gs_enable_framebuffer_srgb(previous);
  635. }
  636. static const char *screen_capture_getname(void *unused __unused)
  637. {
  638. if (@available(macOS 13.0, *))
  639. return obs_module_text("SCK.Name");
  640. else
  641. return obs_module_text("SCK.Name.Beta");
  642. }
  643. static uint32_t screen_capture_getwidth(void *data)
  644. {
  645. struct screen_capture *sc = data;
  646. return (uint32_t)sc->frame.size.width;
  647. }
  648. static uint32_t screen_capture_getheight(void *data)
  649. {
  650. struct screen_capture *sc = data;
  651. return (uint32_t)sc->frame.size.height;
  652. }
  653. static void screen_capture_defaults(obs_data_t *settings)
  654. {
  655. CGDirectDisplayID initial_display = 0;
  656. {
  657. NSScreen *mainScreen = [NSScreen mainScreen];
  658. if (mainScreen) {
  659. NSNumber *screen_num =
  660. mainScreen.deviceDescription[@"NSScreenNumber"];
  661. if (screen_num) {
  662. initial_display =
  663. (CGDirectDisplayID)(uintptr_t)
  664. screen_num.pointerValue;
  665. }
  666. }
  667. }
  668. CFUUIDRef display_uuid =
  669. CGDisplayCreateUUIDFromDisplayID(initial_display);
  670. CFStringRef uuid_string =
  671. CFUUIDCreateString(kCFAllocatorDefault, display_uuid);
  672. obs_data_set_default_string(
  673. settings, "display_uuid",
  674. CFStringGetCStringPtr(uuid_string, kCFStringEncodingUTF8));
  675. CFRelease(uuid_string);
  676. CFRelease(display_uuid);
  677. obs_data_set_default_obj(settings, "application", NULL);
  678. obs_data_set_default_int(settings, "type", ScreenCaptureDisplayStream);
  679. obs_data_set_default_int(settings, "window", kCGNullWindowID);
  680. obs_data_set_default_bool(settings, "show_cursor", true);
  681. obs_data_set_default_bool(settings, "hide_obs", false);
  682. obs_data_set_default_bool(settings, "show_empty_names", false);
  683. obs_data_set_default_bool(settings, "show_hidden_windows", false);
  684. }
  685. static void screen_capture_update(void *data, obs_data_t *settings)
  686. {
  687. struct screen_capture *sc = data;
  688. CGWindowID old_window_id = sc->window;
  689. CGWindowID new_window_id =
  690. (CGWindowID)obs_data_get_int(settings, "window");
  691. if (new_window_id > 0 && new_window_id != old_window_id)
  692. sc->window = new_window_id;
  693. ScreenCaptureStreamType capture_type =
  694. (ScreenCaptureStreamType)obs_data_get_int(settings, "type");
  695. CFStringRef uuid_string = CFStringCreateWithCString(
  696. kCFAllocatorDefault,
  697. obs_data_get_string(settings, "display_uuid"),
  698. kCFStringEncodingUTF8);
  699. CFUUIDRef display_uuid =
  700. CFUUIDCreateFromString(kCFAllocatorDefault, uuid_string);
  701. CGDirectDisplayID display = CGDisplayGetDisplayIDFromUUID(display_uuid);
  702. CFRelease(uuid_string);
  703. CFRelease(display_uuid);
  704. NSString *application_id = [[NSString alloc]
  705. initWithUTF8String:obs_data_get_string(settings,
  706. "application")];
  707. bool show_cursor = obs_data_get_bool(settings, "show_cursor");
  708. bool hide_obs = obs_data_get_bool(settings, "hide_obs");
  709. bool show_empty_names = obs_data_get_bool(settings, "show_empty_names");
  710. bool show_hidden_windows =
  711. obs_data_get_bool(settings, "show_hidden_windows");
  712. if (capture_type == sc->capture_type) {
  713. switch (sc->capture_type) {
  714. case ScreenCaptureDisplayStream: {
  715. if (sc->display == display &&
  716. sc->hide_cursor != show_cursor &&
  717. sc->hide_obs == hide_obs) {
  718. [application_id release];
  719. return;
  720. }
  721. } break;
  722. case ScreenCaptureWindowStream: {
  723. if (old_window_id == sc->window &&
  724. sc->hide_cursor != show_cursor) {
  725. [application_id release];
  726. return;
  727. }
  728. } break;
  729. case ScreenCaptureApplicationStream: {
  730. if (sc->display == display &&
  731. [application_id
  732. isEqualToString:sc->application_id] &&
  733. sc->hide_cursor != show_cursor) {
  734. [application_id release];
  735. return;
  736. }
  737. } break;
  738. }
  739. }
  740. obs_enter_graphics();
  741. destroy_screen_stream(sc);
  742. sc->capture_type = capture_type;
  743. sc->display = display;
  744. [sc->application_id release];
  745. sc->application_id = application_id;
  746. sc->hide_cursor = !show_cursor;
  747. sc->hide_obs = hide_obs;
  748. sc->show_empty_names = show_empty_names;
  749. sc->show_hidden_windows = show_hidden_windows;
  750. init_screen_stream(sc);
  751. obs_leave_graphics();
  752. }
  753. #pragma mark - obs_properties
  754. static bool build_display_list(struct screen_capture *sc,
  755. obs_properties_t *props)
  756. {
  757. os_sem_wait(sc->shareable_content_available);
  758. obs_property_t *display_list =
  759. obs_properties_get(props, "display_uuid");
  760. obs_property_list_clear(display_list);
  761. [sc->shareable_content.displays enumerateObjectsUsingBlock:^(
  762. SCDisplay *_Nonnull display,
  763. NSUInteger idx __unused,
  764. BOOL *_Nonnull _stop __unused) {
  765. NSUInteger screen_index =
  766. [NSScreen.screens indexOfObjectPassingTest:^BOOL(
  767. NSScreen *_Nonnull screen,
  768. NSUInteger index __unused,
  769. BOOL *_Nonnull stop) {
  770. NSNumber *screen_num =
  771. screen.deviceDescription
  772. [@"NSScreenNumber"];
  773. CGDirectDisplayID screen_display_id =
  774. (CGDirectDisplayID)screen_num.intValue;
  775. *stop = (screen_display_id ==
  776. display.displayID);
  777. return *stop;
  778. }];
  779. NSScreen *screen =
  780. [NSScreen.screens objectAtIndex:screen_index];
  781. char dimension_buffer[4][12] = {};
  782. char name_buffer[256] = {};
  783. snprintf(dimension_buffer[0], sizeof(dimension_buffer[0]), "%u",
  784. (uint32_t)screen.frame.size.width);
  785. snprintf(dimension_buffer[1], sizeof(dimension_buffer[0]), "%u",
  786. (uint32_t)screen.frame.size.height);
  787. snprintf(dimension_buffer[2], sizeof(dimension_buffer[0]), "%d",
  788. (int32_t)screen.frame.origin.x);
  789. snprintf(dimension_buffer[3], sizeof(dimension_buffer[0]), "%d",
  790. (int32_t)screen.frame.origin.y);
  791. snprintf(name_buffer, sizeof(name_buffer),
  792. "%.200s: %.12sx%.12s @ %.12s,%.12s",
  793. screen.localizedName.UTF8String, dimension_buffer[0],
  794. dimension_buffer[1], dimension_buffer[2],
  795. dimension_buffer[3]);
  796. CFUUIDRef display_uuid =
  797. CGDisplayCreateUUIDFromDisplayID(display.displayID);
  798. CFStringRef uuid_string =
  799. CFUUIDCreateString(kCFAllocatorDefault, display_uuid);
  800. obs_property_list_add_string(
  801. display_list, name_buffer,
  802. CFStringGetCStringPtr(uuid_string,
  803. kCFStringEncodingUTF8));
  804. CFRelease(uuid_string);
  805. CFRelease(display_uuid);
  806. }];
  807. os_sem_post(sc->shareable_content_available);
  808. return true;
  809. }
  810. static bool build_window_list(struct screen_capture *sc,
  811. obs_properties_t *props)
  812. {
  813. os_sem_wait(sc->shareable_content_available);
  814. obs_property_t *window_list = obs_properties_get(props, "window");
  815. obs_property_list_clear(window_list);
  816. NSArray<SCWindow *> *filteredWindows;
  817. filteredWindows = [sc->shareable_content.windows
  818. filteredArrayUsingPredicate:
  819. [NSPredicate predicateWithBlock:^BOOL(
  820. SCWindow *window,
  821. NSDictionary *bindings __unused) {
  822. NSString *app_name =
  823. window.owningApplication.applicationName;
  824. NSString *title = window.title;
  825. if (!sc->show_empty_names) {
  826. if (app_name == NULL || title == NULL) {
  827. return false;
  828. } else if ([app_name
  829. isEqualToString:@""] ||
  830. [title isEqualToString:@""]) {
  831. return false;
  832. }
  833. }
  834. return true;
  835. }]];
  836. NSArray<SCWindow *> *sortedWindows;
  837. sortedWindows =
  838. [filteredWindows sortedArrayUsingComparator:^NSComparisonResult(
  839. SCWindow *window, SCWindow *other) {
  840. NSComparisonResult appNameCmp =
  841. [window.owningApplication.applicationName
  842. compare:other.owningApplication
  843. .applicationName
  844. options:NSCaseInsensitiveSearch];
  845. if (appNameCmp == NSOrderedAscending) {
  846. return NSOrderedAscending;
  847. } else if (appNameCmp == NSOrderedSame) {
  848. return [window.title
  849. compare:other.title
  850. options:NSCaseInsensitiveSearch];
  851. } else {
  852. return NSOrderedDescending;
  853. }
  854. }];
  855. [sortedWindows enumerateObjectsUsingBlock:^(
  856. SCWindow *_Nonnull window,
  857. NSUInteger idx __unused,
  858. BOOL *_Nonnull stop __unused) {
  859. NSString *app_name = window.owningApplication.applicationName;
  860. NSString *title = window.title;
  861. const char *list_text =
  862. [[NSString stringWithFormat:@"[%@] %@", app_name, title]
  863. UTF8String];
  864. obs_property_list_add_int(window_list, list_text,
  865. window.windowID);
  866. }];
  867. os_sem_post(sc->shareable_content_available);
  868. return true;
  869. }
  870. static bool build_application_list(struct screen_capture *sc,
  871. obs_properties_t *props)
  872. {
  873. os_sem_wait(sc->shareable_content_available);
  874. obs_property_t *application_list =
  875. obs_properties_get(props, "application");
  876. obs_property_list_clear(application_list);
  877. NSArray<SCRunningApplication *> *filteredApplications;
  878. filteredApplications = [sc->shareable_content.applications
  879. filteredArrayUsingPredicate:
  880. [NSPredicate predicateWithBlock:^BOOL(
  881. SCRunningApplication *app,
  882. NSDictionary *bindings __unused) {
  883. const char *name =
  884. [app.applicationName UTF8String];
  885. if (strcmp(name, "") == 0) {
  886. return false;
  887. }
  888. return true;
  889. }]];
  890. NSArray<SCRunningApplication *> *sortedApplications;
  891. sortedApplications = [filteredApplications
  892. sortedArrayUsingComparator:^NSComparisonResult(
  893. SCRunningApplication *app,
  894. SCRunningApplication *other) {
  895. return [app.applicationName
  896. compare:other.applicationName
  897. options:NSCaseInsensitiveSearch];
  898. }];
  899. [sortedApplications enumerateObjectsUsingBlock:^(
  900. SCRunningApplication *_Nonnull application,
  901. NSUInteger idx __unused,
  902. BOOL *_Nonnull stop __unused) {
  903. const char *name = [application.applicationName UTF8String];
  904. const char *bundle_id =
  905. [application.bundleIdentifier UTF8String];
  906. obs_property_list_add_string(application_list, name, bundle_id);
  907. }];
  908. os_sem_post(sc->shareable_content_available);
  909. return true;
  910. }
  911. static bool content_settings_changed(void *data, obs_properties_t *props,
  912. obs_property_t *list __unused,
  913. obs_data_t *settings)
  914. {
  915. struct screen_capture *sc = data;
  916. unsigned int capture_type_id =
  917. (unsigned int)obs_data_get_int(settings, "type");
  918. obs_property_t *display_list =
  919. obs_properties_get(props, "display_uuid");
  920. obs_property_t *window_list = obs_properties_get(props, "window");
  921. obs_property_t *app_list = obs_properties_get(props, "application");
  922. obs_property_t *empty = obs_properties_get(props, "show_empty_names");
  923. obs_property_t *hidden =
  924. obs_properties_get(props, "show_hidden_windows");
  925. obs_property_t *hide_obs = obs_properties_get(props, "hide_obs");
  926. obs_property_t *capture_type_error =
  927. obs_properties_get(props, "capture_type_info");
  928. if (sc->capture_type != capture_type_id) {
  929. switch (capture_type_id) {
  930. case 0: {
  931. obs_property_set_visible(display_list, true);
  932. obs_property_set_visible(window_list, false);
  933. obs_property_set_visible(app_list, false);
  934. obs_property_set_visible(empty, false);
  935. obs_property_set_visible(hidden, false);
  936. obs_property_set_visible(hide_obs, true);
  937. if (capture_type_error) {
  938. obs_property_set_visible(capture_type_error,
  939. true);
  940. }
  941. break;
  942. }
  943. case 1: {
  944. obs_property_set_visible(display_list, false);
  945. obs_property_set_visible(window_list, true);
  946. obs_property_set_visible(app_list, false);
  947. obs_property_set_visible(empty, true);
  948. obs_property_set_visible(hidden, true);
  949. obs_property_set_visible(hide_obs, false);
  950. if (capture_type_error) {
  951. obs_property_set_visible(capture_type_error,
  952. false);
  953. }
  954. break;
  955. }
  956. case 2: {
  957. obs_property_set_visible(display_list, true);
  958. obs_property_set_visible(app_list, true);
  959. obs_property_set_visible(window_list, false);
  960. obs_property_set_visible(empty, false);
  961. obs_property_set_visible(hidden, true);
  962. obs_property_set_visible(hide_obs, false);
  963. if (capture_type_error) {
  964. obs_property_set_visible(capture_type_error,
  965. true);
  966. }
  967. break;
  968. }
  969. }
  970. }
  971. screen_capture_build_content_list(
  972. sc, capture_type_id == ScreenCaptureDisplayStream);
  973. build_display_list(sc, props);
  974. build_window_list(sc, props);
  975. build_application_list(sc, props);
  976. sc->show_empty_names = obs_data_get_bool(settings, "show_empty_names");
  977. sc->show_hidden_windows =
  978. obs_data_get_bool(settings, "show_hidden_windows");
  979. sc->hide_obs = obs_data_get_bool(settings, "hide_obs");
  980. return true;
  981. }
  982. static obs_properties_t *screen_capture_properties(void *data)
  983. {
  984. struct screen_capture *sc = data;
  985. obs_properties_t *props = obs_properties_create();
  986. obs_property_t *capture_type = obs_properties_add_list(
  987. props, "type", obs_module_text("SCK.Method"),
  988. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  989. obs_property_list_add_int(capture_type,
  990. obs_module_text("DisplayCapture"), 0);
  991. obs_property_list_add_int(capture_type,
  992. obs_module_text("WindowCapture"), 1);
  993. obs_property_list_add_int(capture_type,
  994. obs_module_text("ApplicationCapture"), 2);
  995. obs_property_t *display_list = obs_properties_add_list(
  996. props, "display_uuid",
  997. obs_module_text("DisplayCapture.Display"), OBS_COMBO_TYPE_LIST,
  998. OBS_COMBO_FORMAT_STRING);
  999. obs_property_t *app_list = obs_properties_add_list(
  1000. props, "application", obs_module_text("Application"),
  1001. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1002. obs_property_t *window_list = obs_properties_add_list(
  1003. props, "window", obs_module_text("WindowUtils.Window"),
  1004. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1005. obs_property_t *empty = obs_properties_add_bool(
  1006. props, "show_empty_names",
  1007. obs_module_text("WindowUtils.ShowEmptyNames"));
  1008. obs_property_t *hidden = obs_properties_add_bool(
  1009. props, "show_hidden_windows",
  1010. obs_module_text("WindowUtils.ShowHidden"));
  1011. obs_properties_add_bool(props, "show_cursor",
  1012. obs_module_text("DisplayCapture.ShowCursor"));
  1013. obs_property_t *hide_obs = obs_properties_add_bool(
  1014. props, "hide_obs", obs_module_text("DisplayCapture.HideOBS"));
  1015. if (sc) {
  1016. obs_property_set_modified_callback2(
  1017. capture_type, content_settings_changed, sc);
  1018. obs_property_set_modified_callback2(
  1019. hidden, content_settings_changed, sc);
  1020. switch (sc->capture_type) {
  1021. case 0: {
  1022. obs_property_set_visible(display_list, true);
  1023. obs_property_set_visible(window_list, false);
  1024. obs_property_set_visible(app_list, false);
  1025. obs_property_set_visible(empty, false);
  1026. obs_property_set_visible(hidden, false);
  1027. obs_property_set_visible(hide_obs, true);
  1028. break;
  1029. }
  1030. case 1: {
  1031. obs_property_set_visible(display_list, false);
  1032. obs_property_set_visible(window_list, true);
  1033. obs_property_set_visible(app_list, false);
  1034. obs_property_set_visible(empty, true);
  1035. obs_property_set_visible(hidden, true);
  1036. obs_property_set_visible(hide_obs, false);
  1037. break;
  1038. }
  1039. case 2: {
  1040. obs_property_set_visible(display_list, true);
  1041. obs_property_set_visible(app_list, true);
  1042. obs_property_set_visible(window_list, false);
  1043. obs_property_set_visible(empty, false);
  1044. obs_property_set_visible(hidden, true);
  1045. obs_property_set_visible(hide_obs, false);
  1046. break;
  1047. }
  1048. }
  1049. obs_property_set_modified_callback2(
  1050. empty, content_settings_changed, sc);
  1051. }
  1052. if (@available(macOS 13.0, *))
  1053. ;
  1054. else {
  1055. obs_property_t *audio_warning = obs_properties_add_text(
  1056. props, "audio_info",
  1057. obs_module_text("SCK.AudioUnavailable"), OBS_TEXT_INFO);
  1058. obs_property_text_set_info_type(audio_warning,
  1059. OBS_TEXT_INFO_WARNING);
  1060. obs_property_t *capture_type_error = obs_properties_add_text(
  1061. props, "capture_type_info",
  1062. obs_module_text("SCK.CaptureTypeUnavailable"),
  1063. OBS_TEXT_INFO);
  1064. obs_property_text_set_info_type(capture_type_error,
  1065. OBS_TEXT_INFO_ERROR);
  1066. if (sc) {
  1067. switch (sc->capture_type) {
  1068. case ScreenCaptureDisplayStream: {
  1069. obs_property_set_visible(capture_type_error,
  1070. true);
  1071. break;
  1072. }
  1073. case ScreenCaptureWindowStream: {
  1074. obs_property_set_visible(capture_type_error,
  1075. false);
  1076. break;
  1077. }
  1078. case ScreenCaptureApplicationStream: {
  1079. obs_property_set_visible(capture_type_error,
  1080. true);
  1081. break;
  1082. }
  1083. }
  1084. } else {
  1085. obs_property_set_visible(capture_type_error, false);
  1086. }
  1087. }
  1088. return props;
  1089. }
  1090. enum gs_color_space screen_capture_video_get_color_space(
  1091. void *data, size_t count, const enum gs_color_space *preferred_spaces)
  1092. {
  1093. UNUSED_PARAMETER(data);
  1094. for (size_t i = 0; i < count; ++i) {
  1095. if (preferred_spaces[i] == GS_CS_SRGB_16F)
  1096. return GS_CS_SRGB_16F;
  1097. }
  1098. for (size_t i = 0; i < count; ++i) {
  1099. if (preferred_spaces[i] == GS_CS_709_EXTENDED)
  1100. return GS_CS_709_EXTENDED;
  1101. }
  1102. for (size_t i = 0; i < count; ++i) {
  1103. if (preferred_spaces[i] == GS_CS_SRGB)
  1104. return GS_CS_SRGB;
  1105. }
  1106. return GS_CS_SRGB_16F;
  1107. }
  1108. #pragma mark - obs_source_info
  1109. struct obs_source_info screen_capture_info = {
  1110. .id = "screen_capture",
  1111. .type = OBS_SOURCE_TYPE_INPUT,
  1112. .get_name = screen_capture_getname,
  1113. .create = screen_capture_create,
  1114. .destroy = screen_capture_destroy,
  1115. .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
  1116. OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_SRGB |
  1117. OBS_SOURCE_AUDIO,
  1118. .video_tick = screen_capture_video_tick,
  1119. .video_render = screen_capture_video_render,
  1120. .get_width = screen_capture_getwidth,
  1121. .get_height = screen_capture_getheight,
  1122. .get_defaults = screen_capture_defaults,
  1123. .get_properties = screen_capture_properties,
  1124. .update = screen_capture_update,
  1125. .icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE,
  1126. .video_get_color_space = screen_capture_video_get_color_space,
  1127. };
  1128. #pragma mark - ScreenCaptureDelegate
  1129. @implementation ScreenCaptureDelegate
  1130. - (void)stream:(SCStream *)stream
  1131. didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
  1132. ofType:(SCStreamOutputType)type
  1133. {
  1134. if (self.sc != NULL) {
  1135. if (type == SCStreamOutputTypeScreen) {
  1136. screen_stream_video_update(self.sc, sampleBuffer);
  1137. }
  1138. #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000
  1139. else if (@available(macOS 13.0, *)) {
  1140. if (type == SCStreamOutputTypeAudio) {
  1141. screen_stream_audio_update(self.sc,
  1142. sampleBuffer);
  1143. }
  1144. }
  1145. #endif
  1146. }
  1147. }
  1148. @end
  1149. // "-Wunguarded-availability-new"
  1150. #pragma clang diagnostic pop
  1151. #endif