#include <string.h>
#include <glib/gprintf.h>
+#include <gst/base/base.h>
+
/* For AVI compatibility mode
and for fourcc stuff */
#include <gst/riff/riff-read.h>
PROP_0,
PROP_METADATA,
PROP_STREAMINFO,
- PROP_MAX_GAP_TIME
+ PROP_MAX_GAP_TIME,
+ PROP_MAX_BACKTRACK_DISTANCE
};
-#define DEFAULT_MAX_GAP_TIME (2 * GST_SECOND)
+#define DEFAULT_MAX_GAP_TIME (2 * GST_SECOND)
+#define DEFAULT_MAX_BACKTRACK_DISTANCE 30
+#define INVALID_DATA_THRESHOLD (2 * 1024 * 1024)
static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
static gboolean gst_matroska_demux_handle_sink_event (GstPad * pad,
GstObject * parent, GstEvent * event);
+static gboolean gst_matroska_demux_handle_sink_query (GstPad * pad,
+ GstObject * parent, GstQuery * query);
static GstFlowReturn gst_matroska_demux_chain (GstPad * pad,
GstObject * object, GstBuffer * buffer);
static GstCaps
* gst_matroska_demux_subtitle_caps (GstMatroskaTrackSubtitleContext *
subtitlecontext, const gchar * codec_id, gpointer data, guint size);
+static const gchar *gst_matroska_track_encryption_algorithm_name (gint val);
+static const gchar *gst_matroska_track_encryption_cipher_mode_name (gint val);
+static const gchar *gst_matroska_track_encoding_scope_name (gint val);
/* stream methods */
static void gst_matroska_demux_reset (GstElement * element);
static gboolean perform_seek_to_offset (GstMatroskaDemux * demux,
- gdouble rate, guint64 offset, guint32 seqnum);
+ gdouble rate, guint64 offset, guint32 seqnum, GstSeekFlags flags);
/* gobject functions */
static void gst_matroska_demux_set_property (GObject * object,
"gaps longer than this (0 = disabled).", 0, G_MAXUINT64,
DEFAULT_MAX_GAP_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_MAX_BACKTRACK_DISTANCE,
+ g_param_spec_uint ("max-backtrack-distance",
+ "Maximum backtrack distance",
+ "Maximum backtrack distance in seconds when seeking without "
+ "and index in pull mode and search for a keyframe "
+ "(0 = disable backtracking).",
+ 0, G_MAXUINT, DEFAULT_MAX_BACKTRACK_DISTANCE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_matroska_demux_change_state);
gstelement_class->send_event =
GST_DEBUG_FUNCPTR (gst_matroska_demux_get_index);
#endif
- gst_element_class_add_pad_template (gstelement_class,
- gst_static_pad_template_get (&video_src_templ));
- gst_element_class_add_pad_template (gstelement_class,
- gst_static_pad_template_get (&audio_src_templ));
- gst_element_class_add_pad_template (gstelement_class,
- gst_static_pad_template_get (&subtitle_src_templ));
- gst_element_class_add_pad_template (gstelement_class,
- gst_static_pad_template_get (&sink_templ));
+ gst_element_class_add_static_pad_template (gstelement_class,
+ &video_src_templ);
+ gst_element_class_add_static_pad_template (gstelement_class,
+ &audio_src_templ);
+ gst_element_class_add_static_pad_template (gstelement_class,
+ &subtitle_src_templ);
+ gst_element_class_add_static_pad_template (gstelement_class, &sink_templ);
gst_element_class_set_static_metadata (gstelement_class, "Matroska demuxer",
"Codec/Demuxer",
GST_DEBUG_FUNCPTR (gst_matroska_demux_chain));
gst_pad_set_event_function (demux->common.sinkpad,
GST_DEBUG_FUNCPTR (gst_matroska_demux_handle_sink_event));
+ gst_pad_set_query_function (demux->common.sinkpad,
+ GST_DEBUG_FUNCPTR (gst_matroska_demux_handle_sink_query));
gst_element_add_pad (GST_ELEMENT (demux), demux->common.sinkpad);
/* init defaults for common read context */
/* property defaults */
demux->max_gap_time = DEFAULT_MAX_GAP_TIME;
+ demux->max_backtrack_distance = DEFAULT_MAX_BACKTRACK_DISTANCE;
GST_OBJECT_FLAG_SET (demux, GST_ELEMENT_FLAG_INDEXABLE);
demux->num_a_streams = 0;
demux->num_t_streams = 0;
demux->num_v_streams = 0;
+ demux->have_nonintraonly_v_streams = FALSE;
demux->have_group_id = FALSE;
demux->group_id = G_MAXUINT;
demux->to_time = GST_CLOCK_TIME_NONE;
demux->cluster_time = GST_CLOCK_TIME_NONE;
demux->cluster_offset = 0;
+ demux->cluster_prevsize = 0;
+ demux->seen_cluster_prevsize = FALSE;
demux->next_cluster_offset = 0;
+ demux->stream_last_time = GST_CLOCK_TIME_NONE;
+ demux->last_cluster_offset = 0;
demux->index_offset = 0;
demux->seekable = FALSE;
demux->need_segment = FALSE;
demux->cached_length = G_MAXUINT64;
+ if (demux->deferred_seek_event)
+ gst_event_unref (demux->deferred_seek_event);
+ demux->deferred_seek_event = NULL;
+ demux->deferred_seek_pad = NULL;
+
gst_flow_combiner_clear (demux->flowcombiner);
}
GstMapInfo map;
gpointer data;
gsize size;
+ GstBuffer *out_buf = buf;
g_return_val_if_fail (GST_IS_BUFFER (buf), NULL);
GST_DEBUG ("decoding buffer %p", buf);
- gst_buffer_map (buf, &map, GST_MAP_READ);
+ gst_buffer_map (out_buf, &map, GST_MAP_READ);
data = map.data;
size = map.size;
if (gst_matroska_decode_data (context->encodings, &data, &size,
GST_MATROSKA_TRACK_ENCODING_SCOPE_FRAME, FALSE)) {
- gst_buffer_unmap (buf, &map);
- gst_buffer_unref (buf);
- return gst_buffer_new_wrapped (data, size);
+ if (data != map.data) {
+ gst_buffer_unmap (out_buf, &map);
+ gst_buffer_unref (out_buf);
+ out_buf = gst_buffer_new_wrapped (data, size);
+ } else {
+ gst_buffer_unmap (out_buf, &map);
+ }
} else {
GST_DEBUG ("decode data failed");
- gst_buffer_unmap (buf, &map);
- gst_buffer_unref (buf);
+ gst_buffer_unmap (out_buf, &map);
+ gst_buffer_unref (out_buf);
return NULL;
}
+ /* Encrypted stream */
+ if (context->protection_info) {
+
+ GstStructure *info_protect = gst_structure_copy (context->protection_info);
+ gboolean encrypted = FALSE;
+
+ gst_buffer_map (out_buf, &map, GST_MAP_READ);
+ data = map.data;
+ size = map.size;
+
+ if (gst_matroska_parse_protection_meta (&data, &size, info_protect,
+ &encrypted)) {
+ if (data != map.data) {
+ GstBuffer *tmp_buf;
+
+ gst_buffer_unmap (out_buf, &map);
+ tmp_buf = out_buf;
+ out_buf = gst_buffer_copy_region (tmp_buf, GST_BUFFER_COPY_ALL,
+ gst_buffer_get_size (tmp_buf) - size, size);
+ gst_buffer_unref (tmp_buf);
+ if (encrypted)
+ gst_buffer_add_protection_meta (out_buf, info_protect);
+ else
+ gst_structure_free (info_protect);
+ } else {
+ gst_buffer_unmap (out_buf, &map);
+ gst_structure_free (info_protect);
+ }
+ } else {
+ GST_WARNING ("Adding protection metadata failed");
+ gst_buffer_unmap (out_buf, &map);
+ gst_buffer_unref (out_buf);
+ gst_structure_free (info_protect);
+ return NULL;
+ }
+ }
+
+ return out_buf;
}
static void
}
static GstFlowReturn
-gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml)
+gst_matroska_demux_parse_colour (GstMatroskaDemux * demux, GstEbmlRead * ebml,
+ GstMatroskaTrackVideoContext * video_context)
+{
+ GstFlowReturn ret;
+ GstVideoColorimetry colorimetry;
+ guint32 id;
+ guint64 num;
+
+ colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
+ colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
+
+ DEBUG_ELEMENT_START (demux, ebml, "TrackVideoColour");
+
+ if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK)
+ goto beach;
+
+ while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) {
+ if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK)
+ goto beach;
+
+ switch (id) {
+ case GST_MATROSKA_ID_VIDEOMATRIXCOEFFICIENTS:{
+ if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK)
+ goto beach;
+
+ switch (num) {
+ case 0:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB;
+ break;
+ case 1:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
+ break;
+ case 2:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
+ break;
+ case 4:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_FCC;
+ break;
+ /* FIXME: "5: BT470BG" is undefined in GstVideoColorMatrix
+ * but it's functionally same as "6: BT601" */
+ case 5:
+ case 6:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
+ break;
+ case 7:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_SMPTE240M;
+ break;
+ case 9:
+ colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
+ break;
+ default:
+ GST_FIXME_OBJECT (demux, "Unsupported color matrix coefficients %"
+ G_GUINT64_FORMAT, num);
+ break;
+ }
+ break;
+ }
+
+ case GST_MATROSKA_ID_VIDEORANGE:{
+ if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK)
+ goto beach;
+
+ switch (num) {
+ case 0:
+ colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
+ break;
+ case 1:
+ colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
+ break;
+ case 2:
+ colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
+ break;
+ default:
+ GST_FIXME_OBJECT (demux, "Unsupported color range %"
+ G_GUINT64_FORMAT, num);
+ break;
+ }
+ break;
+ }
+
+ case GST_MATROSKA_ID_VIDEOTRANSFERCHARACTERISTICS:{
+ if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK)
+ goto beach;
+
+ switch (num) {
+ /* FIXME: "6: BT601" and "14: BT2020_10" are undefined in
+ * GstVideoTransferFunction, but functionally same as "1: BT709" */
+ case 1:
+ case 6:
+ case 14:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
+ break;
+ case 2:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
+ break;
+ case 4:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA22;
+ break;
+ case 5:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA28;
+ break;
+ case 7:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_SMPTE240M;
+ break;
+ case 8:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
+ break;
+ case 9:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_LOG100;
+ break;
+ case 10:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_LOG316;
+ break;
+ case 13:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
+ break;
+ case 15:
+ colorimetry.transfer = GST_VIDEO_TRANSFER_BT2020_12;
+ break;
+ default:
+ GST_FIXME_OBJECT (demux,
+ "Unsupported color transfer characteristics %"
+ G_GUINT64_FORMAT, num);
+ break;
+ }
+ break;
+ }
+
+ case GST_MATROSKA_ID_VIDEOPRIMARIES:{
+ if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK)
+ goto beach;
+
+ switch (num) {
+ case 1:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
+ break;
+ case 2:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
+ break;
+ case 4:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT470M;
+ break;
+ case 5:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT470BG;
+ break;
+ case 6:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
+ break;
+ case 7:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE240M;
+ break;
+ case 8:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_FILM;
+ break;
+ case 9:
+ colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
+ break;
+ default:
+ GST_FIXME_OBJECT (demux, "Unsupported color primaries %"
+ G_GUINT64_FORMAT, num);
+ break;
+ }
+ break;
+ }
+
+ default:
+ GST_FIXME_OBJECT (demux, "Unsupported subelement 0x%x in Colour", id);
+ ret = gst_ebml_read_skip (ebml);
+ break;
+ }
+ }
+
+ memcpy (&video_context->colorimetry, &colorimetry,
+ sizeof (GstVideoColorimetry));
+
+beach:
+ DEBUG_ELEMENT_STOP (demux, ebml, "TrackVideoColour", ret);
+ return ret;
+}
+
+static GstFlowReturn
+gst_matroska_demux_parse_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml,
+ GstMatroskaTrackContext ** dest_context)
{
- GstElementClass *klass = GST_ELEMENT_GET_CLASS (demux);
GstMatroskaTrackContext *context;
- GstPadTemplate *templ = NULL;
- GstStreamFlags stream_flags;
GstCaps *caps = NULL;
GstTagList *cached_taglist;
- gchar *padname = NULL;
GstFlowReturn ret;
guint32 id, riff_fourcc = 0;
guint16 riff_audio_fmt = 0;
- GstEvent *stream_start;
gchar *codec = NULL;
- gchar *stream_id;
DEBUG_ELEMENT_START (demux, ebml, "TrackEntry");
/* allocate generic... if we know the type, we'll g_renew()
* with the precise type */
context = g_new0 (GstMatroskaTrackContext, 1);
- g_ptr_array_add (demux->common.src, context);
- context->index = demux->common.num_streams;
context->index_writer_id = -1;
context->type = 0; /* no type yet */
context->default_duration = 0;
context->dts_only = FALSE;
context->intra_only = FALSE;
context->tags = gst_tag_list_new_empty ();
- demux->common.num_streams++;
- g_assert (demux->common.src->len == demux->common.num_streams);
+ g_queue_init (&context->protection_event_queue);
+ context->protection_info = NULL;
- GST_DEBUG_OBJECT (demux, "Stream number %d", context->index);
+ GST_DEBUG_OBJECT (demux, "Parsing a TrackEntry (%d tracks parsed so far)",
+ demux->common.num_streams);
/* try reading the trackentry headers */
while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) {
GST_ERROR_OBJECT (demux, "Invalid TrackNumber 0");
ret = GST_FLOW_ERROR;
break;
- } else if (!gst_matroska_read_common_tracknumber_unique (&demux->common,
- num)) {
- GST_ERROR_OBJECT (demux, "TrackNumber %" G_GUINT64_FORMAT
- " is not unique", num);
- ret = GST_FLOW_ERROR;
- break;
}
GST_DEBUG_OBJECT (demux, "TrackNumber: %" G_GUINT64_FORMAT, num);
context->type = 0;
break;
}
- g_ptr_array_index (demux->common.src, demux->common.num_streams - 1)
- = context;
break;
}
break;
}
videocontext = (GstMatroskaTrackVideoContext *) context;
- g_ptr_array_index (demux->common.src, demux->common.num_streams - 1)
- = context;
while (ret == GST_FLOW_OK &&
gst_ebml_read_has_remaining (ebml, 1, TRUE)) {
if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK)
break;
- if (num)
- context->flags |= GST_MATROSKA_VIDEOTRACK_INTERLACED;
+ if (num == 1)
+ videocontext->interlace_mode =
+ GST_MATROSKA_INTERLACE_MODE_INTERLACED;
+ else if (num == 2)
+ videocontext->interlace_mode =
+ GST_MATROSKA_INTERLACE_MODE_PROGRESSIVE;
else
- context->flags &= ~GST_MATROSKA_VIDEOTRACK_INTERLACED;
- GST_DEBUG_OBJECT (demux, "TrackVideoInterlaced: %d",
- (context->flags & GST_MATROSKA_VIDEOTRACK_INTERLACED) ? 1 :
- 0);
+ videocontext->interlace_mode =
+ GST_MATROSKA_INTERLACE_MODE_UNKNOWN;
+
+ GST_DEBUG_OBJECT (demux, "video track interlacing mode: %d",
+ videocontext->interlace_mode);
break;
}
g_free (data);
break;
}
+
+ /* color info */
+ case GST_MATROSKA_ID_VIDEOCOLOUR:{
+ ret = gst_matroska_demux_parse_colour (demux, ebml, videocontext);
+ break;
+ }
+
case GST_MATROSKA_ID_VIDEOSTEREOMODE:
{
guint64 num;
break;
audiocontext = (GstMatroskaTrackAudioContext *) context;
- g_ptr_array_index (demux->common.src, demux->common.num_streams - 1)
- = context;
while (ret == GST_FLOW_OK &&
gst_ebml_read_has_remaining (ebml, 1, TRUE)) {
if (ret == GST_FLOW_OK || ret == GST_FLOW_EOS)
GST_WARNING_OBJECT (ebml, "Unknown stream/codec in track entry header");
- demux->common.num_streams--;
- g_ptr_array_remove_index (demux->common.src, demux->common.num_streams);
- g_assert (demux->common.src->len == demux->common.num_streams);
gst_matroska_track_free (context);
-
+ context = NULL;
+ *dest_context = NULL;
return ret;
}
if (cached_taglist)
gst_tag_list_insert (context->tags, cached_taglist, GST_TAG_MERGE_APPEND);
- /* now create the GStreamer connectivity */
+ /* compute caps */
switch (context->type) {
case GST_MATROSKA_TRACK_TYPE_VIDEO:{
GstMatroskaTrackVideoContext *videocontext =
(GstMatroskaTrackVideoContext *) context;
- padname = g_strdup_printf ("video_%u", demux->num_v_streams++);
- templ = gst_element_class_get_pad_template (klass, "video_%u");
caps = gst_matroska_demux_video_caps (videocontext,
context->codec_id, context->codec_priv,
context->codec_priv_size, &codec, &riff_fourcc);
GstMatroskaTrackAudioContext *audiocontext =
(GstMatroskaTrackAudioContext *) context;
- padname = g_strdup_printf ("audio_%u", demux->num_a_streams++);
- templ = gst_element_class_get_pad_template (klass, "audio_%u");
caps = gst_matroska_demux_audio_caps (audiocontext,
context->codec_id, context->codec_priv, context->codec_priv_size,
&codec, &riff_audio_fmt);
GstMatroskaTrackSubtitleContext *subtitlecontext =
(GstMatroskaTrackSubtitleContext *) context;
- padname = g_strdup_printf ("subtitle_%u", demux->num_t_streams++);
- templ = gst_element_class_get_pad_template (klass, "subtitle_%u");
caps = gst_matroska_demux_subtitle_caps (subtitlecontext,
context->codec_id, context->codec_priv, context->codec_priv_size);
break;
lang = gst_tag_get_language_code (context->language);
gst_tag_list_add (context->tags, GST_TAG_MERGE_REPLACE,
GST_TAG_LANGUAGE_CODE, (lang) ? lang : context->language, NULL);
+
+ if (context->name) {
+ gst_tag_list_add (context->tags, GST_TAG_MERGE_REPLACE,
+ GST_TAG_TITLE, context->name, NULL);
+ }
context->tags_changed = TRUE;
}
context->stream_headers, caps);
}
+ if (context->encodings) {
+ GstMatroskaTrackEncoding *enc;
+ guint i;
+
+ for (i = 0; i < context->encodings->len; i++) {
+ enc = &g_array_index (context->encodings, GstMatroskaTrackEncoding, i);
+ if (enc->type == GST_MATROSKA_ENCODING_ENCRYPTION /* encryption */ ) {
+ GstStructure *s = gst_caps_get_structure (caps, 0);
+ if (!gst_structure_has_name (s, "application/x-webm-enc")) {
+ gst_structure_set (s, "original-media-type", G_TYPE_STRING,
+ gst_structure_get_name (s), NULL);
+ gst_structure_set (s, "encryption-algorithm", G_TYPE_STRING,
+ gst_matroska_track_encryption_algorithm_name (enc->enc_algo),
+ NULL);
+ gst_structure_set (s, "encoding-scope", G_TYPE_STRING,
+ gst_matroska_track_encoding_scope_name (enc->scope), NULL);
+ gst_structure_set (s, "cipher-mode", G_TYPE_STRING,
+ gst_matroska_track_encryption_cipher_mode_name
+ (enc->enc_cipher_mode), NULL);
+ gst_structure_set_name (s, "application/x-webm-enc");
+ }
+ }
+ }
+ }
+
+ context->caps = caps;
+
+ /* tadaah! */
+ *dest_context = context;
+ return ret;
+}
+
+static void
+gst_matroska_demux_add_stream (GstMatroskaDemux * demux,
+ GstMatroskaTrackContext * context)
+{
+ GstElementClass *klass = GST_ELEMENT_GET_CLASS (demux);
+ gchar *padname = NULL;
+ GstPadTemplate *templ = NULL;
+ GstStreamFlags stream_flags;
+
+ GstEvent *stream_start;
+
+ gchar *stream_id;
+
+ g_ptr_array_add (demux->common.src, context);
+ context->index = demux->common.num_streams++;
+ g_assert (demux->common.src->len == demux->common.num_streams);
+ g_ptr_array_index (demux->common.src, demux->common.num_streams - 1) =
+ context;
+
+ /* now create the GStreamer connectivity */
+ switch (context->type) {
+ case GST_MATROSKA_TRACK_TYPE_VIDEO:
+ padname = g_strdup_printf ("video_%u", demux->num_v_streams++);
+ templ = gst_element_class_get_pad_template (klass, "video_%u");
+
+ if (!context->intra_only)
+ demux->have_nonintraonly_v_streams = TRUE;
+ break;
+
+ case GST_MATROSKA_TRACK_TYPE_AUDIO:
+ padname = g_strdup_printf ("audio_%u", demux->num_a_streams++);
+ templ = gst_element_class_get_pad_template (klass, "audio_%u");
+ break;
+
+ case GST_MATROSKA_TRACK_TYPE_SUBTITLE:
+ padname = g_strdup_printf ("subtitle_%u", demux->num_t_streams++);
+ templ = gst_element_class_get_pad_template (klass, "subtitle_%u");
+ break;
+
+ default:
+ /* we should already have quit by now */
+ g_assert_not_reached ();
+ }
+
/* the pad in here */
context->pad = gst_pad_new_from_template (templ, padname);
- context->caps = caps;
gst_pad_set_event_function (context->pad,
GST_DEBUG_FUNCPTR (gst_matroska_demux_handle_src_event));
GST_DEBUG_FUNCPTR (gst_matroska_demux_handle_src_query));
GST_INFO_OBJECT (demux, "Adding pad '%s' with caps %" GST_PTR_FORMAT,
- padname, caps);
+ padname, context->caps);
gst_pad_set_element_private (context->pad, context);
stream_id =
gst_pad_create_stream_id_printf (context->pad, GST_ELEMENT_CAST (demux),
- "%03" G_GUINT64_FORMAT, context->uid);
+ "%03" G_GUINT64_FORMAT ":%03" G_GUINT64_FORMAT,
+ context->num, context->uid);
stream_start =
gst_pad_get_sticky_event (demux->common.sinkpad, GST_EVENT_STREAM_START,
0);
stream_flags |= GST_STREAM_FLAG_SPARSE;
if (context->flags & GST_MATROSKA_TRACK_DEFAULT)
stream_flags |= GST_STREAM_FLAG_SELECT;
+ else if (!(context->flags & GST_MATROSKA_TRACK_ENABLED))
+ stream_flags |= GST_STREAM_FLAG_UNSELECT;
+
gst_event_set_stream_flags (stream_start, stream_flags);
gst_pad_push_event (context->pad, stream_start);
gst_pad_set_caps (context->pad, context->caps);
gst_flow_combiner_add_pad (demux->flowcombiner, context->pad);
g_free (padname);
-
- /* tadaah! */
- return ret;
}
static gboolean
g_return_val_if_fail (event != NULL, FALSE);
if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) {
+ /* no seeking until we are (safely) ready */
+ if (demux->common.state != GST_MATROSKA_READ_STATE_DATA) {
+ GST_DEBUG_OBJECT (demux,
+ "not ready for seeking yet, deferring seek: %" GST_PTR_FORMAT, event);
+ if (demux->deferred_seek_event)
+ gst_event_unref (demux->deferred_seek_event);
+ demux->deferred_seek_event = event;
+ demux->deferred_seek_pad = NULL;
+ return TRUE;
+ }
res = gst_matroska_demux_handle_seek_event (demux, NULL, event);
} else {
GST_WARNING_OBJECT (demux, "Unhandled event of type %s",
/* searches for a cluster start from @pos,
* return GST_FLOW_OK and cluster position in @pos if found */
static GstFlowReturn
-gst_matroska_demux_search_cluster (GstMatroskaDemux * demux, gint64 * pos)
+gst_matroska_demux_search_cluster (GstMatroskaDemux * demux, gint64 * pos,
+ gboolean forward)
{
gint64 newpos = *pos;
gint64 orig_offset;
GstFlowReturn ret = GST_FLOW_OK;
- const guint chunk = 64 * 1024;
+ const guint chunk = 128 * 1024;
GstBuffer *buf = NULL;
GstMapInfo map;
gpointer data = NULL;
orig_offset = demux->common.offset;
- GST_LOG_OBJECT (demux, "searching cluster following offset %" G_GINT64_FORMAT,
- *pos);
+ GST_LOG_OBJECT (demux, "searching cluster %s offset %" G_GINT64_FORMAT,
+ forward ? "following" : "preceding", *pos);
if (demux->clusters) {
gint64 *cpos;
cpos = gst_util_array_binary_search (demux->clusters->data,
demux->clusters->len, sizeof (gint64),
(GCompareDataFunc) gst_matroska_cluster_compare,
- GST_SEARCH_MODE_AFTER, pos, NULL);
+ forward ? GST_SEARCH_MODE_AFTER : GST_SEARCH_MODE_BEFORE, pos, NULL);
/* sanity check */
if (cpos) {
GST_DEBUG_OBJECT (demux,
while (1) {
GstByteReader reader;
gint cluster_pos;
+ guint toread = chunk;
+ if (!forward) {
+ /* never read beyond the requested target */
+ if (G_UNLIKELY (newpos < chunk)) {
+ toread = newpos;
+ newpos = 0;
+ } else {
+ newpos -= chunk;
+ }
+ }
if (buf != NULL) {
gst_buffer_unmap (buf, &map);
gst_buffer_unref (buf);
buf = NULL;
}
- ret = gst_pad_pull_range (demux->common.sinkpad, newpos, chunk, &buf);
+ ret = gst_pad_pull_range (demux->common.sinkpad, newpos, toread, &buf);
if (ret != GST_FLOW_OK)
break;
GST_DEBUG_OBJECT (demux,
}
gst_byte_reader_init (&reader, data, size);
- resume:
- cluster_pos = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff,
- GST_MATROSKA_ID_CLUSTER, 0, gst_byte_reader_get_remaining (&reader));
+ cluster_pos = -1;
+ while (1) {
+ gint found = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff,
+ GST_MATROSKA_ID_CLUSTER, 0, gst_byte_reader_get_remaining (&reader));
+ if (forward) {
+ cluster_pos = found;
+ break;
+ }
+ /* need last occurrence when searching backwards */
+ if (found >= 0) {
+ cluster_pos = gst_byte_reader_get_pos (&reader) + found;
+ gst_byte_reader_skip (&reader, found + 4);
+ } else {
+ break;
+ }
+ }
+
if (cluster_pos >= 0) {
newpos += cluster_pos;
- /* prepare resuming at next byte */
- if (!gst_byte_reader_skip (&reader, cluster_pos + 1)) {
- GST_DEBUG_OBJECT (demux, "Need more data -> continue");
- continue;
- }
GST_DEBUG_OBJECT (demux,
"found cluster ebml id at offset %" G_GINT64_FORMAT, newpos);
/* extra checks whether we really sync'ed to a cluster:
GST_DEBUG_OBJECT (demux, "cluster is first cluster -> OK");
break;
}
- demux->common.offset = newpos;
- ret = gst_matroska_read_common_peek_id_length_pull (&demux->common,
- GST_ELEMENT_CAST (demux), &id, &length, &needed);
- if (ret != GST_FLOW_OK) {
- GST_DEBUG_OBJECT (demux, "need more data -> continue");
- continue;
- }
- g_assert (id == GST_MATROSKA_ID_CLUSTER);
- GST_DEBUG_OBJECT (demux, "cluster size %" G_GUINT64_FORMAT ", prefix %d",
- length, needed);
- /* ok if undefined length or first cluster */
- if (length == GST_EBML_SIZE_UNKNOWN || length == G_MAXUINT64) {
- GST_DEBUG_OBJECT (demux, "cluster has undefined length -> OK");
+ demux->common.offset = newpos;
+ ret = gst_matroska_read_common_peek_id_length_pull (&demux->common,
+ GST_ELEMENT_CAST (demux), &id, &length, &needed);
+ if (ret != GST_FLOW_OK) {
+ GST_DEBUG_OBJECT (demux, "need more data -> continue");
+ goto next;
+ }
+ g_assert (id == GST_MATROSKA_ID_CLUSTER);
+ GST_DEBUG_OBJECT (demux, "cluster size %" G_GUINT64_FORMAT ", prefix %d",
+ length, needed);
+ /* ok if undefined length or first cluster */
+ if (length == GST_EBML_SIZE_UNKNOWN || length == G_MAXUINT64) {
+ GST_DEBUG_OBJECT (demux, "cluster has undefined length -> OK");
+ break;
+ }
+ /* skip cluster */
+ demux->common.offset += length + needed;
+ ret = gst_matroska_read_common_peek_id_length_pull (&demux->common,
+ GST_ELEMENT_CAST (demux), &id, &length, &needed);
+ if (ret != GST_FLOW_OK)
+ goto next;
+ GST_DEBUG_OBJECT (demux, "next element is %scluster",
+ id == GST_MATROSKA_ID_CLUSTER ? "" : "not ");
+ if (id == GST_MATROSKA_ID_CLUSTER)
+ break;
+ next:
+ if (forward)
+ newpos += 1;
+ } else {
+ /* partial cluster id may have been in tail of buffer */
+ newpos +=
+ forward ? MAX (gst_byte_reader_get_remaining (&reader), 4) - 3 : 3;
+ }
+ }
+
+ if (buf) {
+ gst_buffer_unmap (buf, &map);
+ gst_buffer_unref (buf);
+ buf = NULL;
+ }
+
+exit:
+ demux->common.offset = orig_offset;
+ *pos = newpos;
+ return ret;
+}
+
+/* Three states to express: starts with I-frame, starts with delta, don't know */
+typedef enum
+{
+ CLUSTER_STATUS_NONE = 0,
+ CLUSTER_STATUS_STARTS_WITH_KEYFRAME,
+ CLUSTER_STATUS_STARTS_WITH_DELTAUNIT,
+} ClusterStatus;
+
+typedef struct
+{
+ guint64 offset;
+ guint64 size;
+ guint64 prev_size;
+ GstClockTime time;
+ ClusterStatus status;
+} ClusterInfo;
+
+static const gchar *
+cluster_status_get_nick (ClusterStatus status)
+{
+ switch (status) {
+ case CLUSTER_STATUS_NONE:
+ return "none";
+ case CLUSTER_STATUS_STARTS_WITH_KEYFRAME:
+ return "key";
+ case CLUSTER_STATUS_STARTS_WITH_DELTAUNIT:
+ return "delta";
+ }
+ return "???";
+}
+
+/* Skip ebml-coded number:
+ * 1xxx.. = 1 byte
+ * 01xx.. = 2 bytes
+ * 001x.. = 3 bytes, etc.
+ */
+static gboolean
+bit_reader_skip_ebml_num (GstBitReader * br)
+{
+ guint8 i, v = 0;
+
+ if (!gst_bit_reader_peek_bits_uint8 (br, &v, 8))
+ return FALSE;
+
+ for (i = 0; i < 8; i++) {
+ if ((v & (0x80 >> i)) != 0)
+ break;
+ }
+ return gst_bit_reader_skip (br, (i + 1) * 8);
+}
+
+/* Don't probe more than that many bytes into the cluster for keyframe info
+ * (random value, mostly for sanity checking) */
+#define MAX_CLUSTER_INFO_PROBE_LENGTH 256
+
+static gboolean
+gst_matroska_demux_peek_cluster_info (GstMatroskaDemux * demux,
+ ClusterInfo * cluster, guint64 offset)
+{
+ demux->common.offset = offset;
+ demux->cluster_time = GST_CLOCK_TIME_NONE;
+
+ cluster->offset = offset;
+ cluster->size = 0;
+ cluster->prev_size = 0;
+ cluster->time = GST_CLOCK_TIME_NONE;
+ cluster->status = CLUSTER_STATUS_NONE;
+
+ /* parse first few elements in cluster */
+ do {
+ GstFlowReturn flow;
+ guint64 length;
+ guint32 id;
+ guint needed;
+
+ flow = gst_matroska_read_common_peek_id_length_pull (&demux->common,
+ GST_ELEMENT_CAST (demux), &id, &length, &needed);
+
+ if (flow != GST_FLOW_OK)
+ break;
+
+ GST_LOG_OBJECT (demux, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, "
+ "size %" G_GUINT64_FORMAT ", needed %d", demux->common.offset, id,
+ length, needed);
+
+ /* Reached start of next cluster without finding data, stop processing */
+ if (id == GST_MATROSKA_ID_CLUSTER && cluster->offset != offset)
+ break;
+
+ /* Not going to parse into these for now, stop processing */
+ if (id == GST_MATROSKA_ID_ENCRYPTEDBLOCK
+ || id == GST_MATROSKA_ID_BLOCKGROUP || id == GST_MATROSKA_ID_BLOCK)
+ break;
+
+ /* SimpleBlock: peek at headers to check if it's a keyframe */
+ if (id == GST_MATROSKA_ID_SIMPLEBLOCK) {
+ GstBitReader br;
+ guint8 *d, hdr_len, v = 0;
+
+ GST_DEBUG_OBJECT (demux, "SimpleBlock found");
+
+ /* SimpleBlock header is max. 21 bytes */
+ hdr_len = MIN (21, length);
+
+ flow = gst_matroska_read_common_peek_bytes (&demux->common,
+ demux->common.offset, hdr_len, NULL, &d);
+
+ if (flow != GST_FLOW_OK)
+ break;
+
+ gst_bit_reader_init (&br, d, hdr_len);
+
+ /* skip prefix: ebml id (SimpleBlock) + element length */
+ if (!gst_bit_reader_skip (&br, 8 * needed))
+ break;
+
+ /* skip track number (ebml coded) */
+ if (!bit_reader_skip_ebml_num (&br))
+ break;
+
+ /* skip Timecode */
+ if (!gst_bit_reader_skip (&br, 16))
+ break;
+
+ /* read flags */
+ if (!gst_bit_reader_get_bits_uint8 (&br, &v, 8))
+ break;
+
+ if ((v & 0x80) != 0)
+ cluster->status = CLUSTER_STATUS_STARTS_WITH_KEYFRAME;
+ else
+ cluster->status = CLUSTER_STATUS_STARTS_WITH_DELTAUNIT;
+
+ break;
+ }
+
+ flow = gst_matroska_demux_parse_id (demux, id, length, needed);
+
+ if (flow != GST_FLOW_OK)
+ break;
+
+ switch (id) {
+ case GST_MATROSKA_ID_CLUSTER:
+ if (length == G_MAXUINT64)
+ cluster->size = 0;
+ else
+ cluster->size = length + needed;
+ break;
+ case GST_MATROSKA_ID_PREVSIZE:
+ cluster->prev_size = demux->cluster_prevsize;
+ break;
+ case GST_MATROSKA_ID_CLUSTERTIMECODE:
+ cluster->time = demux->cluster_time * demux->common.time_scale;
+ break;
+ case GST_MATROSKA_ID_SILENTTRACKS:
+ /* ignore and continue */
+ break;
+ default:
+ GST_WARNING_OBJECT (demux, "Unknown ebml id 0x%08x (possibly garbage), "
+ "bailing out", id);
+ goto out;
+ }
+ } while (demux->common.offset - offset < MAX_CLUSTER_INFO_PROBE_LENGTH);
+
+out:
+
+ GST_INFO_OBJECT (demux, "Cluster @ %" G_GUINT64_FORMAT ": "
+ "time %" GST_TIME_FORMAT ", size %" G_GUINT64_FORMAT ", "
+ "prev_size %" G_GUINT64_FORMAT ", %s", cluster->offset,
+ GST_TIME_ARGS (cluster->time), cluster->size, cluster->prev_size,
+ cluster_status_get_nick (cluster->status));
+
+ /* return success as long as we could extract the minimum useful information */
+ return cluster->time != GST_CLOCK_TIME_NONE;
+}
+
+/* returns TRUE if the cluster offset was updated */
+static gboolean
+gst_matroska_demux_scan_back_for_keyframe_cluster (GstMatroskaDemux * demux,
+ gint64 * cluster_offset, GstClockTime * cluster_time)
+{
+ GstClockTime stream_start_time = demux->stream_start_time;
+ guint64 first_cluster_offset = demux->first_cluster_offset;
+ gint64 off = *cluster_offset;
+ ClusterInfo cluster = { 0, };
+
+ GST_INFO_OBJECT (demux, "Checking if cluster starts with keyframe");
+ while (off > first_cluster_offset) {
+ if (!gst_matroska_demux_peek_cluster_info (demux, &cluster, off)) {
+ GST_LOG_OBJECT (demux,
+ "Couldn't get info on cluster @ %" G_GUINT64_FORMAT, off);
+ break;
+ }
+
+ /* Keyframe? Then we're done */
+ if (cluster.status == CLUSTER_STATUS_STARTS_WITH_KEYFRAME) {
+ GST_LOG_OBJECT (demux,
+ "Found keyframe at start of cluster @ %" G_GUINT64_FORMAT, off);
+ break;
+ }
+
+ /* We only scan back if we *know* we landed on a cluster that
+ * starts with a delta frame. */
+ if (cluster.status != CLUSTER_STATUS_STARTS_WITH_DELTAUNIT) {
+ GST_LOG_OBJECT (demux,
+ "No delta frame at start of cluster @ %" G_GUINT64_FORMAT, off);
+ break;
+ }
+
+ GST_DEBUG_OBJECT (demux, "Cluster starts with delta frame, backtracking");
+
+ /* Don't scan back more than this much in time from the cluster we
+ * originally landed on. This is mostly a sanity check in case a file
+ * always has keyframes in the middle of clusters and never at the
+ * beginning. Without this we would always scan back to the beginning
+ * of the file in that case. */
+ if (cluster.time != GST_CLOCK_TIME_NONE) {
+ GstClockTimeDiff distance = GST_CLOCK_DIFF (cluster.time, *cluster_time);
+
+ if (distance < 0 || distance > demux->max_backtrack_distance * GST_SECOND) {
+ GST_DEBUG_OBJECT (demux, "Haven't found cluster with keyframe within "
+ "%u secs of original seek target cluster, stopping",
+ demux->max_backtrack_distance);
+ break;
+ }
+ }
+
+ /* If we have cluster prev_size we can skip back efficiently. If not,
+ * we'll just do a brute force search for a cluster identifier */
+ if (cluster.prev_size > 0 && off >= cluster.prev_size) {
+ off -= cluster.prev_size;
+ } else {
+ GstFlowReturn flow;
+
+ GST_LOG_OBJECT (demux, "Cluster has no or invalid prev size, searching "
+ "for previous cluster instead then");
+
+ flow = gst_matroska_demux_search_cluster (demux, &off, FALSE);
+ if (flow != GST_FLOW_OK) {
+ GST_DEBUG_OBJECT (demux, "cluster search yielded flow %s, stopping",
+ gst_flow_get_name (flow));
break;
}
- /* skip cluster */
- demux->common.offset += length + needed;
- ret = gst_matroska_read_common_peek_id_length_pull (&demux->common,
- GST_ELEMENT_CAST (demux), &id, &length, &needed);
- if (ret != GST_FLOW_OK)
- goto resume;
- GST_DEBUG_OBJECT (demux, "next element is %scluster",
- id == GST_MATROSKA_ID_CLUSTER ? "" : "not ");
- if (id == GST_MATROSKA_ID_CLUSTER)
- break;
- /* not ok, resume */
- goto resume;
- } else {
- /* partial cluster id may have been in tail of buffer */
- newpos += MAX (gst_byte_reader_get_remaining (&reader), 4) - 3;
}
+
+ if (off <= first_cluster_offset) {
+ GST_LOG_OBJECT (demux, "Reached first cluster, stopping");
+ *cluster_offset = first_cluster_offset;
+ *cluster_time = stream_start_time;
+ return TRUE;
+ }
+ GST_LOG_OBJECT (demux, "Trying prev cluster @ %" G_GUINT64_FORMAT, off);
}
- if (buf) {
- gst_buffer_unmap (buf, &map);
- gst_buffer_unref (buf);
- buf = NULL;
+ /* If we found a cluster starting with a keyframe jump to that instead,
+ * otherwise leave everything as it was before */
+ if (cluster.time != GST_CLOCK_TIME_NONE
+ && (cluster.offset == first_cluster_offset
+ || cluster.status == CLUSTER_STATUS_STARTS_WITH_KEYFRAME)) {
+ *cluster_offset = cluster.offset;
+ *cluster_time = cluster.time;
+ return TRUE;
}
-exit:
- demux->common.offset = orig_offset;
- *pos = newpos;
- return ret;
+ return FALSE;
}
/* bisect and scan through file for cluster starting before @time,
GstMatroskaIndex *entry = NULL;
GstMatroskaReadState current_state;
GstClockTime otime, prev_cluster_time, current_cluster_time, cluster_time;
- gint64 opos, newpos, startpos = 0, current_offset;
+ GstClockTime atime;
+ gint64 opos, newpos, current_offset;
gint64 prev_cluster_offset = -1, current_cluster_offset, cluster_offset;
- const guint chunk = 64 * 1024;
+ gint64 apos, maxpos;
+ guint64 cluster_size = 0;
GstFlowReturn ret;
guint64 length;
guint32 id;
guint needed;
- /* (under)estimate new position, resync using cluster ebml id,
- * and scan forward to appropriate cluster
- * (and re-estimate if need to go backward) */
+ /* estimate new position, resync using cluster ebml id,
+ * and bisect further or scan forward to appropriate cluster */
- prev_cluster_time = GST_CLOCK_TIME_NONE;
-
- /* store some current state */
+ /* save some current global state which will be touched by our scanning */
current_state = demux->common.state;
g_return_val_if_fail (current_state == GST_MATROSKA_READ_STATE_DATA, NULL);
demux->common.state = GST_MATROSKA_READ_STATE_SCANNING;
- /* estimate using start and current position */
+ /* estimate using start and last known cluster */
GST_OBJECT_LOCK (demux);
- opos = demux->common.offset - demux->common.ebml_segment_start;
- otime = demux->common.segment.position;
+ apos = demux->first_cluster_offset;
+ atime = demux->stream_start_time;
+ opos = demux->last_cluster_offset;
+ otime = demux->stream_last_time;
GST_OBJECT_UNLOCK (demux);
/* sanitize */
- time = MAX (time, demux->stream_start_time);
+ time = MAX (time, atime);
+ otime = MAX (otime, atime);
+ opos = MAX (opos, apos);
- /* avoid division by zero in first estimation below */
- if (otime <= demux->stream_start_time)
- otime = time;
+ maxpos = gst_matroska_read_common_get_length (&demux->common);
+
+ /* invariants;
+ * apos <= opos
+ * atime <= otime
+ * apos always refer to a cluster before target time;
+ * opos may or may not be after target time, but if it is once so,
+ * then also in next iteration
+ * */
retry:
GST_LOG_OBJECT (demux,
+ "apos: %" G_GUINT64_FORMAT ", atime: %" GST_TIME_FORMAT ", %"
+ GST_TIME_FORMAT " in stream time, "
"opos: %" G_GUINT64_FORMAT ", otime: %" GST_TIME_FORMAT ", %"
GST_TIME_FORMAT " in stream time (start %" GST_TIME_FORMAT "), time %"
- GST_TIME_FORMAT, opos, GST_TIME_ARGS (otime),
- GST_TIME_ARGS (otime - demux->stream_start_time),
+ GST_TIME_FORMAT, apos, GST_TIME_ARGS (atime),
+ GST_TIME_ARGS (atime - demux->stream_start_time), opos,
+ GST_TIME_ARGS (otime), GST_TIME_ARGS (otime - demux->stream_start_time),
GST_TIME_ARGS (demux->stream_start_time), GST_TIME_ARGS (time));
- if (otime <= demux->stream_start_time) {
- newpos = 0;
+ g_assert (atime <= otime);
+ g_assert (apos <= opos);
+ if (time == GST_CLOCK_TIME_NONE) {
+ GST_DEBUG_OBJECT (demux, "searching last cluster");
+ newpos = maxpos;
+ if (newpos == -1) {
+ GST_DEBUG_OBJECT (demux, "unknown file size; bailing out");
+ goto exit;
+ }
+ } else if (otime <= atime) {
+ newpos = apos;
} else {
- newpos =
- gst_util_uint64_scale (opos - demux->common.ebml_segment_start,
- time - demux->stream_start_time,
- otime - demux->stream_start_time) - chunk;
- if (newpos < 0)
- newpos = 0;
+ newpos = apos +
+ gst_util_uint64_scale (opos - apos, time - atime, otime - atime);
+ if (maxpos != -1 && newpos > maxpos)
+ newpos = maxpos;
}
- /* favour undershoot */
- newpos = newpos * 90 / 100;
- newpos += demux->common.ebml_segment_start;
GST_DEBUG_OBJECT (demux,
"estimated offset for %" GST_TIME_FORMAT ": %" G_GINT64_FORMAT,
GST_TIME_ARGS (time), newpos);
- /* and at least start scanning before previous scan start to avoid looping */
- startpos = startpos * 90 / 100;
- if (startpos && startpos < newpos)
- newpos = startpos;
-
- /* read in at newpos and scan for ebml cluster id */
- startpos = newpos;
- while (1) {
-
- ret = gst_matroska_demux_search_cluster (demux, &newpos);
- if (ret == GST_FLOW_EOS) {
- /* heuristic HACK */
- newpos = startpos * 80 / 100;
- GST_DEBUG_OBJECT (demux, "EOS; "
- "new estimated offset for %" GST_TIME_FORMAT ": %" G_GINT64_FORMAT,
- GST_TIME_ARGS (time), newpos);
- startpos = newpos;
- continue;
- } else if (ret != GST_FLOW_OK) {
+ /* search backwards */
+ if (newpos > apos) {
+ ret = gst_matroska_demux_search_cluster (demux, &newpos, FALSE);
+ if (ret != GST_FLOW_OK)
goto exit;
- } else {
- break;
- }
}
/* then start scanning and parsing for cluster time,
- * re-estimate if overshoot, otherwise next cluster and so on */
+ * re-estimate if possible, otherwise next cluster and so on */
+ /* note that each re-estimate is entered with a change in apos or opos,
+ * avoiding infinite loop */
demux->common.offset = newpos;
demux->cluster_time = cluster_time = GST_CLOCK_TIME_NONE;
+ cluster_size = 0;
+ prev_cluster_time = GST_CLOCK_TIME_NONE;
while (1) {
- guint64 cluster_size = 0;
-
/* peek and parse some elements */
ret = gst_matroska_read_common_peek_id_length_pull (&demux->common,
GST_ELEMENT_CAST (demux), &id, &length, &needed);
GST_DEBUG_OBJECT (demux, "found cluster at offset %" G_GINT64_FORMAT
" with time %" GST_TIME_FORMAT, cluster_offset,
GST_TIME_ARGS (cluster_time));
+ if (time == GST_CLOCK_TIME_NONE) {
+ GST_DEBUG_OBJECT (demux, "found last cluster");
+ prev_cluster_time = cluster_time;
+ prev_cluster_offset = cluster_offset;
+ break;
+ }
if (cluster_time > time) {
GST_DEBUG_OBJECT (demux, "overshot target");
/* cluster overshoots */
goto retry;
}
} else {
+ /* cluster undershoots */
+ GST_DEBUG_OBJECT (demux, "undershot target");
+ /* ok if close enough */
+ if (GST_CLOCK_DIFF (cluster_time, time) < 5 * GST_SECOND) {
+ GST_DEBUG_OBJECT (demux, "target close enough");
+ prev_cluster_time = cluster_time;
+ prev_cluster_offset = cluster_offset;
+ break;
+ }
+ if (otime > time) {
+ /* we are in between atime and otime => can bisect if worthwhile */
+ if (prev_cluster_time != GST_CLOCK_TIME_NONE &&
+ cluster_time > prev_cluster_time &&
+ (GST_CLOCK_DIFF (prev_cluster_time, cluster_time) * 10 <
+ GST_CLOCK_DIFF (cluster_time, time))) {
+ /* we moved at least one cluster forward,
+ * and it looks like target is still far away,
+ * let's estimate again */
+ GST_DEBUG_OBJECT (demux, "bisecting with new apos");
+ apos = cluster_offset;
+ atime = cluster_time;
+ goto retry;
+ }
+ }
/* cluster undershoots, goto next one */
prev_cluster_time = cluster_time;
prev_cluster_offset = cluster_offset;
goto exit;
}
+ /* In the bisect loop above we always undershoot and then jump forward
+ * cluster-by-cluster until we overshoot, so if we get here we've gone
+ * over and the previous cluster is where we need to go to. */
+ cluster_offset = prev_cluster_offset;
+ cluster_time = prev_cluster_time;
+
+ /* If we have video and can easily backtrack, check if we landed on a cluster
+ * that starts with a keyframe - and if not backtrack until we find one that
+ * does. */
+ if (demux->have_nonintraonly_v_streams && demux->max_backtrack_distance > 0) {
+ if (gst_matroska_demux_scan_back_for_keyframe_cluster (demux,
+ &cluster_offset, &cluster_time)) {
+ GST_INFO_OBJECT (demux, "Adjusted cluster to %" GST_TIME_FORMAT " @ "
+ "%" G_GUINT64_FORMAT, GST_TIME_ARGS (cluster_time), cluster_offset);
+ }
+ }
+
entry = g_new0 (GstMatroskaIndex, 1);
- entry->time = prev_cluster_time;
- entry->pos = prev_cluster_offset - demux->common.ebml_segment_start;
+ entry->time = cluster_time;
+ entry->pos = cluster_offset - demux->common.ebml_segment_start;
GST_DEBUG_OBJECT (demux, "simulated index entry; time %" GST_TIME_FORMAT
", pos %" G_GUINT64_FORMAT, GST_TIME_ARGS (entry->time), entry->pos);
}
finish:
- if (keyunit) {
+ if (keyunit && seeksegment.rate > 0) {
GST_DEBUG_OBJECT (demux, "seek to key unit, adjusting segment start from %"
GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
GST_TIME_ARGS (seeksegment.start), GST_TIME_ARGS (entry->time));
seeksegment.start = MAX (entry->time, demux->stream_start_time);
seeksegment.position = seeksegment.start;
seeksegment.time = seeksegment.start - demux->stream_start_time;
+ } else if (keyunit) {
+ GST_DEBUG_OBJECT (demux, "seek to key unit, adjusting segment stop from %"
+ GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (seeksegment.stop), GST_TIME_ARGS (entry->time));
+ seeksegment.stop = MAX (entry->time, demux->stream_start_time);
+ seeksegment.position = seeksegment.stop;
}
if (demux->streaming) {
/* upstream takes care of flushing and all that
* ... and newsegment event handling takes care of the rest */
return perform_seek_to_offset (demux, rate,
- entry->pos + demux->common.ebml_segment_start, seqnum);
+ entry->pos + demux->common.ebml_segment_start, seqnum, flags);
}
exit:
demux->to_time = demux->common.segment.position;
else
demux->to_time = GST_CLOCK_TIME_NONE;
+ demux->segment_seqnum = seqnum;
GST_OBJECT_UNLOCK (demux);
/* restart our task since it might have been stopped when we did the
/* seek to the first subindex or legacy index */
GST_INFO_OBJECT (demux, "Seeking to Cues at %" G_GUINT64_FORMAT, offset);
return perform_seek_to_offset (demux, rate, offset,
- gst_event_get_seqnum (event));
+ gst_event_get_seqnum (event), GST_SEEK_FLAG_NONE);
}
/* well, we are handling it already */
case GST_EVENT_SEEK:
/* no seeking until we are (safely) ready */
if (demux->common.state != GST_MATROSKA_READ_STATE_DATA) {
- GST_DEBUG_OBJECT (demux, "not ready for seeking yet");
- return FALSE;
+ GST_DEBUG_OBJECT (demux,
+ "not ready for seeking yet, deferring seek event: %" GST_PTR_FORMAT,
+ event);
+ if (demux->deferred_seek_event)
+ gst_event_unref (demux->deferred_seek_event);
+ demux->deferred_seek_event = event;
+ demux->deferred_seek_pad = pad;
+ return TRUE;
+ }
+
+ {
+ guint32 seqnum = gst_event_get_seqnum (event);
+ if (seqnum == demux->segment_seqnum) {
+ GST_LOG_OBJECT (pad,
+ "Drop duplicated SEEK event seqnum %" G_GUINT32_FORMAT, seqnum);
+ gst_event_unref (event);
+ return TRUE;
+ }
}
+
if (!demux->streaming)
res = gst_matroska_demux_handle_seek_event (demux, pad, event);
else
return res;
}
+static gboolean
+gst_matroska_demux_handle_sink_query (GstPad * pad, GstObject * parent,
+ GstQuery * query)
+{
+ GstMatroskaDemux *demux = GST_MATROSKA_DEMUX (parent);
+ gboolean res = FALSE;
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_BITRATE:
+ {
+ if (G_UNLIKELY (demux->cached_length == G_MAXUINT64 ||
+ demux->common.offset >= demux->cached_length)) {
+ demux->cached_length =
+ gst_matroska_read_common_get_length (&demux->common);
+ }
+
+ if (demux->cached_length < G_MAXUINT64
+ && demux->common.segment.duration > 0) {
+ /* TODO: better results based on ranges/index tables */
+ guint bitrate =
+ gst_util_uint64_scale (8 * demux->cached_length, GST_SECOND,
+ demux->common.segment.duration);
+
+ GST_LOG_OBJECT (demux, "bitrate query byte length: %" G_GUINT64_FORMAT
+ " duration %" GST_TIME_FORMAT " resulting in a bitrate of %u",
+ demux->cached_length,
+ GST_TIME_ARGS (demux->common.segment.duration), bitrate);
+
+ gst_query_set_bitrate (query, bitrate);
+ res = TRUE;
+ }
+ break;
+ }
+ default:
+ res = gst_pad_query_default (pad, (GstObject *) demux, query);
+ break;
+ }
+
+ return res;
+}
+
static GstFlowReturn
gst_matroska_demux_seek_to_previous_keyframe (GstMatroskaDemux * demux)
{
switch (id) {
/* one track within the "all-tracks" header */
- case GST_MATROSKA_ID_TRACKENTRY:
- ret = gst_matroska_demux_add_stream (demux, ebml);
+ case GST_MATROSKA_ID_TRACKENTRY:{
+ GstMatroskaTrackContext *track;
+ ret = gst_matroska_demux_parse_stream (demux, ebml, &track);
+ if (track != NULL) {
+ if (gst_matroska_read_common_tracknumber_unique (&demux->common,
+ track->num)) {
+ gst_matroska_demux_add_stream (demux, track);
+ } else {
+ GST_ERROR_OBJECT (demux,
+ "TrackNumber %" G_GUINT64_FORMAT " is not unique", track->num);
+ ret = GST_FLOW_ERROR;
+ gst_matroska_track_free (track);
+ track = NULL;
+ }
+ }
break;
+ }
default:
ret = gst_matroska_read_common_parse_skip (&demux->common, ebml,
DEBUG_ELEMENT_STOP (demux, ebml, "Tracks", ret);
demux->tracks_parsed = TRUE;
+ GST_DEBUG_OBJECT (demux, "signaling no more pads");
+ gst_element_no_more_pads (GST_ELEMENT (demux));
+
+ return ret;
+}
+
+static GstFlowReturn
+gst_matroska_demux_update_tracks (GstMatroskaDemux * demux, GstEbmlRead * ebml)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+ guint num_tracks_found = 0;
+ guint32 id;
+
+ GST_INFO_OBJECT (demux, "Reparsing Tracks element");
+
+ DEBUG_ELEMENT_START (demux, ebml, "Tracks");
+
+ if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) {
+ DEBUG_ELEMENT_STOP (demux, ebml, "Tracks", ret);
+ return ret;
+ }
+
+ while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) {
+ if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK)
+ break;
+
+ switch (id) {
+ /* one track within the "all-tracks" header */
+ case GST_MATROSKA_ID_TRACKENTRY:{
+ GstMatroskaTrackContext *new_track;
+ gint old_track_index;
+ GstMatroskaTrackContext *old_track;
+ ret = gst_matroska_demux_parse_stream (demux, ebml, &new_track);
+ if (new_track == NULL)
+ break;
+ num_tracks_found++;
+
+ if (gst_matroska_read_common_tracknumber_unique (&demux->common,
+ new_track->num)) {
+ GST_ERROR_OBJECT (demux,
+ "Unexpected new TrackNumber: %" G_GUINT64_FORMAT, new_track->num);
+ goto track_mismatch_error;
+ }
+
+ old_track_index =
+ gst_matroska_read_common_stream_from_num (&demux->common,
+ new_track->num);
+ g_assert (old_track_index != -1);
+ old_track = g_ptr_array_index (demux->common.src, old_track_index);
+
+ if (old_track->type != new_track->type) {
+ GST_ERROR_OBJECT (demux,
+ "Mismatch reparsing track %" G_GUINT64_FORMAT
+ " on track type. Expected %d, found %d", new_track->num,
+ old_track->type, new_track->type);
+ goto track_mismatch_error;
+ }
+
+ if (g_strcmp0 (old_track->codec_id, new_track->codec_id) != 0) {
+ GST_ERROR_OBJECT (demux,
+ "Mismatch reparsing track %" G_GUINT64_FORMAT
+ " on codec id. Expected '%s', found '%s'", new_track->num,
+ old_track->codec_id, new_track->codec_id);
+ goto track_mismatch_error;
+ }
+
+ /* The new track matches the old track. No problems on our side.
+ * Let's make it replace the old track. */
+ new_track->pad = old_track->pad;
+ new_track->index = old_track->index;
+ new_track->pos = old_track->pos;
+ g_ptr_array_index (demux->common.src, old_track_index) = new_track;
+ gst_pad_set_element_private (new_track->pad, new_track);
+
+ if (!gst_caps_is_equal (old_track->caps, new_track->caps)) {
+ gst_pad_set_caps (new_track->pad, new_track->caps);
+ }
+ gst_caps_replace (&old_track->caps, NULL);
+
+ if (!gst_tag_list_is_equal (old_track->tags, new_track->tags)) {
+ GST_DEBUG_OBJECT (old_track->pad, "Sending tags %p: %"
+ GST_PTR_FORMAT, new_track->tags, new_track->tags);
+ gst_pad_push_event (new_track->pad,
+ gst_event_new_tag (gst_tag_list_copy (new_track->tags)));
+ }
+
+ gst_matroska_track_free (old_track);
+ break;
+
+ track_mismatch_error:
+ gst_matroska_track_free (new_track);
+ new_track = NULL;
+ ret = GST_FLOW_ERROR;
+ break;
+ }
+
+ default:
+ ret = gst_matroska_read_common_parse_skip (&demux->common, ebml,
+ "Track", id);
+ break;
+ }
+ }
+ DEBUG_ELEMENT_STOP (demux, ebml, "Tracks", ret);
+
+ if (ret != GST_FLOW_ERROR && demux->common.num_streams != num_tracks_found) {
+ GST_ERROR_OBJECT (demux,
+ "Mismatch on the number of tracks. Expected %du tracks, found %du",
+ demux->common.num_streams, num_tracks_found);
+ ret = GST_FLOW_ERROR;
+ }
return ret;
}
{
gint stream_nr;
- GST_OBJECT_LOCK (demux);
-
g_assert (demux->common.num_streams == demux->common.src->len);
for (stream_nr = 0; stream_nr < demux->common.src->len; stream_nr++) {
GstMatroskaTrackContext *stream;
}
}
- GST_OBJECT_UNLOCK (demux);
}
static GstFlowReturn
GST_WRITE_UINT8 (data + 11, wvh.index_no);
GST_WRITE_UINT32_LE (data + 12, wvh.total_samples);
GST_WRITE_UINT32_LE (data + 16, wvh.block_index);
+ gst_buffer_unmap (newbuf, &outmap);
/* Append data from buf: */
gst_buffer_copy_into (newbuf, *buf, GST_BUFFER_COPY_TIMESTAMPS |
gboolean delta_unit = FALSE;
guint64 duration = 0;
gint64 lace_time = 0;
+ GstEvent *protect_event;
stream = g_ptr_array_index (demux->common.src, stream_num);
} else {
lace_time = GST_CLOCK_TIME_NONE;
}
+ /* Send the GST_PROTECTION event */
+ while ((protect_event = g_queue_pop_head (&stream->protection_event_queue))) {
+ GST_TRACE_OBJECT (demux, "pushing protection event for stream %d:%s",
+ stream->index, GST_STR_NULL (stream->name));
+ gst_pad_push_event (stream->pad, protect_event);
+ }
/* need to refresh segment info ASAP */
if (GST_CLOCK_TIME_IS_VALID (lace_time) && demux->need_segment) {
GST_DEBUG_OBJECT (demux,
"using stored seek position %" GST_TIME_FORMAT,
GST_TIME_ARGS (demux->common.segment.position));
- clace_time = demux->common.segment.position + demux->stream_start_time;
+ clace_time = demux->common.segment.position;
segment->position = GST_CLOCK_TIME_NONE;
}
segment->start = clace_time;
}
/* else duration is diff between timecode of this and next block */
- /* For SimpleBlock, look at the keyframe bit in flags. Otherwise,
- a ReferenceBlock implies that this is not a keyframe. In either
- case, it only makes sense for video streams. */
if (stream->type == GST_MATROSKA_TRACK_TYPE_VIDEO) {
+ /* For SimpleBlock, look at the keyframe bit in flags. Otherwise,
+ a ReferenceBlock implies that this is not a keyframe. In either
+ case, it only makes sense for video streams. */
if ((is_simpleblock && !(flags & 0x80)) || referenceblock) {
delta_unit = TRUE;
invisible_frame = ((flags & 0x08)) &&
(!strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VP8) ||
- !strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VP9));
+ !strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VP9) ||
+ !strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_VIDEO_AV1));
+ }
+
+ /* If we're doing a keyframe-only trickmode, only push keyframes on video
+ * streams */
+ if (delta_unit
+ && demux->common.segment.
+ flags & GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS) {
+ GST_LOG_OBJECT (demux, "Skipping non-keyframe on stream %d",
+ stream->index);
+ ret = GST_FLOW_OK;
+ goto done;
}
}
GST_BUFFER_PTS (sub) -= stream->codec_delay;
} else {
GST_BUFFER_PTS (sub) = 0;
+
+ /* Opus GstAudioClippingMeta units are scaled by 48000/sample_rate.
+ That is, if a Opus track has audio encoded at 24000 Hz and 132
+ samples need to be clipped, GstAudioClippingMeta.start will be
+ set to 264. (This is also the case for buffer offsets.)
+ Opus sample rates are always divisors of 48000 Hz, which is the
+ maximum allowed sample rate. */
start_clip =
gst_util_uint64_scale_round (stream->codec_delay, 48000,
GST_SECOND);
* search for cluster mark following current pos */
pos = demux->common.offset;
GST_WARNING_OBJECT (demux, "parse error, looking for next cluster");
- if ((ret = gst_matroska_demux_search_cluster (demux, &pos)) != GST_FLOW_OK) {
+ if ((ret = gst_matroska_demux_search_cluster (demux, &pos, TRUE)) !=
+ GST_FLOW_OK) {
/* did not work, give up */
return ret;
} else {
break;
case GST_MATROSKA_READ_STATE_SCANNING:
if (id != GST_MATROSKA_ID_CLUSTER &&
- id != GST_MATROSKA_ID_CLUSTERTIMECODE)
+ id != GST_MATROSKA_ID_PREVSIZE &&
+ id != GST_MATROSKA_ID_CLUSTERTIMECODE) {
+ if (demux->common.start_resync_offset != -1) {
+ /* we need to skip byte per byte if we are scanning for a new cluster
+ * after invalid data is found
+ */
+ read = 1;
+ }
goto skip;
+ } else {
+ if (demux->common.start_resync_offset != -1) {
+ GST_LOG_OBJECT (demux, "Resync done, new cluster found!");
+ demux->common.start_resync_offset = -1;
+ demux->common.state = demux->common.state_to_restore;
+ }
+ }
/* fall-through */
case GST_MATROSKA_READ_STATE_HEADER:
case GST_MATROSKA_READ_STATE_DATA:
case GST_MATROSKA_READ_STATE_SEEK:
switch (id) {
+ case GST_EBML_ID_HEADER:
+ GST_READ_CHECK (gst_matroska_demux_flush (demux, read));
+ demux->common.state = GST_MATROSKA_READ_STATE_SEGMENT;
+ gst_matroska_demux_check_seekability (demux);
+ break;
case GST_MATROSKA_ID_SEGMENTINFO:
if (!demux->common.segmentinfo_parsed) {
GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml));
}
break;
case GST_MATROSKA_ID_TRACKS:
+ GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml));
if (!demux->tracks_parsed) {
- GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml));
ret = gst_matroska_demux_parse_tracks (demux, &ebml);
} else {
- GST_READ_CHECK (gst_matroska_demux_flush (demux, read));
+ ret = gst_matroska_demux_update_tracks (demux, &ebml);
}
break;
case GST_MATROSKA_ID_CLUSTER:
goto no_tracks;
}
}
- if (G_UNLIKELY (demux->common.state
- == GST_MATROSKA_READ_STATE_HEADER)) {
+ if (demux->common.state == GST_MATROSKA_READ_STATE_HEADER) {
demux->common.state = GST_MATROSKA_READ_STATE_DATA;
demux->first_cluster_offset = demux->common.offset;
- GST_DEBUG_OBJECT (demux, "signaling no more pads");
- gst_element_no_more_pads (GST_ELEMENT (demux));
+
+ if (!demux->streaming &&
+ !GST_CLOCK_TIME_IS_VALID (demux->common.segment.duration)) {
+ GstMatroskaIndex *last = NULL;
+
+ GST_DEBUG_OBJECT (demux,
+ "estimating duration using last cluster");
+ if ((last = gst_matroska_demux_search_pos (demux,
+ GST_CLOCK_TIME_NONE)) != NULL) {
+ demux->last_cluster_offset =
+ last->pos + demux->common.ebml_segment_start;
+ demux->stream_last_time = last->time;
+ demux->common.segment.duration =
+ demux->stream_last_time - demux->stream_start_time;
+ /* above estimate should not be taken all too strongly */
+ demux->invalid_duration = TRUE;
+ GST_DEBUG_OBJECT (demux,
+ "estimated duration as %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (demux->common.segment.duration));
+
+ g_free (last);
+ }
+ }
+
+ /* Peek at second cluster in order to figure out if we have cluster
+ * prev_size or not (which is never set on the first cluster for
+ * obvious reasons). This is useful in case someone initiates a
+ * seek or direction change before we reach the second cluster. */
+ if (!demux->streaming) {
+ ClusterInfo cluster = { 0, };
+
+ if (gst_matroska_demux_peek_cluster_info (demux, &cluster,
+ demux->first_cluster_offset) && cluster.size > 0) {
+ gst_matroska_demux_peek_cluster_info (demux, &cluster,
+ demux->first_cluster_offset + cluster.size);
+ }
+ demux->common.offset = demux->first_cluster_offset;
+ }
+
+ if (demux->deferred_seek_event) {
+ GstEvent *seek_event;
+ GstPad *seek_pad;
+ seek_event = demux->deferred_seek_event;
+ seek_pad = demux->deferred_seek_pad;
+ demux->deferred_seek_event = NULL;
+ demux->deferred_seek_pad = NULL;
+ GST_DEBUG_OBJECT (demux,
+ "Handling deferred seek event: %" GST_PTR_FORMAT, seek_event);
+ gst_matroska_demux_handle_seek_event (demux, seek_pad,
+ seek_event);
+ gst_event_unref (seek_event);
+ }
+
/* send initial segment - we wait till we know the first
incoming timestamp, so we can properly set the start of
the segment. */
}
demux->cluster_time = GST_CLOCK_TIME_NONE;
demux->cluster_offset = demux->common.offset;
+ demux->cluster_prevsize = 0;
if (G_UNLIKELY (!demux->seek_first && demux->seek_block)) {
GST_DEBUG_OBJECT (demux, "seek target block %" G_GUINT64_FORMAT
" not found in Cluster, trying next Cluster's first block instead",
goto parse_failed;
GST_DEBUG_OBJECT (demux, "ClusterTimeCode: %" G_GUINT64_FORMAT, num);
demux->cluster_time = num;
+ /* track last cluster */
+ if (demux->cluster_offset > demux->last_cluster_offset) {
+ demux->last_cluster_offset = demux->cluster_offset;
+ demux->stream_last_time =
+ demux->cluster_time * demux->common.time_scale;
+ }
#if 0
if (demux->common.element_index) {
if (demux->common.element_index_writer_id == -1)
GST_OBJECT_UNLOCK (demux);
}
break;
+ case GST_MATROSKA_ID_PREVSIZE:{
+ guint64 num;
+
+ GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml));
+ if ((ret = gst_ebml_read_uint (&ebml, &id, &num)) != GST_FLOW_OK)
+ goto parse_failed;
+ GST_LOG_OBJECT (demux, "ClusterPrevSize: %" G_GUINT64_FORMAT, num);
+ demux->cluster_prevsize = num;
+ demux->seen_cluster_prevsize = TRUE;
+ break;
+ }
case GST_MATROSKA_ID_POSITION:
- case GST_MATROSKA_ID_PREVSIZE:
case GST_MATROSKA_ID_ENCRYPTEDBLOCK:
+ /* The WebM doesn't support the EncryptedBlock element.
+ * The Matroska spec doesn't give us more detail, how to parse this element,
+ * for example the field TransformID isn't specified yet.*/
case GST_MATROSKA_ID_SILENTTRACKS:
GST_DEBUG_OBJECT (demux,
"Skipping Cluster subelement 0x%x - ignoring", id);
}
if (demux->common.segment.flags & GST_SEEK_FLAG_SEGMENT) {
+ GstEvent *event;
+ GstMessage *msg;
gint64 stop;
/* for segment playback we need to post when (in stream time)
stop = demux->last_stop_end;
GST_LOG_OBJECT (demux, "Sending segment done, at end of segment");
- gst_element_post_message (GST_ELEMENT (demux),
- gst_message_new_segment_done (GST_OBJECT (demux), GST_FORMAT_TIME,
- stop));
- gst_matroska_demux_send_event (demux,
- gst_event_new_segment_done (GST_FORMAT_TIME, stop));
+ msg = gst_message_new_segment_done (GST_OBJECT (demux), GST_FORMAT_TIME,
+ stop);
+ if (demux->segment_seqnum)
+ gst_message_set_seqnum (msg, demux->segment_seqnum);
+ gst_element_post_message (GST_ELEMENT (demux), msg);
+
+ event = gst_event_new_segment_done (GST_FORMAT_TIME, stop);
+ if (demux->segment_seqnum)
+ gst_event_set_seqnum (event, demux->segment_seqnum);
+ gst_matroska_demux_send_event (demux, event);
} else {
push_eos = TRUE;
}
} else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) {
/* for fatal errors we post an error message */
- GST_ELEMENT_ERROR (demux, STREAM, FAILED, (NULL),
- ("stream stopped, reason %s", reason));
+ GST_ELEMENT_FLOW_ERROR (demux, ret);
push_eos = TRUE;
}
if (push_eos) {
+ GstEvent *event;
+
/* send EOS, and prevent hanging if no streams yet */
GST_LOG_OBJECT (demux, "Sending EOS, at end of stream");
- if (!gst_matroska_demux_send_event (demux, gst_event_new_eos ()) &&
+ event = gst_event_new_eos ();
+ if (demux->segment_seqnum)
+ gst_event_set_seqnum (event, demux->segment_seqnum);
+ if (!gst_matroska_demux_send_event (demux, event) &&
(ret == GST_FLOW_EOS)) {
GST_ELEMENT_ERROR (demux, STREAM, DEMUX,
(NULL), ("got eos but no streams (yet)"));
*/
static gboolean
perform_seek_to_offset (GstMatroskaDemux * demux, gdouble rate, guint64 offset,
- guint32 seqnum)
+ guint32 seqnum, GstSeekFlags flags)
{
GstEvent *event;
gboolean res = 0;
event =
gst_event_new_seek (rate, GST_FORMAT_BYTES,
- GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, offset,
- GST_SEEK_TYPE_NONE, -1);
+ flags | GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
+ GST_SEEK_TYPE_SET, offset, GST_SEEK_TYPE_NONE, -1);
gst_event_set_seqnum (event, seqnum);
res = gst_pad_push_event (demux->common.sinkpad, event);
if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_EOS)) {
if (demux->common.ebml_segment_length != G_MAXUINT64
&& demux->common.offset >=
- demux->common.ebml_segment_start + demux->common.ebml_segment_length)
- ret = GST_FLOW_EOS;
- return ret;
+ demux->common.ebml_segment_start + demux->common.ebml_segment_length) {
+ return GST_FLOW_OK;
+ } else {
+ gint64 bytes_scanned;
+ if (demux->common.start_resync_offset == -1) {
+ demux->common.start_resync_offset = demux->common.offset;
+ demux->common.state_to_restore = demux->common.state;
+ }
+ bytes_scanned = demux->common.offset - demux->common.start_resync_offset;
+ if (bytes_scanned <= INVALID_DATA_THRESHOLD) {
+ GST_WARNING_OBJECT (demux,
+ "parse error, looking for next cluster, actual offset %"
+ G_GUINT64_FORMAT ", start resync offset %" G_GUINT64_FORMAT,
+ demux->common.offset, demux->common.start_resync_offset);
+ demux->common.state = GST_MATROSKA_READ_STATE_SCANNING;
+ ret = GST_FLOW_OK;
+ } else {
+ GST_WARNING_OBJECT (demux,
+ "unrecoverable parse error, next cluster not found and threshold "
+ "exceeded, bytes scanned %" G_GINT64_FORMAT, bytes_scanned);
+ return ret;
+ }
+ }
}
GST_LOG_OBJECT (demux, "Offset %" G_GUINT64_FORMAT ", Element id 0x%x, "
demux->common.segment.position = GST_CLOCK_TIME_NONE;
demux->cluster_time = GST_CLOCK_TIME_NONE;
demux->cluster_offset = 0;
+ demux->cluster_prevsize = 0;
demux->need_segment = TRUE;
demux->segment_seqnum = gst_event_get_seqnum (event);
/* but keep some of the upstream segment */
demux->common.segment.rate = segment->rate;
+ demux->common.segment.flags = segment->flags;
/* also check if need to keep some of the requested seek position */
if (demux->seek_offset == segment->start) {
GST_DEBUG_OBJECT (demux, "position matches requested seek");
}
case GST_EVENT_EOS:
{
- if (demux->common.state != GST_MATROSKA_READ_STATE_DATA) {
+ if (demux->common.state != GST_MATROSKA_READ_STATE_DATA
+ && demux->common.state != GST_MATROSKA_READ_STATE_SCANNING) {
gst_event_unref (event);
GST_ELEMENT_ERROR (demux, STREAM, DEMUX,
(NULL), ("got eos and didn't receive a complete header object"));
demux->common.segment.duration = dur;
demux->cluster_time = GST_CLOCK_TIME_NONE;
demux->cluster_offset = 0;
+ demux->cluster_prevsize = 0;
GST_OBJECT_UNLOCK (demux);
/* fall-through */
}
static GstStaticCaps intra_caps = GST_STATIC_CAPS ("image/jpeg; "
"video/x-raw; image/png; video/x-dv; video/x-huffyuv; video/x-ffv; "
"video/x-compressed-yuv");
+ GstCaps *tmp = gst_static_caps_get (&intra_caps);
+
context->intra_only =
- gst_caps_can_intersect (gst_static_caps_get (&intra_caps), caps);
+ gst_caps_can_intersect (tmp, caps);
+ gst_caps_unref(tmp);
}
if (buf)
} else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_VP9)) {
caps = gst_caps_new_empty_simple ("video/x-vp9");
*codec_name = g_strdup_printf ("On2 VP9");
+ } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_AV1)) {
+ caps = gst_caps_new_empty_simple ("video/x-av1");
+ if (data) {
+ GstBuffer *priv;
+
+ priv = gst_buffer_new_wrapped (g_memdup (data, size), size);
+ gst_caps_set_simple (caps, "codec_data", GST_TYPE_BUFFER, priv, NULL);
+ gst_buffer_unref (priv);
+ } else {
+ GST_WARNING ("No AV1 codec data found!");
+ }
+ *codec_name = g_strdup_printf ("AOM AV1");
} else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_PRORES)) {
guint32 fourcc;
const gchar *variant, *variant_descr = "";
0, 1, NULL);
}
- if (videocontext->parent.flags & GST_MATROSKA_VIDEOTRACK_INTERLACED)
- gst_structure_set (structure, "interlace-mode", G_TYPE_STRING,
- "mixed", NULL);
+ switch (videocontext->interlace_mode) {
+ case GST_MATROSKA_INTERLACE_MODE_PROGRESSIVE:
+ gst_structure_set (structure,
+ "interlace-mode", G_TYPE_STRING, "progressive", NULL);
+ break;
+ case GST_MATROSKA_INTERLACE_MODE_INTERLACED:
+ gst_structure_set (structure,
+ "interlace-mode", G_TYPE_STRING, "mixed", NULL);
+ break;
+ default:
+ break;
+ }
}
if (videocontext->multiview_mode != GST_VIDEO_MULTIVIEW_MODE_NONE) {
if (gst_video_multiview_guess_half_aspect (videocontext->multiview_mode,
GST_FLAG_SET_MASK_EXACT, NULL);
}
+ if (videocontext->colorimetry.range != GST_VIDEO_COLOR_RANGE_UNKNOWN ||
+ videocontext->colorimetry.matrix != GST_VIDEO_COLOR_MATRIX_UNKNOWN ||
+ videocontext->colorimetry.transfer != GST_VIDEO_TRANSFER_UNKNOWN ||
+ videocontext->colorimetry.primaries !=
+ GST_VIDEO_COLOR_PRIMARIES_UNKNOWN) {
+ gchar *colorimetry =
+ gst_video_colorimetry_to_string (&videocontext->colorimetry);
+ gst_caps_set_simple (caps, "colorimetry", G_TYPE_STRING, colorimetry,
+ NULL);
+ GST_DEBUG ("setting colorimetry to %s", colorimetry);
+ g_free (colorimetry);
+ }
+
caps = gst_caps_simplify (caps);
}
caps = gst_codec_utils_opus_create_caps_from_header (tmp, NULL);
gst_buffer_unref (tmp);
*codec_name = g_strdup ("Opus");
+ } else if (context->codec_priv_size == 0) {
+ GST_WARNING ("No Opus codec data found, trying to create one");
+ if (audiocontext->channels <= 2) {
+ guint8 streams, coupled, channels;
+ guint32 samplerate;
+
+ samplerate =
+ audiocontext->samplerate == 0 ? 48000 : audiocontext->samplerate;
+ channels = audiocontext->channels == 0 ? 2 : audiocontext->channels;
+ if (channels == 1) {
+ streams = 1;
+ coupled = 0;
+ } else {
+ streams = 1;
+ coupled = 1;
+ }
+
+ caps =
+ gst_codec_utils_opus_create_caps (samplerate, channels, 0, streams,
+ coupled, NULL);
+ if (caps) {
+ *codec_name = g_strdup ("Opus");
+ } else {
+ GST_WARNING ("Failed to create Opus caps from audio context");
+ }
+ } else {
+ GST_WARNING ("No Opus codec data, and not enough info to create one");
+ }
} else {
- GST_WARNING ("Invalid Opus codec data size");
+ GST_WARNING ("Invalid Opus codec data size (got %" G_GSIZE_FORMAT
+ ", expected 19)", context->codec_priv_size);
}
} else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_ACM)) {
gst_riff_strf_auds auds;
*riff_audio_fmt = auds.format;
/* FIXME: Handle reorder map */
- caps = gst_riff_create_audio_caps (auds.format, NULL, &auds, NULL,
- codec_data, codec_name, NULL);
+ caps = gst_riff_create_audio_caps (auds.format, NULL, &auds, codec_data,
+ NULL, codec_name, NULL);
if (codec_data)
gst_buffer_unref (codec_data);
guint sample_width;
guint extra_data_size;
- GST_ERROR ("real audio raversion:%d", raversion);
+ GST_DEBUG ("real audio raversion:%d", raversion);
if (raversion == 8) {
/* COOK */
flavor = GST_READ_UINT16_BE (data + 22);
sample_width = GST_READ_UINT16_BE (data + 58);
extra_data_size = GST_READ_UINT32_BE (data + 74);
- GST_ERROR
+ GST_DEBUG
("flavor:%d, packet_size:%d, height:%d, leaf_size:%d, sample_width:%d, extra_data_size:%d",
flavor, packet_size, height, leaf_size, sample_width,
extra_data_size);
demux->max_gap_time = g_value_get_uint64 (value);
GST_OBJECT_UNLOCK (demux);
break;
+ case PROP_MAX_BACKTRACK_DISTANCE:
+ GST_OBJECT_LOCK (demux);
+ demux->max_backtrack_distance = g_value_get_uint (value);
+ GST_OBJECT_UNLOCK (demux);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
g_value_set_uint64 (value, demux->max_gap_time);
GST_OBJECT_UNLOCK (demux);
break;
+ case PROP_MAX_BACKTRACK_DISTANCE:
+ GST_OBJECT_LOCK (demux);
+ g_value_set_uint (value, demux->max_backtrack_distance);
+ GST_OBJECT_UNLOCK (demux);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
+static const gchar *
+gst_matroska_track_encryption_algorithm_name (gint val)
+{
+ GEnumValue *en;
+ GEnumClass *enum_class =
+ g_type_class_ref (MATROSKA_TRACK_ENCRYPTION_ALGORITHM_TYPE);
+ en = g_enum_get_value (G_ENUM_CLASS (enum_class), val);
+ return en ? en->value_nick : NULL;
+}
+
+static const gchar *
+gst_matroska_track_encryption_cipher_mode_name (gint val)
+{
+ GEnumValue *en;
+ GEnumClass *enum_class =
+ g_type_class_ref (MATROSKA_TRACK_ENCRYPTION_CIPHER_MODE_TYPE);
+ en = g_enum_get_value (G_ENUM_CLASS (enum_class), val);
+ return en ? en->value_nick : NULL;
+}
+
+static const gchar *
+gst_matroska_track_encoding_scope_name (gint val)
+{
+ GEnumValue *en;
+ GEnumClass *enum_class =
+ g_type_class_ref (MATROSKA_TRACK_ENCODING_SCOPE_TYPE);
+
+ en = g_enum_get_value (G_ENUM_CLASS (enum_class), val);
+ return en ? en->value_nick : NULL;
+}
+
gboolean
gst_matroska_demux_plugin_init (GstPlugin * plugin)
{