transition-stinger.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. #include <obs-module.h>
  2. #include <util/dstr.h>
  3. #define TIMING_TIME 0
  4. #define TIMING_FRAME 1
  5. #define MATTE_LAYOUT_HORIZONTAL 0
  6. #define MATTE_LAYOUT_VERTICAL 1
  7. #define MATTE_LAYOUT_SEPARATE_FILE 2
  8. enum fade_style { FADE_STYLE_FADE_OUT_FADE_IN, FADE_STYLE_CROSS_FADE };
  9. struct stinger_info {
  10. obs_source_t *source;
  11. obs_source_t *media_source;
  12. obs_source_t *matte_source;
  13. uint64_t duration_ns;
  14. uint64_t duration_frames;
  15. uint64_t transition_point_ns;
  16. uint64_t transition_point_frame;
  17. float transition_point;
  18. float transition_a_mul;
  19. float transition_b_mul;
  20. bool transitioning;
  21. bool transition_point_is_frame;
  22. int monitoring_type;
  23. enum fade_style fade_style;
  24. bool track_matte_enabled;
  25. int matte_layout;
  26. float matte_width_factor;
  27. float matte_height_factor;
  28. bool invert_matte;
  29. gs_effect_t *matte_effect;
  30. gs_eparam_t *ep_a_tex;
  31. gs_eparam_t *ep_b_tex;
  32. gs_eparam_t *ep_matte_tex;
  33. gs_eparam_t *ep_invert_matte;
  34. gs_texrender_t *matte_tex;
  35. float (*mix_a)(void *data, float t);
  36. float (*mix_b)(void *data, float t);
  37. };
  38. static const char *stinger_get_name(void *type_data)
  39. {
  40. UNUSED_PARAMETER(type_data);
  41. return obs_module_text("StingerTransition");
  42. }
  43. static float mix_a_fade_in_out(void *data, float t);
  44. static float mix_b_fade_in_out(void *data, float t);
  45. static float mix_a_cross_fade(void *data, float t);
  46. static float mix_b_cross_fade(void *data, float t);
  47. static void stinger_update(void *data, obs_data_t *settings)
  48. {
  49. struct stinger_info *s = data;
  50. const char *path = obs_data_get_string(settings, "path");
  51. bool hw_decode = obs_data_get_bool(settings, "hw_decode");
  52. obs_data_t *media_settings = obs_data_create();
  53. obs_data_set_string(media_settings, "local_file", path);
  54. obs_data_set_bool(media_settings, "hw_decode", hw_decode);
  55. obs_source_release(s->media_source);
  56. struct dstr name;
  57. dstr_init_copy(&name, obs_source_get_name(s->source));
  58. dstr_cat(&name, " (Stinger)");
  59. s->media_source = obs_source_create_private("ffmpeg_source", name.array,
  60. media_settings);
  61. dstr_free(&name);
  62. obs_data_release(media_settings);
  63. int64_t point = obs_data_get_int(settings, "transition_point");
  64. s->transition_point_is_frame = obs_data_get_int(settings, "tp_type") ==
  65. TIMING_FRAME;
  66. if (s->transition_point_is_frame)
  67. s->transition_point_frame = (uint64_t)point;
  68. else
  69. s->transition_point_ns = (uint64_t)(point * 1000000LL);
  70. s->track_matte_enabled =
  71. obs_data_get_bool(settings, "track_matte_enabled");
  72. s->matte_layout = obs_data_get_int(settings, "track_matte_layout");
  73. s->matte_width_factor =
  74. (s->matte_layout == MATTE_LAYOUT_HORIZONTAL ? 2.0f : 1.0f);
  75. s->matte_height_factor =
  76. (s->matte_layout == MATTE_LAYOUT_VERTICAL ? 2.0f : 1.0f);
  77. s->invert_matte = obs_data_get_bool(settings, "invert_matte");
  78. if (s->matte_source) {
  79. obs_source_release(s->matte_source);
  80. s->matte_source = NULL;
  81. }
  82. if (s->track_matte_enabled &&
  83. s->matte_layout == MATTE_LAYOUT_SEPARATE_FILE) {
  84. const char *tm_path =
  85. obs_data_get_string(settings, "track_matte_path");
  86. obs_data_t *tm_media_settings = obs_data_create();
  87. obs_data_set_string(tm_media_settings, "local_file", tm_path);
  88. s->matte_source = obs_source_create_private(
  89. "ffmpeg_source", NULL, tm_media_settings);
  90. obs_data_release(tm_media_settings);
  91. // no need to output sound from the matte video
  92. obs_source_set_muted(s->matte_source, true);
  93. }
  94. s->monitoring_type =
  95. (int)obs_data_get_int(settings, "audio_monitoring");
  96. obs_source_set_monitoring_type(s->media_source, s->monitoring_type);
  97. s->fade_style =
  98. (enum fade_style)obs_data_get_int(settings, "audio_fade_style");
  99. switch (s->fade_style) {
  100. default:
  101. case FADE_STYLE_FADE_OUT_FADE_IN:
  102. s->mix_a = mix_a_fade_in_out;
  103. s->mix_b = mix_b_fade_in_out;
  104. break;
  105. case FADE_STYLE_CROSS_FADE:
  106. s->mix_a = mix_a_cross_fade;
  107. s->mix_b = mix_b_cross_fade;
  108. break;
  109. }
  110. }
  111. static void *stinger_create(obs_data_t *settings, obs_source_t *source)
  112. {
  113. struct stinger_info *s = bzalloc(sizeof(*s));
  114. s->source = source;
  115. s->mix_a = mix_a_fade_in_out;
  116. s->mix_b = mix_b_fade_in_out;
  117. char *effect_file = obs_module_file("stinger_matte_transition.effect");
  118. char *error_string = NULL;
  119. obs_enter_graphics();
  120. s->matte_effect =
  121. gs_effect_create_from_file(effect_file, &error_string);
  122. obs_leave_graphics();
  123. if (!s->matte_effect) {
  124. blog(LOG_ERROR,
  125. "Could not open stinger_matte_transition.effect: %s",
  126. error_string);
  127. bfree(error_string);
  128. bfree(s);
  129. return NULL;
  130. }
  131. bfree(effect_file);
  132. s->ep_a_tex = gs_effect_get_param_by_name(s->matte_effect, "a_tex");
  133. s->ep_b_tex = gs_effect_get_param_by_name(s->matte_effect, "b_tex");
  134. s->ep_matte_tex =
  135. gs_effect_get_param_by_name(s->matte_effect, "matte_tex");
  136. s->ep_invert_matte =
  137. gs_effect_get_param_by_name(s->matte_effect, "invert_matte");
  138. s->matte_tex = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
  139. obs_transition_enable_fixed(s->source, true, 0);
  140. obs_source_update(source, settings);
  141. return s;
  142. }
  143. static void stinger_destroy(void *data)
  144. {
  145. struct stinger_info *s = data;
  146. obs_source_release(s->media_source);
  147. obs_source_release(s->matte_source);
  148. gs_texrender_destroy(s->matte_tex);
  149. gs_effect_destroy(s->matte_effect);
  150. bfree(s);
  151. }
  152. static void stinger_defaults(obs_data_t *settings)
  153. {
  154. obs_data_set_default_bool(settings, "hw_decode", true);
  155. }
  156. static void stinger_matte_render(void *data, gs_texture_t *a, gs_texture_t *b,
  157. float t, uint32_t cx, uint32_t cy)
  158. {
  159. struct stinger_info *s = data;
  160. struct vec4 background;
  161. vec4_zero(&background);
  162. obs_source_t *matte_source =
  163. (s->matte_layout == MATTE_LAYOUT_SEPARATE_FILE
  164. ? s->matte_source
  165. : s->media_source);
  166. float matte_cx = (float)obs_source_get_width(matte_source) /
  167. s->matte_width_factor;
  168. float matte_cy = (float)obs_source_get_height(matte_source) /
  169. s->matte_height_factor;
  170. float width_offset = (s->matte_layout == MATTE_LAYOUT_HORIZONTAL
  171. ? (-matte_cx)
  172. : 0.0f);
  173. float height_offset =
  174. (s->matte_layout == MATTE_LAYOUT_VERTICAL ? (-matte_cy) : 0.0f);
  175. // Track matte media render
  176. gs_texrender_reset(s->matte_tex);
  177. if (matte_cx > 0 && matte_cy > 0) {
  178. float scale_x = (float)cx / matte_cx;
  179. float scale_y = (float)cy / matte_cy;
  180. if (gs_texrender_begin(s->matte_tex, cx, cy)) {
  181. gs_matrix_push();
  182. gs_matrix_scale3f(scale_x, scale_y, 1.0f);
  183. gs_matrix_translate3f(width_offset, height_offset,
  184. 0.0f);
  185. gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0);
  186. obs_source_video_render(matte_source);
  187. gs_matrix_pop();
  188. gs_texrender_end(s->matte_tex);
  189. }
  190. }
  191. gs_effect_set_texture(s->ep_a_tex, a);
  192. gs_effect_set_texture(s->ep_b_tex, b);
  193. gs_effect_set_texture(s->ep_matte_tex,
  194. gs_texrender_get_texture(s->matte_tex));
  195. gs_effect_set_bool(s->ep_invert_matte, s->invert_matte);
  196. while (gs_effect_loop(s->matte_effect, "StingerMatte"))
  197. gs_draw_sprite(NULL, 0, cx, cy);
  198. UNUSED_PARAMETER(t);
  199. }
  200. static void stinger_video_render(void *data, gs_effect_t *effect)
  201. {
  202. struct stinger_info *s = data;
  203. if (s->track_matte_enabled) {
  204. obs_transition_video_render(s->source, stinger_matte_render);
  205. } else {
  206. float t = obs_transition_get_time(s->source);
  207. bool use_a = t < s->transition_point;
  208. enum obs_transition_target target =
  209. use_a ? OBS_TRANSITION_SOURCE_A
  210. : OBS_TRANSITION_SOURCE_B;
  211. if (!obs_transition_video_render_direct(s->source, target))
  212. return;
  213. }
  214. /* --------------------- */
  215. float source_cx = (float)obs_source_get_width(s->source);
  216. float source_cy = (float)obs_source_get_height(s->source);
  217. uint32_t media_cx = obs_source_get_width(s->media_source);
  218. uint32_t media_cy = obs_source_get_height(s->media_source);
  219. if (!media_cx || !media_cy)
  220. return;
  221. float scale_x, scale_y;
  222. if (s->track_matte_enabled) {
  223. scale_x = source_cx / ((float)media_cx / s->matte_width_factor);
  224. scale_y =
  225. source_cy / ((float)media_cy / s->matte_height_factor);
  226. } else {
  227. scale_x = source_cx / (float)media_cx;
  228. scale_y = source_cy / (float)media_cy;
  229. }
  230. gs_matrix_push();
  231. gs_matrix_scale3f(scale_x, scale_y, 1.0f);
  232. obs_source_video_render(s->media_source);
  233. gs_matrix_pop();
  234. UNUSED_PARAMETER(effect);
  235. }
  236. static inline float calc_fade(float t, float mul)
  237. {
  238. t *= mul;
  239. return t > 1.0f ? 1.0f : t;
  240. }
  241. static float mix_a_fade_in_out(void *data, float t)
  242. {
  243. struct stinger_info *s = data;
  244. return 1.0f - calc_fade(t, s->transition_a_mul);
  245. }
  246. static float mix_b_fade_in_out(void *data, float t)
  247. {
  248. struct stinger_info *s = data;
  249. return 1.0f - calc_fade(1.0f - t, s->transition_b_mul);
  250. }
  251. static float mix_a_cross_fade(void *data, float t)
  252. {
  253. UNUSED_PARAMETER(data);
  254. return 1.0f - t;
  255. }
  256. static float mix_b_cross_fade(void *data, float t)
  257. {
  258. UNUSED_PARAMETER(data);
  259. return t;
  260. }
  261. static bool stinger_audio_render(void *data, uint64_t *ts_out,
  262. struct obs_source_audio_mix *audio,
  263. uint32_t mixers, size_t channels,
  264. size_t sample_rate)
  265. {
  266. struct stinger_info *s = data;
  267. uint64_t ts = 0;
  268. if (!s) {
  269. return false;
  270. }
  271. if (!obs_source_audio_pending(s->media_source)) {
  272. ts = obs_source_get_audio_timestamp(s->media_source);
  273. if (!ts)
  274. return false;
  275. }
  276. bool success = obs_transition_audio_render(s->source, ts_out, audio,
  277. mixers, channels,
  278. sample_rate, s->mix_a,
  279. s->mix_b);
  280. if (!ts)
  281. return success;
  282. if (!*ts_out || ts < *ts_out)
  283. *ts_out = ts;
  284. struct obs_source_audio_mix child_audio;
  285. obs_source_get_audio_mix(s->media_source, &child_audio);
  286. for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
  287. if ((mixers & (1 << mix)) == 0)
  288. continue;
  289. for (size_t ch = 0; ch < channels; ch++) {
  290. register float *out = audio->output[mix].data[ch];
  291. register float *in = child_audio.output[mix].data[ch];
  292. register float *end = in + AUDIO_OUTPUT_FRAMES;
  293. while (in < end)
  294. *(out++) += *(in++);
  295. }
  296. }
  297. return true;
  298. }
  299. static void stinger_transition_start(void *data)
  300. {
  301. struct stinger_info *s = data;
  302. if (s->media_source) {
  303. calldata_t cd = {0};
  304. proc_handler_t *ph =
  305. obs_source_get_proc_handler(s->media_source);
  306. proc_handler_t *matte_ph =
  307. obs_source_get_proc_handler(s->matte_source);
  308. if (s->transitioning) {
  309. proc_handler_call(ph, "restart", &cd);
  310. if (matte_ph) {
  311. proc_handler_call(matte_ph, "restart", &cd);
  312. }
  313. return;
  314. }
  315. proc_handler_call(ph, "get_duration", &cd);
  316. proc_handler_call(ph, "get_nb_frames", &cd);
  317. s->duration_ns =
  318. (uint64_t)calldata_int(&cd, "duration") + 250000000ULL;
  319. s->duration_frames = (uint64_t)calldata_int(&cd, "num_frames");
  320. if (s->transition_point_is_frame)
  321. s->transition_point =
  322. (float)((long double)s->transition_point_frame /
  323. (long double)s->duration_frames);
  324. else
  325. s->transition_point =
  326. (float)((long double)s->transition_point_ns /
  327. (long double)s->duration_ns);
  328. if (s->transition_point > 0.999f)
  329. s->transition_point = 0.999f;
  330. else if (s->transition_point < 0.001f)
  331. s->transition_point = 0.001f;
  332. s->transition_a_mul = (1.0f / s->transition_point);
  333. s->transition_b_mul = (1.0f / (1.0f - s->transition_point));
  334. if (s->track_matte_enabled) {
  335. proc_handler_call(matte_ph, "get_duration", &cd);
  336. uint64_t tm_duration_ns =
  337. (uint64_t)calldata_int(&cd, "duration");
  338. s->duration_ns = ((tm_duration_ns > s->duration_ns)
  339. ? (tm_duration_ns)
  340. : (s->duration_ns));
  341. obs_source_add_active_child(s->source, s->matte_source);
  342. }
  343. obs_transition_enable_fixed(
  344. s->source, true, (uint32_t)(s->duration_ns / 1000000));
  345. calldata_free(&cd);
  346. obs_source_add_active_child(s->source, s->media_source);
  347. }
  348. s->transitioning = true;
  349. }
  350. static void stinger_transition_stop(void *data)
  351. {
  352. struct stinger_info *s = data;
  353. if (s->media_source)
  354. obs_source_remove_active_child(s->source, s->media_source);
  355. if (s->matte_source)
  356. obs_source_remove_active_child(s->source, s->matte_source);
  357. s->transitioning = false;
  358. }
  359. static void stinger_enum_active_sources(void *data,
  360. obs_source_enum_proc_t enum_callback,
  361. void *param)
  362. {
  363. struct stinger_info *s = data;
  364. if (s->media_source && s->transitioning)
  365. enum_callback(s->source, s->media_source, param);
  366. if (s->matte_source && s->transitioning)
  367. enum_callback(s->source, s->matte_source, param);
  368. }
  369. static void stinger_enum_all_sources(void *data,
  370. obs_source_enum_proc_t enum_callback,
  371. void *param)
  372. {
  373. struct stinger_info *s = data;
  374. if (s->media_source)
  375. enum_callback(s->source, s->media_source, param);
  376. if (s->matte_source)
  377. enum_callback(s->source, s->matte_source, param);
  378. }
  379. #define FILE_FILTER \
  380. "Video Files (*.mp4 *.ts *.mov *.wmv *.flv *.mkv *.avi *.gif *.webm);;"
  381. static bool transition_point_type_modified(obs_properties_t *ppts,
  382. obs_property_t *p, obs_data_t *s)
  383. {
  384. int64_t type = obs_data_get_int(s, "tp_type");
  385. obs_property_t *prop_transition_point =
  386. obs_properties_get(ppts, "transition_point");
  387. if (type == TIMING_TIME) {
  388. obs_property_set_description(
  389. prop_transition_point,
  390. obs_module_text("TransitionPoint"));
  391. } else {
  392. obs_property_set_description(
  393. prop_transition_point,
  394. obs_module_text("TransitionPointFrame"));
  395. }
  396. bool uses_ms_prefix = (type == TIMING_TIME);
  397. obs_property_int_set_suffix(p, (uses_ms_prefix ? " ms" : ""));
  398. return true;
  399. }
  400. static bool track_matte_layout_modified(obs_properties_t *ppts,
  401. obs_property_t *p, obs_data_t *s)
  402. {
  403. int matte_layout = obs_data_get_int(s, "track_matte_layout");
  404. obs_property_t *prop_matte_path =
  405. obs_properties_get(ppts, "track_matte_path");
  406. bool uses_separate_file = (matte_layout == MATTE_LAYOUT_SEPARATE_FILE);
  407. obs_property_set_visible(prop_matte_path, uses_separate_file);
  408. UNUSED_PARAMETER(p);
  409. return true;
  410. }
  411. static bool track_matte_enabled_modified(obs_properties_t *ppts,
  412. obs_property_t *p, obs_data_t *s)
  413. {
  414. bool track_matte_enabled = obs_data_get_bool(s, "track_matte_enabled");
  415. obs_property_t *prop_tp_type = obs_properties_get(ppts, "tp_type");
  416. if (track_matte_enabled) {
  417. obs_property_set_description(
  418. prop_tp_type,
  419. obs_module_text("AudioTransitionPointType"));
  420. } else {
  421. obs_property_set_description(
  422. prop_tp_type, obs_module_text("TransitionPointType"));
  423. }
  424. UNUSED_PARAMETER(p);
  425. return true;
  426. }
  427. static obs_properties_t *stinger_properties(void *data)
  428. {
  429. obs_properties_t *ppts = obs_properties_create();
  430. obs_properties_set_flags(ppts, OBS_PROPERTIES_DEFER_UPDATE);
  431. // main stinger settings
  432. obs_properties_add_path(ppts, "path", obs_module_text("VideoFile"),
  433. OBS_PATH_FILE, FILE_FILTER, NULL);
  434. obs_property_t *p = obs_properties_add_list(
  435. ppts, "tp_type", obs_module_text("TransitionPointType"),
  436. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  437. #ifndef __APPLE__
  438. obs_properties_add_bool(ppts, "hw_decode",
  439. obs_module_text("HardwareDecode"));
  440. #endif
  441. obs_property_list_add_int(p, obs_module_text("TransitionPointTypeTime"),
  442. TIMING_TIME);
  443. obs_property_list_add_int(
  444. p, obs_module_text("TransitionPointTypeFrame"), TIMING_FRAME);
  445. obs_property_set_modified_callback(p, transition_point_type_modified);
  446. obs_properties_add_int(ppts, "transition_point",
  447. obs_module_text("TransitionPoint"), 0, 120000,
  448. 1);
  449. // track matte properties
  450. {
  451. obs_properties_t *track_matte_group = obs_properties_create();
  452. p = obs_properties_add_list(track_matte_group,
  453. "track_matte_layout",
  454. obs_module_text("TrackMatteLayout"),
  455. OBS_COMBO_TYPE_LIST,
  456. OBS_COMBO_FORMAT_INT);
  457. obs_property_list_add_int(
  458. p, obs_module_text("TrackMatteLayoutHorizontal"),
  459. MATTE_LAYOUT_HORIZONTAL);
  460. obs_property_list_add_int(
  461. p, obs_module_text("TrackMatteLayoutVertical"),
  462. MATTE_LAYOUT_VERTICAL);
  463. obs_property_list_add_int(
  464. p, obs_module_text("TrackMatteLayoutSeparateFile"),
  465. MATTE_LAYOUT_SEPARATE_FILE);
  466. obs_property_set_modified_callback(p,
  467. track_matte_layout_modified);
  468. obs_properties_add_path(track_matte_group, "track_matte_path",
  469. obs_module_text("TrackMatteVideoFile"),
  470. OBS_PATH_FILE, FILE_FILTER, NULL);
  471. obs_properties_add_bool(track_matte_group, "invert_matte",
  472. obs_module_text("InvertTrackMatte"));
  473. p = obs_properties_add_group(
  474. ppts, "track_matte_enabled",
  475. obs_module_text("TrackMatteEnabled"),
  476. OBS_GROUP_CHECKABLE, track_matte_group);
  477. obs_property_set_modified_callback(
  478. p, track_matte_enabled_modified);
  479. }
  480. // audio output settings
  481. obs_property_t *monitor_list = obs_properties_add_list(
  482. ppts, "audio_monitoring", obs_module_text("AudioMonitoring"),
  483. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  484. obs_property_list_add_int(monitor_list,
  485. obs_module_text("AudioMonitoring.None"),
  486. OBS_MONITORING_TYPE_NONE);
  487. obs_property_list_add_int(
  488. monitor_list, obs_module_text("AudioMonitoring.MonitorOnly"),
  489. OBS_MONITORING_TYPE_MONITOR_ONLY);
  490. obs_property_list_add_int(monitor_list,
  491. obs_module_text("AudioMonitoring.Both"),
  492. OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT);
  493. // audio fade settings
  494. obs_property_t *audio_fade_style = obs_properties_add_list(
  495. ppts, "audio_fade_style", obs_module_text("AudioFadeStyle"),
  496. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  497. obs_property_list_add_int(
  498. audio_fade_style,
  499. obs_module_text("AudioFadeStyle.FadeOutFadeIn"),
  500. FADE_STYLE_FADE_OUT_FADE_IN);
  501. obs_property_list_add_int(audio_fade_style,
  502. obs_module_text("AudioFadeStyle.CrossFade"),
  503. FADE_STYLE_CROSS_FADE);
  504. UNUSED_PARAMETER(data);
  505. return ppts;
  506. }
  507. struct obs_source_info stinger_transition = {
  508. .id = "obs_stinger_transition",
  509. .type = OBS_SOURCE_TYPE_TRANSITION,
  510. .get_name = stinger_get_name,
  511. .create = stinger_create,
  512. .destroy = stinger_destroy,
  513. .update = stinger_update,
  514. .get_defaults = stinger_defaults,
  515. .video_render = stinger_video_render,
  516. .audio_render = stinger_audio_render,
  517. .get_properties = stinger_properties,
  518. .enum_active_sources = stinger_enum_active_sources,
  519. .enum_all_sources = stinger_enum_all_sources,
  520. .transition_start = stinger_transition_start,
  521. .transition_stop = stinger_transition_stop,
  522. };