matroska: re-activate and update TOC support

TOC support in mastroskamux has been deactivated for a couple of years. This commit updates it to recent GstToc evolutions and introduces toc unit tests for both matroska-mux and matroska-demux.

There are two UIDs for Chapters in Matroska's specifications:
- The ChapterUID is a mandatory unsigned integer which internally refers to a given chapter. Except for 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".

During muxing, the ChapterUID is generated and checked for unicity, while the ChapterStringUID receives the user defined UID. In order to be able to refer to chapters from the tags section, we maintain an internal Toc tree with the generated ChapterUID.

When demuxing, the ChapterStringUIDs (if available) are assigned to the GstTocEntries UIDs and an internal toc mimicking the toc is used to keep track of the ChapterUIDs and match the tags with the appropriate GstTocEntries.

https://bugzilla.gnome.org/show_bug.cgi?id=790686
This commit is contained in:
fengalin 2017-12-01 18:17:06 +01:00 committed by Sebastian Dröge
parent 4f7b995ae7
commit a6702a76d5
7 changed files with 1095 additions and 161 deletions

View file

@ -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

View file

@ -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);
/* aggregate all chapters without root edition */
/* Check whether we have editions or chapters at the root level. */
cur = gst_toc_get_entries (toc);
while (cur != NULL) {
toc_entry->subentries =
g_list_prepend (toc_entry->subentries, cur->data);
cur = cur->next;
}
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 */
if (cur != NULL) {
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;
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);
}
/* 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);
}
}
#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 {

View file

@ -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;

View file

@ -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);
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 */

View file

@ -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 */

View file

@ -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;
}

View file

@ -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;
}