mac-window-capture.m 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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(
  23. kCGWindowListOptionIncludingWindow, wc->window.window_id);
  24. [arr autorelease];
  25. if (arr.count)
  26. return CGWindowListCreateImage(
  27. CGRectNull, kCGWindowListOptionIncludingWindow,
  28. wc->window.window_id, wc->image_option);
  29. if (!find_window(&wc->window, NULL, false))
  30. return NULL;
  31. return CGWindowListCreateImage(CGRectNull,
  32. kCGWindowListOptionIncludingWindow,
  33. wc->window.window_id, wc->image_option);
  34. }
  35. static inline void capture_frame(struct window_capture *wc)
  36. {
  37. uint64_t ts = os_gettime_ns();
  38. CGImageRef img = get_image(wc);
  39. if (!img)
  40. return;
  41. size_t width = CGImageGetWidth(img);
  42. size_t height = CGImageGetHeight(img);
  43. CGRect rect = {{0, 0}, {width, height}};
  44. da_reserve(wc->buffer, width * height * 4);
  45. uint8_t *data = wc->buffer.array;
  46. CGContextRef cg_context = CGBitmapContextCreate(
  47. data, width, height, 8, width * 4, wc->color_space,
  48. kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
  49. CGContextSetBlendMode(cg_context, kCGBlendModeCopy);
  50. CGContextDrawImage(cg_context, rect, img);
  51. CGContextRelease(cg_context);
  52. CGImageRelease(img);
  53. struct obs_source_frame frame = {
  54. .format = VIDEO_FORMAT_BGRA,
  55. .width = width,
  56. .height = height,
  57. .data[0] = data,
  58. .linesize[0] = width * 4,
  59. .timestamp = ts,
  60. };
  61. obs_source_output_video(wc->source, &frame);
  62. }
  63. static void *capture_thread(void *data)
  64. {
  65. struct window_capture *wc = data;
  66. for (;;) {
  67. os_event_wait(wc->capture_event);
  68. if (os_event_try(wc->stop_event) != EAGAIN)
  69. break;
  70. @autoreleasepool {
  71. capture_frame(wc);
  72. }
  73. }
  74. return NULL;
  75. }
  76. static inline void *window_capture_create_internal(obs_data_t *settings,
  77. obs_source_t *source)
  78. {
  79. struct window_capture *wc = bzalloc(sizeof(struct window_capture));
  80. wc->source = source;
  81. wc->color_space = CGColorSpaceCreateDeviceRGB();
  82. da_init(wc->buffer);
  83. init_window(&wc->window, settings);
  84. wc->image_option = obs_data_get_bool(settings, "show_shadow")
  85. ? kCGWindowImageDefault
  86. : kCGWindowImageBoundsIgnoreFraming;
  87. os_event_init(&wc->capture_event, OS_EVENT_TYPE_AUTO);
  88. os_event_init(&wc->stop_event, OS_EVENT_TYPE_MANUAL);
  89. pthread_create(&wc->capture_thread, NULL, capture_thread, wc);
  90. return wc;
  91. }
  92. static void *window_capture_create(obs_data_t *settings, obs_source_t *source)
  93. {
  94. @autoreleasepool {
  95. return window_capture_create_internal(settings, source);
  96. }
  97. }
  98. static void window_capture_destroy(void *data)
  99. {
  100. struct window_capture *cap = data;
  101. os_event_signal(cap->stop_event);
  102. os_event_signal(cap->capture_event);
  103. pthread_join(cap->capture_thread, NULL);
  104. CGColorSpaceRelease(cap->color_space);
  105. da_free(cap->buffer);
  106. os_event_destroy(cap->capture_event);
  107. os_event_destroy(cap->stop_event);
  108. destroy_window(&cap->window);
  109. bfree(cap);
  110. }
  111. static void window_capture_defaults(obs_data_t *settings)
  112. {
  113. obs_data_set_default_bool(settings, "show_shadow", false);
  114. window_defaults(settings);
  115. }
  116. static obs_properties_t *window_capture_properties(void *unused)
  117. {
  118. UNUSED_PARAMETER(unused);
  119. obs_properties_t *props = obs_properties_create();
  120. add_window_properties(props);
  121. obs_properties_add_bool(props, "show_shadow",
  122. obs_module_text("WindowCapture.ShowShadow"));
  123. return props;
  124. }
  125. static inline void window_capture_update_internal(struct window_capture *wc,
  126. obs_data_t *settings)
  127. {
  128. wc->image_option = obs_data_get_bool(settings, "show_shadow")
  129. ? kCGWindowImageDefault
  130. : kCGWindowImageBoundsIgnoreFraming;
  131. update_window(&wc->window, settings);
  132. if (wc->window.window_name.length) {
  133. blog(LOG_INFO,
  134. "[window-capture: '%s'] update settings:\n"
  135. "\twindow: %s\n"
  136. "\towner: %s",
  137. obs_source_get_name(wc->source),
  138. [wc->window.window_name UTF8String],
  139. [wc->window.owner_name UTF8String]);
  140. }
  141. }
  142. static void window_capture_update(void *data, obs_data_t *settings)
  143. {
  144. @autoreleasepool {
  145. return window_capture_update_internal(data, settings);
  146. }
  147. }
  148. static const char *window_capture_getname(void *unused)
  149. {
  150. UNUSED_PARAMETER(unused);
  151. return obs_module_text("WindowCapture");
  152. }
  153. static inline void window_capture_tick_internal(struct window_capture *wc,
  154. float seconds)
  155. {
  156. UNUSED_PARAMETER(seconds);
  157. os_event_signal(wc->capture_event);
  158. }
  159. static void window_capture_tick(void *data, float seconds)
  160. {
  161. struct window_capture *wc = data;
  162. if (!obs_source_showing(wc->source))
  163. return;
  164. @autoreleasepool {
  165. return window_capture_tick_internal(data, seconds);
  166. }
  167. }
  168. struct obs_source_info window_capture_info = {
  169. .id = "window_capture",
  170. .type = OBS_SOURCE_TYPE_INPUT,
  171. .get_name = window_capture_getname,
  172. .create = window_capture_create,
  173. .destroy = window_capture_destroy,
  174. .output_flags = OBS_SOURCE_ASYNC_VIDEO,
  175. .video_tick = window_capture_tick,
  176. .get_defaults = window_capture_defaults,
  177. .get_properties = window_capture_properties,
  178. .update = window_capture_update,
  179. };