splitmuxsink: Don't send too many force key unit event

splitmuxsink should requst keyframe depending on configured
threshold and previously requested time in order to avoid too many
keyframe request.
This commit is contained in:
Seungha Yang 2020-04-03 13:45:56 +09:00
parent 78eaa7c6ed
commit 599066726f
3 changed files with 112 additions and 13 deletions

View file

@ -567,6 +567,7 @@ gst_splitmux_sink_init (GstSplitMuxSink * splitmux)
splitmux->do_split_next_gop = FALSE;
splitmux->times_to_split = gst_queue_array_new_for_struct (8, 8);
splitmux->last_fku_time = GST_CLOCK_TIME_NONE;
splitmux->next_fku_time = GST_CLOCK_TIME_NONE;
}
static void
@ -1342,6 +1343,7 @@ request_next_keyframe (GstSplitMuxSink * splitmux, GstBuffer * buffer,
GstClockTime target_time;
gboolean timecode_based = FALSE;
GstClockTime next_max_tc_time = GST_CLOCK_TIME_NONE;
GstClockTime next_fku_time = GST_CLOCK_TIME_NONE;
if (splitmux->tc_interval) {
GstVideoTimeCodeMeta *tc_meta;
@ -1361,6 +1363,14 @@ request_next_keyframe (GstSplitMuxSink * splitmux, GstBuffer * buffer,
}
}
/* even if we don't send keyframe request, this should be done here in order
* to calculate the threshold timecode */
if (timecode_based && !GST_CLOCK_TIME_IS_VALID (splitmux->threshold_timecode)) {
splitmux->threshold_timecode = next_max_tc_time - running_time;
GST_DEBUG_OBJECT (splitmux, "Calculated threshold timecode duration %"
GST_TIME_FORMAT, GST_TIME_ARGS (splitmux->threshold_timecode));
}
if (splitmux->send_keyframe_requests == FALSE
|| (splitmux->threshold_time == 0 && !timecode_based)
|| splitmux->threshold_bytes != 0)
@ -1373,25 +1383,28 @@ request_next_keyframe (GstSplitMuxSink * splitmux, GstBuffer * buffer,
target_time = running_time + splitmux->threshold_time;
}
if (GST_CLOCK_TIME_IS_VALID (splitmux->last_fku_time) &&
splitmux->last_fku_time > target_time) {
if (GST_CLOCK_TIME_IS_VALID (splitmux->next_fku_time) &&
target_time < splitmux->next_fku_time) {
GST_DEBUG_OBJECT (splitmux, "Target time %" GST_TIME_FORMAT
" is smaller than last requested keyframe time %" GST_TIME_FORMAT,
GST_TIME_ARGS (target_time), GST_TIME_ARGS (splitmux->last_fku_time));
" is smaller than expected next keyframe time %" GST_TIME_FORMAT,
GST_TIME_ARGS (target_time), GST_TIME_ARGS (splitmux->next_fku_time));
return TRUE;
}
splitmux->last_fku_time = target_time;
if (timecode_based && !GST_CLOCK_TIME_IS_VALID (splitmux->threshold_timecode)) {
splitmux->threshold_timecode = next_max_tc_time - running_time;
GST_DEBUG_OBJECT (splitmux, "Calculated threshold timecode duration %"
GST_TIME_FORMAT, GST_TIME_ARGS (splitmux->threshold_timecode));
if (timecode_based) {
next_fku_time = target_time + splitmux->threshold_timecode;
} else {
next_fku_time = target_time + splitmux->threshold_time;
}
splitmux->last_fku_time = target_time;
splitmux->next_fku_time = next_fku_time;
ev = gst_video_event_new_upstream_force_key_unit (target_time, TRUE, 0);
GST_INFO_OBJECT (splitmux, "Requesting next keyframe at %" GST_TIME_FORMAT,
GST_TIME_ARGS (target_time));
GST_INFO_OBJECT (splitmux, "Requesting keyframe at %" GST_TIME_FORMAT
", the next expected keyframe is %" GST_TIME_FORMAT,
GST_TIME_ARGS (target_time), GST_TIME_ARGS (next_fku_time));
return gst_pad_push_event (splitmux->reference_ctx->sinkpad, ev);
}
@ -3275,6 +3288,7 @@ gst_splitmux_sink_reset (GstSplitMuxSink * splitmux)
g_atomic_int_set (&(splitmux->do_split_next_gop), FALSE);
splitmux->last_fku_time = GST_CLOCK_TIME_NONE;
splitmux->next_fku_time = GST_CLOCK_TIME_NONE;
gst_queue_array_clear (splitmux->times_to_split);
g_list_foreach (splitmux->contexts, (GFunc) mq_stream_ctx_reset, NULL);

View file

@ -122,7 +122,10 @@ struct _GstSplitMuxSink
GstClockTime threshold_timecode;
GstClockTime next_max_tc_time;
GstClockTime alignment_threshold;
/* previously sent running time of force keyframe unit event */
GstClockTime last_fku_time;
/* expected running time of next force keyframe unit event */
GstClockTime next_fku_time;
gboolean reset_muxer;

View file

@ -27,6 +27,7 @@
#include <gst/check/gstcheck.h>
#include <gst/app/app.h>
#include <gst/video/video.h>
#include <stdlib.h>
gchar *tmpdir = NULL;
@ -930,6 +931,24 @@ GST_START_TEST (test_splitmuxsink_muxer_pad_map)
GST_END_TEST;
static GstPadProbeReturn
count_upstrea_fku (GstPad * pad, GstPadProbeInfo * info,
guint * upstream_fku_count)
{
GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CUSTOM_UPSTREAM:
if (gst_video_event_is_force_key_unit (event))
*upstream_fku_count += 1;
break;
default:
break;
}
return GST_PAD_PROBE_OK;
}
static void
splitmuxsink_split_by_keyframe (gboolean send_keyframe_request,
guint max_size_time_sec, guint encoder_key_interval_sec)
@ -937,17 +956,21 @@ splitmuxsink_split_by_keyframe (gboolean send_keyframe_request,
GstMessage *msg;
GstElement *pipeline;
GstElement *sink;
GstElement *enc;
GstPad *srcpad;
gchar *pipeline_str;
gchar *dest_pattern;
guint count;
guint expected_count;
gchar *in_pattern;
guint upstream_fku_count = 0;
guint expected_fku_count;
pipeline_str = g_strdup_printf ("splitmuxsink name=splitsink "
"max-size-time=%" G_GUINT64_FORMAT
" send-keyframe-requests=%s muxer=qtmux "
"videotestsrc num-buffers=30 ! video/x-raw,width=80,height=64,framerate=5/1 "
"! videoconvert ! queue ! vp8enc keyframe-max-dist=%d ! splitsink.video ",
"! videoconvert ! queue ! vp8enc name=enc keyframe-max-dist=%d ! splitsink.video ",
max_size_time_sec * GST_SECOND, send_keyframe_request ? "true" : "false",
encoder_key_interval_sec * 5);
@ -964,6 +987,16 @@ splitmuxsink_split_by_keyframe (gboolean send_keyframe_request,
g_free (dest_pattern);
g_object_unref (sink);
enc = gst_bin_get_by_name (GST_BIN (pipeline), "enc");
fail_if (enc == NULL);
srcpad = gst_element_get_static_pad (enc, "src");
fail_if (srcpad == NULL);
gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
(GstPadProbeCallback) count_upstrea_fku, &upstream_fku_count, NULL);
gst_object_unref (srcpad);
gst_object_unref (enc);
msg = run_pipeline (pipeline);
if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR)
@ -978,6 +1011,18 @@ splitmuxsink_split_by_keyframe (gboolean send_keyframe_request,
fail_unless (count == expected_count,
"Expected %d output files, got %d", expected_count, count);
if (!send_keyframe_request) {
expected_fku_count = 0;
} else {
expected_fku_count = count;
}
GST_INFO ("Upstream force keyunit event count %d", upstream_fku_count);
fail_unless (upstream_fku_count == expected_fku_count,
"Expected upstream force keyunit event count %d, got %d",
expected_fku_count, upstream_fku_count);
in_pattern = g_build_filename (tmpdir, "out*.m4v", NULL);
/* FIXME: Reverse playback works poorly with multiple video streams
* in qtdemux (at least, maybe other demuxers) at the time this was
@ -1035,17 +1080,21 @@ splitmuxsink_split_by_keyframe_timecode (gboolean send_keyframe_request,
GstMessage *msg;
GstElement *pipeline;
GstElement *sink;
GstElement *enc;
GstPad *srcpad;
gchar *pipeline_str;
gchar *dest_pattern;
guint count;
guint expected_count;
gchar *in_pattern;
guint upstream_fku_count = 0;
guint expected_fku_count;
pipeline_str = g_strdup_printf ("splitmuxsink name=splitsink "
"max-size-timecode=%s"
" send-keyframe-requests=%s muxer=qtmux "
"videotestsrc num-buffers=30 ! video/x-raw,width=80,height=64,framerate=5/1 "
"! videoconvert ! timecodestamper ! queue ! vp8enc keyframe-max-dist=%d ! splitsink.video ",
"! videoconvert ! timecodestamper ! queue ! vp8enc name=enc keyframe-max-dist=%d ! splitsink.video ",
maxsize_timecode_string, send_keyframe_request ? "true" : "false",
encoder_key_interval_sec ? encoder_key_interval_sec * 5 : 1);
@ -1062,6 +1111,16 @@ splitmuxsink_split_by_keyframe_timecode (gboolean send_keyframe_request,
g_free (dest_pattern);
g_object_unref (sink);
enc = gst_bin_get_by_name (GST_BIN (pipeline), "enc");
fail_if (enc == NULL);
srcpad = gst_element_get_static_pad (enc, "src");
fail_if (srcpad == NULL);
gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
(GstPadProbeCallback) count_upstrea_fku, &upstream_fku_count, NULL);
gst_object_unref (srcpad);
gst_object_unref (enc);
msg = run_pipeline (pipeline);
if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR)
@ -1077,6 +1136,18 @@ splitmuxsink_split_by_keyframe_timecode (gboolean send_keyframe_request,
fail_unless (count == expected_count,
"Expected %d output files, got %d", expected_count, count);
if (!send_keyframe_request) {
expected_fku_count = 0;
} else {
expected_fku_count = count;
}
GST_INFO ("Upstream force keyunit event count %d", upstream_fku_count);
fail_unless (upstream_fku_count == expected_fku_count,
"Expected upstream force keyunit event count %d, got %d",
expected_fku_count, upstream_fku_count);
in_pattern = g_build_filename (tmpdir, "out*.m4v", NULL);
/* FIXME: Reverse playback works poorly with multiple video streams
* in qtdemux (at least, maybe other demuxers) at the time this was
@ -1087,6 +1158,15 @@ splitmuxsink_split_by_keyframe_timecode (gboolean send_keyframe_request,
g_free (in_pattern);
}
GST_START_TEST (test_splitmuxsink_without_keyframe_request_timecode)
{
/* This encoding option is intended to produce keyframe per 1 second
* but splitmuxsink will split file per 2 second without keyframe request */
splitmuxsink_split_by_keyframe_timecode (FALSE, "00:00:02:00", 2, 1);
}
GST_END_TEST;
GST_START_TEST (test_splitmuxsink_keyframe_request_timecode)
{
/* This encoding option is intended to produce keyframe per 1 second
@ -1195,6 +1275,8 @@ splitmux_suite (void)
}
if (have_qtmux && have_vp8 && have_timecodestamper) {
tcase_add_test (tc_chain,
test_splitmuxsink_without_keyframe_request_timecode);
tcase_add_test (tc_chain, test_splitmuxsink_keyframe_request_timecode);
tcase_add_test (tc_chain,
test_splitmuxsink_keyframe_request_timecode_trailing_small_segment);