diff options
author | Jorge Zapata <jorgeluis.zapata@gmail.com> | 2013-01-15 10:34:56 +0100 |
---|---|---|
committer | Josep Torra <n770galaxy@gmail.com> | 2013-05-22 14:27:23 +0200 |
commit | eaba11937aad8245d9e21a4463bda03134000487 (patch) | |
tree | af646bcfbda938835eb43eab69b06705947ce7b8 | |
parent | 9e99e3c628cd4d5e14f4d997eb6ee0a6db7d3ee9 (diff) |
qtdemux: Add support for multiple stsd entries
It is possible to find files with multiple entries on the stsd atom with
different sample descriptions. Now qtdemux can identify the streams
a sample refers to by taking into account not only the track id but also the
sample description index.
Use the track id and the description index instead of the stream autoincremented
value on the pad name.
-rw-r--r-- | gst/isomp4/qtdemux.c | 695 |
1 files changed, 436 insertions, 259 deletions
diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c index 0336732c2..3b5ad2835 100644 --- a/gst/isomp4/qtdemux.c +++ b/gst/isomp4/qtdemux.c @@ -212,6 +212,9 @@ struct _QtDemuxStream /* track id */ guint track_id; + /* description index */ + guint description_idx; + /* duration/scale */ guint64 duration; /* in timescale */ guint32 timescale; @@ -344,6 +347,7 @@ struct _QtDemuxStream guint32 def_sample_duration; guint32 def_sample_size; guint32 def_sample_flags; + guint32 def_sample_description_idx; gboolean encrypted; guint32 senc_sample_index; @@ -373,6 +377,7 @@ static GNode *qtdemux_tree_get_child_by_type_full (GNode * node, static GNode *qtdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc); static GNode *qtdemux_tree_get_sibling_by_type_full (GNode * node, guint32 fourcc, GstByteReader * parser); +static GNode *qtdemux_tree_get_parent_by_type (GNode * node, guint32 fourcc); static GstStaticPadTemplate gst_qtdemux_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", @@ -383,19 +388,19 @@ static GstStaticPadTemplate gst_qtdemux_sink_template = ); static GstStaticPadTemplate gst_qtdemux_videosrc_template = -GST_STATIC_PAD_TEMPLATE ("video_%02d", +GST_STATIC_PAD_TEMPLATE ("video_%02d_%02d", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate gst_qtdemux_audiosrc_template = -GST_STATIC_PAD_TEMPLATE ("audio_%02d", +GST_STATIC_PAD_TEMPLATE ("audio_%02d_%02d", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate gst_qtdemux_subsrc_template = -GST_STATIC_PAD_TEMPLATE ("subtitle_%02d", +GST_STATIC_PAD_TEMPLATE ("subtitle_%02d_%02d", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); @@ -2189,10 +2194,17 @@ failed: } static gboolean -qtdemux_parse_trex (GstQTDemux * qtdemux, QtDemuxStream * stream, - guint32 * ds_duration, guint32 * ds_size, guint32 * ds_flags) +qtdemux_parse_trex (GstQTDemux * qtdemux, guint track_id, + guint32 * ds_duration, guint32 * ds_size, guint32 * ds_flags, + guint32 * ds_description_idx) { - if (!stream->parsed_trex && qtdemux->moov_node) { + guint32 def_sample_duration = 0; + guint32 def_sample_size = 0; + guint32 def_sample_flags = 0; + guint32 def_sample_description_idx = 1; + gboolean parsed_trex = FALSE; + + if (qtdemux->moov_node) { GNode *mvex, *trex; GstByteReader trex_data; @@ -2201,17 +2213,16 @@ qtdemux_parse_trex (GstQTDemux * qtdemux, QtDemuxStream * stream, trex = qtdemux_tree_get_child_by_type_full (mvex, FOURCC_trex, &trex_data); while (trex) { - guint32 id = 0, dur = 0, size = 0, flags = 0; + guint32 id = 0, dur = 0, size = 0, flags = 0, didx = 0; /* skip version/flags */ if (!gst_byte_reader_skip (&trex_data, 4)) goto next; if (!gst_byte_reader_get_uint32_be (&trex_data, &id)) goto next; - if (id != stream->track_id) + if (id != track_id) goto next; - /* sample description index; ignore */ - if (!gst_byte_reader_get_uint32_be (&trex_data, &dur)) + if (!gst_byte_reader_get_uint32_be (&trex_data, &didx)) goto next; if (!gst_byte_reader_get_uint32_be (&trex_data, &dur)) goto next; @@ -2220,14 +2231,16 @@ qtdemux_parse_trex (GstQTDemux * qtdemux, QtDemuxStream * stream, if (!gst_byte_reader_get_uint32_be (&trex_data, &flags)) goto next; - GST_DEBUG_OBJECT (qtdemux, "fragment defaults for stream %d; " - "duration %d, size %d, flags 0x%x", stream->track_id, - dur, size, flags); + GST_DEBUG_OBJECT (qtdemux, "fragment defaults for track %d; " + "description idx %d, duration %d, size %d, flags 0x%x", + track_id, didx, dur, size, flags); - stream->parsed_trex = TRUE; - stream->def_sample_duration = dur; - stream->def_sample_size = size; - stream->def_sample_flags = flags; + parsed_trex = TRUE; + def_sample_duration = dur; + def_sample_size = size; + def_sample_flags = flags; + def_sample_description_idx = didx; + break; next: /* iterate all siblings */ @@ -2237,14 +2250,14 @@ qtdemux_parse_trex (GstQTDemux * qtdemux, QtDemuxStream * stream, } } - *ds_duration = stream->def_sample_duration; - *ds_size = stream->def_sample_size; - *ds_size = stream->def_sample_size; - + *ds_duration = def_sample_duration; + *ds_size = def_sample_size; + *ds_flags = def_sample_flags; + *ds_description_idx = def_sample_description_idx; /* even then, above values are better than random ... */ - if (G_UNLIKELY (!stream->parsed_trex)) { + if (G_UNLIKELY (!parsed_trex)) { GST_WARNING_OBJECT (qtdemux, - "failed to find fragment defaults for stream %d", stream->track_id); + "failed to find fragment defaults for track %d", track_id); return FALSE; } @@ -2266,10 +2279,10 @@ qtdemux_parse_trun (GstQTDemux * qtdemux, GstByteReader * trun, QtDemuxSample *sample; gboolean ismv = FALSE; - GST_LOG_OBJECT (qtdemux, "parsing trun stream %d; " - "default dur %d, size %d, flags 0x%x, base offset %" G_GINT64_FORMAT, - stream->track_id, d_sample_duration, d_sample_size, d_sample_flags, - *base_offset); + GST_LOG_OBJECT (qtdemux, "parsing trun track %d; " + "stream %d, default dur %d, size %d, flags 0x%x, base offset %" + G_GINT64_FORMAT, stream->track_id, stream->description_idx, + d_sample_duration, d_sample_size, d_sample_flags, *base_offset); /* presence of stss or not can't really tell us much, * and flags and so on tend to be marginally reliable in these files */ @@ -2456,9 +2469,9 @@ index_too_big: } } -/* find stream with @id */ +/* find stream with @id and description index @idx */ static inline QtDemuxStream * -qtdemux_find_stream (GstQTDemux * qtdemux, guint32 id) +qtdemux_find_stream (GstQTDemux * qtdemux, guint32 id, guint32 idx) { QtDemuxStream *stream; gint i; @@ -2472,25 +2485,60 @@ qtdemux_find_stream (GstQTDemux * qtdemux, guint32 id) /* try to get it fast and simple */ if (G_LIKELY (id <= qtdemux->n_streams)) { stream = qtdemux->streams[id - 1]; - if (G_LIKELY (stream->track_id == id)) + if (G_LIKELY ((stream->track_id == id) && (stream->description_idx == idx))) return stream; } /* linear search otherwise */ for (i = 0; i < qtdemux->n_streams; i++) { stream = qtdemux->streams[i]; - if (stream->track_id == id) + if ((stream->track_id == id) && (stream->description_idx == idx)) return stream; } + GST_ERROR_OBJECT (qtdemux, + "Impossible to find the stream for track id %d and description idx %d", + id, idx); + return NULL; } +static inline void +qtdemux_get_track_defaults (GstQTDemux * qtdemux, guint track_id, + guint32 * ds_duration, guint32 * ds_size, guint32 * ds_flags, + guint32 * ds_description_idx) +{ + gint i; + + /* check */ + if (G_UNLIKELY (!track_id)) { + GST_DEBUG_OBJECT (qtdemux, "invalid track id 0"); + return; + } + + /* get the first stream and check if it is alread parsed */ + for (i = 0; i < qtdemux->n_streams; i++) { + QtDemuxStream *stream; + + stream = qtdemux->streams[i]; + if ((stream->track_id == track_id) && (stream->parsed_trex)) { + *ds_duration = stream->def_sample_duration; + *ds_size = stream->def_sample_size; + *ds_flags = stream->def_sample_flags; + *ds_description_idx = stream->def_sample_description_idx; + return; + } + } + + GST_WARNING_OBJECT (qtdemux, "Impossible to find the track id %d", track_id); +} + + static gboolean qtdemux_parse_tfhd (GstQTDemux * qtdemux, GstByteReader * tfhd, QtDemuxStream ** stream, guint32 * default_sample_duration, guint32 * default_sample_size, guint32 * default_sample_flags, - gint64 * base_offset) + guint32 * default_sample_description_idx, gint64 * base_offset) { guint32 flags = 0; guint32 track_id = 0; @@ -2502,7 +2550,23 @@ qtdemux_parse_tfhd (GstQTDemux * qtdemux, GstByteReader * tfhd, if (!gst_byte_reader_get_uint32_be (tfhd, &track_id)) goto invalid_track; - *stream = qtdemux_find_stream (qtdemux, track_id); + /* obtain stream defaults */ + if (!qtdemux_parse_trex (qtdemux, track_id, + default_sample_duration, default_sample_size, default_sample_flags, + default_sample_description_idx)) { + GST_LOG_OBJECT (qtdemux, "check if the default values are already parsed"); + /* or it is not found, or the trex was already parsed */ + qtdemux_get_track_defaults (qtdemux, track_id, + default_sample_duration, default_sample_size, default_sample_flags, + default_sample_description_idx); + } + + if (flags & TF_SAMPLE_DESCRIPTION_INDEX) + if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_description_idx)) + goto invalid_track; + + *stream = qtdemux_find_stream (qtdemux, track_id, + *default_sample_description_idx); if (G_UNLIKELY (!*stream)) goto unknown_stream; @@ -2510,15 +2574,6 @@ qtdemux_parse_tfhd (GstQTDemux * qtdemux, GstByteReader * tfhd, if (!gst_byte_reader_get_uint64_be (tfhd, (guint64 *) base_offset)) goto invalid_track; - /* obtain stream defaults */ - qtdemux_parse_trex (qtdemux, *stream, - default_sample_duration, default_sample_size, default_sample_flags); - - /* FIXME: Handle TF_SAMPLE_DESCRIPTION_INDEX properly */ - if (flags & TF_SAMPLE_DESCRIPTION_INDEX) - if (!gst_byte_reader_skip (tfhd, 4)) - goto invalid_track; - if (flags & TF_DEFAULT_SAMPLE_DURATION) if (!gst_byte_reader_get_uint32_be (tfhd, default_sample_duration)) goto invalid_track; @@ -2583,7 +2638,7 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, { GNode *moof_node, *traf_node, *tfhd_node, *trun_node, *tfdt_node; GstByteReader trun_data, tfhd_data, tfdt_data; - guint32 ds_size = 0, ds_duration = 0, ds_flags = 0; + guint32 ds_size = 0, ds_duration = 0, ds_flags = 0, ds_description_idx = 0; gint64 base_offset, running_offset; guint64 decode_time = 0; @@ -2604,7 +2659,7 @@ qtdemux_parse_moof (GstQTDemux * qtdemux, const guint8 * buffer, guint length, if (!tfhd_node) goto missing_tfhd; if (!qtdemux_parse_tfhd (qtdemux, &tfhd_data, &stream, &ds_duration, - &ds_size, &ds_flags, &base_offset)) + &ds_size, &ds_flags, &ds_description_idx, &base_offset)) goto missing_tfhd; tfdt_node = qtdemux_tree_get_child_by_type_full (traf_node, FOURCC_tfdt, @@ -5234,6 +5289,25 @@ qtdemux_tree_get_sibling_by_type (GNode * node, guint32 fourcc) return qtdemux_tree_get_sibling_by_type_full (node, fourcc, NULL); } +static GNode * +qtdemux_tree_get_parent_by_type (GNode * node, guint32 fourcc) +{ + GNode *parent; + guint8 *buffer; + guint32 parent_fourcc; + + for (parent = node->parent; parent; parent = parent->parent) { + buffer = (guint8 *) parent->data; + + parent_fourcc = QT_FOURCC (buffer + 4); + + if (G_UNLIKELY (parent_fourcc == fourcc)) { + return parent; + } + } + return NULL; +} + static gboolean gst_qtdemux_add_stream (GstQTDemux * qtdemux, QtDemuxStream * stream, GstTagList * list) @@ -5244,7 +5318,8 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux, 0, GST_CLOCK_TIME_NONE, 0); if (stream->subtype == FOURCC_vide) { - gchar *name = g_strdup_printf ("video_%02d", qtdemux->n_video_streams); + gchar *name = g_strdup_printf ("video_%02d_%02d", stream->track_id, + stream->description_idx); stream->pad = gst_pad_new_from_static_template (&gst_qtdemux_videosrc_template, name); @@ -5362,7 +5437,8 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux, } qtdemux->n_video_streams++; } else if (stream->subtype == FOURCC_soun) { - gchar *name = g_strdup_printf ("audio_%02d", qtdemux->n_audio_streams); + gchar *name = g_strdup_printf ("audio_%02d_%02d", stream->track_id, + stream->description_idx); stream->pad = gst_pad_new_from_static_template (&gst_qtdemux_audiosrc_template, name); @@ -5376,7 +5452,8 @@ gst_qtdemux_add_stream (GstQTDemux * qtdemux, } else if (stream->subtype == FOURCC_strm) { GST_DEBUG_OBJECT (qtdemux, "stream type, not creating pad"); } else if (stream->subtype == FOURCC_subp || stream->subtype == FOURCC_text) { - gchar *name = g_strdup_printf ("subtitle_%02d", qtdemux->n_sub_streams); + gchar *name = g_strdup_printf ("subtitle_%02d_%02d", stream->track_id, + stream->description_idx); stream->pad = gst_pad_new_from_static_template (&gst_qtdemux_subsrc_template, name); @@ -5768,8 +5845,9 @@ qtdemux_parse_samples (GstQTDemux * qtdemux, QtDemuxStream * stream, guint32 n) guint32 n_samples_per_chunk; guint32 n_samples; - GST_LOG_OBJECT (qtdemux, "parsing samples for stream fourcc %" - GST_FOURCC_FORMAT ", pad %s", GST_FOURCC_ARGS (stream->fourcc), + GST_LOG_OBJECT (qtdemux, "parsing samples for stream at track %d, idx %d, " + "fourcc %" GST_FOURCC_FORMAT ", pad %s", stream->track_id, + stream->description_idx, GST_FOURCC_ARGS (stream->fourcc), stream->pad ? GST_PAD_NAME (stream->pad) : "(NULL)"); n_samples = stream->n_samples; @@ -6304,13 +6382,11 @@ done: * the SMI and gama atoms. */ static void -qtdemux_parse_svq3_stsd_data (GstQTDemux * qtdemux, GNode * stsd, - guint8 ** gamma, GstBuffer ** seqh) +qtdemux_parse_svq3_stsd_data (GstQTDemux * qtdemux, const guint8 * stsd_data, + guint32 length, const guint8 ** gamma, GstBuffer ** seqh) { - guint8 *_gamma = NULL; + const guint8 *_gamma = NULL; GstBuffer *_seqh = NULL; - guint8 *stsd_data = stsd->data; - guint32 length = QT_UINT32 (stsd_data); guint16 version; if (length < 32) { @@ -6327,7 +6403,7 @@ qtdemux_parse_svq3_stsd_data (GstQTDemux * qtdemux, GNode * stsd, stsd_data += 70; while (length > 8) { guint32 fourcc, size; - guint8 *data; + const guint8 *data; size = QT_UINT32 (stsd_data); fourcc = QT_FOURCC (stsd_data + 4); data = stsd_data + 8; @@ -6664,171 +6740,20 @@ qtdemux_parse_encx (GstQTDemux * qtdemux, QtDemuxStream * stream, return qtdemux_parse_sinf (qtdemux, stream, sinf, fourcc); } -/* parse the traks. - * With each track we associate a new QtDemuxStream that contains all the info - * about the trak. - * traks that do not decode to something (like strm traks) will not have a pad. - */ static gboolean -qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) +qtdemux_parse_stsd_entry (GstQTDemux * qtdemux, const guint8 * stsd_data, + guint len, GNode * fourcc_node, guint32 fourcc, QtDemuxStream * stream) { - GstByteReader tkhd; - int offset; - GNode *mdia; - GNode *mdhd; - GNode *hdlr; - GNode *minf; - GNode *stbl; - GNode *stsd; + GstTagList *list = NULL; GNode *mp4a; GNode *mp4v; GNode *wave; GNode *esds; GNode *pasp; - QtDemuxStream *stream; - GstTagList *list = NULL; + GNode *stbl; + GNode *trak; gchar *codec = NULL; - const guint8 *stsd_data; - guint16 lang_code; /* quicktime lang code or packed iso code */ - guint32 version; - guint32 tkhd_flags = 0; - guint8 tkhd_version = 0; - guint32 fourcc; - guint value_size, len; - - stream = g_new0 (QtDemuxStream, 1); - /* new streams always need a discont */ - stream->discont = TRUE; - /* we enable clipping for raw audio/video streams */ - stream->need_clip = FALSE; - stream->need_process = FALSE; - stream->segment_index = -1; - stream->time_position = 0; - stream->sample_index = -1; - stream->last_ret = GST_FLOW_OK; - - if (!qtdemux_tree_get_child_by_type_full (trak, FOURCC_tkhd, &tkhd) - || !gst_byte_reader_get_uint8 (&tkhd, &tkhd_version) - || !gst_byte_reader_get_uint24_be (&tkhd, &tkhd_flags)) - goto corrupt_file; - - /* pick between 64 or 32 bits */ - value_size = tkhd_version == 1 ? 8 : 4; - if (!gst_byte_reader_skip (&tkhd, value_size * 2) || - !gst_byte_reader_get_uint32_be (&tkhd, &stream->track_id)) - goto corrupt_file; - - GST_LOG_OBJECT (qtdemux, "track[tkhd] version/flags/id: 0x%02x/%06x/%u", - tkhd_version, tkhd_flags, stream->track_id); - - if (!(mdia = qtdemux_tree_get_child_by_type (trak, FOURCC_mdia))) - goto corrupt_file; - - if (!(mdhd = qtdemux_tree_get_child_by_type (mdia, FOURCC_mdhd))) { - /* be nice for some crooked mjp2 files that use mhdr for mdhd */ - if (qtdemux->major_brand != FOURCC_mjp2 || - !(mdhd = qtdemux_tree_get_child_by_type (mdia, FOURCC_mhdr))) - goto corrupt_file; - } - - len = QT_UINT32 ((guint8 *) mdhd->data); - version = QT_UINT32 ((guint8 *) mdhd->data + 8); - GST_LOG_OBJECT (qtdemux, "track version/flags: %08x", version); - if (version == 0x01000000) { - if (len < 38) - goto corrupt_file; - stream->timescale = QT_UINT32 ((guint8 *) mdhd->data + 28); - stream->duration = QT_UINT64 ((guint8 *) mdhd->data + 32); - lang_code = QT_UINT16 ((guint8 *) mdhd->data + 36); - } else { - if (len < 30) - goto corrupt_file; - stream->timescale = QT_UINT32 ((guint8 *) mdhd->data + 20); - stream->duration = QT_UINT32 ((guint8 *) mdhd->data + 24); - lang_code = QT_UINT16 ((guint8 *) mdhd->data + 28); - } - - if (lang_code < 0x800) { - qtdemux_lang_map_qt_code_to_iso (stream->lang_id, lang_code); - } else { - stream->lang_id[0] = 0x60 + ((lang_code >> 10) & 0x1F); - stream->lang_id[1] = 0x60 + ((lang_code >> 5) & 0x1F); - stream->lang_id[2] = 0x60 + (lang_code & 0x1F); - stream->lang_id[3] = 0; - } - - GST_LOG_OBJECT (qtdemux, "track timescale: %" G_GUINT32_FORMAT, - stream->timescale); - GST_LOG_OBJECT (qtdemux, "track duration: %" G_GUINT64_FORMAT, - stream->duration); - GST_LOG_OBJECT (qtdemux, "track language code/id: 0x%04x/%s", - lang_code, stream->lang_id); - - if (G_UNLIKELY (stream->timescale == 0 || qtdemux->timescale == 0)) - goto corrupt_file; - - /* fragmented files may have bogus duration in moov */ - if (!qtdemux->fragmented && - qtdemux->duration != G_MAXINT64 && stream->duration != G_MAXINT32) { - guint64 tdur1, tdur2; - - /* don't overflow */ - tdur1 = stream->timescale * (guint64) qtdemux->duration; - tdur2 = qtdemux->timescale * (guint64) stream->duration; - - /* HACK: - * some of those trailers, nowadays, have prologue images that are - * themselves vide tracks as well. I haven't really found a way to - * identify those yet, except for just looking at their duration. */ - if (tdur1 != 0 && (tdur2 * 10 / tdur1) < 2) { - GST_WARNING_OBJECT (qtdemux, - "Track shorter than 20%% (%" G_GUINT64_FORMAT "/%" G_GUINT32_FORMAT - " vs. %" G_GUINT64_FORMAT "/%" G_GUINT32_FORMAT ") of the stream " - "found, assuming preview image or something; skipping track", - stream->duration, stream->timescale, qtdemux->duration, - qtdemux->timescale); - g_free (stream); - return TRUE; - } - } - - if (!(hdlr = qtdemux_tree_get_child_by_type (mdia, FOURCC_hdlr))) - goto corrupt_file; - - GST_LOG_OBJECT (qtdemux, "track type: %" GST_FOURCC_FORMAT, - GST_FOURCC_ARGS (QT_FOURCC ((guint8 *) hdlr->data + 12))); - - len = QT_UINT32 ((guint8 *) hdlr->data); - if (len >= 20) - stream->subtype = QT_FOURCC ((guint8 *) hdlr->data + 16); - GST_LOG_OBJECT (qtdemux, "track subtype: %" GST_FOURCC_FORMAT, - GST_FOURCC_ARGS (stream->subtype)); - - if (!(minf = qtdemux_tree_get_child_by_type (mdia, FOURCC_minf))) - goto corrupt_file; - - if (!(stbl = qtdemux_tree_get_child_by_type (minf, FOURCC_stbl))) - goto corrupt_file; - - /* parse stsd */ - if (!(stsd = qtdemux_tree_get_child_by_type (stbl, FOURCC_stsd))) - goto corrupt_file; - stsd_data = (const guint8 *) stsd->data; - - /* stsd should at least have one entry */ - len = QT_UINT32 (stsd_data); - if (len < 24) - goto corrupt_file; - - /* and that entry should fit within stsd */ - len = QT_UINT32 (stsd_data + 16); - if (len > QT_UINT32 (stsd_data) + 16) - goto corrupt_file; - GST_LOG_OBJECT (qtdemux, "stsd len: %d", len); - - fourcc = QT_FOURCC (stsd_data + 16 + 4); - GST_LOG_OBJECT (qtdemux, "stsd type: %" GST_FOURCC_FORMAT, - GST_FOURCC_ARGS (fourcc)); + int offset; if ((fourcc == FOURCC_drms) || (fourcc == FOURCC_drmi)) goto error_encrypted; @@ -6840,8 +6765,10 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) if (!qtdemux->supports_drm) goto error_encrypted; - if (!(encx = qtdemux_tree_get_child_by_type (stsd, fourcc))) + encx = fourcc_node; + if (!encx) goto corrupt_file; + if (!qtdemux_parse_encx (qtdemux, stream, encx, &(stream->fourcc))) goto corrupt_file; @@ -6853,20 +6780,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) } if (stream->subtype == FOURCC_vide) { - guint32 w = 0, h = 0; - - stream->sampled = TRUE; - - /* version 1 uses some 64-bit ints */ - if (!gst_byte_reader_skip (&tkhd, 56 + value_size) - || !gst_byte_reader_get_uint32_be (&tkhd, &w) - || !gst_byte_reader_get_uint32_be (&tkhd, &h)) - goto corrupt_file; - - stream->display_width = w >> 16; - stream->display_height = h >> 16; - - offset = 16; + offset = 0; if (len < 86) goto corrupt_file; @@ -6893,7 +6807,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) esds = NULL; pasp = NULL; /* pick 'the' stsd child */ - mp4v = qtdemux_tree_get_child_by_type (stsd, fourcc); + mp4v = fourcc_node; if (mp4v) { esds = qtdemux_tree_get_child_by_type (mp4v, FOURCC_esds); pasp = qtdemux_tree_get_child_by_type (mp4v, FOURCC_pasp); @@ -6915,8 +6829,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) switch (stream->fourcc) { case FOURCC_avc1: { - gint len = QT_UINT32 (stsd_data) - 0x66; - const guint8 *avc_data = stsd_data + 0x66; + gint len = QT_UINT32 (stsd_data) - 0x56; + const guint8 *avc_data = stsd_data + 0x56; /* find avcC */ while (len >= 0x8) { @@ -7047,7 +6961,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) GST_DEBUG_OBJECT (qtdemux, "found mjp2"); /* some required atoms */ - mjp2 = qtdemux_tree_get_child_by_type (stsd, FOURCC_mjp2); + mjp2 = fourcc_node; if (!mjp2) break; jp2h = qtdemux_tree_get_child_by_type (mjp2, FOURCC_jp2h); @@ -7221,10 +7135,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) { GstBuffer *buf; GstBuffer *seqh = NULL; - guint8 *gamma_data = NULL; + const guint8 *gamma_data = NULL; gint len = QT_UINT32 (stsd_data); - qtdemux_parse_svq3_stsd_data (qtdemux, stsd, &gamma_data, &seqh); + qtdemux_parse_svq3_stsd_data (qtdemux, stsd_data, len, &gamma_data, + &seqh); if (gamma_data) { gst_caps_set_simple (stream->caps, "applied-gamma", G_TYPE_DOUBLE, QT_FP32 (gamma_data), NULL); @@ -7255,7 +7170,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) GNode *xith, *xdxt; GST_DEBUG_OBJECT (qtdemux, "found XiTh"); - xith = qtdemux_tree_get_child_by_type (stsd, FOURCC_XiTh); + xith = fourcc_node; if (!xith) break; @@ -7277,7 +7192,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) GstBuffer *buf; GST_DEBUG_OBJECT (qtdemux, "parse ovc1 header"); - ovc1 = qtdemux_tree_get_child_by_type (stsd, FOURCC_ovc1); + ovc1 = fourcc_node; if (!ovc1) break; ovc1_data = ovc1->data; @@ -7307,7 +7222,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) guint16 compression_id; gboolean amrwb = FALSE; - offset = 32; + offset = 16; if (len < 36) goto corrupt_file; @@ -7452,8 +7367,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) GNode *enda; GNode *in24; - in24 = qtdemux_tree_get_child_by_type (stsd, FOURCC_in24); - + in24 = fourcc_node; enda = qtdemux_tree_get_child_by_type (in24, FOURCC_enda); if (!enda) { wave = qtdemux_tree_get_child_by_type (in24, FOURCC_wave); @@ -7489,7 +7403,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) WAVEFORMATEX *wfex; GST_DEBUG_OBJECT (qtdemux, "parse owma"); - owma = qtdemux_tree_get_child_by_type (stsd, FOURCC_owma); + owma = fourcc_node; if (!owma) break; owma_data = owma->data; @@ -7552,7 +7466,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) bitrate, NULL); } - mp4a = qtdemux_tree_get_child_by_type (stsd, fourcc); + mp4a = fourcc_node; wave = NULL; esds = NULL; if (mp4a) { @@ -7654,7 +7568,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) /* apparently, m4a has this atom appended directly in the stsd entry, * while mov has it in a wave atom */ - alac = qtdemux_tree_get_child_by_type (stsd, FOURCC_alac); + alac = fourcc_node; if (alac) { /* alac now refers to stsd entry atom */ wave = qtdemux_tree_get_child_by_type (alac, FOURCC_wave); @@ -7722,6 +7636,9 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) GST_FOURCC_ARGS (stream->fourcc), stream->caps); } else if (stream->subtype == FOURCC_strm) { + GNode *minf; + + minf = qtdemux_tree_get_parent_by_type (fourcc_node, FOURCC_minf); if (stream->fourcc == FOURCC_rtsp) { stream->redirect_uri = qtdemux_get_rtsp_uri_from_hndl (qtdemux, minf); } else { @@ -7734,7 +7651,7 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) stream->sampled = TRUE; - offset = 16; + offset = 0; stream->caps = qtdemux_sub_caps (qtdemux, stream, stream->fourcc, stsd_data, &codec); @@ -7814,13 +7731,14 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) stream->sampled = TRUE; } + stbl = qtdemux_tree_get_parent_by_type (fourcc_node, FOURCC_stbl); /* collect sample information */ if (!qtdemux_stbl_init (qtdemux, stream, stbl)) goto samples_failed; if (qtdemux->fragmented) { - guint32 dummy; guint64 offset; + guint32 ds_size = 0, ds_duration = 0, ds_flags = 0, ds_description_idx = 0; /* need all moov samples as basis; probably not many if any at all */ /* prevent moof parsing taking of at this time */ @@ -7838,10 +7756,18 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) stream->duration = gst_util_uint64_scale (qtdemux->segment.duration, stream->timescale, GST_SECOND); /* need defaults for fragments */ - qtdemux_parse_trex (qtdemux, stream, &dummy, &dummy, &dummy); + if (qtdemux_parse_trex (qtdemux, stream->track_id, &ds_duration, &ds_size, + &ds_flags, &ds_description_idx)) { + stream->def_sample_duration = ds_duration; + stream->def_sample_size = ds_size; + stream->def_sample_flags = ds_flags; + stream->def_sample_description_idx = ds_description_idx; + stream->parsed_trex = TRUE; + } } /* configure segments */ + trak = qtdemux_tree_get_parent_by_type (fourcc_node, FOURCC_trak); if (!qtdemux_parse_segments (qtdemux, stream, trak)) goto segments_failed; @@ -7859,29 +7785,19 @@ qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) GST_TAG_LANGUAGE_CODE, (lang_code) ? lang_code : stream->lang_id, NULL); } - /* now we are ready to add the stream */ - if (qtdemux->n_streams >= GST_QTDEMUX_MAX_STREAMS) - goto too_many_streams; - stream->pending_tags = list; - qtdemux->streams[qtdemux->n_streams] = stream; - qtdemux->n_streams++; - GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d", qtdemux->n_streams); return TRUE; - /* ERRORS */ corrupt_file: { GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, (_("This file is corrupt and cannot be played.")), (NULL)); - g_free (stream); return FALSE; } error_encrypted: { GST_ELEMENT_ERROR (qtdemux, STREAM, DECRYPT, (NULL), (NULL)); - g_free (stream); return FALSE; } samples_failed: @@ -7890,15 +7806,102 @@ segments_failed: /* we posted an error already */ /* free stbl sub-atoms */ gst_qtdemux_stbl_free (stream); - g_free (stream); return FALSE; } unknown_stream: { GST_INFO_OBJECT (qtdemux, "unknown subtype %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (stream->subtype)); - g_free (stream); - return TRUE; + return FALSE; + } +} + +static gboolean +qtdemux_parse_stsd (GstQTDemux * qtdemux, GNode * stsd, + const QtDemuxStream * trackinfo) +{ + GNode *fourcc_node = NULL; + const guint8 *stsd_data; + guint32 num_entries; + guint len; + gint i; + + stsd_data = (const guint8 *) stsd->data; + + /* stsd should at least have one entry */ + len = QT_UINT32 (stsd_data); + if (len < 24) + goto corrupt_file; + + num_entries = QT_UINT32 (stsd_data + 12); + + stsd_data += 16; + len -= 16; + + for (i = 0; i < num_entries; i++) { + QtDemuxStream *stream; + guint entry_len; + guint32 fourcc; + + /* check if can add a new stream */ + if (qtdemux->n_streams >= GST_QTDEMUX_MAX_STREAMS) + goto too_many_streams; + + /* and that entry should fit within stsd */ + entry_len = QT_UINT32 (stsd_data); + if (entry_len > len) + goto corrupt_file; + + GST_LOG_OBJECT (qtdemux, "stsd len: %d", entry_len); + fourcc = QT_FOURCC (stsd_data + 4); + GST_LOG_OBJECT (qtdemux, "stsd type: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (fourcc)); + + if (!fourcc_node) + fourcc_node = qtdemux_tree_get_child_by_type (stsd, fourcc); + else { + fourcc_node = g_node_next_sibling (fourcc_node); + /* TODO check that the type is of the same type */ + } + + if (!fourcc_node) { + GST_ERROR_OBJECT (qtdemux, "can not find a node for fourcc %" + GST_FOURCC_FORMAT " skipping this stsd entry", + GST_FOURCC_ARGS (fourcc)); + goto next; + } + + /* ok, we are ready to create a new stream and try to parse it */ + stream = g_new0 (QtDemuxStream, 1); + /* first copy the common information */ + memcpy (stream, trackinfo, sizeof (QtDemuxStream)); + /* set the description index */ + stream->description_idx = i + 1; + + if (!qtdemux_parse_stsd_entry (qtdemux, stsd_data, entry_len, fourcc_node, + fourcc, stream)) { + GST_WARNING_OBJECT (qtdemux, "Parsing of stsd entry failed"); + g_free (stream); + goto next; + } + + qtdemux->streams[qtdemux->n_streams] = stream; + qtdemux->n_streams++; + GST_DEBUG_OBJECT (qtdemux, "n_streams is now %d", qtdemux->n_streams); + + next: + stsd_data += entry_len; + len -= entry_len; + } + + return TRUE; + +/* ERRORS */ +corrupt_file: + { + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is corrupt and cannot be played.")), (NULL)); + return FALSE; } too_many_streams: { @@ -7909,6 +7912,179 @@ too_many_streams: } } +/* parse the traks. + * With each track we associate a new QtDemuxStream that contains all the info + * about the trak. + * traks that do not decode to something (like strm traks) will not have a pad. + */ +static gboolean +qtdemux_parse_trak (GstQTDemux * qtdemux, GNode * trak) +{ + GstByteReader tkhd; + GNode *mdia; + GNode *mdhd; + GNode *hdlr; + GNode *minf; + GNode *stbl; + GNode *stsd; + QtDemuxStream *stream; + guint16 lang_code; /* quicktime lang code or packed iso code */ + guint32 version; + guint32 tkhd_flags = 0; + guint8 tkhd_version = 0; + guint value_size, len; + + stream = g_new0 (QtDemuxStream, 1); + /* new streams always need a discont */ + stream->discont = TRUE; + /* we enable clipping for raw audio/video streams */ + stream->need_clip = FALSE; + stream->need_process = FALSE; + stream->segment_index = -1; + stream->time_position = 0; + stream->sample_index = -1; + stream->last_ret = GST_FLOW_OK; + + if (!qtdemux_tree_get_child_by_type_full (trak, FOURCC_tkhd, &tkhd) + || !gst_byte_reader_get_uint8 (&tkhd, &tkhd_version) + || !gst_byte_reader_get_uint24_be (&tkhd, &tkhd_flags)) + goto corrupt_file; + + /* pick between 64 or 32 bits */ + value_size = tkhd_version == 1 ? 8 : 4; + if (!gst_byte_reader_skip (&tkhd, value_size * 2) || + !gst_byte_reader_get_uint32_be (&tkhd, &stream->track_id)) + goto corrupt_file; + + GST_LOG_OBJECT (qtdemux, "track[tkhd] version/flags/id: 0x%02x/%06x/%u", + tkhd_version, tkhd_flags, stream->track_id); + + if (!(mdia = qtdemux_tree_get_child_by_type (trak, FOURCC_mdia))) + goto corrupt_file; + + if (!(mdhd = qtdemux_tree_get_child_by_type (mdia, FOURCC_mdhd))) { + /* be nice for some crooked mjp2 files that use mhdr for mdhd */ + if (qtdemux->major_brand != FOURCC_mjp2 || + !(mdhd = qtdemux_tree_get_child_by_type (mdia, FOURCC_mhdr))) + goto corrupt_file; + } + + len = QT_UINT32 ((guint8 *) mdhd->data); + version = QT_UINT32 ((guint8 *) mdhd->data + 8); + GST_LOG_OBJECT (qtdemux, "track version/flags: %08x", version); + if (version == 0x01000000) { + if (len < 38) + goto corrupt_file; + stream->timescale = QT_UINT32 ((guint8 *) mdhd->data + 28); + stream->duration = QT_UINT64 ((guint8 *) mdhd->data + 32); + lang_code = QT_UINT16 ((guint8 *) mdhd->data + 36); + } else { + if (len < 30) + goto corrupt_file; + stream->timescale = QT_UINT32 ((guint8 *) mdhd->data + 20); + stream->duration = QT_UINT32 ((guint8 *) mdhd->data + 24); + lang_code = QT_UINT16 ((guint8 *) mdhd->data + 28); + } + + if (lang_code < 0x800) { + qtdemux_lang_map_qt_code_to_iso (stream->lang_id, lang_code); + } else { + stream->lang_id[0] = 0x60 + ((lang_code >> 10) & 0x1F); + stream->lang_id[1] = 0x60 + ((lang_code >> 5) & 0x1F); + stream->lang_id[2] = 0x60 + (lang_code & 0x1F); + stream->lang_id[3] = 0; + } + + GST_LOG_OBJECT (qtdemux, "track timescale: %" G_GUINT32_FORMAT, + stream->timescale); + GST_LOG_OBJECT (qtdemux, "track duration: %" G_GUINT64_FORMAT, + stream->duration); + GST_LOG_OBJECT (qtdemux, "track language code/id: 0x%04x/%s", + lang_code, stream->lang_id); + + if (G_UNLIKELY (stream->timescale == 0 || qtdemux->timescale == 0)) + goto corrupt_file; + + /* fragmented files may have bogus duration in moov */ + if (!qtdemux->fragmented && + qtdemux->duration != G_MAXINT64 && stream->duration != G_MAXINT32) { + guint64 tdur1, tdur2; + + /* don't overflow */ + tdur1 = stream->timescale * (guint64) qtdemux->duration; + tdur2 = qtdemux->timescale * (guint64) stream->duration; + + /* HACK: + * some of those trailers, nowadays, have prologue images that are + * themselves vide tracks as well. I haven't really found a way to + * identify those yet, except for just looking at their duration. */ + if (tdur1 != 0 && (tdur2 * 10 / tdur1) < 2) { + GST_WARNING_OBJECT (qtdemux, + "Track shorter than 20%% (%" G_GUINT64_FORMAT "/%" G_GUINT32_FORMAT + " vs. %" G_GUINT64_FORMAT "/%" G_GUINT32_FORMAT ") of the stream " + "found, assuming preview image or something; skipping track", + stream->duration, stream->timescale, qtdemux->duration, + qtdemux->timescale); + return TRUE; + } + } + + if (!(hdlr = qtdemux_tree_get_child_by_type (mdia, FOURCC_hdlr))) + goto corrupt_file; + + GST_LOG_OBJECT (qtdemux, "track type: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (QT_FOURCC ((guint8 *) hdlr->data + 12))); + + len = QT_UINT32 ((guint8 *) hdlr->data); + if (len >= 20) + stream->subtype = QT_FOURCC ((guint8 *) hdlr->data + 16); + GST_LOG_OBJECT (qtdemux, "track subtype: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (stream->subtype)); + + if (stream->subtype == FOURCC_vide) { + guint32 w = 0, h = 0; + + stream->sampled = TRUE; + + /* version 1 uses some 64-bit ints */ + if (!gst_byte_reader_skip (&tkhd, 56 + value_size) + || !gst_byte_reader_get_uint32_be (&tkhd, &w) + || !gst_byte_reader_get_uint32_be (&tkhd, &h)) + goto corrupt_file; + + stream->display_width = w >> 16; + stream->display_height = h >> 16; + } + + if (!(minf = qtdemux_tree_get_child_by_type (mdia, FOURCC_minf))) + goto corrupt_file; + + if (!(stbl = qtdemux_tree_get_child_by_type (minf, FOURCC_stbl))) + goto corrupt_file; + + /* parse stsd */ + if (!(stsd = qtdemux_tree_get_child_by_type (stbl, FOURCC_stsd))) + goto corrupt_file; + + /* stsd should at least have one entry */ + if (!qtdemux_parse_stsd (qtdemux, stsd, stream)) + goto corrupt_file; + + /* finally free the temporary stream */ + g_free (stream); + + return TRUE; + +/* ERRORS */ +corrupt_file: + { + GST_ELEMENT_ERROR (qtdemux, STREAM, DEMUX, + (_("This file is corrupt and cannot be played.")), (NULL)); + g_free (stream); + return FALSE; + } +} + /* If we can estimate the overall bitrate, and don't have information about the * stream bitrate for exactly one stream, this guesses the stream bitrate as * the overall bitrate minus the sum of the bitrates of all other streams. This @@ -8017,8 +8193,9 @@ qtdemux_expose_streams (GstQTDemux * qtdemux) GArray *durations; GstTagList *list; - GST_DEBUG_OBJECT (qtdemux, "stream %d, id %d, fourcc %" GST_FOURCC_FORMAT, - i, stream->track_id, GST_FOURCC_ARGS (stream->fourcc)); + GST_DEBUG_OBJECT (qtdemux, "stream %d, id %d, idx %d, fourcc %" + GST_FOURCC_FORMAT, i, stream->track_id, stream->description_idx, + GST_FOURCC_ARGS (stream->fourcc)); if (qtdemux->fragmented) { /* need all moov samples first */ |