matroskamux: Add new property to offset all streams to start at zero

This takes the timestamp of the earliest stream and offsets it so that
it starts at 0. Some software (VLC, ffmpeg-based) does not properly
handle Matroska files that start at timestamps much bigger than zero.

Closes https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/issues/449
This commit is contained in:
Sebastian Dröge 2019-04-18 15:31:00 +03:00 committed by Sebastian Dröge
parent b47f3c9c50
commit cced65ee21
2 changed files with 55 additions and 1 deletions

View file

@ -72,7 +72,8 @@ enum
PROP_STREAMABLE, PROP_STREAMABLE,
PROP_TIMECODESCALE, PROP_TIMECODESCALE,
PROP_MIN_CLUSTER_DURATION, PROP_MIN_CLUSTER_DURATION,
PROP_MAX_CLUSTER_DURATION PROP_MAX_CLUSTER_DURATION,
PROP_OFFSET_TO_ZERO,
}; };
#define DEFAULT_DOCTYPE_VERSION 2 #define DEFAULT_DOCTYPE_VERSION 2
@ -82,6 +83,7 @@ enum
#define DEFAULT_TIMECODESCALE GST_MSECOND #define DEFAULT_TIMECODESCALE GST_MSECOND
#define DEFAULT_MIN_CLUSTER_DURATION 500 * GST_MSECOND #define DEFAULT_MIN_CLUSTER_DURATION 500 * GST_MSECOND
#define DEFAULT_MAX_CLUSTER_DURATION 65535 * GST_MSECOND #define DEFAULT_MAX_CLUSTER_DURATION 65535 * GST_MSECOND
#define DEFAULT_OFFSET_TO_ZERO FALSE
/* WAVEFORMATEX is gst_riff_strf_auds + an extra guint16 extension size */ /* WAVEFORMATEX is gst_riff_strf_auds + an extra guint16 extension size */
#define WAVEFORMATEX_SIZE (2 + sizeof (gst_riff_strf_auds)) #define WAVEFORMATEX_SIZE (2 + sizeof (gst_riff_strf_auds))
@ -363,6 +365,10 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
"0 means no maximum duration.", 0, "0 means no maximum duration.", 0,
G_MAXINT64, DEFAULT_MAX_CLUSTER_DURATION, G_MAXINT64, DEFAULT_MAX_CLUSTER_DURATION,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_OFFSET_TO_ZERO,
g_param_spec_boolean ("offset-to-zero", "Offset To Zero",
"Offsets all streams so that the " "earliest stream starts at 0.",
DEFAULT_OFFSET_TO_ZERO, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstelement_class->change_state = gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_matroska_mux_change_state); GST_DEBUG_FUNCPTR (gst_matroska_mux_change_state);
@ -3067,6 +3073,7 @@ gst_matroska_mux_start (GstMatroskaMux * mux, GstMatroskaPad * first_pad,
GSList *collected; GSList *collected;
int i; int i;
guint tracknum = 1; guint tracknum = 1;
GstClockTime earliest_time = GST_CLOCK_TIME_NONE;
GstClockTime duration = 0; GstClockTime duration = 0;
guint32 segment_uid[4]; guint32 segment_uid[4];
GTimeVal time = { 0, 0 }; GTimeVal time = { 0, 0 };
@ -3226,6 +3233,29 @@ gst_matroska_mux_start (GstMatroskaMux * mux, GstMatroskaPad * first_pad,
if (collect_pad->track->codec_id == NULL) if (collect_pad->track->codec_id == NULL)
continue; continue;
/* Find the smallest timestamp so we can offset all streams by this to
* start at 0 */
if (mux->offset_to_zero) {
GstClockTime ts;
if (collect_pad == first_pad)
buf = first_pad_buf ? gst_buffer_ref (first_pad_buf) : NULL;
else
buf = gst_collect_pads_peek (mux->collect, collected->data);
if (buf) {
ts = gst_matroska_track_get_buffer_timestamp (collect_pad->track, buf);
if (earliest_time == GST_CLOCK_TIME_NONE)
earliest_time = ts;
else if (ts != GST_CLOCK_TIME_NONE && ts < earliest_time)
earliest_time = ts;
}
if (buf)
gst_buffer_unref (buf);
}
/* For audio tracks, use the first buffers duration as the default /* For audio tracks, use the first buffers duration as the default
* duration if we didn't get any better idea from the caps event already * duration if we didn't get any better idea from the caps event already
*/ */
@ -3254,6 +3284,8 @@ gst_matroska_mux_start (GstMatroskaMux * mux, GstMatroskaPad * first_pad,
} }
gst_ebml_write_master_finish (ebml, master); gst_ebml_write_master_finish (ebml, master);
mux->earliest_time = earliest_time == GST_CLOCK_TIME_NONE ? 0 : earliest_time;
/* chapters */ /* chapters */
toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux)); toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux));
if (toc != NULL && !mux->ebml_write->streamable) { if (toc != NULL && !mux->ebml_write->streamable) {
@ -3910,6 +3942,11 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad,
buffer_timestamp = buffer_timestamp =
gst_matroska_track_get_buffer_timestamp (collect_pad->track, buf); gst_matroska_track_get_buffer_timestamp (collect_pad->track, buf);
if (buffer_timestamp >= mux->earliest_time) {
buffer_timestamp -= mux->earliest_time;
} else {
buffer_timestamp = 0;
}
/* hm, invalid timestamp (due to --to be fixed--- element upstream); /* hm, invalid timestamp (due to --to be fixed--- element upstream);
* this would wreak havoc with time stored in matroska file */ * this would wreak havoc with time stored in matroska file */
@ -4188,6 +4225,14 @@ gst_matroska_mux_handle_buffer (GstCollectPads * pads, GstCollectData * data,
g_assert (buf); g_assert (buf);
buffer_timestamp = gst_matroska_track_get_buffer_timestamp (best->track, buf); buffer_timestamp = gst_matroska_track_get_buffer_timestamp (best->track, buf);
if (buffer_timestamp >= mux->earliest_time) {
buffer_timestamp -= mux->earliest_time;
} else {
GST_ERROR_OBJECT (mux,
"PTS before first PTS (%" GST_TIME_FORMAT " < %" GST_TIME_FORMAT ")",
GST_TIME_ARGS (buffer_timestamp), GST_TIME_ARGS (mux->earliest_time));
buffer_timestamp = 0;
}
GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %" GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %"
GST_TIME_FORMAT " dur %" GST_TIME_FORMAT, GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
@ -4304,6 +4349,9 @@ gst_matroska_mux_set_property (GObject * object,
case PROP_MAX_CLUSTER_DURATION: case PROP_MAX_CLUSTER_DURATION:
mux->max_cluster_duration = g_value_get_int64 (value); mux->max_cluster_duration = g_value_get_int64 (value);
break; break;
case PROP_OFFSET_TO_ZERO:
mux->offset_to_zero = g_value_get_boolean (value);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -4341,6 +4389,9 @@ gst_matroska_mux_get_property (GObject * object,
case PROP_MAX_CLUSTER_DURATION: case PROP_MAX_CLUSTER_DURATION:
g_value_set_int64 (value, mux->max_cluster_duration); g_value_set_int64 (value, mux->max_cluster_duration);
break; break;
case PROP_OFFSET_TO_ZERO:
g_value_set_boolean (value, mux->offset_to_zero);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;

View file

@ -111,6 +111,9 @@ struct _GstMatroskaMux {
guint64 max_cluster_duration; guint64 max_cluster_duration;
guint64 min_cluster_duration; guint64 min_cluster_duration;
/* earliest timestamp (time, ns) if offsetting to zero */
gboolean offset_to_zero;
guint64 earliest_time;
/* length, position (time, ns) */ /* length, position (time, ns) */
guint64 duration; guint64 duration;