Commit 3932dcb3 authored by Seungha Yang's avatar Seungha Yang

qtdemux: Add support HEIF compatible media

HEIF file format is a variant of ISOBMFF for image (or image sequence).
Although HEIF files are compatible with ISOBMFF, there should be special
concern since 'moov' atom is not mandatory for that specification.
For instance, top-level 'meta' atom can take the role of moov.
Also, top-level 'meta' and 'moov' can co-exist in a file.

See http://standards.iso.org/ittf/PubliclyAvailableStandards/c066067_ISO_IEC_23008-12_2017.zip
parent d7e1dcce
......@@ -58,6 +58,7 @@ G_BEGIN_DECLS
#define FOURCC_MAC6 GST_MAKE_FOURCC('M','A','C','6')
#define FOURCC_MP4V GST_MAKE_FOURCC('M','P','4','V')
#define FOURCC_PICT GST_MAKE_FOURCC('P','I','C','T')
#define FOURCC_pict GST_MAKE_FOURCC('p','i','c','t')
#define FOURCC_QDM2 GST_MAKE_FOURCC('Q','D','M','2')
#define FOURCC_SVQ3 GST_MAKE_FOURCC('S','V','Q','3')
#define FOURCC_VP31 GST_MAKE_FOURCC('V','P','3','1')
......@@ -391,6 +392,21 @@ G_BEGIN_DECLS
#define FOURCC_tenc GST_MAKE_FOURCC('t','e','n','c')
#define FOURCC_cenc GST_MAKE_FOURCC('c','e','n','c')
/* child atoms of meta */
#define FOURCC_pitm GST_MAKE_FOURCC('p','i','t','m')
#define FOURCC_iloc GST_MAKE_FOURCC('i','l','o','c')
#define FOURCC_iinf GST_MAKE_FOURCC('i','i','n','f')
#define FOURCC_infe GST_MAKE_FOURCC('i','n','f','e')
#define FOURCC_iref GST_MAKE_FOURCC('i','r','e','f')
/* High Efficiency Image File Format (HEIF) */
#define FOURCC_mif1 GST_MAKE_FOURCC('m','i','f','1')
#define FOURCC_msf1 GST_MAKE_FOURCC('m','s','f','1')
#define FOURCC_iprp GST_MAKE_FOURCC('i','p','r','p')
#define FOURCC_ipco GST_MAKE_FOURCC('i','p','c','o')
#define FOURCC_ispe GST_MAKE_FOURCC('i','s','p','e')
#define FOURCC_ipma GST_MAKE_FOURCC('i','p','m','a')
G_END_DECLS
#endif /* __FOURCC_H__ */
......@@ -488,6 +488,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_child_by_index (GNode * node, guint index);
static GstFlowReturn qtdemux_add_fragmented_samples (GstQTDemux * qtdemux);
......@@ -2140,6 +2141,10 @@ gst_qtdemux_reset (GstQTDemux * qtdemux, gboolean hard)
if (qtdemux->moov_node)
g_node_destroy (qtdemux->moov_node);
qtdemux->moov_node = NULL;
if (qtdemux->meta_node)
g_node_destroy (qtdemux->meta_node);
qtdemux->meta_node = NULL;
gst_clear_buffer (&qtdemux->metabuffer);
if (qtdemux->tag_list)
gst_mini_object_unref (GST_MINI_OBJECT_CAST (qtdemux->tag_list));
qtdemux->tag_list = gst_tag_list_new_empty ();
......@@ -4531,6 +4536,589 @@ add_offset (guint64 offset, guint64 advance)
return offset + advance;
}
/* check if major or compatible brand is mif1 */
static inline gboolean
qtdemux_is_brand_mif1 (GstQTDemux * qtdemux)
{
GstMapInfo map;
guint8 *data;
gsize size;
gboolean ret = FALSE;
if (qtdemux->major_brand == FOURCC_mif1)
return TRUE;
if (!qtdemux->comp_brands)
return FALSE;
gst_buffer_map (qtdemux->comp_brands, &map, GST_MAP_READ);
data = map.data;
size = map.size;
while (size >= 4) {
if (QT_FOURCC (data) == FOURCC_mif1) {
ret = TRUE;
break;
}
data += 4;
size -= 4;
}
return ret;
}
static gboolean
qtdemux_parse_infe (GstQTDemux * qtdemux, GNode * infe_node)
{
guint32 version;
guint32 item_id;
guint16 item_protection_index;
guint32 item_type;
const gchar *item_name = NULL;
GstByteReader infe;
QtDemuxStream *stream;
gchar *codec;
gst_byte_reader_init (&infe, infe_node->data, QT_UINT32 (infe_node->data));
if (!gst_byte_reader_skip (&infe, 8))
return FALSE;
if (!gst_byte_reader_get_uint32_be (&infe, &version))
return FALSE;
version = version >> 24;
/* required infe version is 2 or 3 */
if (version != 2 && version != 3) {
GST_WARNING_OBJECT (qtdemux, "unsupported infe version %d", version);
return FALSE;
}
if (version == 2) {
guint16 val;
if (!gst_byte_reader_get_uint16_be (&infe, &val))
return FALSE;
item_id = val;
} else if (!gst_byte_reader_get_uint32_be (&infe, &item_id)) {
return FALSE;
}
if (!gst_byte_reader_get_uint16_be (&infe, &item_protection_index) ||
!qt_atom_parser_get_fourcc (&infe, &item_type) ||
!gst_byte_reader_get_string (&infe, &item_name))
return FALSE;
GST_LOG_OBJECT (qtdemux, "item id: %" G_GUINT32_FORMAT, item_id);
GST_LOG_OBJECT (qtdemux, "item type: %" GST_FOURCC_FORMAT,
GST_FOURCC_ARGS (item_type));
GST_LOG_OBJECT (qtdemux, "item name: %s", item_name);
stream = _create_stream (qtdemux, item_id);
stream->subtype = FOURCC_pict;
/* picture must be keyframe */
stream->all_keyframe = TRUE;
/* dummy */
stream->timescale = GST_SECOND;
/* image items have no stsd box. use id 0 as default` */
stream->stsd_entries_length = 1;
stream->stsd_sample_description_id = stream->cur_stsd_entry_index = 0;
stream->stsd_entries = g_new0 (QtDemuxStreamStsdEntry, 1);
CUR_STREAM (stream)->fourcc = item_type;
CUR_STREAM (stream)->caps =
qtdemux_video_caps (qtdemux, stream, CUR_STREAM (stream), item_type,
NULL, &codec);
if (!CUR_STREAM (stream)->caps) {
GST_WARNING_OBJECT (qtdemux, "unknown stream");
gst_qtdemux_stream_unref (stream);
return FALSE;
}
if (codec) {
gst_tag_list_add (stream->stream_tags, GST_TAG_MERGE_REPLACE,
GST_TAG_VIDEO_CODEC, codec, NULL);
g_free (codec);
}
g_ptr_array_add (qtdemux->active_streams, stream);
return TRUE;
}
typedef struct _QTDemuxIpmaEntry
{
guint32 item_id;
gboolean essential;
guint16 property_index;
} QTDemuxIpmaEntry;
static GList *
qtdemux_parse_ipma (GstQTDemux * qtdemux, GNode * ipma_node, GList * entries)
{
guint32 ver_flags;
guint32 version;
guint32 entry_count;
GstByteReader ipma;
gint i, j;
GList *list = NULL;
gst_byte_reader_init (&ipma, ipma_node->data, QT_UINT32 (ipma_node->data));
if (!gst_byte_reader_skip (&ipma, 8))
return entries;
if (!gst_byte_reader_get_uint32_be (&ipma, &ver_flags) ||
!gst_byte_reader_get_uint32_be (&ipma, &entry_count))
return entries;
version = ver_flags >> 24;
for (i = 0; i < entry_count; i++) {
guint32 item_id;
guint8 association_count;
if (version < 1) {
guint16 val = 0;
if (!gst_byte_reader_get_uint16_be (&ipma, &val))
goto error;
item_id = val;
} else if (!gst_byte_reader_get_uint32_be (&ipma, &item_id)) {
goto error;
}
if (!gst_byte_reader_get_uint8 (&ipma, &association_count))
goto error;
for (j = 0; j < association_count; j++) {
guint8 essential;
guint16 property_index;
QTDemuxIpmaEntry *entry;
if (!gst_byte_reader_peek_uint8 (&ipma, &essential))
goto error;
essential = essential >> 7;
if (ver_flags & 0x1) {
if (!gst_byte_reader_get_uint16_be (&ipma, &property_index))
goto error;
property_index &= 0x7fff;
} else {
guint8 val = 0;
if (!gst_byte_reader_get_uint8 (&ipma, &val))
goto error;
property_index = val & 0x7f;
}
if (property_index == 0) {
GST_ERROR_OBJECT (qtdemux, "property index shoudl be non-zero");
goto error;
}
entry = g_new0 (QTDemuxIpmaEntry, 1);
entry->item_id = item_id;
entry->essential = essential;
entry->property_index = property_index;
list = g_list_append (list, entry);
}
}
return g_list_concat (entries, list);
error:
if (list)
g_list_free_full (list, g_free);
return entries;
}
static gboolean
reader_get_with_size (GstByteReader * data, guint8 scale, guint64 * ret)
{
switch (scale) {
case 0:
*ret = 0; /* unused */
break;
case 4:
{
guint32 tmp;
if (!gst_byte_reader_get_uint32_be (data, &tmp))
return FALSE;
*ret = tmp;
}
break;
case 8:
{
guint64 tmp;
if (!gst_byte_reader_get_uint64_be (data, &tmp))
return FALSE;
*ret = tmp;
}
break;
default:
return FALSE;
}
return TRUE;
}
static gboolean
qtdemux_parse_iloc (GstQTDemux * qtdemux, GNode * iloc_node)
{
guint32 version;
guint8 offset_length_size;
guint8 offset_size;
guint8 length_size;
guint8 base_offset_size;
guint8 index_size = 0;
guint32 item_count = 0;
guint16 val = 0;
GstByteReader iloc;
gint i, j;
gst_byte_reader_init (&iloc, iloc_node->data, QT_UINT32 (iloc_node->data));
if (!gst_byte_reader_skip (&iloc, 8))
return FALSE;
if (!gst_byte_reader_get_uint32_be (&iloc, &version))
return FALSE;
if (!gst_byte_reader_get_uint8 (&iloc, &offset_length_size) ||
!gst_byte_reader_get_uint8 (&iloc, &base_offset_size))
return FALSE;
offset_size = offset_length_size >> 4;
length_size = offset_length_size & 0x4;
version = version >> 24;
if (version == 1 || version == 2) {
index_size = base_offset_size & 0xf;
}
base_offset_size = base_offset_size >> 4;
/* validate some values {0, 4, 8} */
if (offset_size != 0 && offset_size != 4 && offset_size != 8)
return FALSE;
if (length_size != 0 && length_size != 4 && length_size != 8)
return FALSE;
if (base_offset_size != 0 && base_offset_size != 4 && base_offset_size != 8)
return FALSE;
if (version < 2) {
if (!gst_byte_reader_get_uint16_be (&iloc, &val))
return FALSE;
item_count = val;
} else if (version == 2) {
if (!gst_byte_reader_get_uint32_be (&iloc, &item_count))
return FALSE;
}
for (i = 0; i < item_count; i++) {
guint32 item_id = 0;
guint16 construction_method = 0;
guint16 data_reference_index;
guint64 base_offset = 0;
guint16 extent_count = 0;
QtDemuxStream *stream;
QtDemuxSample *sample;
if (version < 2) {
if (!gst_byte_reader_get_uint16_be (&iloc, &val))
return FALSE;
item_id = val;
} else if (version == 2) {
if (!gst_byte_reader_get_uint32_be (&iloc, &item_id))
return FALSE;
}
stream = qtdemux_find_stream (qtdemux, item_id);
if (!stream) {
GST_WARNING_OBJECT (qtdemux,
"cannot find stream for item id %d", item_id);
return FALSE;
}
if (version == 1 || version == 2) {
if (!gst_byte_reader_get_uint16_be (&iloc, &construction_method))
return FALSE;
construction_method = construction_method & 0xf;
}
if (!gst_byte_reader_get_uint16_be (&iloc, &data_reference_index))
return FALSE;
if (!reader_get_with_size (&iloc, base_offset_size, &base_offset))
return FALSE;
if (!gst_byte_reader_get_uint16_be (&iloc, &extent_count)
|| extent_count < 1)
return FALSE;
if (stream->n_samples == 0) {
g_assert (stream->samples == NULL);
stream->samples = g_try_new0 (QtDemuxSample, extent_count);
} else {
stream->samples = g_try_renew (QtDemuxSample, stream->samples,
stream->n_samples + extent_count);
}
if (!stream->samples) {
GST_ERROR_OBJECT (qtdemux, "couldn't allocated memory for sample table");
return FALSE;
}
sample = stream->samples + stream->n_samples;
for (j = 0; j < extent_count; j++) {
guint64 extent_index = 0;
guint64 extent_offset;
guint64 extent_length;
if ((version == 1 || version == 2) && index_size > 0) {
if (!reader_get_with_size (&iloc, index_size, &extent_index))
return FALSE;
}
if (!reader_get_with_size (&iloc, offset_size, &extent_offset) ||
!reader_get_with_size (&iloc, length_size, &extent_length))
return FALSE;
sample->offset = base_offset + extent_offset;
sample->size = extent_length;
sample++;
}
stream->n_samples += extent_count;
/* configure dummy segment */
if (stream->n_segments == 0) {
if (stream->segments == NULL)
stream->segments = g_new (QtDemuxSegment, 1);
stream->duration = GST_CLOCK_TIME_NONE;
stream->segments[0].time = 0;
stream->segments[0].stop_time = GST_CLOCK_TIME_NONE;
stream->segments[0].duration = GST_CLOCK_TIME_NONE;
stream->segments[0].media_start = 0;
stream->segments[0].media_stop = GST_CLOCK_TIME_NONE;
stream->segments[0].rate = 1.0;
stream->segments[0].trak_media_start = 0;
stream->n_segments = 1;
stream->dummy_segment = TRUE;
}
}
return TRUE;
}
/* Parsing top level 'meta' atom which is HEIF specification's requirement.
* Note that, ISOBMFF doesn't allow top level 'meta' atom */
static gboolean
qtdemux_parse_meta (GstQTDemux * qtdemux)
{
GNode *meta_node;
GNode *hdlr;
GNode *iinf;
GNode *infe;
GNode *iprp;
GNode *ipco;
GNode *iloc;
GNode *ipma;
GList *ipma_list = NULL;
GList *iter;
guint32 fourcc;
gint i;
meta_node = qtdemux->meta_node;
if (G_UNLIKELY (!meta_node))
return FALSE;
if (!(hdlr = qtdemux_tree_get_child_by_type (meta_node, FOURCC_hdlr))) {
GST_DEBUG_OBJECT (qtdemux, "missing hdlr box");
return FALSE;
}
fourcc = QT_FOURCC ((guint8 *) hdlr->data + 16);
if (fourcc != FOURCC_pict) {
GST_DEBUG_OBJECT (qtdemux, "Unexpected top level meta atom's handler type %"
GST_FOURCC_FORMAT, GST_FOURCC_ARGS (fourcc));
return FALSE;
}
/* item properteis */
if (!(iprp = qtdemux_tree_get_child_by_type (meta_node, FOURCC_iprp))) {
GST_DEBUG_OBJECT (qtdemux, "missing iprp box");
return FALSE;
}
/* item property container */
if (!(ipco = qtdemux_tree_get_child_by_type (iprp, FOURCC_ipco))) {
GST_DEBUG_OBJECT (qtdemux, "missing ipco box");
return FALSE;
}
/* item property association */
if (!(ipma = qtdemux_tree_get_child_by_type (iprp, FOURCC_ipma))) {
GST_DEBUG_OBJECT (qtdemux, "missing ipma box");
return FALSE;
}
/* item information */
if (!(iinf = qtdemux_tree_get_child_by_type (meta_node, FOURCC_iinf))) {
GST_DEBUG_OBJECT (qtdemux, "missing iinf box");
return FALSE;
}
/* item location */
if (!(iloc = qtdemux_tree_get_child_by_type (meta_node, FOURCC_iloc))) {
GST_DEBUG_OBJECT (qtdemux, "missing iloc box");
return FALSE;
}
/* item information entry */
infe = qtdemux_tree_get_child_by_type (iinf, FOURCC_infe);
while (infe) {
qtdemux_parse_infe (qtdemux, infe);
/* iterate all siblings */
infe = qtdemux_tree_get_sibling_by_type (infe, FOURCC_infe);
}
/* item property association */
while (ipma) {
ipma_list = qtdemux_parse_ipma (qtdemux, ipma, ipma_list);
ipma = qtdemux_tree_get_sibling_by_type (ipma, FOURCC_ipma);
}
for (iter = ipma_list, i = 0; iter; iter = g_list_next (iter), i++) {
QTDemuxIpmaEntry *entry = (QTDemuxIpmaEntry *) iter->data;
QtDemuxStream *stream;
GNode *child_node;
guint32 child_fourcc;
guint32 child_length;
GST_LOG_OBJECT (qtdemux,
"%dth association, id %d, essential %s, property_index %d",
i, entry->item_id, entry->essential ? "TRUE" : "FALSE",
entry->property_index);
if (entry->property_index == 0) {
GST_WARNING_OBJECT (qtdemux,
"invalid property index zero for %d th entry", i);
continue;
}
stream = qtdemux_find_stream (qtdemux, entry->item_id);
if (!stream) {
GST_WARNING_OBJECT (qtdemux, "unknown item id %d", entry->item_id);
continue;
}
child_node = qtdemux_tree_get_child_by_index (ipco,
entry->property_index - 1);
if (!child_node) {
GST_WARNING_OBJECT (qtdemux,
"ipco box does not have %d th child", entry->property_index - 1);
continue;
}
child_length = QT_UINT32 ((guint8 *) child_node->data);
child_fourcc = QT_FOURCC ((guint8 *) child_node->data + 4);
GST_LOG_OBJECT (qtdemux, "handle ipco::%" GST_FOURCC_FORMAT,
GST_FOURCC_ARGS (child_fourcc));
switch (child_fourcc) {
case FOURCC_ispe:
{
guint32 width, height;
child_length = QT_UINT32 ((guint8 *) child_node->data);
if (G_UNLIKELY (child_length < 20)) {
GST_WARNING_OBJECT (qtdemux, "ispe size is too small");
continue;
}
width = QT_UINT32 ((guint8 *) child_node->data + 12);
height = QT_UINT32 ((guint8 *) child_node->data + 16);
GST_DEBUG_OBJECT (qtdemux, "parsed resolution %dx%d", width, height);
CUR_STREAM (stream)->width = width;
CUR_STREAM (stream)->height = height;
}
break;
case FOURCC_hvcC:
{
GstBuffer *buf;
gint codec_data_size = child_length - 0x8;
guint8 *codec_data = (guint8 *) child_node->data + 8;
GST_DEBUG_OBJECT (qtdemux, "found hvcC codec data in ipco");
/* First 4 bytes are the length of the atom, the next 4 bytes
* are the fourcc, the next 1 byte is the version, and the
* subsequent bytes are sequence parameter set like data. */
gst_codec_utils_h265_caps_set_level_tier_and_profile
(CUR_STREAM (stream)->caps, codec_data + 1, codec_data_size - 1);
buf = gst_buffer_new_and_alloc (codec_data_size);
gst_buffer_fill (buf, 0, codec_data, codec_data_size);
gst_caps_set_simple (CUR_STREAM (stream)->caps,
"codec_data", GST_TYPE_BUFFER, buf, NULL);
gst_buffer_unref (buf);
}
break;
case FOURCC_avcC:
{
GstBuffer *buf;
gint codec_data_size = child_length - 0x8;
guint8 *codec_data = (guint8 *) child_node->data + 8;
GST_DEBUG_OBJECT (qtdemux, "found avcC codec data in ipco");
/* First 4 bytes are the length of the atom, the next 4 bytes
* are the fourcc, the next 1 byte is the version, and the
* subsequent bytes are sequence parameter set like data. */
gst_codec_utils_h264_caps_set_level_and_profile
(CUR_STREAM (stream)->caps, codec_data + 1, codec_data_size - 1);
buf = gst_buffer_new_and_alloc (codec_data_size);
gst_buffer_fill (buf, 0, codec_data, codec_data_size);
gst_caps_set_simple (CUR_STREAM (stream)->caps,
"codec_data", GST_TYPE_BUFFER, buf, NULL);
gst_buffer_unref (buf);
}
break;
default:
break;
}
}
/* parse item location */
return qtdemux_parse_iloc (qtdemux, iloc);
}
static GstFlowReturn
gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
{
......@@ -4703,6 +5291,46 @@ gst_qtdemux_loop_state_header (GstQTDemux * qtdemux)
gst_buffer_unref (sidx);
break;
}
case FOURCC_meta:
{
GstBuffer *meta = NULL;
if (qtdemux->meta_node) {
GST_DEBUG_OBJECT (qtdemux, "Skipping meta atom as we have one already");
qtdemux->offset = add_offset (qtdemux->offset, length);
goto beach;
}
/* HEIF specifies that 'meta' atom is required at top level and
* the specification does not mandate a 'moov' atom */
ret = gst_qtdemux_pull_atom (qtdemux, cur_offset, length, &meta);
if (ret != GST_FLOW_OK)
goto beach;
if (!qtdemux_is_brand_mif1 (qtdemux))
break;
qtdemux->offset += length;
gst_buffer_map (meta, &map, GST_MAP_READ);
qtdemux->meta_node = g_node_new (map.data);
qtdemux_parse_node (qtdemux, qtdemux->meta_node, map.data, map.size);
qtdemux_node_dump (qtdemux, qtdemux->meta_node);
qtdemux->header_size += length;
gst_buffer_unmap (meta, &map);
qtdemux->metabuffer = meta;
if (qtdemux_parse_meta (qtdemux)) {
GST_DEBUG_OBJECT (qtdemux, "initial meta atom parsing done");
qtdemux->header_state |= QTDEMUX_HEADER_INIT;
} else {
gst_clear_buffer (&qtdemux->metabuffer);
g_node_destroy (qtdemux->meta_node);
qtdemux->meta_node = NULL;
}
break;
}
default:
{
GstBuffer *unknown = NULL;
......@@ -7013,6 +7641,11 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
if (QTDEMUX_N_STREAMS (demux) > 0 && (next_entry != -1
|| !demux->fragmented)) {
/* we have the headers, start playback */
if (!demux->exposed) {
QTDEMUX_EXPOSE_LOCK (demux);
qtdemux_expose_streams (demux);
QTDEMUX_EXPOSE_UNLOCK (demux);
}
demux->state = QTDEMUX_STATE_MOVIE;
demux->neededbytes = next_entry;
demux->mdatleft = size;
......@@ -7134,6 +7767,8 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
qtdemux_parse_moov (demux, data, demux->neededbytes);
qtdemux_node_dump (demux, demux->moov_node);
qtdemux_parse_tree (demux);
if (demux->meta_node)
qtdemux_parse_meta (demux);
qtdemux_prepare_streams (demux);
QTDEMUX_EXPOSE_LOCK (demux);
qtdemux_expose_streams (demux);
......@@ -7245,6 +7880,40 @@ gst_qtdemux_process_adapter (GstQTDemux * demux, gboolean force)
} else if (fourcc == FOURCC_sidx) {
GST_DEBUG_OBJECT (demux, "Parsing [sidx]");
qtdemux_parse_sidx (demux, data, demux->neededbytes);
} else if (fourcc == FOURCC_meta && qtdemux_is_brand_mif1 (demux)) {
GstMapInfo meta_info;
GST_DEBUG_OBJECT (demux, "Parsing [meta]");
if (demux->meta_node)
g_node_destroy (demux->meta_node);
gst_clear_buffer (&demux->metabuffer);
demux->metabuffer = gst_buffer_new_and_alloc (demux->neededbytes);
gst_buffer_map (demux->metabuffer, &meta_info, GST_MAP_WRITE);
memcpy (meta_info.data, data, demux->neededbytes);
demux->meta_node = g_node_new (meta_info.data);
qtdemux_parse_node (demux, demux->meta_node,
meta_info.data, meta_info.size);
gst_buffer_unmap (demux->metabuffer, &meta_info);
qtdemux_node_dump (demux, demux->meta_node);
if (qtdemux_parse_meta (demux)) {