提交 506434c5 编写于 作者: S Stéphane Lepin 提交者: jp9000

obs-transitions: add track matte feature to the stinger transition

This adds the ability to use a secondary black-and-white video as a mask
between source A and B of the transition. The greyscale value of each
pixel is used as the "slider" value in a linear interpolation between the
corresponding pixels in source A and source B.
The track matte can either be in the same file as the stinger itself
(next to the stinger or under the stinger, doubling the width or height
of the stinger depending of the selected layout) or a in a separate
dedicated file.
The same file/separate file behavior is controlled by the
"Matte Layout" option in the stinger settings.
上级 6cf22c54
......@@ -15,8 +15,16 @@ VideoFile="Video File"
TransitionPoint="Transition Point"
TransitionPointFrame="Transition Point (frame)"
TransitionPointType="Transition Point Type"
AudioTransitionPointType="Audio Transition Point Type"
TransitionPointTypeFrame="Frame"
TransitionPointTypeTime="Time (milliseconds)"
TrackMatteEnabled="Use a Track Matte"
InvertTrackMatte="Invert Matte Colors"
TrackMatteVideoFile="Track Matte Video File"
TrackMatteLayout="Matte Layout"
TrackMatteLayoutHorizontal="Same file, side-by-side (stinger on the left, track matte on the right)"
TrackMatteLayoutVertical="Same file, stacked (stinger on top, track matte at the bottom)"
TrackMatteLayoutSeparateFile="Separate file (warning: matte can get out of sync)"
AudioFadeStyle="Audio Fade Style"
AudioFadeStyle.FadeOutFadeIn="Fade out to transition point then fade in"
AudioFadeStyle.CrossFade="Crossfade"
......
uniform float4x4 ViewProj;
uniform texture2d a_tex;
uniform texture2d b_tex;
uniform texture2d matte_tex;
uniform bool invert_matte;
sampler_state textureSampler {
Filter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
struct VertData {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
VertData VSDefault(VertData v_in)
{
VertData vert_out;
vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj);
vert_out.uv = v_in.uv;
return vert_out;
}
float4 PSStingerMatte(VertData v_in) : TARGET
{
float2 uv = v_in.uv;
float4 a_color = a_tex.Sample(textureSampler, uv);
float4 b_color = b_tex.Sample(textureSampler, uv);
float4 matte_color = matte_tex.Sample(textureSampler, uv);
// RGB -> Luma conversion using Rec. 709 factors
float matte_luma = (
(matte_color.x * 0.2126) +
(matte_color.y * 0.7152) +
(matte_color.z * 0.0722)
);
// if matte invert is enabled, invert the matte color
matte_luma = (invert_matte ? (1.0 - matte_luma) : matte_luma);
return lerp(a_color, b_color, matte_luma);
}
technique StingerMatte
{
pass
{
vertex_shader = VSDefault(v_in);
pixel_shader = PSStingerMatte(v_in);
}
}
......@@ -4,12 +4,17 @@
#define TIMING_TIME 0
#define TIMING_FRAME 1
#define MATTE_LAYOUT_HORIZONTAL 0
#define MATTE_LAYOUT_VERTICAL 1
#define MATTE_LAYOUT_SEPARATE_FILE 2
enum fade_style { FADE_STYLE_FADE_OUT_FADE_IN, FADE_STYLE_CROSS_FADE };
struct stinger_info {
obs_source_t *source;
obs_source_t *media_source;
obs_source_t *matte_source;
uint64_t duration_ns;
uint64_t duration_frames;
......@@ -23,6 +28,20 @@ struct stinger_info {
int monitoring_type;
enum fade_style fade_style;
bool track_matte_enabled;
int matte_layout;
float matte_width_factor;
float matte_height_factor;
bool invert_matte;
gs_effect_t *matte_effect;
gs_eparam_t *ep_a_tex;
gs_eparam_t *ep_b_tex;
gs_eparam_t *ep_matte_tex;
gs_eparam_t *ep_invert_matte;
gs_texrender_t *matte_tex;
float (*mix_a)(void *data, float t);
float (*mix_b)(void *data, float t);
};
......@@ -67,6 +86,36 @@ static void stinger_update(void *data, obs_data_t *settings)
else
s->transition_point_ns = (uint64_t)(point * 1000000LL);
s->track_matte_enabled =
obs_data_get_bool(settings, "track_matte_enabled");
s->matte_layout = obs_data_get_int(settings, "track_matte_layout");
s->matte_width_factor =
(s->matte_layout == MATTE_LAYOUT_HORIZONTAL ? 2.0f : 1.0f);
s->matte_height_factor =
(s->matte_layout == MATTE_LAYOUT_VERTICAL ? 2.0f : 1.0f);
s->invert_matte = obs_data_get_bool(settings, "invert_matte");
if (s->matte_source) {
obs_source_release(s->matte_source);
s->matte_source = NULL;
}
if (s->track_matte_enabled &&
s->matte_layout == MATTE_LAYOUT_SEPARATE_FILE) {
const char *tm_path =
obs_data_get_string(settings, "track_matte_path");
obs_data_t *tm_media_settings = obs_data_create();
obs_data_set_string(tm_media_settings, "local_file", tm_path);
s->matte_source = obs_source_create_private(
"ffmpeg_source", NULL, tm_media_settings);
obs_data_release(tm_media_settings);
// no need to output sound from the matte video
obs_source_set_muted(s->matte_source, true);
}
s->monitoring_type =
(int)obs_data_get_int(settings, "audio_monitoring");
obs_source_set_monitoring_type(s->media_source, s->monitoring_type);
......@@ -95,6 +144,33 @@ static void *stinger_create(obs_data_t *settings, obs_source_t *source)
s->mix_a = mix_a_fade_in_out;
s->mix_b = mix_b_fade_in_out;
char *effect_file = obs_module_file("stinger_matte_transition.effect");
char *error_string = NULL;
obs_enter_graphics();
s->matte_effect =
gs_effect_create_from_file(effect_file, &error_string);
obs_leave_graphics();
if (!s->matte_effect) {
blog(LOG_ERROR,
"Could not open stinger_matte_transition.effect: %s",
error_string);
bfree(error_string);
bfree(s);
return NULL;
}
bfree(effect_file);
s->ep_a_tex = gs_effect_get_param_by_name(s->matte_effect, "a_tex");
s->ep_b_tex = gs_effect_get_param_by_name(s->matte_effect, "b_tex");
s->ep_matte_tex =
gs_effect_get_param_by_name(s->matte_effect, "matte_tex");
s->ep_invert_matte =
gs_effect_get_param_by_name(s->matte_effect, "invert_matte");
s->matte_tex = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
obs_transition_enable_fixed(s->source, true, 0);
obs_source_update(source, settings);
return s;
......@@ -104,6 +180,12 @@ static void stinger_destroy(void *data)
{
struct stinger_info *s = data;
obs_source_release(s->media_source);
obs_source_release(s->matte_source);
gs_texrender_destroy(s->matte_tex);
gs_effect_destroy(s->matte_effect);
bfree(s);
}
......@@ -112,31 +194,92 @@ static void stinger_defaults(obs_data_t *settings)
obs_data_set_default_bool(settings, "hw_decode", true);
}
static void stinger_matte_render(void *data, gs_texture_t *a, gs_texture_t *b,
float t, uint32_t cx, uint32_t cy)
{
struct stinger_info *s = data;
struct vec4 background;
vec4_zero(&background);
obs_source_t *matte_source =
(s->matte_layout == MATTE_LAYOUT_SEPARATE_FILE
? s->matte_source
: s->media_source);
float matte_cx = (float)obs_source_get_width(matte_source) /
s->matte_width_factor;
float matte_cy = (float)obs_source_get_height(matte_source) /
s->matte_height_factor;
float width_offset = (s->matte_layout == MATTE_LAYOUT_HORIZONTAL
? (-matte_cx)
: 0.0f);
float height_offset =
(s->matte_layout == MATTE_LAYOUT_VERTICAL ? (-matte_cy) : 0.0f);
// Track matte media render
gs_texrender_reset(s->matte_tex);
if (matte_cx > 0 && matte_cy > 0) {
float scale_x = (float)cx / matte_cx;
float scale_y = (float)cy / matte_cy;
if (gs_texrender_begin(s->matte_tex, cx, cy)) {
gs_matrix_push();
gs_matrix_scale3f(scale_x, scale_y, 1.0f);
gs_matrix_translate3f(width_offset, height_offset,
0.0f);
gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0);
obs_source_video_render(matte_source);
gs_matrix_pop();
gs_texrender_end(s->matte_tex);
}
}
gs_effect_set_texture(s->ep_a_tex, a);
gs_effect_set_texture(s->ep_b_tex, b);
gs_effect_set_texture(s->ep_matte_tex,
gs_texrender_get_texture(s->matte_tex));
gs_effect_set_bool(s->ep_invert_matte, s->invert_matte);
while (gs_effect_loop(s->matte_effect, "StingerMatte"))
gs_draw_sprite(NULL, 0, cx, cy);
UNUSED_PARAMETER(t);
}
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;
if (s->track_matte_enabled) {
obs_transition_video_render(s->source, stinger_matte_render);
} else {
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;
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;
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;
float scale_x = source_cx / ((float)media_cx / s->matte_width_factor);
float scale_y = source_cy / ((float)media_cy / s->matte_height_factor);
gs_matrix_push();
gs_matrix_scale3f(scale_x, scale_y, 1.0f);
......@@ -184,6 +327,10 @@ static bool stinger_audio_render(void *data, uint64_t *ts_out,
struct stinger_info *s = data;
uint64_t ts = 0;
if (!s) {
return false;
}
if (!obs_source_audio_pending(s->media_source)) {
ts = obs_source_get_audio_timestamp(s->media_source);
if (!ts)
......@@ -229,9 +376,14 @@ static void stinger_transition_start(void *data)
proc_handler_t *ph =
obs_source_get_proc_handler(s->media_source);
proc_handler_t *matte_ph =
obs_source_get_proc_handler(s->matte_source);
if (s->transitioning) {
proc_handler_call(ph, "restart", &cd);
if (matte_ph) {
proc_handler_call(matte_ph, "restart", &cd);
}
return;
}
......@@ -258,6 +410,18 @@ static void stinger_transition_start(void *data)
s->transition_a_mul = (1.0f / s->transition_point);
s->transition_b_mul = (1.0f / (1.0f - s->transition_point));
if (s->track_matte_enabled) {
proc_handler_call(matte_ph, "get_duration", &cd);
uint64_t tm_duration_ns =
(uint64_t)calldata_int(&cd, "duration");
s->duration_ns = ((tm_duration_ns > s->duration_ns)
? (tm_duration_ns)
: (s->duration_ns));
obs_source_add_active_child(s->source, s->matte_source);
}
obs_transition_enable_fixed(
s->source, true, (uint32_t)(s->duration_ns / 1000000));
......@@ -276,6 +440,9 @@ static void stinger_transition_stop(void *data)
if (s->media_source)
obs_source_remove_active_child(s->source, s->media_source);
if (s->matte_source)
obs_source_remove_active_child(s->source, s->matte_source);
s->transitioning = false;
}
......@@ -286,6 +453,9 @@ static void stinger_enum_active_sources(void *data,
struct stinger_info *s = data;
if (s->media_source && s->transitioning)
enum_callback(s->source, s->media_source, param);
if (s->matte_source && s->transitioning)
enum_callback(s->source, s->matte_source, param);
}
static void stinger_enum_all_sources(void *data,
......@@ -295,6 +465,9 @@ static void stinger_enum_all_sources(void *data,
struct stinger_info *s = data;
if (s->media_source)
enum_callback(s->source, s->media_source, param);
if (s->matte_source)
enum_callback(s->source, s->matte_source, param);
}
#define FILE_FILTER \
......@@ -304,17 +477,56 @@ static bool transition_point_type_modified(obs_properties_t *ppts,
obs_property_t *p, obs_data_t *s)
{
int64_t type = obs_data_get_int(s, "tp_type");
p = obs_properties_get(ppts, "transition_point");
obs_property_t *prop_transition_point =
obs_properties_get(ppts, "transition_point");
if (type == TIMING_TIME) {
obs_property_set_description(
p, obs_module_text("TransitionPoint"));
obs_property_int_set_suffix(p, " ms");
prop_transition_point,
obs_module_text("TransitionPoint"));
} else {
obs_property_set_description(
prop_transition_point,
obs_module_text("TransitionPointFrame"));
}
bool uses_ms_prefix = (type == TIMING_TIME);
obs_property_int_set_suffix(p, (uses_ms_prefix ? " ms" : ""));
return true;
}
static bool track_matte_layout_modified(obs_properties_t *ppts,
obs_property_t *p, obs_data_t *s)
{
int matte_layout = obs_data_get_int(s, "track_matte_layout");
obs_property_t *prop_matte_path =
obs_properties_get(ppts, "track_matte_path");
bool uses_separate_file = (matte_layout == MATTE_LAYOUT_SEPARATE_FILE);
obs_property_set_visible(prop_matte_path, uses_separate_file);
UNUSED_PARAMETER(p);
return true;
}
static bool track_matte_enabled_modified(obs_properties_t *ppts,
obs_property_t *p, obs_data_t *s)
{
bool track_matte_enabled = obs_data_get_bool(s, "track_matte_enabled");
obs_property_t *prop_tp_type = obs_properties_get(ppts, "tp_type");
if (track_matte_enabled) {
obs_property_set_description(
prop_tp_type,
obs_module_text("AudioTransitionPointType"));
} else {
obs_property_set_description(
p, obs_module_text("TransitionPointFrame"));
obs_property_int_set_suffix(p, "");
prop_tp_type, obs_module_text("TransitionPointType"));
}
UNUSED_PARAMETER(p);
return true;
}
......@@ -324,8 +536,10 @@ static obs_properties_t *stinger_properties(void *data)
obs_properties_set_flags(ppts, OBS_PROPERTIES_DEFER_UPDATE);
// main stinger settings
obs_properties_add_path(ppts, "path", obs_module_text("VideoFile"),
OBS_PATH_FILE, FILE_FILTER, NULL);
obs_property_t *p = obs_properties_add_list(
ppts, "tp_type", obs_module_text("TransitionPointType"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
......@@ -344,6 +558,45 @@ static obs_properties_t *stinger_properties(void *data)
obs_module_text("TransitionPoint"), 0, 120000,
1);
// track matte properties
{
obs_properties_t *track_matte_group = obs_properties_create();
p = obs_properties_add_list(track_matte_group,
"track_matte_layout",
obs_module_text("TrackMatteLayout"),
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_list_add_int(
p, obs_module_text("TrackMatteLayoutHorizontal"),
MATTE_LAYOUT_HORIZONTAL);
obs_property_list_add_int(
p, obs_module_text("TrackMatteLayoutVertical"),
MATTE_LAYOUT_VERTICAL);
obs_property_list_add_int(
p, obs_module_text("TrackMatteLayoutSeparateFile"),
MATTE_LAYOUT_SEPARATE_FILE);
obs_property_set_modified_callback(p,
track_matte_layout_modified);
obs_properties_add_path(track_matte_group, "track_matte_path",
obs_module_text("TrackMatteVideoFile"),
OBS_PATH_FILE, FILE_FILTER, NULL);
obs_properties_add_bool(track_matte_group, "invert_matte",
obs_module_text("InvertTrackMatte"));
p = obs_properties_add_group(
ppts, "track_matte_enabled",
obs_module_text("TrackMatteEnabled"),
OBS_GROUP_CHECKABLE, track_matte_group);
obs_property_set_modified_callback(
p, track_matte_enabled_modified);
}
// audio output settings
obs_property_t *monitor_list = obs_properties_add_list(
ppts, "audio_monitoring", obs_module_text("AudioMonitoring"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
......@@ -357,6 +610,7 @@ static obs_properties_t *stinger_properties(void *data)
obs_module_text("AudioMonitoring.Both"),
OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT);
// audio fade settings
obs_property_t *audio_fade_style = obs_properties_add_list(
ppts, "audio_fade_style", obs_module_text("AudioFadeStyle"),
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册