mac-display-capture.m 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. #include <stdlib.h>
  2. #include <obs.h>
  3. #include <util/threading.h>
  4. #include <pthread.h>
  5. #import <CoreGraphics/CGDisplayStream.h>
  6. #import <Cocoa/Cocoa.h>
  7. struct display_capture {
  8. obs_source_t source;
  9. samplerstate_t sampler;
  10. effect_t draw_effect;
  11. texture_t tex;
  12. unsigned display;
  13. uint32_t width, height;
  14. bool hide_cursor;
  15. os_event_t disp_finished;
  16. CGDisplayStreamRef disp;
  17. IOSurfaceRef current, prev;
  18. pthread_mutex_t mutex;
  19. };
  20. static void destroy_display_stream(struct display_capture *dc)
  21. {
  22. if (dc->disp) {
  23. CGDisplayStreamStop(dc->disp);
  24. os_event_wait(dc->disp_finished);
  25. }
  26. if (dc->tex) {
  27. texture_destroy(dc->tex);
  28. dc->tex = NULL;
  29. }
  30. if (dc->current) {
  31. IOSurfaceDecrementUseCount(dc->current);
  32. CFRelease(dc->current);
  33. dc->current = NULL;
  34. }
  35. if (dc->prev) {
  36. IOSurfaceDecrementUseCount(dc->prev);
  37. CFRelease(dc->prev);
  38. dc->prev = NULL;
  39. }
  40. if (dc->disp) {
  41. CFRelease(dc->disp);
  42. dc->disp = NULL;
  43. }
  44. os_event_destroy(dc->disp_finished);
  45. }
  46. static void display_capture_destroy(void *data)
  47. {
  48. struct display_capture *dc = data;
  49. if (!dc)
  50. return;
  51. gs_entercontext(obs_graphics());
  52. destroy_display_stream(dc);
  53. if (dc->sampler)
  54. samplerstate_destroy(dc->sampler);
  55. if (dc->draw_effect)
  56. effect_destroy(dc->draw_effect);
  57. gs_leavecontext();
  58. pthread_mutex_destroy(&dc->mutex);
  59. bfree(dc);
  60. }
  61. static inline void display_stream_update(struct display_capture *dc,
  62. CGDisplayStreamFrameStatus status, uint64_t display_time,
  63. IOSurfaceRef frame_surface, CGDisplayStreamUpdateRef update_ref)
  64. {
  65. UNUSED_PARAMETER(display_time);
  66. UNUSED_PARAMETER(update_ref);
  67. if (status == kCGDisplayStreamFrameStatusStopped) {
  68. os_event_signal(dc->disp_finished);
  69. return;
  70. }
  71. IOSurfaceRef prev_current = NULL;
  72. if (frame_surface && !pthread_mutex_lock(&dc->mutex)) {
  73. prev_current = dc->current;
  74. dc->current = frame_surface;
  75. CFRetain(dc->current);
  76. IOSurfaceIncrementUseCount(dc->current);
  77. pthread_mutex_unlock(&dc->mutex);
  78. }
  79. if (prev_current) {
  80. IOSurfaceDecrementUseCount(prev_current);
  81. CFRelease(prev_current);
  82. }
  83. size_t dropped_frames = CGDisplayStreamUpdateGetDropCount(update_ref);
  84. if (dropped_frames > 0)
  85. blog(LOG_INFO, "%s: Dropped %zu frames",
  86. obs_source_getname(dc->source), dropped_frames);
  87. }
  88. static bool init_display_stream(struct display_capture *dc)
  89. {
  90. if (dc->display >= [NSScreen screens].count)
  91. return false;
  92. NSScreen *screen = [NSScreen screens][dc->display];
  93. NSRect frame = [screen convertRectToBacking:screen.frame];
  94. dc->width = frame.size.width;
  95. dc->height = frame.size.height;
  96. NSNumber *screen_num = screen.deviceDescription[@"NSScreenNumber"];
  97. CGDirectDisplayID disp_id = (CGDirectDisplayID)screen_num.pointerValue;
  98. NSDictionary *rect_dict = CFBridgingRelease(
  99. CGRectCreateDictionaryRepresentation(
  100. CGRectMake(0, 0,
  101. screen.frame.size.width,
  102. screen.frame.size.height)));
  103. NSDictionary *dict = @{
  104. (__bridge NSString*)kCGDisplayStreamSourceRect: rect_dict,
  105. (__bridge NSString*)kCGDisplayStreamQueueDepth: @5,
  106. (__bridge NSString*)kCGDisplayStreamShowCursor:
  107. @(!dc->hide_cursor),
  108. };
  109. os_event_init(&dc->disp_finished, OS_EVENT_TYPE_MANUAL);
  110. dc->disp = CGDisplayStreamCreateWithDispatchQueue(disp_id,
  111. dc->width, dc->height, 'BGRA',
  112. (__bridge CFDictionaryRef)dict,
  113. dispatch_queue_create(NULL, NULL),
  114. ^(CGDisplayStreamFrameStatus status,
  115. uint64_t displayTime,
  116. IOSurfaceRef frameSurface,
  117. CGDisplayStreamUpdateRef updateRef)
  118. {
  119. display_stream_update(dc, status, displayTime,
  120. frameSurface, updateRef);
  121. }
  122. );
  123. return !CGDisplayStreamStart(dc->disp);
  124. }
  125. static void *display_capture_create(obs_data_t settings,
  126. obs_source_t source)
  127. {
  128. UNUSED_PARAMETER(source);
  129. UNUSED_PARAMETER(settings);
  130. struct display_capture *dc = bzalloc(sizeof(struct display_capture));
  131. dc->source = source;
  132. gs_entercontext(obs_graphics());
  133. struct gs_sampler_info info = {
  134. .filter = GS_FILTER_LINEAR,
  135. .address_u = GS_ADDRESS_CLAMP,
  136. .address_v = GS_ADDRESS_CLAMP,
  137. .address_w = GS_ADDRESS_CLAMP,
  138. .max_anisotropy = 1,
  139. };
  140. dc->sampler = gs_create_samplerstate(&info);
  141. if (!dc->sampler)
  142. goto fail;
  143. char *effect_file = obs_find_plugin_file(
  144. "mac-capture/draw_rect.effect");
  145. dc->draw_effect = gs_create_effect_from_file(effect_file, NULL);
  146. bfree(effect_file);
  147. if (!dc->draw_effect)
  148. goto fail;
  149. gs_leavecontext();
  150. dc->display = obs_data_getint(settings, "display");
  151. pthread_mutex_init(&dc->mutex, NULL);
  152. if (!init_display_stream(dc))
  153. goto fail;
  154. return dc;
  155. fail:
  156. gs_leavecontext();
  157. display_capture_destroy(dc);
  158. return NULL;
  159. }
  160. static void display_capture_video_tick(void *data, float seconds)
  161. {
  162. UNUSED_PARAMETER(seconds);
  163. struct display_capture *dc = data;
  164. if (!dc->current)
  165. return;
  166. IOSurfaceRef prev_prev = dc->prev;
  167. if (pthread_mutex_lock(&dc->mutex))
  168. return;
  169. dc->prev = dc->current;
  170. dc->current = NULL;
  171. pthread_mutex_unlock(&dc->mutex);
  172. if (prev_prev == dc->prev)
  173. return;
  174. gs_entercontext(obs_graphics());
  175. if (dc->tex)
  176. texture_rebind_iosurface(dc->tex, dc->prev);
  177. else
  178. dc->tex = gs_create_texture_from_iosurface(dc->prev);
  179. gs_leavecontext();
  180. if (prev_prev) {
  181. IOSurfaceDecrementUseCount(prev_prev);
  182. CFRelease(prev_prev);
  183. }
  184. }
  185. static void display_capture_video_render(void *data, effect_t effect)
  186. {
  187. UNUSED_PARAMETER(effect);
  188. struct display_capture *dc = data;
  189. if (!dc->tex)
  190. return;
  191. gs_load_samplerstate(dc->sampler, 0);
  192. technique_t tech = effect_gettechnique(dc->draw_effect, "Default");
  193. effect_settexture(effect_getparambyidx(dc->draw_effect, 1),
  194. dc->tex);
  195. technique_begin(tech);
  196. technique_beginpass(tech, 0);
  197. gs_draw_sprite(dc->tex, 0, 0, 0);
  198. technique_endpass(tech);
  199. technique_end(tech);
  200. }
  201. static const char *display_capture_getname(void)
  202. {
  203. return "Display Capture";
  204. }
  205. static uint32_t display_capture_getwidth(void *data)
  206. {
  207. struct display_capture *dc = data;
  208. return dc->width;
  209. }
  210. static uint32_t display_capture_getheight(void *data)
  211. {
  212. struct display_capture *dc = data;
  213. return dc->height;
  214. }
  215. static void display_capture_defaults(obs_data_t settings)
  216. {
  217. obs_data_set_default_int(settings, "display", 0);
  218. obs_data_set_default_bool(settings, "show_cursor", true);
  219. }
  220. static void display_capture_update(void *data, obs_data_t settings)
  221. {
  222. struct display_capture *dc = data;
  223. unsigned display = obs_data_getint(settings, "display");
  224. bool show_cursor = obs_data_getbool(settings, "show_cursor");
  225. if (dc->display == display && dc->hide_cursor != show_cursor)
  226. return;
  227. gs_entercontext(obs_graphics());
  228. destroy_display_stream(dc);
  229. dc->display = display;
  230. dc->hide_cursor = !show_cursor;
  231. init_display_stream(dc);
  232. gs_leavecontext();
  233. }
  234. static obs_properties_t display_capture_properties(void)
  235. {
  236. obs_properties_t props = obs_properties_create();
  237. obs_property_t list = obs_properties_add_list(props,
  238. "display", "Display",
  239. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  240. for (unsigned i = 0; i < [NSScreen screens].count; i++) {
  241. char buf[10];
  242. sprintf(buf, "%u", i);
  243. obs_property_list_add_int(list, buf, i);
  244. }
  245. obs_properties_add_bool(props, "show_cursor", "Show Cursor");
  246. return props;
  247. }
  248. struct obs_source_info display_capture_info = {
  249. .id = "display_capture",
  250. .type = OBS_SOURCE_TYPE_INPUT,
  251. .getname = display_capture_getname,
  252. .create = display_capture_create,
  253. .destroy = display_capture_destroy,
  254. .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW,
  255. .video_tick = display_capture_video_tick,
  256. .video_render = display_capture_video_render,
  257. .getwidth = display_capture_getwidth,
  258. .getheight = display_capture_getheight,
  259. .defaults = display_capture_defaults,
  260. .properties = display_capture_properties,
  261. .update = display_capture_update,
  262. };