test-desktop.m 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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("test-input/draw_rect.effect");
  144. dc->draw_effect = gs_create_effect_from_file(effect_file, NULL);
  145. bfree(effect_file);
  146. if (!dc->draw_effect)
  147. goto fail;
  148. gs_leavecontext();
  149. dc->display = obs_data_getint(settings, "display");
  150. pthread_mutex_init(&dc->mutex, NULL);
  151. if (!init_display_stream(dc))
  152. goto fail;
  153. return dc;
  154. fail:
  155. gs_leavecontext();
  156. display_capture_destroy(dc);
  157. return NULL;
  158. }
  159. static void display_capture_video_tick(void *data, float seconds)
  160. {
  161. UNUSED_PARAMETER(seconds);
  162. struct display_capture *dc = data;
  163. if (!dc->current)
  164. return;
  165. IOSurfaceRef prev_prev = dc->prev;
  166. if (pthread_mutex_lock(&dc->mutex))
  167. return;
  168. dc->prev = dc->current;
  169. dc->current = NULL;
  170. pthread_mutex_unlock(&dc->mutex);
  171. if (prev_prev == dc->prev)
  172. return;
  173. gs_entercontext(obs_graphics());
  174. if (dc->tex)
  175. texture_rebind_iosurface(dc->tex, dc->prev);
  176. else
  177. dc->tex = gs_create_texture_from_iosurface(dc->prev);
  178. gs_leavecontext();
  179. if (prev_prev) {
  180. IOSurfaceDecrementUseCount(prev_prev);
  181. CFRelease(prev_prev);
  182. }
  183. }
  184. static void display_capture_video_render(void *data, effect_t effect)
  185. {
  186. UNUSED_PARAMETER(effect);
  187. struct display_capture *dc = data;
  188. if (!dc->tex)
  189. return;
  190. gs_load_samplerstate(dc->sampler, 0);
  191. technique_t tech = effect_gettechnique(dc->draw_effect, "Default");
  192. effect_settexture(dc->draw_effect,
  193. 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(const char *locale)
  202. {
  203. UNUSED_PARAMETER(locale);
  204. return "Display Capture";
  205. }
  206. static uint32_t display_capture_getwidth(void *data)
  207. {
  208. struct display_capture *dc = data;
  209. return dc->width;
  210. }
  211. static uint32_t display_capture_getheight(void *data)
  212. {
  213. struct display_capture *dc = data;
  214. return dc->height;
  215. }
  216. static void display_capture_defaults(obs_data_t settings)
  217. {
  218. obs_data_set_default_int(settings, "display", 0);
  219. obs_data_set_default_bool(settings, "show_cursor", true);
  220. }
  221. static void display_capture_update(void *data, obs_data_t settings)
  222. {
  223. struct display_capture *dc = data;
  224. unsigned display = obs_data_getint(settings, "display");
  225. bool show_cursor = obs_data_getbool(settings, "show_cursor");
  226. if (dc->display == display && dc->hide_cursor != show_cursor)
  227. return;
  228. gs_entercontext(obs_graphics());
  229. destroy_display_stream(dc);
  230. dc->display = display;
  231. dc->hide_cursor = !show_cursor;
  232. init_display_stream(dc);
  233. gs_leavecontext();
  234. }
  235. static obs_properties_t display_capture_properties(char const *locale)
  236. {
  237. obs_properties_t props = obs_properties_create(locale);
  238. obs_property_t list = obs_properties_add_list(props,
  239. "display", "Display",
  240. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  241. for (unsigned i = 0; i < [NSScreen screens].count; i++) {
  242. char buf[10];
  243. sprintf(buf, "%u", i);
  244. obs_property_list_add_int(list, buf, i);
  245. }
  246. obs_properties_add_bool(props, "show_cursor", "Show Cursor");
  247. return props;
  248. }
  249. struct obs_source_info display_capture_info = {
  250. .id = "display_capture",
  251. .type = OBS_SOURCE_TYPE_INPUT,
  252. .getname = 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. .getwidth = display_capture_getwidth,
  259. .getheight = display_capture_getheight,
  260. .defaults = display_capture_defaults,
  261. .properties = display_capture_properties,
  262. .update = display_capture_update,
  263. };