/* GStreamer Matroska muxer/demuxer * (c) 2003 Ronald Bultje * (c) 2006 Tim-Philipp Müller * (c) 2008 Sebastian Dröge * (c) 2011 Debarshi Ray * * matroska-read-common.c: shared by matroska file/stream demuxer and parser * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #ifdef HAVE_ZLIB #include #endif #ifdef HAVE_BZ2 #include #endif #include #include #include "lzo.h" #include "ebml-read.h" #include "matroska-read-common.h" GST_DEBUG_CATEGORY (matroskareadcommon_debug); #define GST_CAT_DEFAULT matroskareadcommon_debug #define DEBUG_ELEMENT_START(common, ebml, element) \ GST_DEBUG_OBJECT (common, "Parsing " element " element at offset %" \ G_GUINT64_FORMAT, gst_ebml_read_get_pos (ebml)) #define DEBUG_ELEMENT_STOP(common, ebml, element, ret) \ GST_DEBUG_OBJECT (common, "Parsing " element " element " \ " finished with '%s'", gst_flow_get_name (ret)) #define GST_MATROSKA_TOC_UID_CHAPTER "chapter" #define GST_MATROSKA_TOC_UID_EDITION "edition" #define GST_MATROSKA_TOC_UID_EMPTY "empty" static gboolean gst_matroska_decompress_data (GstMatroskaTrackEncoding * enc, gpointer * data_out, gsize * size_out, GstMatroskaTrackCompressionAlgorithm algo) { guint8 *new_data = NULL; guint new_size = 0; guint8 *data = *data_out; guint size = *size_out; gboolean ret = TRUE; if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_ZLIB) { #ifdef HAVE_ZLIB /* zlib encoded data */ z_stream zstream; guint orig_size; int result; orig_size = size; zstream.zalloc = (alloc_func) 0; zstream.zfree = (free_func) 0; zstream.opaque = (voidpf) 0; if (inflateInit (&zstream) != Z_OK) { GST_WARNING ("zlib initialization failed."); ret = FALSE; goto out; } zstream.next_in = (Bytef *) data; zstream.avail_in = orig_size; new_size = orig_size; new_data = g_malloc (new_size); zstream.avail_out = new_size; zstream.next_out = (Bytef *) new_data; do { result = inflate (&zstream, Z_NO_FLUSH); if (result != Z_OK && result != Z_STREAM_END) { GST_WARNING ("zlib decompression failed."); g_free (new_data); inflateEnd (&zstream); break; } new_size += 4000; new_data = g_realloc (new_data, new_size); zstream.next_out = (Bytef *) (new_data + zstream.total_out); zstream.avail_out += 4000; } while (zstream.avail_in != 0 && result != Z_STREAM_END); if (result != Z_STREAM_END) { ret = FALSE; goto out; } else { new_size = zstream.total_out; inflateEnd (&zstream); } #else GST_WARNING ("zlib encoded tracks not supported."); ret = FALSE; goto out; #endif } else if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_BZLIB) { #ifdef HAVE_BZ2 /* bzip2 encoded data */ bz_stream bzstream; guint orig_size; int result; bzstream.bzalloc = NULL; bzstream.bzfree = NULL; bzstream.opaque = NULL; orig_size = size; if (BZ2_bzDecompressInit (&bzstream, 0, 0) != BZ_OK) { GST_WARNING ("bzip2 initialization failed."); ret = FALSE; goto out; } bzstream.next_in = (char *) data; bzstream.avail_in = orig_size; new_size = orig_size; new_data = g_malloc (new_size); bzstream.avail_out = new_size; bzstream.next_out = (char *) new_data; do { result = BZ2_bzDecompress (&bzstream); if (result != BZ_OK && result != BZ_STREAM_END) { GST_WARNING ("bzip2 decompression failed."); g_free (new_data); BZ2_bzDecompressEnd (&bzstream); break; } new_size += 4000; new_data = g_realloc (new_data, new_size); bzstream.next_out = (char *) (new_data + bzstream.total_out_lo32); bzstream.avail_out += 4000; } while (bzstream.avail_in != 0 && result != BZ_STREAM_END); if (result != BZ_STREAM_END) { ret = FALSE; goto out; } else { new_size = bzstream.total_out_lo32; BZ2_bzDecompressEnd (&bzstream); } #else GST_WARNING ("bzip2 encoded tracks not supported."); ret = FALSE; goto out; #endif } else if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_LZO1X) { /* lzo encoded data */ int result; int orig_size, out_size; orig_size = size; out_size = size; new_size = size; new_data = g_malloc (new_size); do { orig_size = size; out_size = new_size; result = lzo1x_decode (new_data, &out_size, data, &orig_size); if (orig_size > 0) { new_size += 4000; new_data = g_realloc (new_data, new_size); } } while (orig_size > 0 && result == LZO_OUTPUT_FULL); new_size -= out_size; if (result != LZO_OUTPUT_FULL) { GST_WARNING ("lzo decompression failed"); g_free (new_data); ret = FALSE; goto out; } } else if (algo == GST_MATROSKA_TRACK_COMPRESSION_ALGORITHM_HEADERSTRIP) { /* header stripped encoded data */ if (enc->comp_settings_length > 0) { new_data = g_malloc (size + enc->comp_settings_length); new_size = size + enc->comp_settings_length; memcpy (new_data, enc->comp_settings, enc->comp_settings_length); memcpy (new_data + enc->comp_settings_length, data, size); } } else { GST_ERROR ("invalid compression algorithm %d", algo); ret = FALSE; } out: if (!ret) { *data_out = NULL; *size_out = 0; } else { *data_out = new_data; *size_out = new_size; } return ret; } GstFlowReturn gst_matroska_decode_content_encodings (GArray * encodings) { gint i; if (encodings == NULL) return GST_FLOW_OK; for (i = 0; i < encodings->len; i++) { GstMatroskaTrackEncoding *enc = &g_array_index (encodings, GstMatroskaTrackEncoding, i); gpointer data = NULL; gsize size; if ((enc->scope & GST_MATROSKA_TRACK_ENCODING_SCOPE_NEXT_CONTENT_ENCODING) == 0) continue; /* Encryption not supported yet */ if (enc->type != 0) return GST_FLOW_ERROR; if (i + 1 >= encodings->len) return GST_FLOW_ERROR; if (enc->comp_settings_length == 0) continue; data = enc->comp_settings; size = enc->comp_settings_length; if (!gst_matroska_decompress_data (enc, &data, &size, enc->comp_algo)) return GST_FLOW_ERROR; g_free (enc->comp_settings); enc->comp_settings = data; enc->comp_settings_length = size; } return GST_FLOW_OK; } gboolean gst_matroska_decode_data (GArray * encodings, gpointer * data_out, gsize * size_out, GstMatroskaTrackEncodingScope scope, gboolean free) { gpointer data; gsize size; gboolean ret = TRUE; gint i; g_return_val_if_fail (encodings != NULL, FALSE); g_return_val_if_fail (data_out != NULL && *data_out != NULL, FALSE); g_return_val_if_fail (size_out != NULL, FALSE); data = *data_out; size = *size_out; for (i = 0; i < encodings->len; i++) { GstMatroskaTrackEncoding *enc = &g_array_index (encodings, GstMatroskaTrackEncoding, i); gpointer new_data = NULL; gsize new_size = 0; if ((enc->scope & scope) == 0) continue; /* Encryption not supported yet */ if (enc->type != 0) { ret = FALSE; break; } new_data = data; new_size = size; ret = gst_matroska_decompress_data (enc, &new_data, &new_size, enc->comp_algo); if (!ret) break; if ((data == *data_out && free) || (data != *data_out)) g_free (data); data = new_data; size = new_size; } if (!ret) { if ((data == *data_out && free) || (data != *data_out)) g_free (data); *data_out = NULL; *size_out = 0; } else { *data_out = data; *size_out = size; } return ret; } static gint gst_matroska_index_compare (GstMatroskaIndex * i1, GstMatroskaIndex * i2) { if (i1->time < i2->time) return -1; else if (i1->time > i2->time) return 1; else if (i1->block < i2->block) return -1; else if (i1->block > i2->block) return 1; else return 0; } gint gst_matroska_index_seek_find (GstMatroskaIndex * i1, GstClockTime * time, gpointer user_data) { if (i1->time < *time) return -1; else if (i1->time > *time) return 1; else return 0; } GstMatroskaIndex * gst_matroska_read_common_do_index_seek (GstMatroskaReadCommon * common, GstMatroskaTrackContext * track, gint64 seek_pos, GArray ** _index, gint * _entry_index, gboolean next) { GstMatroskaIndex *entry = NULL; GArray *index; if (!common->index || !common->index->len) return NULL; /* find entry just before or at the requested position */ if (track && track->index_table) index = track->index_table; else index = common->index; entry = gst_util_array_binary_search (index->data, index->len, sizeof (GstMatroskaIndex), (GCompareDataFunc) gst_matroska_index_seek_find, next ? GST_SEARCH_MODE_AFTER : GST_SEARCH_MODE_BEFORE, &seek_pos, NULL); if (entry == NULL) { if (next) { return NULL; } else { entry = &g_array_index (index, GstMatroskaIndex, 0); } } if (_index) *_index = index; if (_entry_index) *_entry_index = entry - (GstMatroskaIndex *) index->data; return entry; } static gint gst_matroska_read_common_encoding_cmp (GstMatroskaTrackEncoding * a, GstMatroskaTrackEncoding * b) { if (b->order > a->order) return 1; else if (b->order < a->order) return -1; else return 0; } static gboolean gst_matroska_read_common_encoding_order_unique (GArray * encodings, guint64 order) { gint i; if (encodings == NULL || encodings->len == 0) return TRUE; for (i = 0; i < encodings->len; i++) if (g_array_index (encodings, GstMatroskaTrackEncoding, i).order == order) return FALSE; return TRUE; } /* takes ownership of taglist */ void gst_matroska_read_common_found_global_tag (GstMatroskaReadCommon * common, GstElement * el, GstTagList * taglist) { if (common->global_tags) { /* nothing sent yet, add to cache */ gst_tag_list_insert (common->global_tags, taglist, GST_TAG_MERGE_APPEND); gst_tag_list_free (taglist); } else { GstEvent *tag_event = gst_event_new_tag (taglist); gint i; /* hm, already sent, no need to cache and wait anymore */ GST_DEBUG_OBJECT (common, "Sending late global tags %" GST_PTR_FORMAT, taglist); for (i = 0; i < common->src->len; i++) { GstMatroskaTrackContext *stream; stream = g_ptr_array_index (common->src, i); gst_pad_push_event (stream->pad, gst_event_ref (tag_event)); } gst_event_unref (tag_event); } } gint64 gst_matroska_read_common_get_length (GstMatroskaReadCommon * common) { gint64 end = -1; if (!gst_pad_peer_query_duration (common->sinkpad, GST_FORMAT_BYTES, &end) || end < 0) GST_DEBUG_OBJECT (common, "no upstream length"); return end; } /* determine track to seek in */ GstMatroskaTrackContext * gst_matroska_read_common_get_seek_track (GstMatroskaReadCommon * common, GstMatroskaTrackContext * track) { gint i; if (track && track->type == GST_MATROSKA_TRACK_TYPE_VIDEO) return track; for (i = 0; i < common->src->len; i++) { GstMatroskaTrackContext *stream; stream = g_ptr_array_index (common->src, i); if (stream->type == GST_MATROSKA_TRACK_TYPE_VIDEO && stream->index_table) track = stream; } return track; } /* skip unknown or alike element */ GstFlowReturn gst_matroska_read_common_parse_skip (GstMatroskaReadCommon * common, GstEbmlRead * ebml, const gchar * parent_name, guint id) { if (id == GST_EBML_ID_VOID) { GST_DEBUG_OBJECT (common, "Skipping EBML Void element"); } else if (id == GST_EBML_ID_CRC32) { GST_DEBUG_OBJECT (common, "Skipping EBML CRC32 element"); } else { GST_WARNING_OBJECT (common, "Unknown %s subelement 0x%x - ignoring", parent_name, id); } return gst_ebml_read_skip (ebml); } static GstFlowReturn gst_matroska_read_common_parse_attached_file (GstMatroskaReadCommon * common, GstEbmlRead * ebml, GstTagList * taglist) { guint32 id; GstFlowReturn ret; gchar *description = NULL; gchar *filename = NULL; gchar *mimetype = NULL; guint8 *data = NULL; guint64 datalen = 0; DEBUG_ELEMENT_START (common, ebml, "AttachedFile"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "AttachedFile", ret); return ret; } while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { /* read all sub-entries */ if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; switch (id) { case GST_MATROSKA_ID_FILEDESCRIPTION: if (description) { GST_WARNING_OBJECT (common, "FileDescription can only appear once"); break; } ret = gst_ebml_read_utf8 (ebml, &id, &description); GST_DEBUG_OBJECT (common, "FileDescription: %s", GST_STR_NULL (description)); break; case GST_MATROSKA_ID_FILENAME: if (filename) { GST_WARNING_OBJECT (common, "FileName can only appear once"); break; } ret = gst_ebml_read_utf8 (ebml, &id, &filename); GST_DEBUG_OBJECT (common, "FileName: %s", GST_STR_NULL (filename)); break; case GST_MATROSKA_ID_FILEMIMETYPE: if (mimetype) { GST_WARNING_OBJECT (common, "FileMimeType can only appear once"); break; } ret = gst_ebml_read_ascii (ebml, &id, &mimetype); GST_DEBUG_OBJECT (common, "FileMimeType: %s", GST_STR_NULL (mimetype)); break; case GST_MATROSKA_ID_FILEDATA: if (data) { GST_WARNING_OBJECT (common, "FileData can only appear once"); break; } ret = gst_ebml_read_binary (ebml, &id, &data, &datalen); GST_DEBUG_OBJECT (common, "FileData of size %" G_GUINT64_FORMAT, datalen); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "AttachedFile", id); break; case GST_MATROSKA_ID_FILEUID: ret = gst_ebml_read_skip (ebml); break; } } DEBUG_ELEMENT_STOP (common, ebml, "AttachedFile", ret); if (filename && mimetype && data && datalen > 0) { GstTagImageType image_type = GST_TAG_IMAGE_TYPE_NONE; GstBuffer *tagbuffer = NULL; GstSample *tagsample = NULL; GstStructure *info = NULL; GstCaps *caps = NULL; gchar *filename_lc = g_utf8_strdown (filename, -1); GST_DEBUG_OBJECT (common, "Creating tag for attachment with " "filename '%s', mimetype '%s', description '%s', " "size %" G_GUINT64_FORMAT, filename, mimetype, GST_STR_NULL (description), datalen); /* TODO: better heuristics for different image types */ if (strstr (filename_lc, "cover")) { if (strstr (filename_lc, "back")) image_type = GST_TAG_IMAGE_TYPE_BACK_COVER; else image_type = GST_TAG_IMAGE_TYPE_FRONT_COVER; } else if (g_str_has_prefix (mimetype, "image/") || g_str_has_suffix (filename_lc, "png") || g_str_has_suffix (filename_lc, "jpg") || g_str_has_suffix (filename_lc, "jpeg") || g_str_has_suffix (filename_lc, "gif") || g_str_has_suffix (filename_lc, "bmp")) { image_type = GST_TAG_IMAGE_TYPE_UNDEFINED; } g_free (filename_lc); /* First try to create an image tag buffer from this */ if (image_type != GST_TAG_IMAGE_TYPE_NONE) { tagsample = gst_tag_image_data_to_image_sample (data, datalen, image_type); if (!tagsample) image_type = GST_TAG_IMAGE_TYPE_NONE; else { data = NULL; tagbuffer = gst_buffer_ref (gst_sample_get_buffer (tagsample)); caps = gst_caps_ref (gst_sample_get_caps (tagsample)); info = gst_structure_copy (gst_sample_get_info (tagsample)); gst_sample_unref (tagsample); } } /* if this failed create an attachment buffer */ if (!tagbuffer) { tagbuffer = gst_buffer_new_wrapped (g_memdup (data, datalen), datalen); caps = gst_type_find_helper_for_buffer (NULL, tagbuffer, NULL); if (caps == NULL) caps = gst_caps_new_empty_simple (mimetype); } /* Set filename and description in the info */ if (info == NULL) info = gst_structure_new_empty ("GstTagImageInfo"); gst_structure_set (info, "filename", G_TYPE_STRING, filename, NULL); if (description) gst_structure_set (info, "description", G_TYPE_STRING, description, NULL); tagsample = gst_sample_new (tagbuffer, caps, NULL, info); GST_DEBUG_OBJECT (common, "Created attachment sample: %" GST_PTR_FORMAT, tagsample); /* and append to the tag list */ if (image_type != GST_TAG_IMAGE_TYPE_NONE) gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, GST_TAG_IMAGE, tagsample, NULL); else gst_tag_list_add (taglist, GST_TAG_MERGE_APPEND, GST_TAG_ATTACHMENT, tagsample, NULL); /* the list adds it own ref */ gst_sample_unref (tagsample); } g_free (filename); g_free (mimetype); g_free (data); g_free (description); return ret; } GstFlowReturn gst_matroska_read_common_parse_attachments (GstMatroskaReadCommon * common, GstElement * el, GstEbmlRead * ebml) { guint32 id; GstFlowReturn ret = GST_FLOW_OK; GstTagList *taglist; DEBUG_ELEMENT_START (common, ebml, "Attachments"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "Attachments", ret); return ret; } taglist = gst_tag_list_new_empty (); 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) { case GST_MATROSKA_ID_ATTACHEDFILE: ret = gst_matroska_read_common_parse_attached_file (common, ebml, taglist); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "Attachments", id); break; } } DEBUG_ELEMENT_STOP (common, ebml, "Attachments", ret); if (gst_structure_n_fields (GST_STRUCTURE (taglist)) > 0) { GST_DEBUG_OBJECT (common, "Storing attachment tags"); gst_matroska_read_common_found_global_tag (common, el, taglist); } else { GST_DEBUG_OBJECT (common, "No valid attachments found"); gst_tag_list_free (taglist); } common->attachments_parsed = TRUE; return ret; } static void gst_matroska_read_common_parse_toc_tag (GstTocEntry * entry, GArray * edition_targets, GArray * chapter_targtes, GstTagList * tags) { gchar *uid; guint i; guint64 tgt; GArray *targets; GList *cur; targets = (entry->type == GST_TOC_ENTRY_TYPE_EDITION) ? edition_targets : chapter_targtes; for (i = 0; i < targets->len; ++i) { tgt = g_array_index (targets, guint64, i); if (tgt == 0) gst_tag_list_insert (entry->tags, tags, GST_TAG_MERGE_APPEND); else { uid = g_strdup_printf ("%" G_GUINT64_FORMAT, tgt); if (g_strcmp0 (entry->uid, uid) == 0) gst_tag_list_insert (entry->tags, tags, GST_TAG_MERGE_APPEND); g_free (uid); } } cur = entry->subentries; while (cur != NULL) { gst_matroska_read_common_parse_toc_tag (cur->data, edition_targets, chapter_targtes, tags); cur = cur->next; } } static GstFlowReturn gst_matroska_read_common_parse_metadata_targets (GstMatroskaReadCommon * common, GstEbmlRead * ebml, GArray * edition_targets, GArray * chapter_targets) { GstFlowReturn ret = GST_FLOW_OK; guint32 id; guint64 uid; DEBUG_ELEMENT_START (common, ebml, "TagTargets"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "TagTargets", 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) { case GST_MATROSKA_ID_TARGETCHAPTERUID: if ((ret = gst_ebml_read_uint (ebml, &id, &uid)) == GST_FLOW_OK) g_array_append_val (chapter_targets, uid); break; case GST_MATROSKA_ID_TARGETEDITIONUID: if ((ret = gst_ebml_read_uint (ebml, &id, &uid)) == GST_FLOW_OK) g_array_append_val (edition_targets, uid); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "TagTargets", id); break; } } DEBUG_ELEMENT_STOP (common, ebml, "TagTargets", ret); return ret; } static void gst_matroska_read_common_postprocess_toc_entries (GList * toc_entries, guint64 max, const gchar * parent_uid) { GstTocEntry *cur_info, *prev_info, *next_info; GList *cur_list, *prev_list, *next_list; gchar *iter_digit; gint i = 0; gint64 cur_start, prev_start, stop; cur_list = toc_entries; while (cur_list != NULL) { ++i; cur_info = cur_list->data; iter_digit = g_strdup_printf ("%d", i); switch (cur_info->type) { case GST_TOC_ENTRY_TYPE_EDITION: /* in Matroska terms edition has duration of full track */ gst_toc_entry_set_start_stop (cur_info, 0, max); if (cur_info->uid == NULL) cur_info->uid = g_strconcat (parent_uid, "/", GST_MATROSKA_TOC_UID_EDITION, iter_digit, NULL); gst_matroska_read_common_postprocess_toc_entries (cur_info->subentries, max, cur_info->uid); break; case GST_TOC_ENTRY_TYPE_CHAPTER: prev_list = cur_list->prev; next_list = cur_list->next; if (prev_list != NULL) prev_info = prev_list->data; else prev_info = NULL; if (next_list != NULL) next_info = next_list->data; else next_info = NULL; if (cur_info->uid == NULL) cur_info->uid = g_strconcat (parent_uid, "/", GST_MATROSKA_TOC_UID_CHAPTER, iter_digit, NULL); /* updated stop time in previous chapter and it's subchapters */ if (prev_info != NULL) { gst_toc_entry_get_start_stop (prev_info, &prev_start, &stop); gst_toc_entry_get_start_stop (cur_info, &cur_start, &stop); stop = cur_start; gst_toc_entry_set_start_stop (prev_info, prev_start, stop); gst_matroska_read_common_postprocess_toc_entries (prev_info->subentries, cur_start, prev_info->uid); } /* updated stop time in current chapter and it's subchapters */ if (next_info == NULL) { gst_toc_entry_get_start_stop (cur_info, &cur_start, &stop); if (stop == -1) { stop = max; gst_toc_entry_set_start_stop (cur_info, cur_start, stop); } gst_matroska_read_common_postprocess_toc_entries (cur_info->subentries, stop, cur_info->uid); } break; } cur_list = cur_list->next; g_free (iter_digit); } } static GstFlowReturn gst_matroska_read_common_parse_chapter_titles (GstMatroskaReadCommon * common, GstEbmlRead * ebml, GstTagList * titles) { guint32 id; gchar *title = NULL; GstFlowReturn ret = GST_FLOW_OK; DEBUG_ELEMENT_START (common, ebml, "ChaptersTitles"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "ChaptersTitles", 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) { case GST_MATROSKA_ID_CHAPSTRING: ret = gst_ebml_read_utf8 (ebml, &id, &title); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "ChaptersTitles", id); break; } } DEBUG_ELEMENT_STOP (common, ebml, "ChaptersTitles", ret); if (title != NULL && ret == GST_FLOW_OK) gst_tag_list_add (titles, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, title, NULL); g_free (title); return ret; } static GstFlowReturn gst_matroska_read_common_parse_chapter_element (GstMatroskaReadCommon * common, GstEbmlRead * ebml, GstTocEntry * toc_entry) { guint32 id; guint64 start_time = -1, stop_time = -1; guint64 is_hidden = 0, is_enabled = 1, uid = 0; GstFlowReturn ret = GST_FLOW_OK; GstTocEntry *chapter_info; GstTagList *titles; DEBUG_ELEMENT_START (common, ebml, "ChaptersElement"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "ChaptersElement", ret); return ret; } titles = gst_tag_list_new_empty (); chapter_info = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, GST_MATROSKA_TOC_UID_EMPTY); 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) { case GST_MATROSKA_ID_CHAPTERUID: ret = gst_ebml_read_uint (ebml, &id, &uid); break; case GST_MATROSKA_ID_CHAPTERTIMESTART: ret = gst_ebml_read_uint (ebml, &id, &start_time); break; case GST_MATROSKA_ID_CHAPTERTIMESTOP: ret = gst_ebml_read_uint (ebml, &id, &stop_time); break; case GST_MATROSKA_ID_CHAPTERATOM: ret = gst_matroska_read_common_parse_chapter_element (common, ebml, chapter_info); break; case GST_MATROSKA_ID_CHAPTERDISPLAY: ret = gst_matroska_read_common_parse_chapter_titles (common, ebml, titles); break; case GST_MATROSKA_ID_CHAPTERFLAGHIDDEN: ret = gst_ebml_read_uint (ebml, &id, &is_hidden); break; case GST_MATROSKA_ID_CHAPTERFLAGENABLED: ret = gst_ebml_read_uint (ebml, &id, &is_enabled); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "ChaptersElement", id); break; } } gst_toc_entry_set_start_stop (chapter_info, start_time, stop_time); DEBUG_ELEMENT_STOP (common, ebml, "ChaptersElement", ret); g_free (chapter_info->uid); if (uid != 0) chapter_info->uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uid); else chapter_info->uid = NULL; /* start time is mandatory and has no default value, * so we should skip chapters without it */ if (is_hidden == 0 && is_enabled > 0 && start_time != -1 && ret == GST_FLOW_OK) { if (!gst_tag_list_is_empty (titles)) gst_tag_list_insert (chapter_info->tags, titles, GST_TAG_MERGE_APPEND); toc_entry->subentries = g_list_append (toc_entry->subentries, chapter_info); } else gst_toc_entry_free (chapter_info); gst_tag_list_free (titles); return ret; } static GstFlowReturn gst_matroska_read_common_parse_chapter_edition (GstMatroskaReadCommon * common, GstEbmlRead * ebml, GstToc * toc) { guint32 id; guint64 is_hidden = 0, uid = 0; GstFlowReturn ret = GST_FLOW_OK; GstTocEntry *edition_info; DEBUG_ELEMENT_START (common, ebml, "ChaptersEdition"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "ChaptersEdition", ret); return ret; } edition_info = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, GST_MATROSKA_TOC_UID_EMPTY); gst_toc_entry_set_start_stop (edition_info, -1, -1); 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) { case GST_MATROSKA_ID_EDITIONUID: ret = gst_ebml_read_uint (ebml, &id, &uid); break; case GST_MATROSKA_ID_CHAPTERATOM: ret = gst_matroska_read_common_parse_chapter_element (common, ebml, edition_info); break; case GST_MATROSKA_ID_EDITIONFLAGHIDDEN: ret = gst_ebml_read_uint (ebml, &id, &is_hidden); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "ChaptersEdition", id); break; } } DEBUG_ELEMENT_STOP (common, ebml, "ChaptersEdition", ret); g_free (edition_info->uid); if (uid != 0) edition_info->uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uid); else edition_info->uid = NULL; if (is_hidden == 0 && edition_info->subentries != NULL && ret == GST_FLOW_OK) toc->entries = g_list_prepend (toc->entries, edition_info); else { GST_DEBUG_OBJECT (common, "Skipping empty or hidden edition in the chapters TOC"); gst_toc_entry_free (edition_info); } return ret; } GstFlowReturn gst_matroska_read_common_parse_chapters (GstMatroskaReadCommon * common, GstEbmlRead * ebml) { guint32 id; GstFlowReturn ret = GST_FLOW_OK; GstToc *toc; DEBUG_ELEMENT_START (common, ebml, "Chapters"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "Chapters", ret); return ret; } toc = gst_toc_new (); 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) { case GST_MATROSKA_ID_EDITIONENTRY: ret = gst_matroska_read_common_parse_chapter_edition (common, ebml, toc); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "Chapters", id); break; } } if (toc->entries != NULL) { toc->entries = g_list_reverse (toc->entries); gst_matroska_read_common_postprocess_toc_entries (toc->entries, common->segment.duration, ""); common->toc = toc; } else gst_toc_free (toc); common->chapters_parsed = TRUE; DEBUG_ELEMENT_STOP (common, ebml, "Chapters", ret); return ret; } GstFlowReturn gst_matroska_read_common_parse_header (GstMatroskaReadCommon * common, GstEbmlRead * ebml) { GstFlowReturn ret; gchar *doctype; guint version; guint32 id; /* this function is the first to be called */ /* default init */ doctype = NULL; version = 1; ret = gst_ebml_peek_id (ebml, &id); if (ret != GST_FLOW_OK) return ret; GST_DEBUG_OBJECT (common, "id: %08x", id); if (id != GST_EBML_ID_HEADER) { GST_ERROR_OBJECT (common, "Failed to read header"); goto exit; } ret = gst_ebml_read_master (ebml, &id); if (ret != GST_FLOW_OK) return ret; while (gst_ebml_read_has_remaining (ebml, 1, TRUE)) { ret = gst_ebml_peek_id (ebml, &id); if (ret != GST_FLOW_OK) return ret; switch (id) { /* is our read version uptodate? */ case GST_EBML_ID_EBMLREADVERSION:{ guint64 num; ret = gst_ebml_read_uint (ebml, &id, &num); if (ret != GST_FLOW_OK) return ret; if (num != GST_EBML_VERSION) { GST_ERROR_OBJECT (ebml, "Unsupported EBML version %" G_GUINT64_FORMAT, num); return GST_FLOW_ERROR; } GST_DEBUG_OBJECT (ebml, "EbmlReadVersion: %" G_GUINT64_FORMAT, num); break; } /* we only handle 8 byte lengths at max */ case GST_EBML_ID_EBMLMAXSIZELENGTH:{ guint64 num; ret = gst_ebml_read_uint (ebml, &id, &num); if (ret != GST_FLOW_OK) return ret; if (num > sizeof (guint64)) { GST_ERROR_OBJECT (ebml, "Unsupported EBML maximum size %" G_GUINT64_FORMAT, num); return GST_FLOW_ERROR; } GST_DEBUG_OBJECT (ebml, "EbmlMaxSizeLength: %" G_GUINT64_FORMAT, num); break; } /* we handle 4 byte IDs at max */ case GST_EBML_ID_EBMLMAXIDLENGTH:{ guint64 num; ret = gst_ebml_read_uint (ebml, &id, &num); if (ret != GST_FLOW_OK) return ret; if (num > sizeof (guint32)) { GST_ERROR_OBJECT (ebml, "Unsupported EBML maximum ID %" G_GUINT64_FORMAT, num); return GST_FLOW_ERROR; } GST_DEBUG_OBJECT (ebml, "EbmlMaxIdLength: %" G_GUINT64_FORMAT, num); break; } case GST_EBML_ID_DOCTYPE:{ gchar *text; ret = gst_ebml_read_ascii (ebml, &id, &text); if (ret != GST_FLOW_OK) return ret; GST_DEBUG_OBJECT (ebml, "EbmlDocType: %s", GST_STR_NULL (text)); if (doctype) g_free (doctype); doctype = text; break; } case GST_EBML_ID_DOCTYPEREADVERSION:{ guint64 num; ret = gst_ebml_read_uint (ebml, &id, &num); if (ret != GST_FLOW_OK) return ret; version = num; GST_DEBUG_OBJECT (ebml, "EbmlReadVersion: %" G_GUINT64_FORMAT, num); break; } default: ret = gst_matroska_read_common_parse_skip (common, ebml, "EBML header", id); if (ret != GST_FLOW_OK) return ret; break; /* we ignore these two, as they don't tell us anything we care about */ case GST_EBML_ID_EBMLVERSION: case GST_EBML_ID_DOCTYPEVERSION: ret = gst_ebml_read_skip (ebml); if (ret != GST_FLOW_OK) return ret; break; } } exit: if ((doctype != NULL && !strcmp (doctype, GST_MATROSKA_DOCTYPE_MATROSKA)) || (doctype != NULL && !strcmp (doctype, GST_MATROSKA_DOCTYPE_WEBM)) || (doctype == NULL)) { if (version <= 2) { if (doctype) { GST_INFO_OBJECT (common, "Input is %s version %d", doctype, version); } else { GST_WARNING_OBJECT (common, "Input is EBML without doctype, assuming " "matroska (version %d)", version); } ret = GST_FLOW_OK; } else { GST_ELEMENT_ERROR (common, STREAM, DEMUX, (NULL), ("Demuxer version (2) is too old to read %s version %d", GST_STR_NULL (doctype), version)); ret = GST_FLOW_ERROR; } g_free (doctype); } else { GST_ELEMENT_ERROR (common, STREAM, WRONG_TYPE, (NULL), ("Input is not a matroska stream (doctype=%s)", doctype)); ret = GST_FLOW_ERROR; g_free (doctype); } return ret; } static GstFlowReturn gst_matroska_read_common_parse_index_cuetrack (GstMatroskaReadCommon * common, GstEbmlRead * ebml, guint * nentries) { guint32 id; GstFlowReturn ret; GstMatroskaIndex idx; idx.pos = (guint64) - 1; idx.track = 0; idx.time = GST_CLOCK_TIME_NONE; idx.block = 1; DEBUG_ELEMENT_START (common, ebml, "CueTrackPositions"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "CueTrackPositions", 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) { /* track number */ case GST_MATROSKA_ID_CUETRACK: { guint64 num; if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) break; if (num == 0) { idx.track = 0; GST_WARNING_OBJECT (common, "Invalid CueTrack 0"); break; } GST_DEBUG_OBJECT (common, "CueTrack: %" G_GUINT64_FORMAT, num); idx.track = num; break; } /* position in file */ case GST_MATROSKA_ID_CUECLUSTERPOSITION: { guint64 num; if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) break; if (num > G_MAXINT64) { GST_WARNING_OBJECT (common, "CueClusterPosition %" G_GUINT64_FORMAT " too large", num); break; } idx.pos = num; break; } /* number of block in the cluster */ case GST_MATROSKA_ID_CUEBLOCKNUMBER: { guint64 num; if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) break; if (num == 0) { GST_WARNING_OBJECT (common, "Invalid CueBlockNumber 0"); break; } GST_DEBUG_OBJECT (common, "CueBlockNumber: %" G_GUINT64_FORMAT, num); idx.block = num; /* mild sanity check, disregard strange cases ... */ if (idx.block > G_MAXUINT16) { GST_DEBUG_OBJECT (common, "... looks suspicious, ignoring"); idx.block = 1; } break; } default: ret = gst_matroska_read_common_parse_skip (common, ebml, "CueTrackPositions", id); break; case GST_MATROSKA_ID_CUECODECSTATE: case GST_MATROSKA_ID_CUEREFERENCE: ret = gst_ebml_read_skip (ebml); break; } } DEBUG_ELEMENT_STOP (common, ebml, "CueTrackPositions", ret); /* (e.g.) lavf typically creates entries without a block number, * which is bogus and leads to contradictory information */ if (common->index->len) { GstMatroskaIndex *last_idx; last_idx = &g_array_index (common->index, GstMatroskaIndex, common->index->len - 1); if (last_idx->block == idx.block && last_idx->pos == idx.pos && last_idx->track == idx.track && idx.time > last_idx->time) { GST_DEBUG_OBJECT (common, "Cue entry refers to same location, " "but has different time than previous entry; discarding"); idx.track = 0; } } if ((ret == GST_FLOW_OK || ret == GST_FLOW_EOS) && idx.pos != (guint64) - 1 && idx.track > 0) { g_array_append_val (common->index, idx); (*nentries)++; } else if (ret == GST_FLOW_OK || ret == GST_FLOW_EOS) { GST_DEBUG_OBJECT (common, "CueTrackPositions without valid content"); } return ret; } static GstFlowReturn gst_matroska_read_common_parse_index_pointentry (GstMatroskaReadCommon * common, GstEbmlRead * ebml) { guint32 id; GstFlowReturn ret; GstClockTime time = GST_CLOCK_TIME_NONE; guint nentries = 0; DEBUG_ELEMENT_START (common, ebml, "CuePoint"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "CuePoint", 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 single index entry ('point') */ case GST_MATROSKA_ID_CUETIME: { if ((ret = gst_ebml_read_uint (ebml, &id, &time)) != GST_FLOW_OK) break; GST_DEBUG_OBJECT (common, "CueTime: %" G_GUINT64_FORMAT, time); time = time * common->time_scale; break; } /* position in the file + track to which it belongs */ case GST_MATROSKA_ID_CUETRACKPOSITIONS: { if ((ret = gst_matroska_read_common_parse_index_cuetrack (common, ebml, &nentries)) != GST_FLOW_OK) break; break; } default: ret = gst_matroska_read_common_parse_skip (common, ebml, "CuePoint", id); break; } } DEBUG_ELEMENT_STOP (common, ebml, "CuePoint", ret); if (nentries > 0) { if (time == GST_CLOCK_TIME_NONE) { GST_WARNING_OBJECT (common, "CuePoint without valid time"); g_array_remove_range (common->index, common->index->len - nentries, nentries); } else { gint i; for (i = common->index->len - nentries; i < common->index->len; i++) { GstMatroskaIndex *idx = &g_array_index (common->index, GstMatroskaIndex, i); idx->time = time; GST_DEBUG_OBJECT (common, "Index entry: pos=%" G_GUINT64_FORMAT ", time=%" GST_TIME_FORMAT ", track=%u, block=%u", idx->pos, GST_TIME_ARGS (idx->time), (guint) idx->track, (guint) idx->block); } } } else { GST_DEBUG_OBJECT (common, "Empty CuePoint"); } return ret; } gint gst_matroska_read_common_stream_from_num (GstMatroskaReadCommon * common, guint track_num) { guint n; g_assert (common->src->len == common->num_streams); for (n = 0; n < common->src->len; n++) { GstMatroskaTrackContext *context = g_ptr_array_index (common->src, n); if (context->num == track_num) { return n; } } if (n == common->num_streams) GST_WARNING_OBJECT (common, "Failed to find corresponding pad for tracknum %d", track_num); return -1; } GstFlowReturn gst_matroska_read_common_parse_index (GstMatroskaReadCommon * common, GstEbmlRead * ebml) { guint32 id; GstFlowReturn ret = GST_FLOW_OK; guint i; if (common->index) g_array_free (common->index, TRUE); common->index = g_array_sized_new (FALSE, FALSE, sizeof (GstMatroskaIndex), 128); DEBUG_ELEMENT_START (common, ebml, "Cues"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "Cues", 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 single index entry ('point') */ case GST_MATROSKA_ID_POINTENTRY: ret = gst_matroska_read_common_parse_index_pointentry (common, ebml); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "Cues", id); break; } } DEBUG_ELEMENT_STOP (common, ebml, "Cues", ret); /* Sort index by time, smallest time first, for easier searching */ g_array_sort (common->index, (GCompareFunc) gst_matroska_index_compare); /* Now sort the track specific index entries into their own arrays */ for (i = 0; i < common->index->len; i++) { GstMatroskaIndex *idx = &g_array_index (common->index, GstMatroskaIndex, i); gint track_num; GstMatroskaTrackContext *ctx; #if 0 if (common->element_index) { gint writer_id; if (idx->track != 0 && (track_num = gst_matroska_read_common_stream_from_num (common, idx->track)) != -1) { ctx = g_ptr_array_index (common->src, track_num); if (ctx->index_writer_id == -1) gst_index_get_writer_id (common->element_index, GST_OBJECT (ctx->pad), &ctx->index_writer_id); writer_id = ctx->index_writer_id; } else { if (common->element_index_writer_id == -1) gst_index_get_writer_id (common->element_index, GST_OBJECT (common), &common->element_index_writer_id); writer_id = common->element_index_writer_id; } GST_LOG_OBJECT (common, "adding association %" GST_TIME_FORMAT "-> %" G_GUINT64_FORMAT " for writer id %d", GST_TIME_ARGS (idx->time), idx->pos, writer_id); gst_index_add_association (common->element_index, writer_id, GST_ASSOCIATION_FLAG_KEY_UNIT, GST_FORMAT_TIME, idx->time, GST_FORMAT_BYTES, idx->pos + common->ebml_segment_start, NULL); } #endif if (idx->track == 0) continue; track_num = gst_matroska_read_common_stream_from_num (common, idx->track); if (track_num == -1) continue; ctx = g_ptr_array_index (common->src, track_num); if (ctx->index_table == NULL) ctx->index_table = g_array_sized_new (FALSE, FALSE, sizeof (GstMatroskaIndex), 128); g_array_append_vals (ctx->index_table, idx, 1); } common->index_parsed = TRUE; /* sanity check; empty index normalizes to no index */ if (common->index->len == 0) { g_array_free (common->index, TRUE); common->index = NULL; } return ret; } GstFlowReturn gst_matroska_read_common_parse_info (GstMatroskaReadCommon * common, GstElement * el, GstEbmlRead * ebml) { GstFlowReturn ret = GST_FLOW_OK; gdouble dur_f = -1.0; guint32 id; DEBUG_ELEMENT_START (common, ebml, "SegmentInfo"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "SegmentInfo", 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) { /* cluster timecode */ case GST_MATROSKA_ID_TIMECODESCALE:{ guint64 num; if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) break; GST_DEBUG_OBJECT (common, "TimeCodeScale: %" G_GUINT64_FORMAT, num); common->time_scale = num; break; } case GST_MATROSKA_ID_DURATION:{ if ((ret = gst_ebml_read_float (ebml, &id, &dur_f)) != GST_FLOW_OK) break; if (dur_f <= 0.0) { GST_WARNING_OBJECT (common, "Invalid duration %lf", dur_f); break; } GST_DEBUG_OBJECT (common, "Duration: %lf", dur_f); break; } case GST_MATROSKA_ID_WRITINGAPP:{ gchar *text; if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) break; GST_DEBUG_OBJECT (common, "WritingApp: %s", GST_STR_NULL (text)); common->writing_app = text; break; } case GST_MATROSKA_ID_MUXINGAPP:{ gchar *text; if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) break; GST_DEBUG_OBJECT (common, "MuxingApp: %s", GST_STR_NULL (text)); common->muxing_app = text; break; } case GST_MATROSKA_ID_DATEUTC:{ gint64 time; if ((ret = gst_ebml_read_date (ebml, &id, &time)) != GST_FLOW_OK) break; GST_DEBUG_OBJECT (common, "DateUTC: %" G_GINT64_FORMAT, time); common->created = time; break; } case GST_MATROSKA_ID_TITLE:{ gchar *text; GstTagList *taglist; if ((ret = gst_ebml_read_utf8 (ebml, &id, &text)) != GST_FLOW_OK) break; GST_DEBUG_OBJECT (common, "Title: %s", GST_STR_NULL (text)); taglist = gst_tag_list_new (GST_TAG_TITLE, text, NULL); gst_matroska_read_common_found_global_tag (common, el, taglist); g_free (text); break; } default: ret = gst_matroska_read_common_parse_skip (common, ebml, "SegmentInfo", id); break; /* fall through */ case GST_MATROSKA_ID_SEGMENTUID: case GST_MATROSKA_ID_SEGMENTFILENAME: case GST_MATROSKA_ID_PREVUID: case GST_MATROSKA_ID_PREVFILENAME: case GST_MATROSKA_ID_NEXTUID: case GST_MATROSKA_ID_NEXTFILENAME: case GST_MATROSKA_ID_SEGMENTFAMILY: case GST_MATROSKA_ID_CHAPTERTRANSLATE: ret = gst_ebml_read_skip (ebml); break; } } if (dur_f > 0.0) { GstClockTime dur_u; dur_u = gst_gdouble_to_guint64 (dur_f * gst_guint64_to_gdouble (common->time_scale)); if (GST_CLOCK_TIME_IS_VALID (dur_u) && dur_u <= G_MAXINT64) common->segment.duration = dur_u; } DEBUG_ELEMENT_STOP (common, ebml, "SegmentInfo", ret); common->segmentinfo_parsed = TRUE; return ret; } static GstFlowReturn gst_matroska_read_common_parse_metadata_id_simple_tag (GstMatroskaReadCommon * common, GstEbmlRead * ebml, GstTagList ** p_taglist) { /* FIXME: check if there are more useful mappings */ static const struct { const gchar *matroska_tagname; const gchar *gstreamer_tagname; } tag_conv[] = { { GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, { GST_MATROSKA_TAG_ID_ARTIST, GST_TAG_ARTIST}, { GST_MATROSKA_TAG_ID_AUTHOR, GST_TAG_ARTIST}, { GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, { GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, { GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, { GST_MATROSKA_TAG_ID_BPS, GST_TAG_BITRATE}, { GST_MATROSKA_TAG_ID_ENCODER, GST_TAG_ENCODER}, { GST_MATROSKA_TAG_ID_DATE, GST_TAG_DATE}, { GST_MATROSKA_TAG_ID_ISRC, GST_TAG_ISRC}, { GST_MATROSKA_TAG_ID_COPYRIGHT, GST_TAG_COPYRIGHT}, { GST_MATROSKA_TAG_ID_BPM, GST_TAG_BEATS_PER_MINUTE}, { GST_MATROSKA_TAG_ID_TERMS_OF_USE, GST_TAG_LICENSE}, { GST_MATROSKA_TAG_ID_COMPOSER, GST_TAG_COMPOSER}, { GST_MATROSKA_TAG_ID_LEAD_PERFORMER, GST_TAG_PERFORMER}, { GST_MATROSKA_TAG_ID_GENRE, GST_TAG_GENRE} }; GstFlowReturn ret; guint32 id; gchar *value = NULL; gchar *tag = NULL; DEBUG_ELEMENT_START (common, ebml, "SimpleTag"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "SimpleTag", ret); return ret; } while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { /* read all sub-entries */ if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; switch (id) { case GST_MATROSKA_ID_TAGNAME: g_free (tag); tag = NULL; ret = gst_ebml_read_ascii (ebml, &id, &tag); GST_DEBUG_OBJECT (common, "TagName: %s", GST_STR_NULL (tag)); break; case GST_MATROSKA_ID_TAGSTRING: g_free (value); value = NULL; ret = gst_ebml_read_utf8 (ebml, &id, &value); GST_DEBUG_OBJECT (common, "TagString: %s", GST_STR_NULL (value)); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "SimpleTag", id); break; /* fall-through */ case GST_MATROSKA_ID_TAGLANGUAGE: case GST_MATROSKA_ID_TAGDEFAULT: case GST_MATROSKA_ID_TAGBINARY: ret = gst_ebml_read_skip (ebml); break; } } DEBUG_ELEMENT_STOP (common, ebml, "SimpleTag", ret); if (tag && value) { guint i; for (i = 0; i < G_N_ELEMENTS (tag_conv); i++) { const gchar *tagname_gst = tag_conv[i].gstreamer_tagname; const gchar *tagname_mkv = tag_conv[i].matroska_tagname; if (strcmp (tagname_mkv, tag) == 0) { GValue dest = { 0, }; GType dest_type = gst_tag_get_type (tagname_gst); /* Ensure that any date string is complete */ if (dest_type == G_TYPE_DATE) { guint year = 1901, month = 1, day = 1; /* Dates can be yyyy-MM-dd, yyyy-MM or yyyy, but we need * the first type */ if (sscanf (value, "%04u-%02u-%02u", &year, &month, &day) != 0) { g_free (value); value = g_strdup_printf ("%04u-%02u-%02u", year, month, day); } } g_value_init (&dest, dest_type); if (gst_value_deserialize (&dest, value)) { gst_tag_list_add_values (*p_taglist, GST_TAG_MERGE_APPEND, tagname_gst, &dest, NULL); } else { GST_WARNING_OBJECT (common, "Can't transform tag '%s' with " "value '%s' to target type '%s'", tag, value, g_type_name (dest_type)); } g_value_unset (&dest); break; } } } g_free (tag); g_free (value); return ret; } static GstFlowReturn gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, GstEbmlRead * ebml, GstTagList ** p_taglist) { guint32 id; GstFlowReturn ret; GArray *chapter_targets, *edition_targets; GstTagList *taglist; GList *cur; DEBUG_ELEMENT_START (common, ebml, "Tag"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "Tag", ret); return ret; } edition_targets = g_array_new (FALSE, FALSE, sizeof (guint64)); chapter_targets = g_array_new (FALSE, FALSE, sizeof (guint64)); taglist = gst_tag_list_new_empty (); while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { /* read all sub-entries */ if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) break; switch (id) { case GST_MATROSKA_ID_SIMPLETAG: ret = gst_matroska_read_common_parse_metadata_id_simple_tag (common, ebml, &taglist); break; case GST_MATROSKA_ID_TARGETS: ret = gst_matroska_read_common_parse_metadata_targets (common, ebml, edition_targets, chapter_targets); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "Tag", id); break; } } DEBUG_ELEMENT_STOP (common, ebml, "Tag", ret); /* if tag is chapter/edition specific - try to find that entry */ if (G_UNLIKELY (chapter_targets->len > 0 || edition_targets->len > 0)) { if (common->toc == NULL) GST_WARNING_OBJECT (common, "Found chapter/edition specific tag, but TOC doesn't present"); else { cur = common->toc->entries; while (cur != NULL) { gst_matroska_read_common_parse_toc_tag (cur->data, edition_targets, chapter_targets, taglist); cur = cur->next; } common->toc_updated = TRUE; } } else gst_tag_list_insert (*p_taglist, taglist, GST_TAG_MERGE_APPEND); gst_tag_list_free (taglist); g_array_unref (chapter_targets); g_array_unref (edition_targets); return ret; } GstFlowReturn gst_matroska_read_common_parse_metadata (GstMatroskaReadCommon * common, GstElement * el, GstEbmlRead * ebml) { GstTagList *taglist; GstFlowReturn ret = GST_FLOW_OK; guint32 id; GList *l; guint64 curpos; curpos = gst_ebml_read_get_pos (ebml); /* Make sure we don't parse a tags element twice and * post it's tags twice */ curpos = gst_ebml_read_get_pos (ebml); for (l = common->tags_parsed; l; l = l->next) { guint64 *pos = l->data; if (*pos == curpos) { GST_DEBUG_OBJECT (common, "Skipping already parsed Tags at offset %" G_GUINT64_FORMAT, curpos); return GST_FLOW_OK; } } common->tags_parsed = g_list_prepend (common->tags_parsed, g_slice_new (guint64)); *((guint64 *) common->tags_parsed->data) = curpos; /* fall-through */ if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "Tags", ret); return ret; } taglist = gst_tag_list_new_empty (); common->toc_updated = FALSE; 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) { case GST_MATROSKA_ID_TAG: ret = gst_matroska_read_common_parse_metadata_id_tag (common, ebml, &taglist); break; default: ret = gst_matroska_read_common_parse_skip (common, ebml, "Tags", id); break; /* FIXME: Use to limit the tags to specific pads */ } } DEBUG_ELEMENT_STOP (common, ebml, "Tags", ret); if (G_LIKELY (!gst_tag_list_is_empty (taglist))) gst_matroska_read_common_found_global_tag (common, el, taglist); else gst_tag_list_free (taglist); return ret; } static GstFlowReturn gst_matroska_read_common_peek_adapter (GstMatroskaReadCommon * common, guint peek, const guint8 ** data) { /* Caller needs to gst_adapter_unmap. */ *data = gst_adapter_map (common->adapter, peek); if (*data == NULL) return GST_FLOW_EOS; return GST_FLOW_OK; } /* * Calls pull_range for (offset,size) without advancing our offset */ GstFlowReturn gst_matroska_read_common_peek_bytes (GstMatroskaReadCommon * common, guint64 offset, guint size, GstBuffer ** p_buf, guint8 ** bytes) { GstFlowReturn ret; /* Caching here actually makes much less difference than one would expect. * We do it mainly to avoid pulling buffers of 1 byte all the time */ if (common->cached_buffer) { guint64 cache_offset = GST_BUFFER_OFFSET (common->cached_buffer); gsize cache_size = gst_buffer_get_size (common->cached_buffer); if (cache_offset <= common->offset && (common->offset + size) <= (cache_offset + cache_size)) { if (p_buf) *p_buf = gst_buffer_copy_region (common->cached_buffer, GST_BUFFER_COPY_ALL, common->offset - cache_offset, size); if (bytes) { if (!common->cached_data) { gst_buffer_map (common->cached_buffer, &common->cached_map, GST_MAP_READ); common->cached_data = common->cached_map.data; } *bytes = common->cached_data + common->offset - cache_offset; } return GST_FLOW_OK; } /* not enough data in the cache, free cache and get a new one */ if (common->cached_data) { gst_buffer_unmap (common->cached_buffer, &common->cached_map); common->cached_data = NULL; } gst_buffer_unref (common->cached_buffer); common->cached_buffer = NULL; } /* refill the cache */ ret = gst_pad_pull_range (common->sinkpad, common->offset, MAX (size, 64 * 1024), &common->cached_buffer); if (ret != GST_FLOW_OK) { common->cached_buffer = NULL; return ret; } if (gst_buffer_get_size (common->cached_buffer) >= size) { if (p_buf) *p_buf = gst_buffer_copy_region (common->cached_buffer, GST_BUFFER_COPY_ALL, 0, size); if (bytes) { gst_buffer_map (common->cached_buffer, &common->cached_map, GST_MAP_READ); common->cached_data = common->cached_map.data; *bytes = common->cached_data; } return GST_FLOW_OK; } /* Not possible to get enough data, try a last time with * requesting exactly the size we need */ gst_buffer_unref (common->cached_buffer); common->cached_buffer = NULL; ret = gst_pad_pull_range (common->sinkpad, common->offset, size, &common->cached_buffer); if (ret != GST_FLOW_OK) { GST_DEBUG_OBJECT (common, "pull_range returned %d", ret); if (p_buf) *p_buf = NULL; if (bytes) *bytes = NULL; return ret; } if (gst_buffer_get_size (common->cached_buffer) < size) { GST_WARNING_OBJECT (common, "Dropping short buffer at offset %" G_GUINT64_FORMAT ": wanted %u bytes, got %" G_GSIZE_FORMAT " bytes", common->offset, size, gst_buffer_get_size (common->cached_buffer)); gst_buffer_unref (common->cached_buffer); common->cached_buffer = NULL; if (p_buf) *p_buf = NULL; if (bytes) *bytes = NULL; return GST_FLOW_EOS; } if (p_buf) *p_buf = gst_buffer_copy_region (common->cached_buffer, GST_BUFFER_COPY_ALL, 0, size); if (bytes) { gst_buffer_map (common->cached_buffer, &common->cached_map, GST_MAP_READ); common->cached_data = common->cached_map.data; *bytes = common->cached_data; } return GST_FLOW_OK; } static GstFlowReturn gst_matroska_read_common_peek_pull (GstMatroskaReadCommon * common, guint peek, guint8 ** data) { return gst_matroska_read_common_peek_bytes (common, common->offset, peek, NULL, data); } GstFlowReturn gst_matroska_read_common_peek_id_length_pull (GstMatroskaReadCommon * common, GstElement * el, guint32 * _id, guint64 * _length, guint * _needed) { return gst_ebml_peek_id_length (_id, _length, _needed, (GstPeekData) gst_matroska_read_common_peek_pull, (gpointer) common, el, common->offset); } GstFlowReturn gst_matroska_read_common_peek_id_length_push (GstMatroskaReadCommon * common, GstElement * el, guint32 * _id, guint64 * _length, guint * _needed) { GstFlowReturn ret; ret = gst_ebml_peek_id_length (_id, _length, _needed, (GstPeekData) gst_matroska_read_common_peek_adapter, (gpointer) common, el, common->offset); gst_adapter_unmap (common->adapter); return ret; } static GstFlowReturn gst_matroska_read_common_read_track_encoding (GstMatroskaReadCommon * common, GstEbmlRead * ebml, GstMatroskaTrackContext * context) { GstMatroskaTrackEncoding enc = { 0, }; GstFlowReturn ret; guint32 id; DEBUG_ELEMENT_START (common, ebml, "ContentEncoding"); /* Set default values */ enc.scope = 1; /* All other default values are 0 */ if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "ContentEncoding", 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) { case GST_MATROSKA_ID_CONTENTENCODINGORDER:{ guint64 num; if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) break; if (!gst_matroska_read_common_encoding_order_unique (context->encodings, num)) { GST_ERROR_OBJECT (common, "ContentEncodingOrder %" G_GUINT64_FORMAT "is not unique for track %d", num, context->num); ret = GST_FLOW_ERROR; break; } GST_DEBUG_OBJECT (common, "ContentEncodingOrder: %" G_GUINT64_FORMAT, num); enc.order = num; break; } case GST_MATROSKA_ID_CONTENTENCODINGSCOPE:{ guint64 num; if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) break; if (num > 7 && num == 0) { GST_ERROR_OBJECT (common, "Invalid ContentEncodingScope %" G_GUINT64_FORMAT, num); ret = GST_FLOW_ERROR; break; } GST_DEBUG_OBJECT (common, "ContentEncodingScope: %" G_GUINT64_FORMAT, num); enc.scope = num; break; } case GST_MATROSKA_ID_CONTENTENCODINGTYPE:{ guint64 num; if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) break; if (num > 1) { GST_ERROR_OBJECT (common, "Invalid ContentEncodingType %" G_GUINT64_FORMAT, num); ret = GST_FLOW_ERROR; break; } else if (num != 0) { GST_ERROR_OBJECT (common, "Encrypted tracks are not supported yet"); ret = GST_FLOW_ERROR; break; } GST_DEBUG_OBJECT (common, "ContentEncodingType: %" G_GUINT64_FORMAT, num); enc.type = num; break; } case GST_MATROSKA_ID_CONTENTCOMPRESSION:{ DEBUG_ELEMENT_START (common, ebml, "ContentCompression"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) break; 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) { case GST_MATROSKA_ID_CONTENTCOMPALGO:{ guint64 num; if ((ret = gst_ebml_read_uint (ebml, &id, &num)) != GST_FLOW_OK) { break; } if (num > 3) { GST_ERROR_OBJECT (common, "Invalid ContentCompAlgo %" G_GUINT64_FORMAT, num); ret = GST_FLOW_ERROR; break; } GST_DEBUG_OBJECT (common, "ContentCompAlgo: %" G_GUINT64_FORMAT, num); enc.comp_algo = num; break; } case GST_MATROSKA_ID_CONTENTCOMPSETTINGS:{ guint8 *data; guint64 size; if ((ret = gst_ebml_read_binary (ebml, &id, &data, &size)) != GST_FLOW_OK) { break; } enc.comp_settings = data; enc.comp_settings_length = size; GST_DEBUG_OBJECT (common, "ContentCompSettings of size %" G_GUINT64_FORMAT, size); break; } default: GST_WARNING_OBJECT (common, "Unknown ContentCompression subelement 0x%x - ignoring", id); ret = gst_ebml_read_skip (ebml); break; } } DEBUG_ELEMENT_STOP (common, ebml, "ContentCompression", ret); break; } case GST_MATROSKA_ID_CONTENTENCRYPTION: GST_ERROR_OBJECT (common, "Encrypted tracks not yet supported"); gst_ebml_read_skip (ebml); ret = GST_FLOW_ERROR; break; default: GST_WARNING_OBJECT (common, "Unknown ContentEncoding subelement 0x%x - ignoring", id); ret = gst_ebml_read_skip (ebml); break; } } DEBUG_ELEMENT_STOP (common, ebml, "ContentEncoding", ret); if (ret != GST_FLOW_OK && ret != GST_FLOW_EOS) return ret; /* TODO: Check if the combination of values is valid */ g_array_append_val (context->encodings, enc); return ret; } GstFlowReturn gst_matroska_read_common_read_track_encodings (GstMatroskaReadCommon * common, GstEbmlRead * ebml, GstMatroskaTrackContext * context) { GstFlowReturn ret; guint32 id; DEBUG_ELEMENT_START (common, ebml, "ContentEncodings"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { DEBUG_ELEMENT_STOP (common, ebml, "ContentEncodings", ret); return ret; } context->encodings = g_array_sized_new (FALSE, FALSE, sizeof (GstMatroskaTrackEncoding), 1); 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) { case GST_MATROSKA_ID_CONTENTENCODING: ret = gst_matroska_read_common_read_track_encoding (common, ebml, context); break; default: GST_WARNING_OBJECT (common, "Unknown ContentEncodings subelement 0x%x - ignoring", id); ret = gst_ebml_read_skip (ebml); break; } } DEBUG_ELEMENT_STOP (common, ebml, "ContentEncodings", ret); if (ret != GST_FLOW_OK && ret != GST_FLOW_EOS) return ret; /* Sort encodings according to their order */ g_array_sort (context->encodings, (GCompareFunc) gst_matroska_read_common_encoding_cmp); return gst_matroska_decode_content_encodings (context->encodings); } /* call with object lock held */ void gst_matroska_read_common_reset_streams (GstMatroskaReadCommon * common, GstClockTime time, gboolean full) { gint i; GST_DEBUG_OBJECT (common, "resetting stream state"); g_assert (common->src->len == common->num_streams); for (i = 0; i < common->src->len; i++) { GstMatroskaTrackContext *context = g_ptr_array_index (common->src, i); context->pos = time; context->set_discont = TRUE; context->eos = FALSE; context->from_time = GST_CLOCK_TIME_NONE; if (full) context->last_flow = GST_FLOW_OK; if (context->type == GST_MATROSKA_TRACK_TYPE_VIDEO) { GstMatroskaTrackVideoContext *videocontext = (GstMatroskaTrackVideoContext *) context; /* demux object lock held by caller */ videocontext->earliest_time = GST_CLOCK_TIME_NONE; } } } gboolean gst_matroska_read_common_tracknumber_unique (GstMatroskaReadCommon * common, guint64 num) { gint i; g_assert (common->src->len == common->num_streams); for (i = 0; i < common->src->len; i++) { GstMatroskaTrackContext *context = g_ptr_array_index (common->src, i); if (context->num == num) return FALSE; } return TRUE; }