transition-stinger.c 7.6 KB

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