From 1dab263403b0c981b44dd922146d9c5b56ef18e2 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Wed, 19 Jul 2017 09:32:47 -0700 Subject: [PATCH] obs-transitions: Add stinger transition Allows using a video file as a means of transitioning, transitioning two targets at a specific time during the video playback. Audio for target A fades out to the transition point, and then audio for target B fades in after the transition point. --- plugins/obs-transitions/CMakeLists.txt | 1 + plugins/obs-transitions/data/locale/en-US.ini | 3 + plugins/obs-transitions/obs-transitions.c | 2 + plugins/obs-transitions/transition-stinger.c | 254 ++++++++++++++++++ 4 files changed, 260 insertions(+) create mode 100644 plugins/obs-transitions/transition-stinger.c diff --git a/plugins/obs-transitions/CMakeLists.txt b/plugins/obs-transitions/CMakeLists.txt index 3b8838d59..16e471bf2 100644 --- a/plugins/obs-transitions/CMakeLists.txt +++ b/plugins/obs-transitions/CMakeLists.txt @@ -8,6 +8,7 @@ set(obs-transitions_SOURCES transition-cut.c transition-fade-to-color.c transition-luma-wipe.c + transition-stinger.c ) add_library(obs-transitions MODULE diff --git a/plugins/obs-transitions/data/locale/en-US.ini b/plugins/obs-transitions/data/locale/en-US.ini index 3bf16840f..ddc04ef78 100644 --- a/plugins/obs-transitions/data/locale/en-US.ini +++ b/plugins/obs-transitions/data/locale/en-US.ini @@ -2,6 +2,7 @@ FadeTransition="Fade" CutTransition="Cut" SwipeTransition="Swipe" SlideTransition="Slide" +StingerTransition="Stinger" FadeToColorTransition="Fade to Color" Direction="Direction" Direction.Left="Left" @@ -10,6 +11,8 @@ Direction.Up="Up" Direction.Down="Down" SwipeIn="Swipe In" Color="Color" +VideoFile="Video File" +TransitionPoint="Transition Point (milliseconds)" SwitchPoint="Peak Color Point (percentage)" LumaWipeTransition="Luma Wipe" LumaWipe.Image="Image" diff --git a/plugins/obs-transitions/obs-transitions.c b/plugins/obs-transitions/obs-transitions.c index 3841f4814..22276d319 100644 --- a/plugins/obs-transitions/obs-transitions.c +++ b/plugins/obs-transitions/obs-transitions.c @@ -8,6 +8,7 @@ extern struct obs_source_info cut_transition; extern struct obs_source_info fade_transition; extern struct obs_source_info swipe_transition; extern struct obs_source_info slide_transition; +extern struct obs_source_info stinger_transition; extern struct obs_source_info fade_to_color_transition; extern struct obs_source_info luma_wipe_transition; @@ -17,6 +18,7 @@ bool obs_module_load(void) obs_register_source(&fade_transition); obs_register_source(&swipe_transition); obs_register_source(&slide_transition); + obs_register_source(&stinger_transition); obs_register_source(&fade_to_color_transition); obs_register_source(&luma_wipe_transition); return true; diff --git a/plugins/obs-transitions/transition-stinger.c b/plugins/obs-transitions/transition-stinger.c new file mode 100644 index 000000000..6442496b8 --- /dev/null +++ b/plugins/obs-transitions/transition-stinger.c @@ -0,0 +1,254 @@ +#include + +struct stinger_info { + obs_source_t *source; + + obs_source_t *media_source; + + uint64_t duration_ns; + uint64_t transition_point_ns; + float transition_point; + float transition_a_mul; + float transition_b_mul; + bool transitioning; +}; + +static const char *stinger_get_name(void *type_data) +{ + UNUSED_PARAMETER(type_data); + return obs_module_text("StingerTransition"); +} + +static void stinger_update(void *data, obs_data_t *settings) +{ + struct stinger_info *s = data; + const char *path = obs_data_get_string(settings, "path"); + + obs_data_t *media_settings = obs_data_create(); + obs_data_set_string(media_settings, "local_file", path); + + obs_source_release(s->media_source); + s->media_source = obs_source_create_private("ffmpeg_source", NULL, + media_settings); + obs_data_release(media_settings); + + int64_t point_ms = obs_data_get_int(settings, "transition_point"); + + s->transition_point_ns = (uint64_t)(point_ms * 1000000LL); +} + +static void *stinger_create(obs_data_t *settings, obs_source_t *source) +{ + struct stinger_info *s = bzalloc(sizeof(*s)); + + s->source = source; + + obs_transition_enable_fixed(s->source, true, 0); + obs_source_update(source, settings); + return s; +} + +static void stinger_destroy(void *data) +{ + struct stinger_info *s = data; + obs_source_release(s->media_source); + bfree(s); +} + +static void stinger_video_render(void *data, gs_effect_t *effect) +{ + struct stinger_info *s = data; + + float t = obs_transition_get_time(s->source); + bool use_a = t < s->transition_point; + + enum obs_transition_target target = use_a + ? OBS_TRANSITION_SOURCE_A + : OBS_TRANSITION_SOURCE_B; + + if (!obs_transition_video_render_direct(s->source, target)) + return; + + /* --------------------- */ + + float source_cx = (float)obs_source_get_width(s->source); + float source_cy = (float)obs_source_get_height(s->source); + uint32_t media_cx = obs_source_get_width(s->media_source); + uint32_t media_cy = obs_source_get_height(s->media_source); + + if (!media_cx || !media_cy) + return; + + float scale_x = source_cx / (float)media_cx; + float scale_y = source_cy / (float)media_cy; + + gs_matrix_push(); + gs_matrix_scale3f(scale_x, scale_y, 1.0f); + obs_source_video_render(s->media_source); + gs_matrix_pop(); + + UNUSED_PARAMETER(effect); +} + +static inline float calc_fade(float t, float mul) +{ + t *= mul; + return t > 1.0f ? 1.0f : t; +} + +static float mix_a(void *data, float t) +{ + struct stinger_info *s = data; + return 1.0f - calc_fade(t, s->transition_a_mul); +} + +static float mix_b(void *data, float t) +{ + struct stinger_info *s = data; + return 1.0f - calc_fade(1.0f - t, s->transition_b_mul); +} + +static bool stinger_audio_render(void *data, uint64_t *ts_out, + struct obs_source_audio_mix *audio, uint32_t mixers, + size_t channels, size_t sample_rate) +{ + struct stinger_info *s = data; + uint64_t ts = 0; + + if (!obs_source_audio_pending(s->media_source)) { + ts = obs_source_get_audio_timestamp(s->media_source); + if (!ts) + return false; + } + + bool success = obs_transition_audio_render(s->source, ts_out, + audio, mixers, channels, sample_rate, mix_a, mix_b); + if (!ts) + return success; + + if (!*ts_out || ts < *ts_out) + *ts_out = ts; + + struct obs_source_audio_mix child_audio; + obs_source_get_audio_mix(s->media_source, &child_audio); + + for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) { + if ((mixers & (1 << mix)) == 0) + continue; + + for (size_t ch = 0; ch < channels; ch++) { + register float *out = audio->output[mix].data[ch]; + register float *in = child_audio.output[mix].data[ch]; + register float *end = in + AUDIO_OUTPUT_FRAMES; + + while (in < end) + *(out++) += *(in++); + } + } + + return true; +} + +static void stinger_transition_start(void *data) +{ + struct stinger_info *s = data; + + if (s->media_source) { + calldata_t cd = {0}; + + proc_handler_t *ph = + obs_source_get_proc_handler(s->media_source); + + if (s->transitioning) { + proc_handler_call(ph, "restart", &cd); + return; + } + + proc_handler_call(ph, "get_duration", &cd); + + s->duration_ns = (uint64_t)calldata_int(&cd, "duration"); + + s->transition_point = (float)( + (long double)s->transition_point_ns / + (long double)s->duration_ns); + if (s->transition_point > 1.0f) + s->transition_point = 1.0f; + else if (s->transition_point < 0.001f) + s->transition_point = 0.001f; + + s->transition_a_mul = (1.0f / s->transition_point); + s->transition_b_mul = (1.0f / (1.0f - s->transition_point)); + + obs_transition_enable_fixed(s->source, true, + (uint32_t)(s->duration_ns / 1000000)); + + calldata_free(&cd); + + obs_source_add_active_child(s->source, s->media_source); + } + + s->transitioning = true; +} + +static void stinger_transition_stop(void *data) +{ + struct stinger_info *s = data; + + if (s->media_source) + obs_source_remove_active_child(s->source, s->media_source); + + s->transitioning = false; +} + +static void stinger_enum_active_sources(void *data, + obs_source_enum_proc_t enum_callback, void *param) +{ + struct stinger_info *s = data; + if (s->media_source && s->transitioning) + enum_callback(s->source, s->media_source, param); +} + +static void stinger_enum_all_sources(void *data, + obs_source_enum_proc_t enum_callback, void *param) +{ + struct stinger_info *s = data; + if (s->media_source) + enum_callback(s->source, s->media_source, param); +} + +#define FILE_FILTER \ + "Video Files (*.mp4 *.ts *.mov *.wmv *.flv *.mkv *.avi *.gif *.webm);;" + +static obs_properties_t *stinger_properties(void *data) +{ + obs_properties_t *ppts = obs_properties_create(); + + obs_properties_set_flags(ppts, OBS_PROPERTIES_DEFER_UPDATE); + + obs_properties_add_path(ppts, "path", + obs_module_text("VideoFile"), + OBS_PATH_FILE, + FILE_FILTER, NULL); + obs_properties_add_int(ppts, "transition_point", + obs_module_text("TransitionPoint"), + 0, 120000, 1); + + UNUSED_PARAMETER(data); + return ppts; +} + +struct obs_source_info stinger_transition = { + .id = "stinger_transition", + .type = OBS_SOURCE_TYPE_TRANSITION, + .get_name = stinger_get_name, + .create = stinger_create, + .destroy = stinger_destroy, + .update = stinger_update, + .video_render = stinger_video_render, + .audio_render = stinger_audio_render, + .get_properties = stinger_properties, + .enum_active_sources = stinger_enum_active_sources, + .enum_all_sources = stinger_enum_all_sources, + .transition_start = stinger_transition_start, + .transition_stop = stinger_transition_stop +};