mac-display-capture.m 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. #include <stdlib.h>
  2. #include <obs-module.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. obs_enter_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. obs_leave_graphics();
  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_get_name(dc->source),
  87. dropped_frames);
  88. }
  89. static bool init_display_stream(struct display_capture *dc)
  90. {
  91. if (dc->display >= [NSScreen screens].count)
  92. return false;
  93. NSScreen *screen = [NSScreen screens][dc->display];
  94. NSRect frame = [screen convertRectToBacking:screen.frame];
  95. dc->width = frame.size.width;
  96. dc->height = frame.size.height;
  97. NSNumber *screen_num = screen.deviceDescription[@"NSScreenNumber"];
  98. CGDirectDisplayID disp_id = (CGDirectDisplayID)screen_num.pointerValue;
  99. NSDictionary *rect_dict = CFBridgingRelease(
  100. CGRectCreateDictionaryRepresentation(
  101. CGRectMake(0, 0,
  102. screen.frame.size.width,
  103. screen.frame.size.height)));
  104. NSDictionary *dict = @{
  105. (__bridge NSString*)kCGDisplayStreamSourceRect: rect_dict,
  106. (__bridge NSString*)kCGDisplayStreamQueueDepth: @5,
  107. (__bridge NSString*)kCGDisplayStreamShowCursor:
  108. @(!dc->hide_cursor),
  109. };
  110. os_event_init(&dc->disp_finished, OS_EVENT_TYPE_MANUAL);
  111. dc->disp = CGDisplayStreamCreateWithDispatchQueue(disp_id,
  112. dc->width, dc->height, 'BGRA',
  113. (__bridge CFDictionaryRef)dict,
  114. dispatch_queue_create(NULL, NULL),
  115. ^(CGDisplayStreamFrameStatus status,
  116. uint64_t displayTime,
  117. IOSurfaceRef frameSurface,
  118. CGDisplayStreamUpdateRef updateRef)
  119. {
  120. display_stream_update(dc, status, displayTime,
  121. frameSurface, updateRef);
  122. }
  123. );
  124. return !CGDisplayStreamStart(dc->disp);
  125. }
  126. static void *display_capture_create(obs_data_t settings,
  127. obs_source_t source)
  128. {
  129. UNUSED_PARAMETER(source);
  130. UNUSED_PARAMETER(settings);
  131. struct display_capture *dc = bzalloc(sizeof(struct display_capture));
  132. dc->source = source;
  133. obs_enter_graphics();
  134. struct gs_sampler_info info = {
  135. .filter = GS_FILTER_LINEAR,
  136. .address_u = GS_ADDRESS_CLAMP,
  137. .address_v = GS_ADDRESS_CLAMP,
  138. .address_w = GS_ADDRESS_CLAMP,
  139. .max_anisotropy = 1,
  140. };
  141. dc->sampler = gs_create_samplerstate(&info);
  142. if (!dc->sampler)
  143. goto fail;
  144. char *effect_file = obs_module_file("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. obs_leave_graphics();
  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. obs_leave_graphics();
  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. obs_enter_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. obs_leave_graphics();
  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 obs_module_text("DisplayCapture");
  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. obs_enter_graphics();
  228. destroy_display_stream(dc);
  229. dc->display = display;
  230. dc->hide_cursor = !show_cursor;
  231. init_display_stream(dc);
  232. obs_leave_graphics();
  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", obs_module_text("DisplayCapture.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",
  246. obs_module_text("DisplayCapture.ShowCursor"));
  247. return props;
  248. }
  249. struct obs_source_info display_capture_info = {
  250. .id = "display_capture",
  251. .type = OBS_SOURCE_TYPE_INPUT,
  252. .get_name = display_capture_getname,
  253. .create = display_capture_create,
  254. .destroy = display_capture_destroy,
  255. .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW,
  256. .video_tick = display_capture_video_tick,
  257. .video_render = display_capture_video_render,
  258. .get_width = display_capture_getwidth,
  259. .get_height = display_capture_getheight,
  260. .get_defaults = display_capture_defaults,
  261. .get_properties = display_capture_properties,
  262. .update = display_capture_update,
  263. };