mac-window-capture.m 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. #include <obs-module.h>
  2. #include <util/darray.h>
  3. #include <util/threading.h>
  4. #include <util/platform.h>
  5. #import <CoreGraphics/CGWindow.h>
  6. #import <Cocoa/Cocoa.h>
  7. #include "window-utils.h"
  8. struct window_capture {
  9. obs_source_t *source;
  10. struct cocoa_window window;
  11. //CGRect bounds;
  12. //CGWindowListOption window_option;
  13. CGWindowImageOption image_option;
  14. CGColorSpaceRef color_space;
  15. DARRAY(uint8_t) buffer;
  16. pthread_t capture_thread;
  17. os_event_t *capture_event;
  18. os_event_t *stop_event;
  19. };
  20. static CGImageRef get_image(struct window_capture *wc)
  21. {
  22. NSArray *arr = (NSArray *) CGWindowListCreate(kCGWindowListOptionIncludingWindow, wc->window.window_id);
  23. [arr autorelease];
  24. if (!arr.count && !find_window(&wc->window, NULL, false))
  25. return NULL;
  26. return CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, wc->window.window_id,
  27. wc->image_option);
  28. }
  29. static inline void capture_frame(struct window_capture *wc)
  30. {
  31. uint64_t ts = os_gettime_ns();
  32. CGImageRef img = get_image(wc);
  33. if (!img)
  34. return;
  35. size_t width = CGImageGetWidth(img);
  36. size_t height = CGImageGetHeight(img);
  37. if (!width || !height || CGImageGetBitsPerPixel(img) != 32 || CGImageGetBitsPerComponent(img) != 8) {
  38. CGImageRelease(img);
  39. return;
  40. }
  41. CGDataProviderRef provider = CGImageGetDataProvider(img);
  42. CFDataRef data = CGDataProviderCopyData(provider);
  43. struct obs_source_frame frame = {
  44. .format = VIDEO_FORMAT_BGRA,
  45. .width = (uint32_t) width,
  46. .height = (uint32_t) height,
  47. .data[0] = (uint8_t *) CFDataGetBytePtr(data),
  48. .linesize[0] = (uint32_t) CGImageGetBytesPerRow(img),
  49. .timestamp = ts,
  50. };
  51. obs_source_output_video(wc->source, &frame);
  52. CGImageRelease(img);
  53. CFRelease(data);
  54. }
  55. static void *capture_thread(void *data)
  56. {
  57. struct window_capture *wc = data;
  58. for (;;) {
  59. os_event_wait(wc->capture_event);
  60. if (os_event_try(wc->stop_event) != EAGAIN)
  61. break;
  62. @autoreleasepool {
  63. capture_frame(wc);
  64. }
  65. }
  66. return NULL;
  67. }
  68. static inline void *window_capture_create_internal(obs_data_t *settings, obs_source_t *source)
  69. {
  70. struct window_capture *wc = bzalloc(sizeof(struct window_capture));
  71. wc->source = source;
  72. wc->color_space = CGColorSpaceCreateDeviceRGB();
  73. da_init(wc->buffer);
  74. init_window(&wc->window, settings);
  75. wc->image_option = obs_data_get_bool(settings, "show_shadow") ? kCGWindowImageDefault
  76. : kCGWindowImageBoundsIgnoreFraming;
  77. os_event_init(&wc->capture_event, OS_EVENT_TYPE_AUTO);
  78. os_event_init(&wc->stop_event, OS_EVENT_TYPE_MANUAL);
  79. pthread_create(&wc->capture_thread, NULL, capture_thread, wc);
  80. return wc;
  81. }
  82. static void *window_capture_create(obs_data_t *settings, obs_source_t *source)
  83. {
  84. @autoreleasepool {
  85. return window_capture_create_internal(settings, source);
  86. }
  87. }
  88. static void window_capture_destroy(void *data)
  89. {
  90. struct window_capture *cap = data;
  91. os_event_signal(cap->stop_event);
  92. os_event_signal(cap->capture_event);
  93. pthread_join(cap->capture_thread, NULL);
  94. CGColorSpaceRelease(cap->color_space);
  95. da_free(cap->buffer);
  96. os_event_destroy(cap->capture_event);
  97. os_event_destroy(cap->stop_event);
  98. destroy_window(&cap->window);
  99. bfree(cap);
  100. }
  101. static void window_capture_defaults(obs_data_t *settings)
  102. {
  103. obs_data_set_default_bool(settings, "show_shadow", false);
  104. window_defaults(settings);
  105. }
  106. static obs_properties_t *window_capture_properties(void *unused)
  107. {
  108. UNUSED_PARAMETER(unused);
  109. obs_properties_t *props = obs_properties_create();
  110. add_window_properties(props);
  111. obs_properties_add_bool(props, "show_shadow", obs_module_text("WindowCapture.ShowShadow"));
  112. return props;
  113. }
  114. static inline void window_capture_update_internal(struct window_capture *wc, obs_data_t *settings)
  115. {
  116. wc->image_option = obs_data_get_bool(settings, "show_shadow") ? kCGWindowImageDefault
  117. : kCGWindowImageBoundsIgnoreFraming;
  118. update_window(&wc->window, settings);
  119. if (wc->window.window_name.length) {
  120. blog(LOG_INFO,
  121. "[window-capture: '%s'] update settings:\n"
  122. "\twindow: %s\n"
  123. "\towner: %s",
  124. obs_source_get_name(wc->source), [wc->window.window_name UTF8String], [wc->window.owner_name UTF8String]);
  125. }
  126. }
  127. static void window_capture_update(void *data, obs_data_t *settings)
  128. {
  129. @autoreleasepool {
  130. return window_capture_update_internal(data, settings);
  131. }
  132. }
  133. static const char *window_capture_getname(void *unused)
  134. {
  135. UNUSED_PARAMETER(unused);
  136. return obs_module_text("WindowCapture");
  137. }
  138. static inline void window_capture_tick_internal(struct window_capture *wc, float seconds)
  139. {
  140. UNUSED_PARAMETER(seconds);
  141. os_event_signal(wc->capture_event);
  142. }
  143. static void window_capture_tick(void *data, float seconds)
  144. {
  145. struct window_capture *wc = data;
  146. if (!obs_source_showing(wc->source))
  147. return;
  148. @autoreleasepool {
  149. return window_capture_tick_internal(data, seconds);
  150. }
  151. }
  152. struct obs_source_info window_capture_info = {
  153. .id = "window_capture",
  154. .type = OBS_SOURCE_TYPE_INPUT,
  155. .get_name = window_capture_getname,
  156. .create = window_capture_create,
  157. .destroy = window_capture_destroy,
  158. .output_flags = OBS_SOURCE_ASYNC_VIDEO,
  159. .video_tick = window_capture_tick,
  160. .get_defaults = window_capture_defaults,
  161. .get_properties = window_capture_properties,
  162. .update = window_capture_update,
  163. .icon_type = OBS_ICON_TYPE_WINDOW_CAPTURE,
  164. };