splitmuxsink: Add option for timecode-based split

If this option is given, it will calculate the next split point based on
timecode difference.

https://bugzilla.gnome.org/show_bug.cgi?id=774209
This commit is contained in:
Vivia Nikolaidou 2016-12-22 17:40:40 +02:00 committed by Jan Schmidt
parent b0e38ed374
commit 05db87de21
2 changed files with 130 additions and 15 deletions

View file

@ -75,6 +75,7 @@ enum
PROP_LOCATION,
PROP_MAX_SIZE_TIME,
PROP_MAX_SIZE_BYTES,
PROP_MAX_SIZE_TIMECODE,
PROP_SEND_KEYFRAME_REQUESTS,
PROP_MAX_FILES,
PROP_MUXER_OVERHEAD,
@ -230,6 +231,12 @@ gst_splitmux_sink_class_init (GstSplitMuxSinkClass * klass)
g_param_spec_uint64 ("max-size-bytes", "Max. size bytes",
"Max. amount of data per file (in bytes, 0=disable)", 0, G_MAXUINT64,
DEFAULT_MAX_SIZE_BYTES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_MAX_SIZE_TIMECODE,
g_param_spec_string ("max-size-timecode", "Maximum timecode difference",
"Maximum difference in timecode between first and last frame. "
"Separator is assumed to be \":\" everywhere (e.g. 01:00:00:00). "
"Will only be effective if a timecode track is present.",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_SEND_KEYFRAME_REQUESTS,
g_param_spec_boolean ("send-keyframe-requests",
"Request keyframes at max-size-time",
@ -293,6 +300,9 @@ gst_splitmux_sink_init (GstSplitMuxSink * splitmux)
splitmux->threshold_bytes = DEFAULT_MAX_SIZE_BYTES;
splitmux->max_files = DEFAULT_MAX_FILES;
splitmux->send_keyframe_requests = DEFAULT_SEND_KEYFRAME_REQUESTS;
splitmux->next_max_tc_time = GST_CLOCK_TIME_NONE;
splitmux->threshold_timecode_str = NULL;
GST_OBJECT_FLAG_SET (splitmux, GST_ELEMENT_FLAG_SINK);
}
@ -340,6 +350,9 @@ gst_splitmux_sink_finalize (GObject * object)
if (splitmux->provided_muxer)
gst_object_unref (splitmux->provided_muxer);
if (splitmux->threshold_timecode_str)
g_free (splitmux->threshold_timecode_str);
g_free (splitmux->location);
/* Make sure to free any un-released contexts */
@ -373,6 +386,11 @@ gst_splitmux_sink_set_property (GObject * object, guint prop_id,
splitmux->threshold_time = g_value_get_uint64 (value);
GST_OBJECT_UNLOCK (splitmux);
break;
case PROP_MAX_SIZE_TIMECODE:
GST_OBJECT_LOCK (splitmux);
splitmux->threshold_timecode_str = g_value_dup_string (value);
GST_OBJECT_UNLOCK (splitmux);
break;
case PROP_SEND_KEYFRAME_REQUESTS:
GST_OBJECT_LOCK (splitmux);
splitmux->send_keyframe_requests = g_value_get_boolean (value);
@ -432,6 +450,11 @@ gst_splitmux_sink_get_property (GObject * object, guint prop_id,
g_value_set_uint64 (value, splitmux->threshold_time);
GST_OBJECT_UNLOCK (splitmux);
break;
case PROP_MAX_SIZE_TIMECODE:
GST_OBJECT_LOCK (splitmux);
g_value_set_string (value, splitmux->threshold_timecode_str);
GST_OBJECT_UNLOCK (splitmux);
break;
case PROP_SEND_KEYFRAME_REQUESTS:
GST_OBJECT_LOCK (splitmux);
g_value_set_boolean (value, splitmux->send_keyframe_requests);
@ -505,6 +528,7 @@ mq_stream_ctx_free (MqStreamCtx * ctx)
gst_bin_remove (GST_BIN (ctx->splitmux), ctx->q);
gst_object_unref (ctx->q);
}
gst_buffer_replace (&ctx->prev_in_keyframe, NULL);
gst_object_unref (ctx->sinkpad);
gst_object_unref (ctx->srcpad);
g_queue_foreach (&ctx->queued_bufs, (GFunc) mq_stream_buf_free, NULL);
@ -684,19 +708,86 @@ complete_or_wait_on_out (GstSplitMuxSink * splitmux, MqStreamCtx * ctx)
while (1);
}
static GstClockTime
calculate_next_max_timecode (GstSplitMuxSink * splitmux,
const GstVideoTimeCode * cur_tc)
{
GstVideoTimeCode *target_tc;
GstVideoTimeCodeInterval *tc_inter;
GstClockTime cur_tc_time, target_tc_time, next_max_tc_time;
if (cur_tc == NULL || splitmux->threshold_timecode_str == NULL)
return GST_CLOCK_TIME_NONE;
tc_inter =
gst_video_time_code_interval_new_from_string
(splitmux->threshold_timecode_str);
target_tc = gst_video_time_code_add_interval (cur_tc, tc_inter);
gst_video_time_code_interval_free (tc_inter);
/* Convert to ns */
target_tc_time = gst_video_time_code_nsec_since_daily_jam (target_tc);
cur_tc_time = gst_video_time_code_nsec_since_daily_jam (cur_tc);
/* Add fragment_start_time, accounting for wraparound */
if (target_tc_time >= cur_tc_time) {
next_max_tc_time =
target_tc_time - cur_tc_time + splitmux->fragment_start_time;
} else {
GstClockTime day_in_ns = 24 * 60 * 60 * GST_SECOND;
next_max_tc_time =
day_in_ns - cur_tc_time + target_tc_time +
splitmux->fragment_start_time;
}
GST_INFO_OBJECT (splitmux, "Next max TC time: %" GST_TIME_FORMAT
" from ref TC: %" GST_TIME_FORMAT, GST_TIME_ARGS (next_max_tc_time),
GST_TIME_ARGS (cur_tc_time));
gst_video_time_code_free (target_tc);
return next_max_tc_time;
}
static gboolean
request_next_keyframe (GstSplitMuxSink * splitmux)
request_next_keyframe (GstSplitMuxSink * splitmux, GstBuffer * buffer)
{
GstEvent *ev;
GstClockTime target_time;
gboolean timecode_based = FALSE;
splitmux->next_max_tc_time = GST_CLOCK_TIME_NONE;
if (splitmux->threshold_timecode_str) {
GstVideoTimeCodeMeta *tc_meta;
if (buffer != NULL) {
tc_meta = gst_buffer_get_video_time_code_meta (buffer);
if (tc_meta) {
splitmux->next_max_tc_time =
calculate_next_max_timecode (splitmux, &tc_meta->tc);
timecode_based = (splitmux->next_max_tc_time != GST_CLOCK_TIME_NONE);
}
} else {
/* This can happen in the presence of GAP events that trigger
* a new fragment start */
GST_WARNING_OBJECT (splitmux,
"No buffer available to calculate next timecode");
}
}
if (splitmux->send_keyframe_requests == FALSE
|| splitmux->threshold_time == 0 || splitmux->threshold_bytes != 0)
|| (splitmux->threshold_time == 0 && !timecode_based)
|| splitmux->threshold_bytes != 0)
return TRUE;
ev = gst_video_event_new_upstream_force_key_unit
(splitmux->fragment_start_time + splitmux->threshold_time, TRUE, 0);
GST_DEBUG_OBJECT (splitmux, "Requesting next keyframe at %" GST_TIME_FORMAT,
GST_TIME_ARGS (splitmux->fragment_start_time + splitmux->threshold_time));
if (timecode_based) {
/* We might have rounding errors: aim slightly earlier */
target_time = splitmux->next_max_tc_time - 5 * GST_USECOND;
} else {
target_time = splitmux->fragment_start_time + splitmux->threshold_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));
return gst_pad_push_event (splitmux->reference_ctx->sinkpad, ev);
}
@ -832,7 +923,7 @@ handle_mq_output (GstPad * pad, GstPadProbeInfo * info, MqStreamCtx * ctx)
splitmux->queued_keyframes--;
ctx->out_running_time = buf_info->run_ts;
ctx->cur_buffer = gst_pad_probe_info_get_buffer (info);
ctx->cur_out_buffer = gst_pad_probe_info_get_buffer (info);
GST_LOG_OBJECT (splitmux,
"Pad %" GST_PTR_FORMAT " buffer with run TS %" GST_STIME_FORMAT
@ -856,7 +947,7 @@ handle_mq_output (GstPad * pad, GstPadProbeInfo * info, MqStreamCtx * ctx)
}
#endif
ctx->cur_buffer = NULL;
ctx->cur_out_buffer = NULL;
GST_SPLITMUX_UNLOCK (splitmux);
/* pending_gap is protected by the STREAM lock */
@ -1045,14 +1136,26 @@ handle_gathered_gop (GstSplitMuxSink * splitmux)
GST_LOG_OBJECT (splitmux, "mq at TS %" GST_STIME_FORMAT
" bytes %" G_GUINT64_FORMAT, GST_STIME_ARGS (queued_time), queued_bytes);
if (splitmux->next_max_tc_time != GST_CLOCK_TIME_NONE) {
GST_LOG_OBJECT (splitmux,
"timecode mq TS %" GST_TIME_FORMAT " vs target %" GST_TIME_FORMAT,
GST_TIME_ARGS (splitmux->reference_ctx->in_running_time),
GST_TIME_ARGS (splitmux->next_max_tc_time + 5 * GST_USECOND));
}
/* Check for overrun - have we output at least one byte and overrun
* either threshold? */
/* Timecode-based threshold accounts for possible rounding errors:
* 5us should be bigger than all possible rounding errors but nowhere near
* big enough to skip to another frame */
if ((splitmux->fragment_total_bytes > 0 &&
((splitmux->threshold_bytes > 0 &&
queued_bytes > splitmux->threshold_bytes) ||
(splitmux->threshold_time > 0 &&
queued_time > splitmux->threshold_time)))) {
queued_time > splitmux->threshold_time) ||
(splitmux->next_max_tc_time != GST_CLOCK_TIME_NONE &&
splitmux->reference_ctx->in_running_time >
splitmux->next_max_tc_time + 5 * GST_USECOND)))) {
/* Tell the output side to start a new fragment */
GST_INFO_OBJECT (splitmux,
@ -1069,10 +1172,12 @@ handle_gathered_gop (GstSplitMuxSink * splitmux)
splitmux->fragment_start_time = splitmux->gop_start_time;
splitmux->fragment_total_bytes = 0;
if (request_next_keyframe (splitmux) == FALSE) {
if (request_next_keyframe (splitmux,
splitmux->reference_ctx->prev_in_keyframe) == FALSE) {
GST_WARNING_OBJECT (splitmux,
"Could not request a keyframe. Files may not split at the exact location they should");
}
gst_buffer_replace (&splitmux->reference_ctx->prev_in_keyframe, NULL);
}
/* And set up to collect the next GOP */
@ -1344,14 +1449,17 @@ handle_mq_input (GstPad * pad, GstPadProbeInfo * info, MqStreamCtx * ctx)
splitmux->gop_start_time = splitmux->fragment_start_time = buf_info->run_ts;
GST_LOG_OBJECT (splitmux, "Mux start time now %" GST_STIME_FORMAT,
GST_STIME_ARGS (splitmux->fragment_start_time));
gst_buffer_replace (&ctx->prev_in_keyframe, buf);
/* Also take this as the first start time when starting up,
* so that we start counting overflow from the first frame */
if (!GST_CLOCK_STIME_IS_VALID (splitmux->max_in_running_time))
splitmux->max_in_running_time = splitmux->fragment_start_time;
if (request_next_keyframe (splitmux) == FALSE) {
if (request_next_keyframe (splitmux, ctx->prev_in_keyframe) == FALSE) {
GST_WARNING_OBJECT (splitmux,
"Could not request a keyframe. Files may not split at the exact location they should");
}
gst_buffer_replace (&splitmux->reference_ctx->prev_in_keyframe, NULL);
}
GST_DEBUG_OBJECT (pad, "Buf TS %" GST_STIME_FORMAT
@ -1386,6 +1494,8 @@ handle_mq_input (GstPad * pad, GstPadProbeInfo * info, MqStreamCtx * ctx)
/* Wake up other input pads to collect this GOP */
GST_SPLITMUX_BROADCAST_INPUT (splitmux);
check_completed_gop (splitmux, ctx);
/* Store this new keyframe to remember the start of GOP */
gst_buffer_replace (&ctx->prev_in_keyframe, buf);
} else {
/* Pass this buffer if the reference ctx is far enough ahead */
if (ctx->in_running_time < splitmux->max_in_running_time) {
@ -1926,11 +2036,12 @@ set_next_filename (GstSplitMuxSink * splitmux, MqStreamCtx * ctx)
gst_splitmux_sink_ensure_max_files (splitmux);
if (ctx->cur_buffer == NULL)
g_warning ("Starting next file without buffer");
if (ctx->cur_out_buffer == NULL) {
GST_WARNING_OBJECT (splitmux, "Starting next file without buffer");
}
caps = gst_pad_get_current_caps (ctx->srcpad);
sample = gst_sample_new (ctx->cur_buffer, caps, &ctx->out_segment, NULL);
sample = gst_sample_new (ctx->cur_out_buffer, caps, &ctx->out_segment, NULL);
g_signal_emit (splitmux, signals[SIGNAL_FORMAT_LOCATION_FULL], 0,
splitmux->fragment_id, sample, &fname);
gst_sample_unref (sample);

View file

@ -89,13 +89,15 @@ typedef struct _MqStreamCtx
GstClockTimeDiff in_running_time;
GstClockTimeDiff out_running_time;
GstBuffer *prev_in_keyframe; /* store keyframe for each GOP */
GstElement *q;
GQueue queued_bufs;
GstPad *sinkpad;
GstPad *srcpad;
GstBuffer *cur_buffer;
GstBuffer *cur_out_buffer;
GstEvent *pending_gap;
} MqStreamCtx;
@ -113,6 +115,8 @@ struct _GstSplitMuxSink
guint64 threshold_bytes;
guint max_files;
gboolean send_keyframe_requests;
gchar *threshold_timecode_str;
GstClockTime next_max_tc_time;
GstElement *muxer;
GstElement *sink;