From 94c5f6dcc92fad688c5ed4e33fd93f2cd48e91f0 Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Thu, 29 Mar 2012 23:22:28 +0400 Subject: [PATCH] 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 { -- 2.34.1