From bd7761635ab00975889ca6f34ac2ce32ce7294ea Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Thu, 29 Mar 2012 23:05:14 +0400 Subject: [PATCH 1/3] matroska: add chapter support in GstMatroskaReadCommon --- gst/matroska/matroska-read-common.c | 449 +++++++++++++++++++++++++++- gst/matroska/matroska-read-common.h | 8 +- 2 files changed, 440 insertions(+), 17 deletions(-) diff --git a/gst/matroska/matroska-read-common.c b/gst/matroska/matroska-read-common.c index d2c2bea946..a08e880b20 100644 --- a/gst/matroska/matroska-read-common.c +++ b/gst/matroska/matroska-read-common.c @@ -56,6 +56,10 @@ GST_DEBUG_CATEGORY (matroskareadcommon_debug); 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, guint8 ** data_out, guint * size_out, @@ -694,21 +698,53 @@ gst_matroska_read_common_parse_attachments (GstMatroskaReadCommon * common, return ret; } -GstFlowReturn -gst_matroska_read_common_parse_chapters (GstMatroskaReadCommon * common, - GstEbmlRead * ebml) +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) { - guint32 id; GstFlowReturn ret = GST_FLOW_OK; + guint32 id; + guint64 uid; - GST_WARNING_OBJECT (common, "Parsing of chapters not implemented yet"); - - /* TODO: implement parsing of chapters */ - - DEBUG_ELEMENT_START (common, ebml, "Chapters"); + DEBUG_ELEMENT_START (common, ebml, "TagTargets"); if ((ret = gst_ebml_read_master (ebml, &id)) != GST_FLOW_OK) { - DEBUG_ELEMENT_STOP (common, ebml, "Chapters", ret); + DEBUG_ELEMENT_STOP (common, ebml, "TagTargets", ret); return ret; } @@ -717,12 +753,358 @@ gst_matroska_read_common_parse_chapters (GstMatroskaReadCommon * common, 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_ebml_read_skip (ebml); + 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 (); + 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; } @@ -1470,6 +1852,9 @@ gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, { guint32 id; GstFlowReturn ret; + GArray *chapter_targets, *edition_targets; + GstTagList *taglist; + GList *cur; DEBUG_ELEMENT_START (common, ebml, "Tag"); @@ -1478,6 +1863,10 @@ gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, 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 (); + while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { /* read all sub-entries */ @@ -1487,7 +1876,13 @@ gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, switch (id) { case GST_MATROSKA_ID_SIMPLETAG: ret = gst_matroska_read_common_parse_metadata_id_simple_tag (common, - ebml, p_taglist); + ebml, &taglist); + break; + + case GST_MATROSKA_ID_TARGETS: + ret = + gst_matroska_read_common_parse_metadata_targets (common, ebml, + edition_targets, chapter_targets); break; default: @@ -1498,6 +1893,27 @@ gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, 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; } @@ -1537,6 +1953,7 @@ gst_matroska_read_common_parse_metadata (GstMatroskaReadCommon * common, } taglist = gst_tag_list_new (); + 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) @@ -1552,15 +1969,15 @@ gst_matroska_read_common_parse_metadata (GstMatroskaReadCommon * common, ret = gst_matroska_read_common_parse_skip (common, ebml, "Tags", id); break; /* FIXME: Use to limit the tags to specific pads */ - case GST_MATROSKA_ID_TARGETS: - ret = gst_ebml_read_skip (ebml); - break; } } DEBUG_ELEMENT_STOP (common, ebml, "Tags", ret); - gst_matroska_read_common_found_global_tag (common, el, taglist); + 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; } diff --git a/gst/matroska/matroska-read-common.h b/gst/matroska/matroska-read-common.h index cf617e6e8e..91e66461dd 100644 --- a/gst/matroska/matroska-read-common.h +++ b/gst/matroska/matroska-read-common.h @@ -59,12 +59,18 @@ typedef struct _GstMatroskaReadCommon { /* state */ GstMatroskaReadState state; + /* did we parse cues/tracks/segmentinfo already? */ gboolean index_parsed; gboolean segmentinfo_parsed; gboolean attachments_parsed; + gboolean chapters_parsed; GList *tags_parsed; + /* chapters stuff */ + GstToc *toc; + gboolean toc_updated; + /* start-of-segment */ guint64 ebml_segment_start; @@ -80,7 +86,7 @@ typedef struct _GstMatroskaReadCommon { GstTagList *global_tags; /* pull mode caching */ - GstBuffer *cached_buffer; + GstBuffer *cached_buffer; /* push and pull mode */ guint64 offset; From 80f8a506bed84e65742cdd025f07e7133cb0d99a Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Thu, 29 Mar 2012 23:12:13 +0400 Subject: [PATCH 2/3] matroska: add support for GstToc in demuxer --- gst/matroska/matroska-demux.c | 83 +++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 4 deletions(-) diff --git a/gst/matroska/matroska-demux.c b/gst/matroska/matroska-demux.c index ab75060731..6db85c2e6c 100644 --- a/gst/matroska/matroska-demux.c +++ b/gst/matroska/matroska-demux.c @@ -423,6 +423,7 @@ gst_matroska_demux_reset (GstElement * element) demux->tracks_parsed = FALSE; demux->common.segmentinfo_parsed = FALSE; demux->common.attachments_parsed = FALSE; + demux->common.chapters_parsed = FALSE; g_list_foreach (demux->common.tags_parsed, (GFunc) gst_matroska_demux_free_parsed_el, NULL); @@ -481,6 +482,12 @@ gst_matroska_demux_reset (GstElement * element) demux->common.cached_buffer = NULL; } + /* free chapters TOC if any */ + if (demux->common.toc) { + gst_toc_free (demux->common.toc); + demux->common.toc = NULL; + } + demux->invalid_duration = FALSE; } @@ -1347,6 +1354,7 @@ gst_matroska_demux_get_src_query_types (GstPad * pad) GST_QUERY_POSITION, GST_QUERY_DURATION, GST_QUERY_SEEKING, + GST_QUERY_TOC, 0 }; @@ -1445,6 +1453,23 @@ gst_matroska_demux_query (GstMatroskaDemux * demux, GstPad * pad, GST_OBJECT_UNLOCK (demux); break; } + + case GST_QUERY_TOC: + { + GstToc *toc; + + GST_OBJECT_LOCK (demux); + if (demux->common.toc) + toc = demux->common.toc; + else + toc = gst_toc_new (); + gst_query_set_toc (query, toc, 0); + res = TRUE; + if (!demux->common.toc) + gst_toc_free (toc); + GST_OBJECT_UNLOCK (demux); + break; + } default: res = gst_pad_query_default (pad, query); break; @@ -1932,8 +1957,7 @@ gst_matroska_demux_handle_seek_event (GstMatroskaDemux * demux, * we might be playing a file that's still being recorded * so, invalidate our current duration, which is only a moving target, * and should not be used to clamp anything */ - if (!demux->streaming && !demux->common.index && - demux->invalid_duration) { + if (!demux->streaming && !demux->common.index && demux->invalid_duration) { gst_segment_set_duration (&seeksegment, GST_FORMAT_TIME, GST_CLOCK_TIME_NONE); } @@ -2236,6 +2260,45 @@ gst_matroska_demux_handle_src_event (GstPad * pad, GstEvent * event) break; } + case GST_EVENT_TOC_SELECT: + { + char *uid = NULL; + GstTocEntry *entry = NULL; + GstEvent *seek_event; + gint64 start_pos; + + if (!demux->common.toc) { + GST_DEBUG_OBJECT (demux, "no TOC to select"); + return FALSE; + } else { + gst_event_parse_toc_select (event, &uid); + if (uid != NULL) { + GST_OBJECT_LOCK (demux); + entry = gst_toc_find_entry (demux->common.toc, uid); + if (entry == NULL) { + GST_OBJECT_UNLOCK (demux); + GST_WARNING_OBJECT (demux, "no TOC entry with given UID: %s", uid); + res = FALSE; + } else { + gst_toc_entry_get_start_stop (entry, &start_pos, NULL); + GST_OBJECT_UNLOCK (demux); + seek_event = gst_event_new_seek (1.0, + GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, start_pos, GST_SEEK_TYPE_SET, -1); + res = gst_matroska_demux_handle_seek_event (demux, pad, seek_event); + gst_event_unref (seek_event); + } + g_free (uid); + } else { + GST_WARNING_OBJECT (demux, "received empty TOC select event"); + res = FALSE; + } + } + gst_event_unref (event); + break; + } + /* events we don't need to handle */ case GST_EVENT_NAVIGATION: gst_event_unref (event); @@ -4254,8 +4317,20 @@ gst_matroska_demux_parse_id (GstMatroskaDemux * demux, guint32 id, GST_ELEMENT_CAST (demux), &ebml); break; case GST_MATROSKA_ID_CHAPTERS: - GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); - ret = gst_matroska_read_common_parse_chapters (&demux->common, &ebml); + if (!demux->common.chapters_parsed) { + GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); + ret = + gst_matroska_read_common_parse_chapters (&demux->common, &ebml); + + if (demux->common.toc) { + gst_matroska_demux_send_event (demux, + gst_event_new_toc (demux->common.toc, FALSE)); + gst_element_post_message (GST_ELEMENT_CAST (demux), + gst_message_new_toc (GST_OBJECT_CAST (demux), + demux->common.toc, FALSE)); + } + } else + GST_READ_CHECK (gst_matroska_demux_flush (demux, read)); break; case GST_MATROSKA_ID_SEEKHEAD: GST_READ_CHECK (gst_matroska_demux_take (demux, read, &ebml)); From 113ba4ac3cb294ab360bf0ab575d60cf1a194b80 Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Thu, 29 Mar 2012 23:22:28 +0400 Subject: [PATCH 3/3] matroska: add GstToc support for muxer --- gst/matroska/matroska-mux.c | 304 ++++++++++++++++++++++++++++++++++-- gst/matroska/matroska-mux.h | 2 +- 2 files changed, 291 insertions(+), 15 deletions(-) diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c index 168b2a9abe..265179efdb 100644 --- a/gst/matroska/matroska-mux.c +++ b/gst/matroska/matroska-mux.c @@ -56,6 +56,8 @@ #include "matroska-mux.h" #include "matroska-ids.h" +#define GST_MATROSKA_MUX_CHAPLANG "und" + GST_DEBUG_CATEGORY_STATIC (matroskamux_debug); #define GST_CAT_DEFAULT matroskamux_debug @@ -273,8 +275,10 @@ static void gst_matroska_mux_add_interfaces (GType type) { static const GInterfaceInfo tag_setter_info = { NULL, NULL, NULL }; + static const GInterfaceInfo toc_setter_info = { NULL, NULL, NULL }; g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, &tag_setter_info); + g_type_add_interface_static (type, GST_TYPE_TOC_SETTER, &toc_setter_info); } static void @@ -673,6 +677,13 @@ gst_matroska_mux_reset (GstElement * element) /* reset tags */ gst_tag_setter_reset_tags (GST_TAG_SETTER (mux)); + + mux->tags_pos = 0; + + /* reset chapters */ + gst_toc_setter_reset_toc (GST_TOC_SETTER (mux)); + + mux->chapters_pos = 0; } /** @@ -806,6 +817,30 @@ gst_matroska_mux_handle_sink_event (GstCollectPads2 * pads, event = NULL; break; } + case GST_EVENT_TOC:{ + GstToc *toc; + + if (mux->chapters_pos > 0) + break; + + GST_DEBUG_OBJECT (mux, "received toc event"); + gst_event_parse_toc (event, &toc, NULL); + + if (toc != NULL) { + if (gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL) { + gst_toc_setter_reset_toc (GST_TOC_SETTER (mux)); + GST_INFO_OBJECT (pad, "Replacing TOC with a new one"); + } + + gst_toc_setter_set_toc (GST_TOC_SETTER (mux), toc); + gst_toc_free (toc); + } + + gst_event_unref (event); + /* handled this, don't want collectpads to forward it downstream */ + event = NULL; + break; + } case GST_EVENT_NEWSEGMENT:{ GstFormat format; @@ -2329,6 +2364,110 @@ gst_matroska_mux_track_header (GstMatroskaMux * mux, context->codec_priv, context->codec_priv_size); } +static void +gst_matroska_mux_write_chapter_title (const gchar * title, GstEbmlWrite * ebml) +{ + guint64 title_master; + + title_master = + gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CHAPTERDISPLAY); + + gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CHAPSTRING, title); + gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CHAPLANGUAGE, + GST_MATROSKA_MUX_CHAPLANG); + + gst_ebml_write_master_finish (ebml, title_master); +} + +static void +gst_matroska_mux_write_chapter (GstMatroskaMux * mux, GstTocEntry * edition, + GstTocEntry * entry, GstEbmlWrite * ebml, guint64 * master_chapters, + guint64 * master_edition) +{ + guint64 uid, master_chapteratom; + GList *cur; + GstTocEntry *cur_entry; + guint count, i; + gchar *title; + gint64 start, stop; + + if (G_UNLIKELY (master_chapters != NULL && *master_chapters == 0)) + *master_chapters = + gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CHAPTERS); + + if (G_UNLIKELY (master_edition != NULL && *master_edition == 0)) { + /* create uid for the parent */ + uid = gst_matroska_mux_create_uid (); + g_free (edition->uid); + edition->uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uid); + + *master_edition = + gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_EDITIONENTRY); + + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONUID, uid); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONFLAGHIDDEN, 0); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONFLAGDEFAULT, 0); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONFLAGORDERED, 0); + } + + uid = gst_matroska_mux_create_uid (); + gst_toc_entry_get_start_stop (entry, &start, &stop); + + master_chapteratom = + gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CHAPTERATOM); + g_free (entry->uid); + entry->uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uid); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERUID, uid); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERTIMESTART, start); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERTIMESTOP, stop); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERFLAGHIDDEN, 0); + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERFLAGENABLED, 1); + + cur = entry->subentries; + while (cur != NULL) { + cur_entry = cur->data; + gst_matroska_mux_write_chapter (mux, NULL, cur_entry, ebml, NULL, NULL); + + cur = cur->next; + } + + if (G_LIKELY (entry->tags != NULL)) { + count = gst_tag_list_get_tag_size (entry->tags, GST_TAG_TITLE); + + for (i = 0; i < count; ++i) { + gst_tag_list_get_string_index (entry->tags, GST_TAG_TITLE, i, &title); + gst_matroska_mux_write_chapter_title (title, ebml); + g_free (title); + } + + /* remove title tag */ + if (G_LIKELY (count > 0)) + gst_tag_list_remove_tag (entry->tags, GST_TAG_TITLE); + } + + gst_ebml_write_master_finish (ebml, master_chapteratom); +} + +static void +gst_matroska_mux_write_chapter_edition (GstMatroskaMux * mux, + GstTocEntry * entry, GstEbmlWrite * ebml, guint64 * master_chapters) +{ + guint64 master_edition = 0; + GList *cur; + GstTocEntry *subentry; + + cur = entry->subentries; + while (cur != NULL) { + subentry = cur->data; + gst_matroska_mux_write_chapter (mux, entry, subentry, ebml, master_chapters, + &master_edition); + + cur = cur->next; + } + + if (G_LIKELY (master_edition != 0)) + gst_ebml_write_master_finish (ebml, master_edition); +} /** * gst_matroska_mux_start: @@ -2343,6 +2482,7 @@ gst_matroska_mux_start (GstMatroskaMux * mux) const gchar *doctype; guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO, GST_MATROSKA_ID_TRACKS, + GST_MATROSKA_ID_CHAPTERS, GST_MATROSKA_ID_CUES, GST_MATROSKA_ID_TAGS, 0 @@ -2503,6 +2643,68 @@ gst_matroska_mux_start (GstMatroskaMux * mux) } gst_ebml_write_master_finish (ebml, master); + /* chapters */ + if (gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL && !mux->streamable) { + guint64 master_chapters = 0; + GstTocEntry *toc_entry; + const GstToc *toc; + GList *cur, *to_write = NULL; + gint64 start, stop; + + GST_DEBUG ("Writing chapters"); + + toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux)); + + /* check whether we have editions or chapters at the root level */ + toc_entry = toc->entries->data; + + if (toc_entry->type != GST_TOC_ENTRY_TYPE_EDITION) { + toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, ""); + gst_toc_entry_set_start_stop (toc_entry, -1, -1); + + /* aggregate all chapters without root edition */ + cur = toc->entries; + while (cur != NULL) { + toc_entry->subentries = + g_list_prepend (toc_entry->subentries, cur->data); + cur = cur->next; + } + + gst_toc_entry_get_start_stop (((GstTocEntry *) toc_entry-> + subentries->data), &start, NULL); + toc_entry->subentries = g_list_reverse (toc_entry->subentries); + gst_toc_entry_get_start_stop (((GstTocEntry *) toc_entry-> + subentries->data), NULL, &stop); + gst_toc_entry_set_start_stop (toc_entry, start, stop); + + to_write = g_list_append (to_write, toc_entry); + } else { + toc_entry = NULL; + to_write = toc->entries; + } + + /* finally write chapters */ + mux->chapters_pos = ebml->pos; + + cur = to_write; + while (cur != NULL) { + gst_matroska_mux_write_chapter_edition (mux, cur->data, ebml, + &master_chapters); + cur = cur->next; + } + + /* close master element if any edition was written */ + if (G_LIKELY (master_chapters != 0)) + gst_ebml_write_master_finish (ebml, master_chapters); + + if (toc_entry != NULL) { + g_list_free (toc_entry->subentries); + toc_entry->subentries = NULL; + gst_toc_entry_free (toc_entry); + g_list_free (to_write); + } + } + /* lastly, flush the cache */ gst_ebml_write_flush_cache (ebml, FALSE, 0); } @@ -2566,6 +2768,44 @@ gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag, } } +static void +gst_matroska_mux_write_toc_entry_tags (GstMatroskaMux * mux, + const GstTocEntry * entry, guint64 * master_tags) +{ + guint64 master_tag, master_targets; + GstEbmlWrite *ebml; + GList *cur; + + ebml = mux->ebml_write; + + if (G_UNLIKELY (entry->tags != NULL && !gst_tag_list_is_empty (entry->tags))) { + if (*master_tags == 0) { + mux->tags_pos = ebml->pos; + *master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS); + } + + master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG); + master_targets = + gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TARGETS); + + if (entry->type == GST_TOC_ENTRY_TYPE_EDITION) + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TARGETEDITIONUID, + g_ascii_strtoull (entry->uid, NULL, 10)); + else + gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TARGETCHAPTERUID, + g_ascii_strtoull (entry->uid, NULL, 10)); + + gst_ebml_write_master_finish (ebml, master_targets); + gst_tag_list_foreach (entry->tags, gst_matroska_mux_write_simple_tag, ebml); + gst_ebml_write_master_finish (ebml, master_tag); + } + + cur = entry->subentries; + while (cur != NULL) { + gst_matroska_mux_write_toc_entry_tags (mux, cur->data, master_tags); + cur = cur->next; + } +} /** * gst_matroska_mux_finish: @@ -2619,22 +2859,45 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) /* tags */ tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux)); - if (tags != NULL && !gst_tag_list_is_empty (tags)) { - guint64 master_tags, master_tag; + if ((tags != NULL && !gst_tag_list_is_empty (tags)) + || gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL) { + guint64 master_tags = 0, master_tag; + GList *cur; + const GstToc *toc; GST_DEBUG_OBJECT (mux, "Writing tags"); - /* TODO: maybe limit via the TARGETS id by looking at the source pad */ - mux->tags_pos = ebml->pos; - master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS); - master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG); - gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml); - gst_ebml_write_master_finish (ebml, master_tag); - gst_ebml_write_master_finish (ebml, master_tags); + toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux)); + + if (tags != NULL) { + /* TODO: maybe limit via the TARGETS id by looking at the source pad */ + mux->tags_pos = ebml->pos; + master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS); + master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG); + + if (tags != NULL) + gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml); + if (toc != NULL) + gst_tag_list_foreach (toc->tags, gst_matroska_mux_write_simple_tag, + ebml); + + gst_ebml_write_master_finish (ebml, master_tag); + } + + if (toc != NULL) { + cur = toc->entries; + while (cur != NULL) { + gst_matroska_mux_write_toc_entry_tags (mux, cur->data, &master_tags); + cur = cur->next; + } + } + + if (master_tags != 0) + gst_ebml_write_master_finish (ebml, master_tags); } /* update seekhead. We know that: - * - a seekhead contains 4 entries. + * - a seekhead contains 5 entries. * - order of entries is as above. * - a seekhead has a 4-byte header + 8-byte length * - each entry is 2-byte master, 2-byte ID pointer, @@ -2647,9 +2910,10 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) mux->info_pos - mux->segment_master); gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60, mux->tracks_pos - mux->segment_master); - if (mux->index != NULL) { + if (gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL + && mux->chapters_pos > 0) { gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88, - mux->cues_pos - mux->segment_master); + mux->chapters_pos - mux->segment_master); } else { /* void'ify */ guint64 my_pos = ebml->pos; @@ -2658,9 +2922,9 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26); gst_ebml_write_seek (ebml, my_pos); } - if (tags != NULL) { + if (mux->index != NULL) { gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116, - mux->tags_pos - mux->segment_master); + mux->cues_pos - mux->segment_master); } else { /* void'ify */ guint64 my_pos = ebml->pos; @@ -2670,6 +2934,18 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) gst_ebml_write_seek (ebml, my_pos); } + if (tags != NULL) { + gst_ebml_replace_uint (ebml, mux->seekhead_pos + 144, + mux->tags_pos - mux->segment_master); + } else { + /* void'ify */ + guint64 my_pos = ebml->pos; + + gst_ebml_write_seek (ebml, mux->seekhead_pos + 124); + gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26); + gst_ebml_write_seek (ebml, my_pos); + } + /* loop tracks: * - first get the overall duration * (a released track may have left a duration in here) diff --git a/gst/matroska/matroska-mux.h b/gst/matroska/matroska-mux.h index 6eb584d111..2e9c398e47 100644 --- a/gst/matroska/matroska-mux.h +++ b/gst/matroska/matroska-mux.h @@ -110,6 +110,7 @@ typedef struct _GstMatroskaMux { guint64 segment_pos, seekhead_pos, cues_pos, + chapters_pos, tags_pos, info_pos, tracks_pos, @@ -125,7 +126,6 @@ typedef struct _GstMatroskaMux { /* GstForceKeyUnit event */ GstEvent *force_key_unit_event; - } GstMatroskaMux; typedef struct _GstMatroskaMuxClass {