diff --git a/gst/matroska/matroska-ids.h b/gst/matroska/matroska-ids.h index 24e052d66a..181dfd8e9d 100644 --- a/gst/matroska/matroska-ids.h +++ b/gst/matroska/matroska-ids.h @@ -304,6 +304,7 @@ /* IDs in the ChapterAtom master */ #define GST_MATROSKA_ID_CHAPTERUID 0x73C4 +#define GST_MATROSKA_ID_CHAPTERSTRINGUID 0x5654 #define GST_MATROSKA_ID_CHAPTERTIMESTART 0x91 #define GST_MATROSKA_ID_CHAPTERTIMESTOP 0x92 #define GST_MATROSKA_ID_CHAPTERFLAGHIDDEN 0x98 diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c index bbcbb0a1bf..93219bafe8 100644 --- a/gst/matroska/matroska-mux.c +++ b/gst/matroska/matroska-mux.c @@ -526,6 +526,10 @@ gst_matroska_mux_finalize (GObject * object) g_array_free (mux->used_uids, TRUE); + if (mux->internal_toc != NULL) { + gst_toc_unref (mux->internal_toc); + } + G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -699,6 +703,7 @@ gst_matroska_mux_reset (GstElement * element) /* reset chapters */ gst_toc_setter_reset (GST_TOC_SETTER (mux)); + mux->internal_toc = NULL; mux->chapters_pos = 0; @@ -2609,7 +2614,6 @@ gst_matroska_mux_track_header (GstMatroskaMux * mux, } } -#if 0 static void gst_matroska_mux_write_chapter_title (const gchar * title, GstEbmlWrite * ebml) { @@ -2625,17 +2629,20 @@ gst_matroska_mux_write_chapter_title (const gchar * title, GstEbmlWrite * ebml) gst_ebml_write_master_finish (ebml, title_master); } -static void +static GstTocEntry * gst_matroska_mux_write_chapter (GstMatroskaMux * mux, GstTocEntry * edition, GstTocEntry * entry, GstEbmlWrite * ebml, guint64 * master_chapters, guint64 * master_edition) { - guint64 uid, master_chapteratom; + guint64 master_chapteratom; GList *cur; - GstTocEntry *cur_entry; guint count, i; gchar *title; gint64 start, stop; + guint64 uid; + gchar s_uid[32]; + GstTocEntry *internal_chapter, *internal_nested; + GstTagList *tags; if (G_UNLIKELY (master_chapters != NULL && *master_chapters == 0)) *master_chapters = @@ -2643,78 +2650,111 @@ gst_matroska_mux_write_chapter (GstMatroskaMux * mux, GstTocEntry * edition, 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_EDITIONUID, + g_ascii_strtoull (gst_toc_entry_get_uid (edition), NULL, 10)); 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_times (entry, &start, &stop); + tags = gst_toc_entry_get_tags (entry); + if (tags != NULL) { + tags = gst_tag_list_copy (tags); + } + /* build internal chapter */ + uid = gst_matroska_mux_create_uid (mux); + g_snprintf (s_uid, sizeof (s_uid), "%" G_GINT64_FORMAT, uid); + internal_chapter = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, s_uid); + + /* Write the chapter entry */ 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); + /* Store the user provided UID in the ChapterStringUID */ + gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CHAPTERSTRINGUID, + gst_toc_entry_get_uid (entry)); 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); + /* write current ChapterDisplays before the nested chapters */ + if (G_LIKELY (tags != NULL)) { + count = gst_tag_list_get_tag_size (tags, GST_TAG_TITLE); for (i = 0; i < count; ++i) { - gst_tag_list_get_string_index (entry->tags, GST_TAG_TITLE, i, &title); + gst_tag_list_get_string_index (tags, GST_TAG_TITLE, i, &title); + /* FIXME: handle ChapterLanguage entries */ 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_tag_list_remove_tag (tags, GST_TAG_TITLE); + + gst_toc_entry_set_tags (internal_chapter, tags); + } + + /* Write nested chapters */ + for (cur = gst_toc_entry_get_sub_entries (entry); cur != NULL; + cur = cur->next) { + internal_nested = gst_matroska_mux_write_chapter (mux, NULL, cur->data, + ebml, NULL, NULL); + + gst_toc_entry_append_sub_entry (internal_chapter, internal_nested); } gst_ebml_write_master_finish (ebml, master_chapteratom); + + return internal_chapter; } -static void +static GstTocEntry * gst_matroska_mux_write_chapter_edition (GstMatroskaMux * mux, - GstTocEntry * entry, GstEbmlWrite * ebml, guint64 * master_chapters) + GstTocEntry * edition, GList * chapters, GstEbmlWrite * ebml, + guint64 * master_chapters) { guint64 master_edition = 0; + gchar s_uid[32]; GList *cur; - GstTocEntry *subentry; + GstTocEntry *internal_edition, *internal_chapter; + GstTagList *tags = NULL; - cur = gst_toc_entry_get_sub_entries (entry); - while (cur != NULL) { - subentry = cur->data; - gst_matroska_mux_write_chapter (mux, entry, subentry, ebml, master_chapters, - &master_edition); + g_snprintf (s_uid, sizeof (s_uid), "%" G_GINT64_FORMAT, + gst_matroska_mux_create_uid (mux)); - cur = cur->next; + if (edition != NULL) { + /* Edition entry defined, get its tags */ + tags = gst_toc_entry_get_tags (edition); + if (tags != NULL) { + tags = gst_tag_list_copy (tags); + } + } + + internal_edition = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, s_uid); + if (tags != NULL) { + gst_toc_entry_set_tags (internal_edition, tags); + } + + for (cur = g_list_first (chapters); cur != NULL; cur = cur->next) { + internal_chapter = gst_matroska_mux_write_chapter (mux, internal_edition, + cur->data, ebml, master_chapters, &master_edition); + + gst_toc_entry_append_sub_entry (internal_edition, internal_chapter); } if (G_LIKELY (master_edition != 0)) gst_ebml_write_master_finish (ebml, master_edition); + + return internal_edition; } -#endif /** * gst_matroska_mux_start: @@ -2745,9 +2785,7 @@ gst_matroska_mux_start (GstMatroskaMux * mux, GstMatroskaPad * first_pad, guint32 segment_uid[4]; GTimeVal time = { 0, 0 }; gchar s_id[32]; -#if 0 GstToc *toc; -#endif /* if not streaming, check if downstream is seekable */ if (!mux->ebml_write->streamable) { @@ -2930,77 +2968,66 @@ gst_matroska_mux_start (GstMatroskaMux * mux, GstMatroskaPad * first_pad, } gst_ebml_write_master_finish (ebml, master); - /* FIXME: Check if we get a TOC that is supported by Matroska - * and clean up the code below */ -#if 0 /* chapters */ toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux)); if (toc != NULL && !mux->ebml_write->streamable) { guint64 master_chapters = 0; - GstTocEntry *toc_entry; - GList *cur, *to_write = NULL; - gint64 start, stop; + GstTocEntry *internal_edition; + GList *cur, *chapters; GST_DEBUG ("Writing chapters"); - /* check whether we have editions or chapters at the root level */ - toc_entry = toc->entries->data; + /* There are two UIDs for Chapters: + * - The ChapterUID is a mandatory unsigned integer which internally + * refers to a given chapter. Except for the title & language which use + * dedicated fields, this UID can also be used to add tags to the Chapter. + * The tags come in a separate section of the container. + * - The ChapterStringUID is an optional UTF-8 string which also uniquely + * refers to a chapter but from an external perspective. It can act as a + * "WebVTT cue identifier" which "can be used to reference a specific cue, + * for example from script or CSS". + * + * The ChapterUID will be generated and checked for unicity, while the + * ChapterStringUID will receive the user defined UID. + * + * In order to be able to refer to chapters from the tags section, + * we must maintain an internal Toc tree with the generated ChapterUID + * (see gst_matroska_mux_write_toc_entry_tags) */ - 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_times (toc_entry, -1, -1); + /* Check whether we have editions or chapters at the root level. */ + cur = gst_toc_get_entries (toc); + if (cur != NULL) { + mux->chapters_pos = ebml->pos; - /* aggregate all chapters without root edition */ - cur = gst_toc_get_entries (toc); - while (cur != NULL) { - toc_entry->subentries = - g_list_prepend (toc_entry->subentries, cur->data); - cur = cur->next; + mux->internal_toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); + + if (gst_toc_entry_get_entry_type (cur->data) == + GST_TOC_ENTRY_TYPE_EDITION) { + /* Editions at the root level */ + for (; cur != NULL; cur = cur->next) { + chapters = gst_toc_entry_get_sub_entries (cur->data); + internal_edition = gst_matroska_mux_write_chapter_edition (mux, + cur->data, chapters, ebml, &master_chapters); + gst_toc_append_entry (mux->internal_toc, internal_edition); + } + } else { + /* Chapters at the root level */ + internal_edition = gst_matroska_mux_write_chapter_edition (mux, + NULL, cur, ebml, &master_chapters); + gst_toc_append_entry (mux->internal_toc, internal_edition); } - gst_toc_entry_get_start_stop_times (((GstTocEntry *) - toc_entry->subentries->data), &start, NULL); - toc_entry->subentries = g_list_reverse (toc_entry->subentries); - gst_toc_entry_get_start_stop_times (((GstTocEntry *) - toc_entry->subentries->data), NULL, &stop); - gst_toc_entry_set_start_stop_times (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_unref (toc_entry); - g_list_free (to_write); + /* close master element if any edition was written */ + if (G_LIKELY (master_chapters != 0)) + gst_ebml_write_master_finish (ebml, master_chapters); } } -#endif /* lastly, flush the cache */ gst_ebml_write_flush_cache (ebml, FALSE, 0); -#if 0 if (toc != NULL) gst_toc_unref (toc); -#endif } /* TODO: more sensible tag mappings */ @@ -3146,19 +3173,21 @@ gst_matroska_mux_streams_have_tags (GstMatroskaMux * mux) return FALSE; } -#if 0 static void gst_matroska_mux_write_toc_entry_tags (GstMatroskaMux * mux, - const GstTocEntry * entry, guint64 * master_tags) + const GstTocEntry * entry, guint64 * master_tags, gboolean * has_tags) { guint64 master_tag, master_targets; GstEbmlWrite *ebml; GList *cur; + const GstTagList *tags; ebml = mux->ebml_write; - if (G_UNLIKELY (entry->tags != NULL - && !gst_matroska_mux_tag_list_is_empty (entry->tags))) { + tags = gst_toc_entry_get_tags (entry); + if (G_UNLIKELY (tags != NULL && !gst_matroska_mux_tag_list_is_empty (tags))) { + *has_tags = TRUE; + if (*master_tags == 0) { mux->tags_pos = ebml->pos; *master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS); @@ -3168,25 +3197,24 @@ gst_matroska_mux_write_toc_entry_tags (GstMatroskaMux * mux, master_targets = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TARGETS); - if (entry->type == GST_TOC_ENTRY_TYPE_EDITION) + if (gst_toc_entry_get_entry_type (entry) == GST_TOC_ENTRY_TYPE_EDITION) gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TARGETEDITIONUID, - g_ascii_strtoull (entry->uid, NULL, 10)); + g_ascii_strtoull (gst_toc_entry_get_uid (entry), NULL, 10)); else gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TARGETCHAPTERUID, - g_ascii_strtoull (entry->uid, NULL, 10)); + g_ascii_strtoull (gst_toc_entry_get_uid (entry), NULL, 10)); gst_ebml_write_master_finish (ebml, master_targets); - gst_tag_list_foreach (entry->tags, gst_matroska_mux_write_simple_tag, ebml); + gst_tag_list_foreach (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; + for (cur = gst_toc_entry_get_sub_entries (entry); cur != NULL; + cur = cur->next) { + gst_matroska_mux_write_toc_entry_tags (mux, cur->data, master_tags, + has_tags); } } -#endif /** * gst_matroska_mux_finish: @@ -3201,8 +3229,9 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) guint64 pos; guint64 duration = 0; GSList *collected; - const GstTagList *tags; - gboolean has_main_tags; + const GstTagList *tags, *toc_tags; + gboolean has_main_tags, toc_has_tags = FALSE; + GList *cur; /* finish last cluster */ if (mux->cluster) { @@ -3245,16 +3274,9 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) if (has_main_tags || gst_matroska_mux_streams_have_tags (mux) || gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL) { guint64 master_tags = 0, master_tag; -#if 0 - const GstToc *toc; -#endif GST_DEBUG_OBJECT (mux, "Writing tags"); -#if 0 - toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux)); -#endif - if (has_main_tags) { /* TODO: maybe limit via the TARGETS id by looking at the source pad */ mux->tags_pos = ebml->pos; @@ -3263,23 +3285,23 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) if (tags != NULL) gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml); -#if 0 - if (toc != NULL) - gst_tag_list_foreach (toc->tags, gst_matroska_mux_write_simple_tag, + if (mux->internal_toc != NULL) { + toc_tags = gst_toc_get_tags (mux->internal_toc); + toc_has_tags = (toc_tags != NULL); + gst_tag_list_foreach (toc_tags, gst_matroska_mux_write_simple_tag, ebml); -#endif + } gst_ebml_write_master_finish (ebml, master_tag); } -#if 0 - 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 (mux->internal_toc != NULL) { + for (cur = gst_toc_get_entries (mux->internal_toc); cur != NULL; + cur = cur->next) { + gst_matroska_mux_write_toc_entry_tags (mux, cur->data, &master_tags, + &toc_has_tags); } } -#endif if (master_tags == 0 && gst_matroska_mux_streams_have_tags (mux)) { mux->tags_pos = ebml->pos; @@ -3329,7 +3351,7 @@ gst_matroska_mux_finish (GstMatroskaMux * mux) gst_ebml_write_seek (ebml, my_pos); } - if (tags != NULL) { + if (tags != NULL || toc_has_tags) { gst_ebml_replace_uint (ebml, mux->seekhead_pos + 144, mux->tags_pos - mux->segment_master); } else { diff --git a/gst/matroska/matroska-mux.h b/gst/matroska/matroska-mux.h index aaeeecdd1d..7283840663 100644 --- a/gst/matroska/matroska-mux.h +++ b/gst/matroska/matroska-mux.h @@ -135,6 +135,9 @@ struct _GstMatroskaMux { /* GstForceKeyUnit event */ GstEvent *force_key_unit_event; + /* Internal Toc (adjusted UIDs and title tags removed when processed) */ + GstToc *internal_toc; + /* Flag to ease handling of WebM specifics */ gboolean is_webm; diff --git a/gst/matroska/matroska-read-common.c b/gst/matroska/matroska-read-common.c index 39bc2dbe44..fcce782c78 100644 --- a/gst/matroska/matroska-read-common.c +++ b/gst/matroska/matroska-read-common.c @@ -731,18 +731,19 @@ gst_matroska_read_common_parse_attachments (GstMatroskaReadCommon * common, static void gst_matroska_read_common_parse_toc_tag (GstTocEntry * entry, - GArray * edition_targets, GArray * chapter_targtes, GstTagList * tags) + GstTocEntry * internal_entry, GArray * edition_targets, + GArray * chapter_targets, GstTagList * tags) { gchar *uid; guint i; guint64 tgt; GArray *targets; - GList *cur; + GList *cur, *internal_cur; GstTagList *etags; targets = (gst_toc_entry_get_entry_type (entry) == - GST_TOC_ENTRY_TYPE_EDITION) ? edition_targets : chapter_targtes; + GST_TOC_ENTRY_TYPE_EDITION) ? edition_targets : chapter_targets; etags = gst_tag_list_new_empty (); @@ -753,7 +754,7 @@ gst_matroska_read_common_parse_toc_tag (GstTocEntry * entry, gst_tag_list_insert (etags, tags, GST_TAG_MERGE_APPEND); else { uid = g_strdup_printf ("%" G_GUINT64_FORMAT, tgt); - if (g_strcmp0 (gst_toc_entry_get_uid (entry), uid) == 0) + if (g_strcmp0 (gst_toc_entry_get_uid (internal_entry), uid) == 0) gst_tag_list_insert (etags, tags, GST_TAG_MERGE_APPEND); g_free (uid); } @@ -763,10 +764,12 @@ gst_matroska_read_common_parse_toc_tag (GstTocEntry * entry, gst_tag_list_unref (etags); cur = gst_toc_entry_get_sub_entries (entry); - while (cur != NULL) { - gst_matroska_read_common_parse_toc_tag (cur->data, edition_targets, - chapter_targtes, tags); + internal_cur = gst_toc_entry_get_sub_entries (internal_entry); + while (cur != NULL && internal_cur != NULL) { + gst_matroska_read_common_parse_toc_tag (cur->data, internal_cur->data, + edition_targets, chapter_targets, tags); cur = cur->next; + internal_cur = internal_cur->next; } } @@ -954,16 +957,16 @@ gst_matroska_read_common_parse_chapter_titles (GstMatroskaReadCommon * common, static GstFlowReturn gst_matroska_read_common_parse_chapter_element (GstMatroskaReadCommon * common, - GstEbmlRead * ebml, GList ** subentries) + GstEbmlRead * ebml, GList ** subentries, GList ** internal_subentries) { 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; + GstTocEntry *chapter_info, *internal_chapter_info; GstTagList *tags; - gchar *uid_str; - GList *subsubentries = NULL, *l; + gchar *uid_str, *string_uid = NULL; + GList *subsubentries = NULL, *internal_subsubentries = NULL, *l, *il; DEBUG_ELEMENT_START (common, ebml, "ChaptersElement"); @@ -983,6 +986,10 @@ gst_matroska_read_common_parse_chapter_element (GstMatroskaReadCommon * common, ret = gst_ebml_read_uint (ebml, &id, &uid); break; + case GST_MATROSKA_ID_CHAPTERSTRINGUID: + ret = gst_ebml_read_utf8 (ebml, &id, &string_uid); + break; + case GST_MATROSKA_ID_CHAPTERTIMESTART: ret = gst_ebml_read_uint (ebml, &id, &start_time); break; @@ -992,9 +999,8 @@ gst_matroska_read_common_parse_chapter_element (GstMatroskaReadCommon * common, break; case GST_MATROSKA_ID_CHAPTERATOM: - ret = - gst_matroska_read_common_parse_chapter_element (common, ebml, - &subsubentries); + ret = gst_matroska_read_common_parse_chapter_element (common, ebml, + &subsubentries, &internal_subsubentries); break; case GST_MATROSKA_ID_CHAPTERDISPLAY: @@ -1021,15 +1027,29 @@ gst_matroska_read_common_parse_chapter_element (GstMatroskaReadCommon * common, if (uid == 0) uid = (((guint64) g_random_int ()) << 32) | g_random_int (); uid_str = g_strdup_printf ("%" G_GUINT64_FORMAT, uid); - chapter_info = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, uid_str); + if (string_uid != NULL) { + /* init toc with provided String UID */ + chapter_info = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, string_uid); + g_free (string_uid); + } else { + /* No String UID provided => use the internal UID instead */ + chapter_info = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, uid_str); + } + /* init internal toc with internal UID */ + internal_chapter_info = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, + uid_str); g_free (uid_str); gst_toc_entry_set_tags (chapter_info, tags); gst_toc_entry_set_start_stop_times (chapter_info, start_time, stop_time); - for (l = subsubentries; l; l = l->next) + for (l = subsubentries, il = internal_subsubentries; + l && il; l = l->next, il = il->next) { gst_toc_entry_append_sub_entry (chapter_info, l->data); + gst_toc_entry_append_sub_entry (internal_chapter_info, il->data); + } g_list_free (subsubentries); + g_list_free (internal_subsubentries); DEBUG_ELEMENT_STOP (common, ebml, "ChaptersElement", ret); @@ -1038,21 +1058,25 @@ gst_matroska_read_common_parse_chapter_element (GstMatroskaReadCommon * common, if (is_hidden == 0 && is_enabled > 0 && start_time != -1 && ret == GST_FLOW_OK) { *subentries = g_list_append (*subentries, chapter_info); - } else + *internal_subentries = g_list_append (*internal_subentries, + internal_chapter_info); + } else { gst_toc_entry_unref (chapter_info); + gst_toc_entry_unref (internal_chapter_info); + } return ret; } static GstFlowReturn gst_matroska_read_common_parse_chapter_edition (GstMatroskaReadCommon * common, - GstEbmlRead * ebml, GstToc * toc) + GstEbmlRead * ebml, GstToc * toc, GstToc * internal_toc) { guint32 id; guint64 is_hidden = 0, uid = 0; GstFlowReturn ret = GST_FLOW_OK; - GstTocEntry *edition_info; - GList *subentries = NULL, *l; + GstTocEntry *edition_info, *internal_edition_info; + GList *subentries = NULL, *internal_subentries = NULL, *l, *il; gchar *uid_str; DEBUG_ELEMENT_START (common, ebml, "ChaptersEdition"); @@ -1072,9 +1096,8 @@ gst_matroska_read_common_parse_chapter_edition (GstMatroskaReadCommon * common, break; case GST_MATROSKA_ID_CHAPTERATOM: - ret = - gst_matroska_read_common_parse_chapter_element (common, ebml, - &subentries); + ret = gst_matroska_read_common_parse_chapter_element (common, ebml, + &subentries, &internal_subentries); break; case GST_MATROSKA_ID_EDITIONFLAGHIDDEN: @@ -1096,18 +1119,26 @@ gst_matroska_read_common_parse_chapter_edition (GstMatroskaReadCommon * common, uid_str = g_strdup_printf ("%" G_GUINT64_FORMAT, uid); edition_info = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, uid_str); gst_toc_entry_set_start_stop_times (edition_info, -1, -1); + internal_edition_info = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, + uid_str); g_free (uid_str); - for (l = subentries; l; l = l->next) + for (l = subentries, il = internal_subentries; l && il; + l = l->next, il = il->next) { gst_toc_entry_append_sub_entry (edition_info, l->data); + gst_toc_entry_append_sub_entry (internal_edition_info, il->data); + } g_list_free (subentries); + g_list_free (internal_subentries); - if (is_hidden == 0 && subentries != NULL && ret == GST_FLOW_OK) + if (is_hidden == 0 && subentries != NULL && ret == GST_FLOW_OK) { gst_toc_append_entry (toc, edition_info); - else { + gst_toc_append_entry (internal_toc, internal_edition_info); + } else { GST_DEBUG_OBJECT (common->sinkpad, "Skipping empty or hidden edition in the chapters TOC"); gst_toc_entry_unref (edition_info); + gst_toc_entry_unref (internal_edition_info); } return ret; @@ -1119,7 +1150,7 @@ gst_matroska_read_common_parse_chapters (GstMatroskaReadCommon * common, { guint32 id; GstFlowReturn ret = GST_FLOW_OK; - GstToc *toc; + GstToc *toc, *internal_toc; DEBUG_ELEMENT_START (common, ebml, "Chapters"); @@ -1130,6 +1161,7 @@ gst_matroska_read_common_parse_chapters (GstMatroskaReadCommon * common, /* FIXME: create CURRENT toc as well */ toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); + internal_toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); while (ret == GST_FLOW_OK && gst_ebml_read_has_remaining (ebml, 1, TRUE)) { if ((ret = gst_ebml_peek_id (ebml, &id)) != GST_FLOW_OK) @@ -1137,8 +1169,8 @@ gst_matroska_read_common_parse_chapters (GstMatroskaReadCommon * common, switch (id) { case GST_MATROSKA_ID_EDITIONENTRY: - ret = - gst_matroska_read_common_parse_chapter_edition (common, ebml, toc); + ret = gst_matroska_read_common_parse_chapter_edition (common, ebml, + toc, internal_toc); break; default: @@ -1151,10 +1183,15 @@ gst_matroska_read_common_parse_chapters (GstMatroskaReadCommon * common, if (gst_toc_get_entries (toc) != NULL) { gst_matroska_read_common_postprocess_toc_entries (gst_toc_get_entries (toc), common->segment.duration, ""); + /* no need to postprocess internal_toc as we don't need to keep track + * of start / end and tags (only UIDs) */ common->toc = toc; - } else + common->internal_toc = internal_toc; + } else { gst_toc_unref (toc); + gst_toc_unref (internal_toc); + } common->chapters_parsed = TRUE; @@ -2228,7 +2265,7 @@ gst_matroska_read_common_apply_target_type_foreach (const GstTagList * list, continue; } else if (ctx->target_type_value >= 50) { gst_tag_list_add_value (ctx->result, GST_TAG_MERGE_APPEND, - GST_TAG_ALBUM, val_ref); + GST_TAG_TITLE, val_ref); continue; } } else if (strcmp (tag, GST_TAG_TITLE_SORTNAME) == 0) { @@ -2238,19 +2275,19 @@ gst_matroska_read_common_apply_target_type_foreach (const GstTagList * list, continue; } else if (ctx->target_type_value >= 50) { gst_tag_list_add_value (ctx->result, GST_TAG_MERGE_APPEND, - GST_TAG_ALBUM_SORTNAME, val_ref); + GST_TAG_TITLE_SORTNAME, val_ref); continue; } } else if (strcmp (tag, GST_TAG_ARTIST) == 0) { if (ctx->target_type_value >= 50) { gst_tag_list_add_value (ctx->result, GST_TAG_MERGE_APPEND, - GST_TAG_ALBUM_ARTIST, val_ref); + GST_TAG_ARTIST, val_ref); continue; } } else if (strcmp (tag, GST_TAG_ARTIST_SORTNAME) == 0) { if (ctx->target_type_value >= 50) { gst_tag_list_add_value (ctx->result, GST_TAG_MERGE_APPEND, - GST_TAG_ALBUM_ARTIST_SORTNAME, val_ref); + GST_TAG_ARTIST_SORTNAME, val_ref); continue; } } else if (strcmp (tag, GST_TAG_TRACK_COUNT) == 0) { @@ -2311,7 +2348,7 @@ gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, GstFlowReturn ret; GArray *chapter_targets, *edition_targets, *track_targets; GstTagList *taglist; - GList *cur; + GList *cur, *internal_cur; guint64 target_type_value = 50; gchar *target_type = NULL; @@ -2371,10 +2408,12 @@ gst_matroska_read_common_parse_metadata_id_tag (GstMatroskaReadCommon * common, "Found chapter/edition specific tag, but TOC is not present"); else { cur = gst_toc_get_entries (common->toc); - while (cur != NULL) { - gst_matroska_read_common_parse_toc_tag (cur->data, edition_targets, - chapter_targets, taglist); + internal_cur = gst_toc_get_entries (common->internal_toc); + while (cur != NULL && internal_cur != NULL) { + gst_matroska_read_common_parse_toc_tag (cur->data, internal_cur->data, + edition_targets, chapter_targets, taglist); cur = cur->next; + internal_cur = internal_cur->next; } common->toc_updated = TRUE; } @@ -2967,6 +3006,10 @@ gst_matroska_read_common_reset (GstElement * element, gst_toc_unref (ctx->toc); ctx->toc = NULL; } + if (ctx->internal_toc) { + gst_toc_unref (ctx->internal_toc); + ctx->internal_toc = NULL; + } } /* call with object lock held */ diff --git a/gst/matroska/matroska-read-common.h b/gst/matroska/matroska-read-common.h index 717323a740..a6282d6989 100644 --- a/gst/matroska/matroska-read-common.h +++ b/gst/matroska/matroska-read-common.h @@ -73,7 +73,11 @@ typedef struct _GstMatroskaReadCommon { GList *tags_parsed; /* chapters stuff */ + /* Internal toc is used to keep track of the internal UID + * which are different from the external StringUID used + * in the user toc */ GstToc *toc; + GstToc *internal_toc; gboolean toc_updated; /* start-of-segment and length */ diff --git a/tests/check/elements/matroskademux.c b/tests/check/elements/matroskademux.c index 93c139a009..71bc1817f0 100644 --- a/tests/check/elements/matroskademux.c +++ b/tests/check/elements/matroskademux.c @@ -32,6 +32,33 @@ const gchar mkv_sub_base64[] = "AA2bggfQoYeBF3AAYmF6oAEAAAAAAAAOm4IH0KGIgScQAGbDtgCgAQAAAAAAABWbggfQoY+BMsgA" "PGk+YmFyPC9pPgCgAQAAAAAAAA6bggfQoYiBPoAAYuR6ABJUw2cBAAAAAAAACnNzAQAAAAAAAAA="; +const gchar mkv_toc_base64[] = + "GkXfowEAAAAAAAAUQoKJbWF0cm9za2EAQoeBAUKFgQEYU4BnAQAAAAAABUoRTZt0AQAAAAAAAIxN" + "uwEAAAAAAAASU6uEFUmpZlOsiAAAAAAAAACYTbsBAAAAAAAAElOrhBZUrmtTrIgAAAAAAAABGk27" + "AQAAAAAAABJTq4QQQ6dwU6yIAAAAAAAAAWFNuwEAAAAAAAASU6uEHFO7a1OsiAAAAAAAAANrTbsB" + "AAAAAAAAElOrhBJUw2dTrIgAAAAAAAADkxVJqWYBAAAAAAAAdnOkkFdJrZAH7YY5MCvJGPwl5E4q" + "17GDD0JARImIP/AAAAAAAABNgKdHU3RyZWFtZXIgbWF0cm9za2FtdXggdmVyc2lvbiAxLjEzLjAu" + "MQBXQZlHU3RyZWFtZXIgTWF0cm9za2EgbXV4ZXIARGGIB2iH12N5DgAWVK5rAQAAAAAAADuuAQAA" + "AAAAADLXgQGDgQJzxYgJixQa+ZhvPSPjg4MPQkBTboZBdWRpbwDhAQAAAAAAAACGhkFfQUMzABBD" + "p3ABAAAAAAAB30W5AQAAAAAAAdVFvIi3DuS4TWeFXUW9gQBF24EARd2BALYBAAAAAAAA1HPEiOV0" + "L8eev+wgVlSGdWlkLjEAkYEAkoMehICYgQBFmIEBgAEAAAAAAAAQhYdjaGFwLjEAQ3yEdW5kALYB" + "AAAAAAAAQnPEiCW5ajpHRzyzVlSIdWlkLjEuMQCRgQCSgw9CQJiBAEWYgQGAAQAAAAAAABSFi25l" + "c3RlZC4xLjEAQ3yEdW5kALYBAAAAAAAARHPEiA9klFqtGkBoVlSIdWlkLjEuMgCRgw9CQJKDHoSA" + "mIEARZiBAYABAAAAAAAAFIWLbmVzdGVkLzEuMgBDfIR1bmQAtgEAAAAAAADYc8SIeu4QRrjscdtW" + "VIZ1aWQuMgCRgx6EgJKDPQkAmIEARZiBAYABAAAAAAAAEIWHY2hhcC4yAEN8hHVuZAC2AQAAAAAA" + "AERzxIik77DMKqRyzFZUiHVpZC4yLjEAkYMehICSgy3GwJiBAEWYgQGAAQAAAAAAABSFi25lc3Rl" + "ZC4yLjEAQ3yEdW5kALYBAAAAAAAARHPEiDvwt+5+V1ktVlSIdWlkLjIuMgCRgy3GwJKDPQkAmIEA" + "RZiBAYABAAAAAAAAFIWLbmVzdGVkLzIuMgBDfIR1bmQAH0O2dQEAAAAAAAAT54EAoAEAAAAAAAAH" + "oYWBAAAAABxTu2sBAAAAAAAAHLsBAAAAAAAAE7OBALcBAAAAAAAAB/eBAfGCA0wSVMNnAQAAAAAA" + "AatzcwEAAAAAAAAxY8ABAAAAAAAAC2PJiLcO5LhNZ4VdZ8gBAAAAAAAAEkWjiUNPTU1FTlRTAESH" + "g0VkAHNzAQAAAAAAADJjwAEAAAAAAAALY8SI5XQvx56/7CBnyAEAAAAAAAATRaOHQVJUSVNUAESH" + "hmFydC4xAHNzAQAAAAAAADRjwAEAAAAAAAALY8SIJblqOkdHPLNnyAEAAAAAAAAVRaOHQVJUSVNU" + "AESHiGFydC4xLjEAc3MBAAAAAAAANGPAAQAAAAAAAAtjxIgPZJRarRpAaGfIAQAAAAAAABVFo4dB" + "UlRJU1QARIeIYXJ0LjEuMgBzcwEAAAAAAAAyY8ABAAAAAAAAC2PEiHruEEa47HHbZ8gBAAAAAAAA" + "E0Wjh0FSVElTVABEh4ZhcnQuMgBzcwEAAAAAAAA0Y8ABAAAAAAAAC2PEiKTvsMwqpHLMZ8gBAAAA" + "AAAAFUWjh0FSVElTVABEh4hhcnQuMi4xAHNzAQAAAAAAADRjwAEAAAAAAAALY8SIO/C37n5XWS1n" + "yAEAAAAAAAAVRaOHQVJUSVNUAESHiGFydC4yLjIA"; + static void pad_added_cb (GstElement * matroskademux, GstPad * pad, gpointer user_data) { @@ -109,6 +136,175 @@ GST_START_TEST (test_sub_terminator) GST_END_TEST; +/* Recusively compare 2 toc entries */ +static void +check_toc_entries (const GstTocEntry * original, const GstTocEntry * other) +{ + gint64 start, stop, other_start, other_stop; + GstTocEntryType original_type, other_type; + const gchar *original_string_uid = NULL, *other_string_uid = NULL; + GstTagList *original_tags, *other_tags; + GList *cur, *other_cur; + + original_type = gst_toc_entry_get_entry_type (original); + other_type = gst_toc_entry_get_entry_type (other); + fail_unless (original_type == other_type); + + if (original_type != GST_TOC_ENTRY_TYPE_EDITION) { + original_string_uid = gst_toc_entry_get_uid (original); + other_string_uid = gst_toc_entry_get_uid (other); + fail_unless (g_strcmp0 (original_string_uid, other_string_uid) == 0); + } + + if (original_type != GST_TOC_ENTRY_TYPE_EDITION) { + gst_toc_entry_get_start_stop_times (original, &start, &stop); + gst_toc_entry_get_start_stop_times (other, &other_start, &other_stop); + + fail_unless (start == other_start && stop == other_stop); + } + + /* tags */ + original_tags = gst_toc_entry_get_tags (original); + other_tags = gst_toc_entry_get_tags (other); + fail_unless (gst_tag_list_is_equal (original_tags, other_tags)); + + other_cur = gst_toc_entry_get_sub_entries (other); + for (cur = gst_toc_entry_get_sub_entries (original); cur != NULL; + cur = cur->next) { + fail_unless (other_cur != NULL); + + check_toc_entries (cur->data, other_cur->data); + + other_cur = other_cur->next; + } +} + +/* Create a new chapter */ +static GstTocEntry * +new_chapter (const guint chapter_nb, const gint64 start, const gint64 stop) +{ + GstTocEntry *toc_entry, *toc_sub_entry; + GstTagList *tags; + gchar title[32]; + gchar artist[32]; + gchar str_uid[32]; + + g_snprintf (str_uid, sizeof (str_uid), "uid.%d", chapter_nb); + toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid); + gst_toc_entry_set_start_stop_times (toc_entry, start, stop); + + g_snprintf (title, sizeof (title), "chap.%d", chapter_nb); + g_snprintf (artist, sizeof (artist), "art.%d", chapter_nb); + tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL); + gst_toc_entry_set_tags (toc_entry, tags); + + g_snprintf (str_uid, sizeof (str_uid), "uid.%d.1", chapter_nb); + toc_sub_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid); + gst_toc_entry_set_start_stop_times (toc_sub_entry, start, (start + stop) / 2); + + g_snprintf (title, sizeof (title), "nested.%d.1", chapter_nb); + g_snprintf (artist, sizeof (artist), "art.%d.1", chapter_nb); + tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL); + gst_toc_entry_set_tags (toc_sub_entry, tags); + + gst_toc_entry_append_sub_entry (toc_entry, toc_sub_entry); + + g_snprintf (str_uid, sizeof (str_uid), "uid.%d.2", chapter_nb); + toc_sub_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid); + gst_toc_entry_set_start_stop_times (toc_sub_entry, (start + stop) / 2, stop); + + g_snprintf (title, sizeof (title), "nested/%d.2", chapter_nb); + g_snprintf (artist, sizeof (artist), "art.%d.2", chapter_nb); + tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL); + gst_toc_entry_set_tags (toc_sub_entry, tags); + + gst_toc_entry_append_sub_entry (toc_entry, toc_sub_entry); + + return toc_entry; +} + +/* Create a reference toc which matches what is expected in mkv_toc_base64 */ +static GstToc * +new_reference_toc (void) +{ + GstToc *ref_toc; + GstTocEntry *toc_edition_entry, *toc_entry; + GstTagList *tags; + + ref_toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); + + toc_edition_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, "00"); + tags = gst_tag_list_new (GST_TAG_COMMENT, "Ed", NULL); + gst_toc_entry_set_tags (toc_edition_entry, tags); + + toc_entry = new_chapter (1, 0 * GST_MSECOND, 2 * GST_MSECOND); + gst_toc_entry_append_sub_entry (toc_edition_entry, toc_entry); + + toc_entry = new_chapter (2, 2 * GST_MSECOND, 4 * GST_MSECOND); + gst_toc_entry_append_sub_entry (toc_edition_entry, toc_entry); + + gst_toc_append_entry (ref_toc, toc_edition_entry); + + return ref_toc; +} + +GST_START_TEST (test_toc_demux) +{ + GstHarness *h; + GstBuffer *buf; + guchar *mkv_data; + gsize mkv_size; + GstEvent *event; + gboolean update; + GstToc *ref_toc, *demuxed_toc = NULL; + GList *ref_cur, *demuxed_cur; + + h = gst_harness_new_with_padnames ("matroskademux", "sink", NULL); + + g_signal_connect (h->element, "pad-added", G_CALLBACK (pad_added_cb), h); + + mkv_data = g_base64_decode (mkv_toc_base64, &mkv_size); + fail_unless (mkv_data != NULL); + + gst_harness_set_src_caps_str (h, "audio/x-matroska"); + + buf = gst_buffer_new_wrapped (mkv_data, mkv_size); + GST_BUFFER_OFFSET (buf) = 0; + + fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK); + gst_harness_push_event (h, gst_event_new_eos ()); + + event = gst_harness_try_pull_event (h); + fail_unless (event != NULL); + + while (event != NULL) { + if (event->type == GST_EVENT_TOC) { + gst_event_parse_toc (event, &demuxed_toc, &update); + break; + } + event = gst_harness_try_pull_event (h); + } + + fail_unless (demuxed_toc != NULL); + ref_toc = new_reference_toc (); + + demuxed_cur = gst_toc_get_entries (demuxed_toc); + for (ref_cur = gst_toc_get_entries (ref_toc); ref_cur != NULL; + ref_cur = ref_cur->next) { + fail_unless (demuxed_cur != NULL); + + check_toc_entries (ref_cur->data, demuxed_cur->data); + demuxed_cur = demuxed_cur->next; + } + + gst_toc_unref (ref_toc); + gst_toc_unref (demuxed_toc); + + gst_harness_teardown (h); +} + +GST_END_TEST; + static Suite * matroskademux_suite (void) { @@ -117,6 +313,7 @@ matroskademux_suite (void) suite_add_tcase (s, tc_chain); tcase_add_test (tc_chain, test_sub_terminator); + tcase_add_test (tc_chain, test_toc_demux); return s; } diff --git a/tests/check/elements/matroskamux.c b/tests/check/elements/matroskamux.c index bffd440a26..97a033f833 100644 --- a/tests/check/elements/matroskamux.c +++ b/tests/check/elements/matroskamux.c @@ -155,6 +155,21 @@ teardown_sink_pad (GstElement * element) } +gboolean downstream_is_seekable; +static gboolean +matroskamux_sinkpad_query (GstPad * pad, GstObject * parent, GstQuery * query) +{ + gboolean ret = FALSE; + + if (GST_QUERY_TYPE (query) == GST_QUERY_SEEKING) { + gst_query_set_seeking (query, GST_FORMAT_BYTES, downstream_is_seekable, 0, + -1); + ret = TRUE; + } + + return ret; +} + static GstElement * setup_matroskamux (GstStaticPadTemplate * srctemplate) { @@ -463,6 +478,654 @@ GST_START_TEST (test_link_webmmux_webm_sink) GST_END_TEST; +/* Create a new chapter */ +static GstTocEntry * +new_chapter (const guint chapter_nb, const gint64 start, const gint64 stop) +{ + GstTocEntry *toc_entry, *toc_sub_entry; + GstTagList *tags; + gchar title[32]; + gchar artist[32]; + gchar str_uid[32]; + + g_snprintf (str_uid, sizeof (str_uid), "uid.%d", chapter_nb); + toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid); + gst_toc_entry_set_start_stop_times (toc_entry, start, stop); + + g_snprintf (title, sizeof (title), "chap.%d", chapter_nb); + g_snprintf (artist, sizeof (artist), "art.%d", chapter_nb); + tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL); + gst_toc_entry_set_tags (toc_entry, tags); + + g_snprintf (str_uid, sizeof (str_uid), "uid.%d.1", chapter_nb); + toc_sub_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid); + gst_toc_entry_set_start_stop_times (toc_sub_entry, start, (start + stop) / 2); + + g_snprintf (title, sizeof (title), "nested.%d.1", chapter_nb); + g_snprintf (artist, sizeof (artist), "art.%d.1", chapter_nb); + tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL); + gst_toc_entry_set_tags (toc_sub_entry, tags); + + gst_toc_entry_append_sub_entry (toc_entry, toc_sub_entry); + + g_snprintf (str_uid, sizeof (str_uid), "uid.%d.2", chapter_nb); + toc_sub_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, str_uid); + gst_toc_entry_set_start_stop_times (toc_sub_entry, (start + stop) / 2, stop); + + g_snprintf (title, sizeof (title), "nested/%d.2", chapter_nb); + g_snprintf (artist, sizeof (artist), "art.%d.2", chapter_nb); + tags = gst_tag_list_new (GST_TAG_TITLE, title, GST_TAG_ARTIST, artist, NULL); + gst_toc_entry_set_tags (toc_sub_entry, tags); + + gst_toc_entry_append_sub_entry (toc_entry, toc_sub_entry); + + return toc_entry; +} + +/* Create a reference toc which includes a master edition entry */ +static GstToc * +new_reference_toc (void) +{ + GstToc *ref_toc; + GstTocEntry *toc_edition_entry, *toc_entry; + GstTagList *tags; + + ref_toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); + + toc_edition_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, "00"); + tags = gst_tag_list_new (GST_TAG_COMMENT, "Ed", NULL); + gst_toc_entry_set_tags (toc_edition_entry, tags); + + toc_entry = new_chapter (1, 0 * GST_MSECOND, 2 * GST_MSECOND); + gst_toc_entry_append_sub_entry (toc_edition_entry, toc_entry); + + toc_entry = new_chapter (2, 2 * GST_MSECOND, 4 * GST_MSECOND); + gst_toc_entry_append_sub_entry (toc_edition_entry, toc_entry); + + gst_toc_append_entry (ref_toc, toc_edition_entry); + + return ref_toc; +} + +/* Create a toc which includes chapters without edition entry */ +static GstToc * +new_no_edition_toc (void) +{ + GstToc *ref_toc; + GstTocEntry *toc_entry; + + ref_toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); + + toc_entry = new_chapter (1, 0 * GST_MSECOND, 2 * GST_MSECOND); + gst_toc_append_entry (ref_toc, toc_entry); + + toc_entry = new_chapter (2, 2 * GST_MSECOND, 4 * GST_MSECOND); + gst_toc_append_entry (ref_toc, toc_entry); + + return ref_toc; +} + +static guint64 +read_integer (GstMapInfo * info, gsize * index, guint64 len) +{ + guint64 total = 0; + + for (; len > 0; --len) { + total = (total << 8) | GST_READ_UINT8 (info->data + *index); + ++(*index); + } + + return total; +} + +static guint64 +read_length (GstMapInfo * info, gsize * index) +{ + gint len_mask = 0x80, read = 1; + guint64 total; + guint8 b; + + b = GST_READ_UINT8 (info->data + *index); + ++(*index); + total = (guint64) b; + while (read <= 8 && !(total & len_mask)) { + read++; + len_mask >>= 1; + } + total &= (len_mask - 1); + + for (; read > 1; --read) { + total = (total << 8) | GST_READ_UINT8 (info->data + *index); + ++(*index); + } + + return total; +} + +static gboolean +check_id (GstMapInfo * info, gsize * index, + guint8 * tag, gint tag_len, guint64 * len) +{ + if (memcmp (info->data + *index, tag, tag_len) == 0) { + *index += tag_len; + *len = read_length (info, index); + return TRUE; + } else { + return FALSE; + } +} + +static gboolean +check_id_read_int (GstMapInfo * info, gsize * index, + guint8 * tag, gint tag_len, guint64 * value) +{ + guint64 len; + + if (check_id (info, index, tag, tag_len, &len)) { + *value = read_integer (info, index, len); + return TRUE; + } else { + return FALSE; + } +} + +/* Check the toc entry against the muxed buffer + * Returns the internal UID */ +static void +check_chapter (GstTocEntry * toc_entry, GstTocEntry * internal_toc_entry, + GstMapInfo * info, gsize * index, gint last_offset) +{ + guint64 len, value, uid; + gint64 start_ref, end_ref; + gchar s_uid[32]; + const gchar *str_uid; + GstTocEntry *internal_chapter; + GList *cur_sub_chap; + GstTagList *tags; + gchar *title; + + guint8 chapter_atom[1] = { 0xb6 }; + guint8 chapter_uid[2] = { 0x73, 0xc4 }; + guint8 chapter_str_uid[2] = { 0x56, 0x54 }; + guint8 chapter_start[1] = { 0x91 }; + guint8 chapter_end[1] = { 0x92 }; + guint8 chapter_flag_hidden[1] = { 0x98 }; + guint8 chapter_flag_enabled[2] = { 0x45, 0x98 }; + guint8 chapter_segment_uid[2] = { 0x6e, 0x67 }; + guint8 chapter_segment_edition_uid[2] = { 0x6e, 0xbc }; + guint8 chapter_physical_equiv[2] = { 0x63, 0xc3 }; + guint8 chapter_track[1] = { 0x8f }; + guint8 chapter_track_nb[1] = { 0x89 }; + guint8 chapter_display[1] = { 0x80 }; + guint8 chapter_string[1] = { 0x85 }; + guint8 chapter_language[2] = { 0x43, 0x7c }; + + fail_unless (check_id (info, index, chapter_atom, + sizeof (chapter_atom), &len)); + + fail_unless (check_id_read_int (info, index, chapter_uid, + sizeof (chapter_uid), &uid)); + + /* optional StringUID */ + if (check_id (info, index, chapter_str_uid, sizeof (chapter_str_uid), &len)) { + str_uid = gst_toc_entry_get_uid (toc_entry); + fail_unless (memcmp (info->data + *index, str_uid, strlen (str_uid)) == 0); + *index += len; + } + + gst_toc_entry_get_start_stop_times (toc_entry, &start_ref, &end_ref); + + fail_unless (check_id_read_int (info, index, chapter_start, + sizeof (chapter_start), &value)); + fail_unless ((gint64) value == start_ref); + + /* optional chapter end */ + if (check_id_read_int (info, index, chapter_end, + sizeof (chapter_end), &value)) { + fail_unless ((gint64) value == end_ref); + } + + fail_unless (check_id_read_int (info, index, chapter_flag_hidden, + sizeof (chapter_flag_hidden), &value)); + + fail_unless (check_id_read_int (info, index, chapter_flag_enabled, + sizeof (chapter_flag_enabled), &value)); + + /* optional segment UID */ + check_id_read_int (info, index, chapter_segment_uid, + sizeof (chapter_segment_uid), &value); + + /* optional segment edition UID */ + check_id_read_int (info, index, chapter_segment_edition_uid, + sizeof (chapter_segment_edition_uid), &value); + + /* optional physical equiv */ + check_id_read_int (info, index, chapter_physical_equiv, + sizeof (chapter_physical_equiv), &value); + + /* optional chapter track */ + if (check_id (info, index, chapter_track, sizeof (chapter_track), &len)) { + fail_unless (check_id_read_int (info, index, chapter_track_nb, + sizeof (chapter_track_nb), &value)); + } + + /* FIXME: there can be several chapter displays */ + if (check_id (info, index, chapter_display, sizeof (chapter_display), &len)) { + /* chapter display */ + fail_unless (check_id (info, index, chapter_string, + sizeof (chapter_string), &len)); + + tags = gst_toc_entry_get_tags (toc_entry); + if (gst_tag_list_get_tag_size (tags, GST_TAG_TITLE) > 0) { + gst_tag_list_get_string_index (tags, GST_TAG_TITLE, 0, &title); + fail_unless (memcmp (info->data + *index, title, strlen (title)) == 0); + } + *index += len; + + fail_unless (check_id (info, index, chapter_language, + sizeof (chapter_language), &len)); + /* TODO: define language - always "und" ATM */ + *index += len; + } + + /* TODO: add remaining fields (not used in current matroska-mux) */ + + g_snprintf (s_uid, sizeof (s_uid), "%" G_GINT64_FORMAT, uid); + internal_chapter = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, s_uid); + gst_toc_entry_append_sub_entry (internal_toc_entry, internal_chapter); + + cur_sub_chap = gst_toc_entry_get_sub_entries (toc_entry); + while (cur_sub_chap != NULL && *index < last_offset) { + check_chapter (cur_sub_chap->data, internal_chapter, info, + index, last_offset); + cur_sub_chap = cur_sub_chap->next; + } + + fail_unless (cur_sub_chap == NULL); +} + +/* Check the reference toc against the muxed buffer */ +static void +check_toc (GstToc * ref_toc, GstToc * internal_toc, + GstMapInfo * info, gsize * index) +{ + guint64 len, value, uid; + gchar s_uid[32]; + gint last_offset; + GList *cur_entry, *cur_chapter; + GstTocEntry *internal_edition; + + guint8 edition_entry[2] = { 0x45, 0xb9 }; + guint8 edition_uid[2] = { 0x45, 0xbc }; + guint8 edition_flag_hidden[2] = { 0x45, 0xbd }; + guint8 edition_flag_default[2] = { 0x45, 0xdb }; + guint8 edition_flag_ordered[2] = { 0x45, 0xdd }; + + /* edition entry */ + fail_unless (check_id (info, index, edition_entry, + sizeof (edition_entry), &len)); + last_offset = *index + (gint) len; + + cur_entry = gst_toc_get_entries (ref_toc); + while (cur_entry != NULL && *index < last_offset) { + uid = 0; + check_id_read_int (info, index, edition_uid, sizeof (edition_uid), &uid); + g_snprintf (s_uid, sizeof (s_uid), "%" G_GINT64_FORMAT, uid); + internal_edition = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, s_uid); + gst_toc_append_entry (internal_toc, internal_edition); + + fail_unless (check_id_read_int (info, index, edition_flag_hidden, + sizeof (edition_flag_hidden), &value)); + + fail_unless (check_id_read_int (info, index, edition_flag_default, + sizeof (edition_flag_default), &value)); + + /* optional */ + check_id_read_int (info, index, edition_flag_ordered, + sizeof (edition_flag_ordered), &value); + + cur_chapter = gst_toc_entry_get_sub_entries (cur_entry->data); + while (cur_chapter != NULL && *index < last_offset) { + check_chapter (cur_chapter->data, internal_edition, info, + index, last_offset); + cur_chapter = cur_chapter->next; + } + fail_unless (cur_chapter == NULL); + + cur_entry = cur_entry->next; + } + + fail_unless (cur_entry == NULL && *index == last_offset); +} + +static GstTocEntry * +find_toc_entry (GstTocEntry * ref_toc_entry, GstTocEntry * internal_toc_entry, + guint64 uid) +{ + GList *cur_ref_entry, *cur_internal_entry; + guint64 internal_uid; + GstTocEntry *result = NULL; + + internal_uid = g_ascii_strtoull (gst_toc_entry_get_uid (internal_toc_entry), + NULL, 10); + if (uid == internal_uid) { + result = ref_toc_entry; + } else { + cur_ref_entry = gst_toc_entry_get_sub_entries (ref_toc_entry); + cur_internal_entry = gst_toc_entry_get_sub_entries (internal_toc_entry); + while (cur_ref_entry != NULL && cur_internal_entry != NULL) { + result = find_toc_entry (cur_ref_entry->data, cur_internal_entry->data, + uid); + + if (result != NULL) { + break; + } + + cur_ref_entry = cur_ref_entry->next; + cur_internal_entry = cur_internal_entry->next; + } + } + + return result; +} + +static void +find_and_check_tags (GstToc * ref_toc, GstToc * internal_toc, GstMapInfo * info, + guint64 uid, gchar * tag_name, gchar * tag_string) +{ + GList *cur_ref_entry, *cur_internal_entry; + GstTocEntry *ref_toc_entry = NULL; + GstTagList *tags; + const gchar *tag_type; + gchar *cur_tag_string; + + /* find the reference toc entry matching the UID */ + cur_ref_entry = gst_toc_get_entries (ref_toc); + cur_internal_entry = gst_toc_get_entries (internal_toc); + while (cur_ref_entry != NULL && cur_internal_entry != NULL) { + ref_toc_entry = find_toc_entry (cur_ref_entry->data, + cur_internal_entry->data, uid); + + if (ref_toc_entry != NULL) { + break; + } + + cur_ref_entry = cur_ref_entry->next; + cur_internal_entry = cur_internal_entry->next; + } + + fail_unless (ref_toc_entry != NULL); + + if (g_strcmp0 (tag_name, "ARTIST") == 0) { + tag_type = GST_TAG_ARTIST; + } else if (g_strcmp0 (tag_name, "COMMENTS") == 0) { + tag_type = GST_TAG_COMMENT; + } else { + tag_type = NULL; + } + + fail_unless (tag_type != NULL); + + tags = gst_toc_entry_get_tags (ref_toc_entry); + fail_unless (gst_tag_list_get_tag_size (tags, tag_type) > 0); + gst_tag_list_get_string_index (tags, tag_type, 0, &cur_tag_string); + fail_unless (g_strcmp0 (cur_tag_string, tag_string) == 0); +} + +static void +check_tags (GstToc * ref_toc, GstToc * internal_toc, + GstMapInfo * info, gsize * index) +{ + gboolean found_tags = FALSE, must_check_tag = FALSE; + guint64 len, value, uid; + gsize last_offset, next_tag; + gchar *tag_name_str, *tag_string_str; + guint8 tags[4] = { 0x12, 0x54, 0xc3, 0x67 }; + guint8 tag[2] = { 0x73, 0x73 }; + guint8 tag_targets[2] = { 0x63, 0xc0 }; + guint8 tag_target_type_value[2] = { 0x68, 0xca }; + guint8 tag_target_type[2] = { 0x63, 0xca }; + guint8 tag_edition_uid[2] = { 0x63, 0xc9 }; + guint8 tag_chapter_uid[2] = { 0x63, 0xc4 }; + guint8 simple_tag[2] = { 0x67, 0xc8 }; + guint8 tag_name[2] = { 0x45, 0xa3 }; + guint8 tag_string[2] = { 0x44, 0x87 }; + + if (info->size > *index + sizeof (tags)) { + for (; *index < info->size - sizeof (tags); ++(*index)) { + if (memcmp (info->data + *index, tags, sizeof (tags)) == 0) { + *index += sizeof (tags); + + len = read_length (info, index); + last_offset = *index + len; + + found_tags = TRUE; + break; + } + } + } + + fail_unless (found_tags); + + while (*index < last_offset) { + fail_unless (check_id (info, index, tag, sizeof (tag), &len)); + next_tag = *index + len; + + fail_unless (check_id (info, index, tag_targets, + sizeof (tag_targets), &len)); + + must_check_tag = FALSE; + check_id_read_int (info, index, tag_target_type_value, + sizeof (tag_target_type_value), &value); + + if (check_id (info, index, tag_target_type, sizeof (tag_target_type), &len)) { + *index += len; + } + + if (check_id_read_int (info, index, tag_chapter_uid, + sizeof (tag_chapter_uid), &uid)) { + must_check_tag = TRUE; + } else if (check_id_read_int (info, index, tag_edition_uid, + sizeof (tag_edition_uid), &uid)) { + must_check_tag = TRUE; + } + + if (must_check_tag) { + fail_unless (check_id (info, index, simple_tag, + sizeof (simple_tag), &len)); + + fail_unless (check_id (info, index, tag_name, sizeof (tag_name), &len)); + tag_name_str = g_strndup ((gchar *) info->data + *index, len); + *index += len; + + fail_unless (check_id (info, index, tag_string, sizeof (tag_string), + &len)); + tag_string_str = g_strndup ((gchar *) info->data + *index, len); + *index += len; + + find_and_check_tags (ref_toc, internal_toc, info, uid, + tag_name_str, tag_string_str); + + g_free (tag_name_str); + g_free (tag_string_str); + } + + *index = next_tag; + } +} + +static void +check_segment (GstToc * ref_toc, GstToc * internal_toc, + GstMapInfo * info, gsize * index) +{ + guint8 matroska_segment[4] = { 0x18, 0x53, 0x80, 0x67 }; + guint8 matroska_seek_id_chapters[7] = { 0x53, 0xab, 0x84, + 0x10, 0x43, 0xA7, 0x70 + }; + guint8 matroska_seek_id_tags[7] = { 0x53, 0xab, 0x84, + 0x12, 0x54, 0xc3, 0x67 + }; + guint8 matroska_seek_pos[2] = { 0x53, 0xac }; + guint8 matroska_chapters[4] = { 0x10, 0x43, 0xA7, 0x70 }; + + guint64 len, value, segment_offset, chapters_offset, tags_offset; + gboolean found_chapters_declaration = FALSE, found_tags_declaration = FALSE; + + /* Segment */ + fail_unless (info->size > sizeof (matroska_segment)); + fail_unless (check_id (info, index, matroska_segment, + sizeof (matroska_segment), &len)); + + segment_offset = *index; + + /* Search chapter declaration in seek head */ + for (; *index < len - sizeof (matroska_seek_id_chapters); ++(*index)) { + if (memcmp (info->data + *index, matroska_seek_id_chapters, + sizeof (matroska_seek_id_chapters)) == 0) { + *index += sizeof (matroska_seek_id_chapters); + + if (check_id_read_int (info, index, matroska_seek_pos, + sizeof (matroska_seek_pos), &value)) { + /* found chapter declaration */ + found_chapters_declaration = TRUE; + chapters_offset = segment_offset + value; + break; + } + } + } + + fail_unless (found_chapters_declaration); + + *index = chapters_offset; + if (check_id (info, index, matroska_chapters, + sizeof (matroska_chapters), &len)) { + check_toc (ref_toc, internal_toc, info, index); + } + + /* Search tags declaration in seek head */ + for (*index = segment_offset; *index < len - sizeof (matroska_seek_id_tags); + ++(*index)) { + if (memcmp (info->data + *index, matroska_seek_id_tags, + sizeof (matroska_seek_id_tags)) == 0) { + *index += sizeof (matroska_seek_id_tags); + + if (check_id_read_int (info, index, matroska_seek_pos, + sizeof (matroska_seek_pos), &value)) { + /* found tags declaration */ + found_tags_declaration = TRUE; + tags_offset = segment_offset + value; + break; + } + } + } + + fail_unless (found_tags_declaration); + + *index = tags_offset; + check_tags (ref_toc, internal_toc, info, index); +} + +static void +test_toc (gboolean with_edition) +{ + GstElement *matroskamux; + GstBuffer *inbuffer, *outbuffer, *merged_buffer; + GstMapInfo info; + GstCaps *caps; + int num_buffers, i; + guint64 len; + gsize index; + GstTocSetter *toc_setter; + GstToc *test_toc, *ref_toc, *internal_toc; + + guint8 ebml_header[4] = { 0x1a, 0x45, 0xdf, 0xa3 }; + + matroskamux = setup_matroskamux (&srcac3template); + downstream_is_seekable = TRUE; + gst_pad_set_query_function (mysinkpad, matroskamux_sinkpad_query); + + toc_setter = GST_TOC_SETTER (matroskamux); + fail_unless (toc_setter != NULL); + + if (with_edition) { + test_toc = new_reference_toc (); + } else { + test_toc = new_no_edition_toc (); + } + gst_toc_setter_set_toc (toc_setter, test_toc); + gst_toc_unref (test_toc); + + caps = gst_caps_from_string (srcac3template.static_caps.string); + gst_check_setup_events (mysrcpad, matroskamux, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + + inbuffer = gst_buffer_new_and_alloc (1); + gst_buffer_memset (inbuffer, 0, 0, 1); + GST_BUFFER_TIMESTAMP (inbuffer) = 0; + GST_BUFFER_DURATION (inbuffer) = 1 * GST_MSECOND; + ASSERT_BUFFER_REFCOUNT (inbuffer, "inbuffer", 1); + fail_unless (gst_pad_push (mysrcpad, inbuffer) == GST_FLOW_OK); + + /* send eos to ensure everything is written */ + fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_eos ())); + + /* Merge buffers */ + num_buffers = g_list_length (buffers); + merged_buffer = gst_buffer_new (); + for (i = 0; i < num_buffers; ++i) { + outbuffer = GST_BUFFER (buffers->data); + fail_if (outbuffer == NULL); + buffers = g_list_remove (buffers, outbuffer); + + if (outbuffer->offset == gst_buffer_get_size (merged_buffer)) { + gst_buffer_append_memory (merged_buffer, + gst_buffer_get_all_memory (outbuffer)); + } else { + fail_unless (gst_buffer_map (outbuffer, &info, GST_MAP_READ)); + gst_buffer_fill (merged_buffer, outbuffer->offset, info.data, info.size); + gst_buffer_unmap (outbuffer, &info); + } + + ASSERT_BUFFER_REFCOUNT (outbuffer, "outbuffer", 1); + gst_buffer_unref (outbuffer); + outbuffer = NULL; + } + + fail_unless (gst_buffer_map (merged_buffer, &info, GST_MAP_READ)); + index = 0; + + fail_unless (check_id (&info, &index, ebml_header, + sizeof (ebml_header), &len)); + /* skip header */ + index += len; + + ref_toc = new_reference_toc (); + internal_toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); + check_segment (ref_toc, internal_toc, &info, &index); + gst_toc_unref (internal_toc); + gst_toc_unref (ref_toc); + + gst_buffer_unmap (merged_buffer, &info); + + cleanup_matroskamux (matroskamux); + g_list_free (buffers); + buffers = NULL; +} + +GST_START_TEST (test_toc_with_edition) +{ + test_toc (TRUE); +} + +GST_END_TEST; + +GST_START_TEST (test_toc_without_edition) +{ + test_toc (FALSE); +} + +GST_END_TEST; + static Suite * matroskamux_suite (void) { @@ -475,7 +1138,8 @@ matroskamux_suite (void) tcase_add_test (tc_chain, test_block_group); tcase_add_test (tc_chain, test_reset); tcase_add_test (tc_chain, test_link_webmmux_webm_sink); - + tcase_add_test (tc_chain, test_toc_with_edition); + tcase_add_test (tc_chain, test_toc_without_edition); return s; }