X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Fmatroska%2Fmatroska-demux.c;h=0939164850c8b7ca3bf728ffe83b090f5238462c;hb=f05e14ba316d67b9cdf1440d9b2129da03625a30;hp=3ec8c704b81276e6eec783acb835c35f15c0cab8;hpb=063f553275cf9d74a44d328ae2312a8f09f0ddf3;p=platform%2Fupstream%2Fgst-plugins-good.git diff --git a/gst/matroska/matroska-demux.c b/gst/matroska/matroska-demux.c index 3ec8c70..0939164 100644 --- a/gst/matroska/matroska-demux.c +++ b/gst/matroska/matroska-demux.c @@ -53,6 +53,8 @@ #include #include +#include + /* For AVI compatibility mode and for fourcc stuff */ #include @@ -83,10 +85,13 @@ enum 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, @@ -146,6 +151,8 @@ static gboolean gst_matroska_demux_handle_src_query (GstPad * pad, 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); @@ -168,11 +175,14 @@ static GstCaps *gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext 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, @@ -214,6 +224,15 @@ gst_matroska_demux_class_init (GstMatroskaDemuxClass * klass) "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 = @@ -227,19 +246,18 @@ gst_matroska_demux_class_init (GstMatroskaDemuxClass * klass) 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", "Demuxes Matroska/WebM streams into video/audio/subtitles", - "GStreamer maintainers "); + "GStreamer maintainers "); } static void @@ -255,6 +273,8 @@ gst_matroska_demux_init (GstMatroskaDemux * demux) 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 */ @@ -262,6 +282,7 @@ gst_matroska_demux_init (GstMatroskaDemux * demux) /* 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); @@ -283,6 +304,7 @@ gst_matroska_demux_reset (GstElement * element) 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; @@ -306,7 +328,11 @@ gst_matroska_demux_reset (GstElement * element) 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; @@ -331,6 +357,11 @@ gst_matroska_demux_reset (GstElement * element) 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); } @@ -340,12 +371,13 @@ gst_matroska_decode_buffer (GstMatroskaTrackContext * context, GstBuffer * buf) 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; @@ -353,15 +385,57 @@ gst_matroska_decode_buffer (GstMatroskaTrackContext * context, GstBuffer * buf) 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 @@ -390,20 +464,199 @@ gst_matroska_demux_add_stream_headers_to_caps (GstMatroskaDemux * demux, } 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; - gchar *padname = NULL; + GstTagList *cached_taglist; 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"); @@ -416,8 +669,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) /* 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; @@ -434,10 +685,11 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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)) { @@ -456,12 +708,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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); @@ -528,8 +774,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) context->type = 0; break; } - g_ptr_array_index (demux->common.src, demux->common.num_streams - 1) - = context; break; } @@ -548,8 +792,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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)) { @@ -676,13 +918,18 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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; } @@ -731,6 +978,13 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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; @@ -818,8 +1072,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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)) { @@ -939,6 +1191,34 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) break; } + /* codec delay */ + case GST_MATROSKA_ID_CODECDELAY:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + context->codec_delay = num; + + GST_DEBUG_OBJECT (demux, "CodecDelay: %" GST_TIME_FORMAT, + GST_TIME_ARGS (num)); + break; + } + + /* codec delay */ + case GST_MATROSKA_ID_SEEKPREROLL:{ + guint64 num; + + if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) + break; + + context->seek_preroll = num; + + GST_DEBUG_OBJECT (demux, "SeekPreroll: %" GST_TIME_FORMAT, + GST_TIME_ARGS (num)); + break; + } + /* name of this track */ case GST_MATROSKA_ID_TRACKNAME:{ gchar *text; @@ -1120,22 +1400,25 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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; } - /* now create the GStreamer connectivity */ + /* check for a cached track taglist */ + cached_taglist = + (GstTagList *) g_hash_table_lookup (demux->common.cached_track_taglists, + GUINT_TO_POINTER (context->uid)); + if (cached_taglist) + gst_tag_list_insert (context->tags, cached_taglist, GST_TAG_MERGE_APPEND); + + /* 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); @@ -1153,8 +1436,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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); @@ -1172,8 +1453,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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; @@ -1202,6 +1481,11 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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; } @@ -1240,9 +1524,84 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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)); @@ -1250,7 +1609,7 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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); @@ -1259,7 +1618,8 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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); @@ -1283,6 +1643,9 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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); @@ -1314,9 +1677,6 @@ gst_matroska_demux_add_stream (GstMatroskaDemux * demux, GstEbmlRead * ebml) gst_flow_combiner_add_pad (demux->flowcombiner, context->pad); g_free (padname); - - /* tadaah! */ - return ret; } static gboolean @@ -1541,6 +1901,16 @@ gst_matroska_demux_element_send_event (GstElement * element, GstEvent * event) 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", @@ -1611,12 +1981,13 @@ gst_matroska_cluster_compare (gint64 * i1, gint64 * i2) /* 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; @@ -1628,8 +1999,8 @@ gst_matroska_demux_search_cluster (GstMatroskaDemux * demux, gint64 * pos) 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; @@ -1637,7 +2008,7 @@ gst_matroska_demux_search_cluster (GstMatroskaDemux * demux, gint64 * pos) 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, @@ -1657,13 +2028,23 @@ gst_matroska_demux_search_cluster (GstMatroskaDemux * demux, gint64 * pos) 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, @@ -1682,16 +2063,25 @@ gst_matroska_demux_search_cluster (GstMatroskaDemux * demux, gint64 * pos) } 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: @@ -1709,7 +2099,7 @@ gst_matroska_demux_search_cluster (GstMatroskaDemux * demux, gint64 * pos) GST_ELEMENT_CAST (demux), &id, &length, &needed); if (ret != GST_FLOW_OK) { GST_DEBUG_OBJECT (demux, "need more data -> continue"); - continue; + goto next; } g_assert (id == GST_MATROSKA_ID_CLUSTER); GST_DEBUG_OBJECT (demux, "cluster size %" G_GUINT64_FORMAT ", prefix %d", @@ -1724,16 +2114,18 @@ gst_matroska_demux_search_cluster (GstMatroskaDemux * demux, gint64 * pos) 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; + goto next; 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; + next: + if (forward) + newpos += 1; } else { /* partial cluster id may have been in tail of buffer */ - newpos += MAX (gst_byte_reader_get_remaining (&reader), 4) - 3; + newpos += + forward ? MAX (gst_byte_reader_get_remaining (&reader), 4) - 3 : 3; } } @@ -1749,6 +2141,273 @@ exit: 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; + } + } + + 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 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; + } + + return FALSE; +} + /* bisect and scan through file for cluster starting before @time, * returns fake index entry with corresponding info on cluster */ static GstMatroskaIndex * @@ -1757,21 +2416,20 @@ gst_matroska_demux_search_pos (GstMatroskaDemux * demux, GstClockTime 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); @@ -1781,77 +2439,78 @@ gst_matroska_demux_search_pos (GstMatroskaDemux * demux, GstClockTime time) 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); + + maxpos = gst_matroska_read_common_get_length (&demux->common); - /* avoid division by zero in first estimation below */ - if (otime <= demux->stream_start_time) - otime = time; + /* 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) { - goto exit; - } else { - break; - } + /* search backwards */ + if (newpos > apos) { + ret = gst_matroska_demux_search_cluster (demux, &newpos, FALSE); + if (ret != GST_FLOW_OK) + goto exit; } /* 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); @@ -1878,6 +2537,12 @@ retry: 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 */ @@ -1898,6 +2563,30 @@ retry: 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; @@ -1922,9 +2611,26 @@ retry: 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); @@ -2122,13 +2828,19 @@ next: } 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) { @@ -2142,7 +2854,7 @@ finish: /* 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: @@ -2185,6 +2897,7 @@ 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 @@ -2283,7 +2996,7 @@ gst_matroska_demux_handle_seek_push (GstMatroskaDemux * demux, GstPad * pad, /* 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 */ @@ -2305,9 +3018,26 @@ gst_matroska_demux_handle_src_event (GstPad * pad, GstObject * parent, 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 @@ -2390,6 +3120,47 @@ gst_matroska_demux_handle_src_event (GstPad * pad, GstObject * parent, 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) { @@ -2462,9 +3233,23 @@ gst_matroska_demux_parse_tracks (GstMatroskaDemux * demux, GstEbmlRead * ebml) 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, @@ -2475,6 +3260,116 @@ gst_matroska_demux_parse_tracks (GstMatroskaDemux * demux, GstEbmlRead * 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; } @@ -2548,6 +3443,7 @@ gst_matroska_ebmlnum_sint (guint8 * data, guint size, gint64 * num) static void gst_matroska_demux_sync_streams (GstMatroskaDemux * demux) { + GstClockTime gap_threshold; gint stream_nr; GST_OBJECT_LOCK (demux); @@ -2565,21 +3461,22 @@ gst_matroska_demux_sync_streams (GstMatroskaDemux * demux) "Checking for resync on stream %d (%" GST_TIME_FORMAT ")", stream_nr, GST_TIME_ARGS (context->pos)); - if (G_LIKELY (context->type != GST_MATROSKA_TRACK_TYPE_SUBTITLE)) { - GST_LOG_OBJECT (demux, "Skipping sync on non-subtitle stream"); - continue; - } + /* Only send gap events on non-subtitle streams if lagging way behind. + * The 0.5 second threshold for subtitle streams is also quite random. */ + if (context->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE) + gap_threshold = GST_SECOND / 2; + else + gap_threshold = 3 * GST_SECOND; - /* does it lag? 0.5 seconds is a random threshold... - * lag need only be considered if we have advanced into requested segment */ + /* Lag need only be considered if we have advanced into requested segment */ if (GST_CLOCK_TIME_IS_VALID (context->pos) && GST_CLOCK_TIME_IS_VALID (demux->common.segment.position) && demux->common.segment.position > demux->common.segment.start && - context->pos + (GST_SECOND / 2) < demux->common.segment.position) { + context->pos + gap_threshold < demux->common.segment.position) { GstEvent *event; guint64 start = context->pos; - guint64 stop = demux->common.segment.position - (GST_SECOND / 2); + guint64 stop = demux->common.segment.position - gap_threshold; GST_DEBUG_OBJECT (demux, "Synchronizing stream %d with other by advancing time from %" @@ -2707,8 +3604,6 @@ gst_matroska_demux_push_codec_data_all (GstMatroskaDemux * demux) { 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; @@ -2733,7 +3628,6 @@ gst_matroska_demux_push_codec_data_all (GstMatroskaDemux * demux) } } - GST_OBJECT_UNLOCK (demux); } static GstFlowReturn @@ -2834,6 +3728,7 @@ gst_matroska_demux_add_wvpk_header (GstElement * element, 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 | @@ -2935,6 +3830,33 @@ gst_matroska_demux_add_wvpk_header (GstElement * element, return GST_FLOW_OK; } +static GstFlowReturn +gst_matroska_demux_add_prores_header (GstElement * element, + GstMatroskaTrackContext * stream, GstBuffer ** buf) +{ + GstBuffer *newbuf = gst_buffer_new_allocate (NULL, 8, NULL); + GstMapInfo map; + guint32 frame_size; + + if (!gst_buffer_map (newbuf, &map, GST_MAP_WRITE)) { + GST_ERROR ("Failed to map newly allocated buffer"); + return GST_FLOW_ERROR; + } + + frame_size = gst_buffer_get_size (*buf); + + GST_WRITE_UINT32_BE (map.data, frame_size); + map.data[4] = 'i'; + map.data[5] = 'c'; + map.data[6] = 'p'; + map.data[7] = 'f'; + + gst_buffer_unmap (newbuf, &map); + *buf = gst_buffer_append (newbuf, *buf); + + return GST_FLOW_OK; +} + /* @text must be null-terminated */ static gboolean gst_matroska_demux_subtitle_chunk_has_tag (GstElement * element, @@ -2986,25 +3908,16 @@ gst_matroska_demux_check_subtitle_buffer (GstElement * element, if (!gst_buffer_get_size (*buf) || !gst_buffer_map (*buf, &map, GST_MAP_READ)) return GST_FLOW_OK; - /* Need \0-terminator at the end */ - if (map.data[map.size - 1] != '\0') { - newbuf = gst_buffer_new_and_alloc (map.size + 1); - - /* Copy old buffer and add a 0 at the end */ - gst_buffer_fill (newbuf, 0, map.data, map.size); - gst_buffer_memset (newbuf, map.size, 0, 1); + /* The subtitle buffer we push out should not include a NUL terminator as + * part of the data. */ + if (map.data[map.size - 1] == '\0') { + gst_buffer_set_size (*buf, map.size - 1); gst_buffer_unmap (*buf, &map); - - gst_buffer_copy_into (newbuf, *buf, - GST_BUFFER_COPY_TIMESTAMPS | GST_BUFFER_COPY_FLAGS | - GST_BUFFER_COPY_META, 0, -1); - gst_buffer_unref (*buf); - *buf = newbuf; gst_buffer_map (*buf, &map, GST_MAP_READ); } if (!sub_stream->invalid_utf8) { - if (g_utf8_validate ((gchar *) map.data, map.size - 1, NULL)) { + if (g_utf8_validate ((gchar *) map.data, map.size, NULL)) { goto next; } GST_WARNING_OBJECT (element, "subtitle stream %" G_GUINT64_FORMAT @@ -3163,6 +4076,7 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, gboolean readblock = FALSE; guint32 id; guint64 block_duration = -1; + gint64 block_discardpadding = 0; GstBuffer *buf = NULL; GstMapInfo map; gint stream_num = -1, n, laces = 0; @@ -3327,6 +4241,13 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, break; } + case GST_MATROSKA_ID_DISCARDPADDING:{ + ret = gst_ebml_read_sint (ebml, &id, &block_discardpadding); + GST_DEBUG_OBJECT (demux, "DiscardPadding: %" GST_STIME_FORMAT, + GST_STIME_ARGS (block_discardpadding)); + break; + } + case GST_MATROSKA_ID_REFERENCEBLOCK:{ ret = gst_ebml_read_sint (ebml, &id, &referenceblock); GST_DEBUG_OBJECT (demux, "ReferenceBlock: %" G_GINT64_FORMAT, @@ -3397,6 +4318,7 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, gboolean delta_unit = FALSE; guint64 duration = 0; gint64 lace_time = 0; + GstEvent *protect_event; stream = g_ptr_array_index (demux->common.src, stream_num); @@ -3416,6 +4338,12 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, } 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) { @@ -3435,7 +4363,7 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, 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; @@ -3472,15 +4400,27 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, } /* 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; } } @@ -3506,7 +4446,8 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, GST_OBJECT_LOCK (demux); earliest_time = videocontext->earliest_time; GST_OBJECT_UNLOCK (demux); - earliest_stream_time = gst_segment_to_position (&demux->common.segment, + earliest_stream_time = + gst_segment_position_from_running_time (&demux->common.segment, GST_FORMAT_TIME, earliest_time); if (GST_CLOCK_TIME_IS_VALID (lace_time) && @@ -3551,8 +4492,6 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, goto next_lace; } - buffer_timestamp = gst_matroska_track_get_buffer_timestamp (stream, sub); - if (!stream->dts_only) { GST_BUFFER_PTS (sub) = lace_time; } else { @@ -3561,6 +4500,8 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, GST_BUFFER_PTS (sub) = lace_time; } + buffer_timestamp = gst_matroska_track_get_buffer_timestamp (stream, sub); + if (GST_CLOCK_TIME_IS_VALID (lace_time)) { GstClockTime last_stop_end; @@ -3701,9 +4642,49 @@ gst_matroska_demux_parse_blockgroup_or_simpleblock (GstMatroskaDemux * demux, for 32 bit samples, etc), or bad things will happen downstream as elements typically assume minimal alignment. Therefore, create an aligned copy if necessary. */ - g_assert (stream->alignment <= G_MEM_ALIGN); sub = gst_matroska_demux_align_buffer (demux, sub, stream->alignment); + if (!strcmp (stream->codec_id, GST_MATROSKA_CODEC_ID_AUDIO_OPUS)) { + guint64 start_clip = 0, end_clip = 0; + + /* Codec delay is part of the timestamps */ + if (GST_BUFFER_PTS_IS_VALID (sub) && stream->codec_delay) { + if (GST_BUFFER_PTS (sub) > stream->codec_delay) { + 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); + + if (GST_BUFFER_DURATION_IS_VALID (sub)) { + if (GST_BUFFER_DURATION (sub) > stream->codec_delay) + GST_BUFFER_DURATION (sub) -= stream->codec_delay; + else + GST_BUFFER_DURATION (sub) = 0; + } + } + } + + if (block_discardpadding) { + end_clip = + gst_util_uint64_scale_round (block_discardpadding, 48000, + GST_SECOND); + } + + if (start_clip || end_clip) { + gst_buffer_add_audio_clipping_meta (sub, GST_FORMAT_DEFAULT, + start_clip, end_clip); + } + } + if (GST_BUFFER_PTS_IS_VALID (sub)) { stream->pos = GST_BUFFER_PTS (sub); if (GST_BUFFER_DURATION_IS_VALID (sub)) @@ -4034,7 +5015,8 @@ gst_matroska_demux_check_parse_error (GstMatroskaDemux * demux) * 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 { @@ -4260,13 +5242,32 @@ gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id, 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)); @@ -4279,11 +5280,11 @@ gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id, } 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: @@ -4297,12 +5298,62 @@ gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id, 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. */ @@ -4310,6 +5361,7 @@ gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id, } 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", @@ -4332,6 +5384,12 @@ gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id, 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) @@ -4435,9 +5493,22 @@ gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id, 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); @@ -4616,6 +5687,8 @@ pause: } 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) @@ -4624,24 +5697,33 @@ pause: 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)")); @@ -4656,7 +5738,7 @@ pause: */ static gboolean perform_seek_to_offset (GstMatroskaDemux * demux, gdouble rate, guint64 offset, - guint32 seqnum) + guint32 seqnum, GstSeekFlags flags) { GstEvent *event; gboolean res = 0; @@ -4665,8 +5747,8 @@ perform_seek_to_offset (GstMatroskaDemux * demux, gdouble rate, guint64 offset, 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); @@ -4705,9 +5787,29 @@ next: 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, " @@ -4776,10 +5878,12 @@ gst_matroska_demux_handle_sink_event (GstPad * pad, GstObject * parent, 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"); @@ -4800,7 +5904,8 @@ gst_matroska_demux_handle_sink_event (GstPad * pad, GstObject * parent, } 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")); @@ -4826,6 +5931,7 @@ gst_matroska_demux_handle_sink_event (GstPad * pad, GstObject * parent, 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 */ } @@ -4960,8 +6066,11 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * 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) @@ -5013,6 +6122,7 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * videocontext->pixel_height); caps = gst_video_info_to_caps (&info); *codec_name = gst_pb_utils_get_codec_description (caps); + context->alignment = 32; } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_SP)) { caps = gst_caps_new_simple ("video/x-divx", "divxversion", G_TYPE_INT, 4, NULL); @@ -5158,6 +6268,60 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * } 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 = ""; + + /* Expect a fourcc in the codec private data */ + if (!data || size < 4) { + GST_WARNING ("No or too small PRORESS fourcc (%d bytes)", size); + return NULL; + } + + fourcc = GST_STR_FOURCC (data); + switch (fourcc) { + case GST_MAKE_FOURCC ('a', 'p', 'c', 's'): + variant_descr = " 4:2:2 LT"; + variant = "lt"; + break; + case GST_MAKE_FOURCC ('a', 'p', 'c', 'h'): + variant = "hq"; + variant_descr = " 4:2:2 HQ"; + break; + case GST_MAKE_FOURCC ('a', 'p', '4', 'h'): + variant = "4444"; + variant_descr = " 4:4:4:4"; + break; + case GST_MAKE_FOURCC ('a', 'p', 'c', 'o'): + variant = "proxy"; + variant_descr = " 4:2:2 Proxy"; + break; + case GST_MAKE_FOURCC ('a', 'p', 'c', 'n'): + default: + variant = "standard"; + variant_descr = " 4:2:2 SD"; + break; + } + + GST_LOG ("Prores video, codec fourcc %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + + caps = gst_caps_new_simple ("video/x-prores", + "format", G_TYPE_STRING, variant, NULL); + *codec_name = g_strdup_printf ("Apple ProRes%s", variant_descr); + context->postprocess_frame = gst_matroska_demux_add_prores_header; } else { GST_WARNING ("Unknown codec '%s', cannot build Caps", codec_id); return NULL; @@ -5227,9 +6391,18 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * 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, @@ -5246,6 +6419,19 @@ gst_matroska_demux_video_caps (GstMatroskaTrackVideoContext * 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); } @@ -5378,7 +6564,9 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * /* FIXME: Channel mask and reordering */ caps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, gst_audio_format_to_string (format), - "layout", G_TYPE_STRING, "interleaved", NULL); + "layout", G_TYPE_STRING, "interleaved", + "channel-mask", GST_TYPE_BITMASK, + gst_audio_channel_get_fallback_mask (audiocontext->channels), NULL); *codec_name = g_strdup_printf ("Raw %d-bit PCM audio", audiocontext->bitdepth); @@ -5393,7 +6581,9 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * /* FIXME: Channel mask and reordering */ caps = gst_caps_new_simple ("audio/x-raw", "format", G_TYPE_STRING, format, - "layout", G_TYPE_STRING, "interleaved", NULL); + "layout", G_TYPE_STRING, "interleaved", + "channel-mask", GST_TYPE_BITMASK, + gst_audio_channel_get_fallback_mask (audiocontext->channels), NULL); *codec_name = g_strdup_printf ("Raw %d-bit floating-point audio", audiocontext->bitdepth); context->alignment = audiocontext->bitdepth / 8; @@ -5437,20 +6627,57 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * /* FIXME: mark stream as broken and skip if there are no stream headers */ context->send_stream_headers = TRUE; } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_OPUS)) { - caps = gst_caps_new_empty_simple ("audio/x-opus"); - *codec_name = g_strdup ("Opus"); - context->stream_headers = - gst_matroska_parse_opus_stream_headers (context->codec_priv, - context->codec_priv_size); - if (context->stream_headers) { - /* There was a valid header. Multistream headers are more than - * 19 bytes, as they include an extra channel mapping table. */ - gboolean multistream = (context->codec_priv_size > 19); - gst_caps_set_simple (caps, "multistream", G_TYPE_BOOLEAN, multistream, - NULL); + GstBuffer *tmp; + + if (context->codec_priv_size >= 19) { + if (audiocontext->samplerate) + GST_WRITE_UINT32_LE ((guint8 *) context->codec_priv + 12, + audiocontext->samplerate); + if (context->codec_delay) { + guint64 delay = + gst_util_uint64_scale_round (context->codec_delay, 48000, + GST_SECOND); + GST_WRITE_UINT16_LE ((guint8 *) context->codec_priv + 10, delay); + } + + tmp = + gst_buffer_new_wrapped (g_memdup (context->codec_priv, + context->codec_priv_size), context->codec_priv_size); + 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 (got %" G_GSIZE_FORMAT + ", expected 19)", context->codec_priv_size); } - /* FIXME: mark stream as broken and skip if there are no stream headers */ - context->send_stream_headers = TRUE; } else if (!strcmp (codec_id, GST_MATROSKA_CODEC_ID_AUDIO_ACM)) { gst_riff_strf_auds auds; @@ -5475,8 +6702,8 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * *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); @@ -5612,7 +6839,7 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * 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); @@ -5622,7 +6849,7 @@ gst_matroska_demux_audio_caps (GstMatroskaTrackAudioContext * 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); @@ -5811,6 +7038,11 @@ gst_matroska_demux_set_property (GObject * object, 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; @@ -5832,12 +7064,48 @@ gst_matroska_demux_get_property (GObject * object, 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) {