transition-stinger.c 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. #include <obs-module.h>
  2. #define TIMING_TIME 0
  3. #define TIMING_FRAME 1
  4. enum fade_style {
  5. FADE_STYLE_FADE_OUT_FADE_IN,
  6. FADE_STYLE_CROSS_FADE
  7. };
  8. struct stinger_info {
  9. obs_source_t *source;
  10. obs_source_t *media_source;
  11. uint64_t duration_ns;
  12. uint64_t duration_frames;
  13. uint64_t transition_point_ns;
  14. uint64_t transition_point_frame;
  15. float transition_point;
  16. float transition_a_mul;
  17. float transition_b_mul;
  18. bool transitioning;
  19. bool transition_point_is_frame;
  20. int monitoring_type;
  21. enum fade_style fade_style;
  22. float (*mix_a)(void *data, float t);
  23. float (*mix_b)(void *data, float t);
  24. };
  25. static const char *stinger_get_name(void *type_data)
  26. {
  27. UNUSED_PARAMETER(type_data);
  28. return obs_module_text("StingerTransition");
  29. }
  30. static float mix_a_fade_in_out(void *data, float t);
  31. static float mix_b_fade_in_out(void *data, float t);
  32. static float mix_a_cross_fade(void *data, float t);
  33. static float mix_b_cross_fade(void *data, float t);
  34. static void stinger_update(void *data, obs_data_t *settings)
  35. {
  36. struct stinger_info *s = data;
  37. const char *path = obs_data_get_string(settings, "path");
  38. obs_data_t *media_settings = obs_data_create();
  39. obs_data_set_string(media_settings, "local_file", path);
  40. obs_source_release(s->media_source);
  41. s->media_source = obs_source_create_private("ffmpeg_source", NULL,
  42. media_settings);
  43. obs_data_release(media_settings);
  44. int64_t point = obs_data_get_int(settings, "transition_point");
  45. s->transition_point_is_frame =
  46. obs_data_get_int(settings, "tp_type") == TIMING_FRAME;
  47. if (s->transition_point_is_frame)
  48. s->transition_point_frame = (uint64_t)point;
  49. else
  50. s->transition_point_ns = (uint64_t)(point * 1000000LL);
  51. s->monitoring_type = (int)obs_data_get_int(settings,"audio_monitoring");
  52. obs_source_set_monitoring_type(s->media_source, s->monitoring_type);
  53. s->fade_style = (enum fade_style)obs_data_get_int(settings,
  54. "audio_fade_style");
  55. switch (s->fade_style) {
  56. default:
  57. case FADE_STYLE_FADE_OUT_FADE_IN:
  58. s->mix_a = mix_a_fade_in_out;
  59. s->mix_b = mix_b_fade_in_out;
  60. break;
  61. case FADE_STYLE_CROSS_FADE:
  62. s->mix_a = mix_a_cross_fade;
  63. s->mix_b = mix_b_cross_fade;
  64. break;
  65. }
  66. }
  67. static void *stinger_create(obs_data_t *settings, obs_source_t *source)
  68. {
  69. struct stinger_info *s = bzalloc(sizeof(*s));
  70. s->source = source;
  71. s->mix_a = mix_a_fade_in_out;
  72. s->mix_b = mix_b_fade_in_out;
  73. obs_transition_enable_fixed(s->source, true, 0);
  74. obs_source_update(source, settings);
  75. return s;
  76. }
  77. static void stinger_destroy(void *data)
  78. {
  79. struct stinger_info *s = data;
  80. obs_source_release(s->media_source);
  81. bfree(s);
  82. }
  83. static void stinger_video_render(void *data, gs_effect_t *effect)
  84. {
  85. struct stinger_info *s = data;
  86. float t = obs_transition_get_time(s->source);
  87. bool use_a = t < s->transition_point;
  88. enum obs_transition_target target = use_a
  89. ? OBS_TRANSITION_SOURCE_A
  90. : OBS_TRANSITION_SOURCE_B;
  91. if (!obs_transition_video_render_direct(s->source, target))
  92. return;
  93. /* --------------------- */
  94. float source_cx = (float)obs_source_get_width(s->source);
  95. float source_cy = (float)obs_source_get_height(s->source);
  96. uint32_t media_cx = obs_source_get_width(s->media_source);
  97. uint32_t media_cy = obs_source_get_height(s->media_source);
  98. if (!media_cx || !media_cy)
  99. return;
  100. float scale_x = source_cx / (float)media_cx;
  101. float scale_y = source_cy / (float)media_cy;
  102. gs_matrix_push();
  103. gs_matrix_scale3f(scale_x, scale_y, 1.0f);
  104. obs_source_video_render(s->media_source);
  105. gs_matrix_pop();
  106. UNUSED_PARAMETER(effect);
  107. }
  108. static inline float calc_fade(float t, float mul)
  109. {
  110. t *= mul;
  111. return t > 1.0f ? 1.0f : t;
  112. }
  113. static float mix_a_fade_in_out(void *data, float t)
  114. {
  115. struct stinger_info *s = data;
  116. return 1.0f - calc_fade(t, s->transition_a_mul);
  117. }
  118. static float mix_b_fade_in_out(void *data, float t)
  119. {
  120. struct stinger_info *s = data;
  121. return 1.0f - calc_fade(1.0f - t, s->transition_b_mul);
  122. }
  123. static float mix_a_cross_fade(void *data, float t)
  124. {
  125. UNUSED_PARAMETER(data);
  126. return 1.0f - t;
  127. }
  128. static float mix_b_cross_fade(void *data, float t)
  129. {
  130. UNUSED_PARAMETER(data);
  131. return t;
  132. }
  133. static bool stinger_audio_render(void *data, uint64_t *ts_out,
  134. struct obs_source_audio_mix *audio, uint32_t mixers,
  135. size_t channels, size_t sample_rate)
  136. {
  137. struct stinger_info *s = data;
  138. uint64_t ts = 0;
  139. if (!obs_source_audio_pending(s->media_source)) {
  140. ts = obs_source_get_audio_timestamp(s->media_source);
  141. if (!ts)
  142. return false;
  143. }
  144. bool success = obs_transition_audio_render(s->source, ts_out,
  145. audio, mixers, channels, sample_rate, s->mix_a, s->mix_b);
  146. if (!ts)
  147. return success;
  148. if (!*ts_out || ts < *ts_out)
  149. *ts_out = ts;
  150. struct obs_source_audio_mix child_audio;
  151. obs_source_get_audio_mix(s->media_source, &child_audio);
  152. for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
  153. if ((mixers & (1 << mix)) == 0)
  154. continue;
  155. for (size_t ch = 0; ch < channels; ch++) {
  156. register float *out = audio->output[mix].data[ch];
  157. register float *in = child_audio.output[mix].data[ch];
  158. register float *end = in + AUDIO_OUTPUT_FRAMES;
  159. while (in < end)
  160. *(out++) += *(in++);
  161. }
  162. }
  163. return true;
  164. }
  165. static void stinger_transition_start(void *data)
  166. {
  167. struct stinger_info *s = data;
  168. if (s->media_source) {
  169. calldata_t cd = {0};
  170. proc_handler_t *ph =
  171. obs_source_get_proc_handler(s->media_source);
  172. if (s->transitioning) {
  173. proc_handler_call(ph, "restart", &cd);
  174. return;
  175. }
  176. proc_handler_call(ph, "get_duration", &cd);
  177. proc_handler_call(ph, "get_nb_frames", &cd);
  178. s->duration_ns = (uint64_t)calldata_int(&cd, "duration");
  179. s->duration_frames = (uint64_t)calldata_int(&cd, "num_frames");
  180. if (s->transition_point_is_frame)
  181. s->transition_point = (float)(
  182. (long double)s->transition_point_frame /
  183. (long double)s->duration_frames);
  184. else
  185. s->transition_point = (float)(
  186. (long double)s->transition_point_ns /
  187. (long double)s->duration_ns);
  188. if (s->transition_point > 0.999f)
  189. s->transition_point = 0.999f;
  190. else if (s->transition_point < 0.001f)
  191. s->transition_point = 0.001f;
  192. s->transition_a_mul = (1.0f / s->transition_point);
  193. s->transition_b_mul = (1.0f / (1.0f - s->transition_point));
  194. obs_transition_enable_fixed(s->source, true,
  195. (uint32_t)(s->duration_ns / 1000000));
  196. calldata_free(&cd);
  197. obs_source_add_active_child(s->source, s->media_source);
  198. }
  199. s->transitioning = true;
  200. }
  201. static void stinger_transition_stop(void *data)
  202. {
  203. struct stinger_info *s = data;
  204. if (s->media_source)
  205. obs_source_remove_active_child(s->source, s->media_source);
  206. s->transitioning = false;
  207. }
  208. static void stinger_enum_active_sources(void *data,
  209. obs_source_enum_proc_t enum_callback, void *param)
  210. {
  211. struct stinger_info *s = data;
  212. if (s->media_source && s->transitioning)
  213. enum_callback(s->source, s->media_source, param);
  214. }
  215. static void stinger_enum_all_sources(void *data,
  216. obs_source_enum_proc_t enum_callback, void *param)
  217. {
  218. struct stinger_info *s = data;
  219. if (s->media_source)
  220. enum_callback(s->source, s->media_source, param);
  221. }
  222. #define FILE_FILTER \
  223. "Video Files (*.mp4 *.ts *.mov *.wmv *.flv *.mkv *.avi *.gif *.webm);;"
  224. static bool transition_point_type_modified(obs_properties_t *ppts,
  225. obs_property_t *p, obs_data_t *s)
  226. {
  227. int64_t type = obs_data_get_int(s, "tp_type");
  228. p = obs_properties_get(ppts, "transition_point");
  229. if (type == TIMING_TIME)
  230. obs_property_set_description(p,
  231. obs_module_text("TransitionPoint"));
  232. else
  233. obs_property_set_description(p,
  234. obs_module_text("TransitionPointFrame"));
  235. return true;
  236. }
  237. static obs_properties_t *stinger_properties(void *data)
  238. {
  239. obs_properties_t *ppts = obs_properties_create();
  240. obs_properties_set_flags(ppts, OBS_PROPERTIES_DEFER_UPDATE);
  241. obs_properties_add_path(ppts, "path",
  242. obs_module_text("VideoFile"),
  243. OBS_PATH_FILE,
  244. FILE_FILTER, NULL);
  245. obs_property_t *list = obs_properties_add_list(ppts, "tp_type",
  246. obs_module_text("TransitionPointType"),
  247. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  248. obs_property_list_add_int(list,
  249. obs_module_text("TransitionPointTypeTime"),
  250. TIMING_TIME);
  251. obs_property_list_add_int(list,
  252. obs_module_text("TransitionPointTypeFrame"),
  253. TIMING_FRAME);
  254. obs_property_set_modified_callback(list, transition_point_type_modified);
  255. obs_properties_add_int(ppts, "transition_point",
  256. obs_module_text("TransitionPoint"),
  257. 0, 120000, 1);
  258. obs_property_t *monitor_list = obs_properties_add_list(ppts,
  259. "audio_monitoring", obs_module_text("AudioMonitoring"),
  260. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  261. obs_property_list_add_int(monitor_list,
  262. obs_module_text("AudioMonitoring.None"),
  263. OBS_MONITORING_TYPE_NONE);
  264. obs_property_list_add_int(monitor_list,
  265. obs_module_text("AudioMonitoring.MonitorOnly"),
  266. OBS_MONITORING_TYPE_MONITOR_ONLY);
  267. obs_property_list_add_int(monitor_list,
  268. obs_module_text("AudioMonitoring.Both"),
  269. OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT);
  270. obs_property_t *audio_fade_style = obs_properties_add_list(ppts,
  271. "audio_fade_style", obs_module_text("AudioFadeStyle"),
  272. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  273. obs_property_list_add_int(audio_fade_style,
  274. obs_module_text("AudioFadeStyle.FadeOutFadeIn"),
  275. FADE_STYLE_FADE_OUT_FADE_IN);
  276. obs_property_list_add_int(audio_fade_style,
  277. obs_module_text("AudioFadeStyle.CrossFade"),
  278. FADE_STYLE_CROSS_FADE);
  279. UNUSED_PARAMETER(data);
  280. return ppts;
  281. }
  282. struct obs_source_info stinger_transition = {
  283. .id = "obs_stinger_transition",
  284. .type = OBS_SOURCE_TYPE_TRANSITION,
  285. .get_name = stinger_get_name,
  286. .create = stinger_create,
  287. .destroy = stinger_destroy,
  288. .update = stinger_update,
  289. .video_render = stinger_video_render,
  290. .audio_render = stinger_audio_render,
  291. .get_properties = stinger_properties,
  292. .enum_active_sources = stinger_enum_active_sources,
  293. .enum_all_sources = stinger_enum_all_sources,
  294. .transition_start = stinger_transition_start,
  295. .transition_stop = stinger_transition_stop
  296. };