From 1f7096b52aafb476daaac4e0e44b20190b1222c9 Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 14 Mar 2012 20:01:51 +0400 Subject: [PATCH] Add generic table of contents (TOC) support --- gst/Makefile.am | 2 + gst/gst.c | 1 + gst/gst.h | 1 + gst/gst_private.h | 14 + gst/gsttoc.c | 1010 +++++++++++++++++++++++++++++++++++++++++++++++++++++ gst/gsttoc.h | 111 ++++++ 6 files changed, 1139 insertions(+) create mode 100644 gst/gsttoc.c create mode 100644 gst/gsttoc.h diff --git a/gst/Makefile.am b/gst/Makefile.am index 60d4b11..a9852de 100644 --- a/gst/Makefile.am +++ b/gst/Makefile.am @@ -98,6 +98,7 @@ libgstreamer_@GST_MAJORMINOR@_la_SOURCES = \ gsttagsetter.c \ gsttask.c \ gsttaskpool.c \ + gsttoc.c \ $(GST_TRACE_SRC) \ gsttypefind.c \ gsttypefindfactory.c \ @@ -188,6 +189,7 @@ gst_headers = \ gsttagsetter.h \ gsttask.h \ gsttaskpool.h \ + gsttoc.h \ gsttrace.h \ gsttypefind.h \ gsttypefindfactory.h \ diff --git a/gst/gst.c b/gst/gst.c index 8e6c542..2323d3f 100644 --- a/gst/gst.c +++ b/gst/gst.c @@ -767,6 +767,7 @@ init_post (GOptionContext * context, GOptionGroup * group, gpointer data, gst_buffer_list_iterator_get_type (); _gst_message_initialize (); _gst_tag_initialize (); + _gst_toc_initialize (); gst_parse_context_get_type (); _gst_plugin_initialize (); diff --git a/gst/gst.h b/gst/gst.h index 35ef290..afe276f 100644 --- a/gst/gst.h +++ b/gst/gst.h @@ -68,6 +68,7 @@ #include #include #include +#include #include #include #include diff --git a/gst/gst_private.h b/gst/gst_private.h index c36a72d..78937e6 100644 --- a/gst/gst_private.h +++ b/gst/gst_private.h @@ -54,6 +54,9 @@ extern const char g_log_domain_gstreamer[]; /* for GstElement */ #include "gstelement.h" +/* for GstToc */ +#include "gsttoc.h" + G_BEGIN_DECLS /* used by gstparse.c and grammar.y */ @@ -109,6 +112,17 @@ void _gst_query_initialize (void); void _gst_tag_initialize (void); void _gst_value_initialize (void); +void _gst_toc_initialize (void); + +/* TOC functions */ +/* These functions are used to parse TOC messages, events and queries */ +GstToc* _gst_toc_from_structure (const GstStructure *toc); +GstStructure* _gst_toc_to_structure (const GstToc *toc); +gboolean _gst_toc_structure_get_updated (const GstStructure * toc); +void _gst_toc_structure_set_updated (GstStructure * toc, gboolean updated); +gchar* _gst_toc_structure_get_extend_uid (const GstStructure * toc); +void _gst_toc_structure_set_extend_uid (GstStructure * toc, const gchar * extend_uid); + /* Private registry functions */ gboolean _priv_gst_registry_remove_cache_plugins (GstRegistry *registry); void _priv_gst_registry_cleanup (void); diff --git a/gst/gsttoc.c b/gst/gsttoc.c new file mode 100644 index 0000000..2553d24 --- /dev/null +++ b/gst/gsttoc.c @@ -0,0 +1,1010 @@ +/* GStreamer + * (c) 2010, 2012 Alexander Saprykin + * + * gsttoc.c: GstToc initialization and parsing/creation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gsttoc + * @short_description: Generic table of contents support + * @see_also: #GstStructure, #GstEvent, #GstMessage, #GstQuery, #GstPad + * + * #GstToc functions are used to create/free #GstToc and #GstTocEntry structures. + * Also they are used to convert #GstToc into #GstStructure and vice versa. + * + * #GstToc lets you to inform other elements in pipeline or application that playing + * source has some kind of table of contents (TOC). These may be chapters, editions, + * angles or other types. For example: DVD chapters, Matroska chapters or cue sheet + * TOC. Such TOC will be useful for applications to display instead of just a + * playlist. + * + * Using TOC is very easy. Firstly, create #GstToc structure which represents root + * contents of the source. You can also attach TOC-specific tags to it. Then fill + * it with #GstTocEntry entries by appending them to #GstToc.entries #GstTocEntry.subentries + * lists. You should use GST_TOC_ENTRY_TYPE_CHAPTER for generic TOC entry and + * GST_TOC_ENTRY_TYPE_EDITION for the entries which are considered to be alternatives + * (like DVD angles, Matroska editions and so on). + * + * Note that root level of the TOC can contain only either editions or chapters. You + * should not mix them together at the same level. Otherwise you will get serialization + * /deserialization errors. Make sure that no one of the entries has negative start and + * stop values. + * + * Please, use #GstToc.info and #GstTocEntry.info fields in that way: create a #GstStructure, + * put all info related to your element there and put this structure into the info field under + * the name of your element. Some fields in the info structure can be used for internal purposes, + * so you should use it in the way described above to not to overwrite already existent fields. + * + * Use gst_event_new_toc() to create a new TOC #GstEvent, and gst_event_parse_toc() to + * parse received TOC event. Use gst_event_new_toc_select() to create a new TOC select #GstEvent, + * and gst_event_parse_toc_select() to parse received TOC select event. The same rule for + * the #GstMessage: gst_message_new_toc() to create new TOC #GstMessage, and + * gst_message_parse_toc() to parse received TOC message. Also you can create a new TOC query + * with gst_query_new_toc(), set it with gst_query_set_toc() and parse it with + * gst_query_parse_toc(). + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst_private.h" +#include "gstenumtypes.h" +#include "gsttaglist.h" +#include "gststructure.h" +#include "gstvalue.h" +#include "gsttoc.h" +#include "gstpad.h" + +#define GST_TOC_TOC_NAME "toc" +#define GST_TOC_ENTRY_NAME "entry" + +#define GST_TOC_TOC_UPDATED_FIELD "updated" +#define GST_TOC_TOC_EXTENDUID_FIELD "extenduid" +#define GST_TOC_INFO_FIELD "info" + +#define GST_TOC_ENTRY_UID_FIELD "uid" +#define GST_TOC_ENTRY_TYPE_FIELD "type" +#define GST_TOC_ENTRY_TAGS_FIELD "tags" + +#define GST_TOC_TOC_ENTRIES_FIELD "subentries" + +#define GST_TOC_INFO_NAME "info-structure" +#define GST_TOC_INFO_TIME_FIELD "time" + +#define GST_TOC_TIME_NAME "time-structure" +#define GST_TOC_TIME_START_FIELD "start" +#define GST_TOC_TIME_STOP_FIELD "stop" + + +enum +{ + GST_TOC_TOC = 0, + GST_TOC_ENTRY = 1, + GST_TOC_UPDATED = 2, + GST_TOC_EXTENDUID = 3, + GST_TOC_UID = 4, + GST_TOC_TYPE = 5, + GST_TOC_TAGS = 6, + GST_TOC_SUBENTRIES = 7, + GST_TOC_INFO = 8, + GST_TOC_INFONAME = 9, + GST_TOC_TIME = 10, + GST_TOC_TIMENAME = 11, + GST_TOC_TIME_START = 12, + GST_TOC_TIME_STOP = 13, + GST_TOC_LAST = 14 +}; + +static GQuark gst_toc_fields[GST_TOC_LAST] = { 0 }; + +void +_gst_toc_initialize (void) +{ + static gboolean inited = FALSE; + + if (G_LIKELY (!inited)) { + gst_toc_fields[GST_TOC_TOC] = g_quark_from_static_string (GST_TOC_TOC_NAME); + gst_toc_fields[GST_TOC_ENTRY] = + g_quark_from_static_string (GST_TOC_ENTRY_NAME); + + gst_toc_fields[GST_TOC_UPDATED] = + g_quark_from_static_string (GST_TOC_TOC_UPDATED_FIELD); + gst_toc_fields[GST_TOC_EXTENDUID] = + g_quark_from_static_string (GST_TOC_TOC_EXTENDUID_FIELD); + gst_toc_fields[GST_TOC_INFO] = + g_quark_from_static_string (GST_TOC_INFO_FIELD); + + gst_toc_fields[GST_TOC_UID] = + g_quark_from_static_string (GST_TOC_ENTRY_UID_FIELD); + gst_toc_fields[GST_TOC_TYPE] = + g_quark_from_static_string (GST_TOC_ENTRY_TYPE_FIELD); + gst_toc_fields[GST_TOC_TAGS] = + g_quark_from_static_string (GST_TOC_ENTRY_TAGS_FIELD); + + gst_toc_fields[GST_TOC_SUBENTRIES] = + g_quark_from_static_string (GST_TOC_TOC_ENTRIES_FIELD); + + gst_toc_fields[GST_TOC_INFONAME] = + g_quark_from_static_string (GST_TOC_INFO_NAME); + gst_toc_fields[GST_TOC_TIME] = + g_quark_from_static_string (GST_TOC_INFO_TIME_FIELD); + gst_toc_fields[GST_TOC_TIMENAME] = + g_quark_from_static_string (GST_TOC_TIME_NAME); + gst_toc_fields[GST_TOC_TIME_START] = + g_quark_from_static_string (GST_TOC_TIME_START_FIELD); + gst_toc_fields[GST_TOC_TIME_STOP] = + g_quark_from_static_string (GST_TOC_TIME_STOP_FIELD); + + inited = TRUE; + } +} + +/** + * gst_toc_new: + * + * Create new #GstToc structure. + * + * Returns: newly allocated #GstToc structure, free it with gst_toc_free(). + * + * Since: 0.10.37 + */ +GstToc * +gst_toc_new (void) +{ + GstToc *toc; + + toc = g_slice_new0 (GstToc); + toc->tags = gst_tag_list_new (); + toc->info = gst_structure_id_empty_new (gst_toc_fields[GST_TOC_INFONAME]); + + return toc; +} + +/** + * gst_toc_entry_new: + * @type: entry type. + * @uid: unique ID (UID) in the whole TOC. + * + * Create new #GstTocEntry structure. + * + * Returns: newly allocated #GstTocEntry structure, free it with gst_toc_entry_free(). + * + * Since: 0.10.37 + */ +GstTocEntry * +gst_toc_entry_new (GstTocEntryType type, const gchar * uid) +{ + GstTocEntry *entry; + + g_return_val_if_fail (uid != NULL, NULL); + + entry = g_slice_new0 (GstTocEntry); + entry->uid = g_strdup (uid); + entry->type = type; + entry->tags = gst_tag_list_new (); + entry->info = gst_structure_id_empty_new (gst_toc_fields[GST_TOC_INFONAME]); + + return entry; +} + +/** + * gst_toc_entry_new_with_pad: + * @type: entry type. + * @uid: unique ID (UID) in the whole TOC. + * @pad: #GstPad related to this entry. + * + * Create new #GstTocEntry structure with #GstPad related. + * + * Returns: newly allocated #GstTocEntry structure, free it with gst_toc_entry_free() + * when done. + * + * Since: 0.10.37 + */ +GstTocEntry * +gst_toc_entry_new_with_pad (GstTocEntryType type, const gchar * uid, + gpointer pad) +{ + GstTocEntry *entry; + + g_return_val_if_fail (uid != NULL, NULL); + + entry = g_slice_new0 (GstTocEntry); + entry->uid = g_strdup (uid); + entry->type = type; + entry->tags = gst_tag_list_new (); + + if (pad != NULL && GST_IS_PAD (pad)) + entry->pads = g_list_append (entry->pads, gst_object_ref (pad)); + + return entry; +} + +/** + * gst_toc_free: + * @toc: #GstToc structure to free. + * + * Free unused #GstToc structure. + * + * Since: 0.10.37 + */ +void +gst_toc_free (GstToc * toc) +{ + g_return_if_fail (toc != NULL); + + g_list_foreach (toc->entries, (GFunc) gst_toc_entry_free, NULL); + g_list_free (toc->entries); + + if (toc->tags != NULL) + gst_tag_list_free (toc->tags); + + if (toc->info != NULL) + gst_structure_free (toc->info); + + g_slice_free (GstToc, toc); +} + +/** + * gst_toc_entry_free: + * @entry: #GstTocEntry structure to free. + * + * Free unused #GstTocEntry structure. Note that #GstTocEntry.uid will + * be freed with g_free() and all #GstPad objects in the #GstTocEntry.pads + * list will be unrefed with gst_object_unref(). + * + * Since: 0.10.37 + */ +void +gst_toc_entry_free (GstTocEntry * entry) +{ + GList *cur; + + g_return_if_fail (entry != NULL); + + g_list_foreach (entry->subentries, (GFunc) gst_toc_entry_free, NULL); + g_list_free (entry->subentries); + + g_free (entry->uid); + + if (entry->tags != NULL) + gst_tag_list_free (entry->tags); + + if (entry->info != NULL) + gst_structure_free (entry->info); + + cur = entry->pads; + while (cur != NULL) { + if (GST_IS_PAD (cur->data)) + gst_object_unref (cur->data); + cur = cur->next; + } + + g_list_free (entry->pads); + + g_slice_free (GstTocEntry, entry); +} + +static GstStructure * +gst_toc_structure_new (GstTagList * tags, GstStructure * info) +{ + GstStructure *ret; + GValue val = { 0 }; + + ret = gst_structure_id_empty_new (gst_toc_fields[GST_TOC_TOC]); + + if (tags != NULL) { + g_value_init (&val, GST_TYPE_STRUCTURE); + gst_value_set_structure (&val, GST_STRUCTURE (tags)); + gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_TAGS], &val); + g_value_unset (&val); + } + + if (info != NULL) { + g_value_init (&val, GST_TYPE_STRUCTURE); + gst_value_set_structure (&val, info); + gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_INFO], &val); + g_value_unset (&val); + } + + return ret; +} + +static GstStructure * +gst_toc_entry_structure_new (GstTocEntryType type, const gchar * uid, + GstTagList * tags, GstStructure * info) +{ + GValue val = { 0 }; + GstStructure *ret; + + ret = gst_structure_id_empty_new (gst_toc_fields[GST_TOC_ENTRY]); + + gst_structure_id_set (ret, gst_toc_fields[GST_TOC_TYPE], + GST_TYPE_TOC_ENTRY_TYPE, type, NULL); + + g_value_init (&val, G_TYPE_STRING); + g_value_set_string (&val, uid); + gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_UID], &val); + g_value_unset (&val); + + if (tags != NULL) { + g_value_init (&val, GST_TYPE_STRUCTURE); + gst_value_set_structure (&val, GST_STRUCTURE (tags)); + gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_TAGS], &val); + g_value_unset (&val); + } + + if (info != NULL) { + g_value_init (&val, GST_TYPE_STRUCTURE); + gst_value_set_structure (&val, info); + gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_INFO], &val); + g_value_unset (&val); + } + + return ret; +} + +static guint +gst_toc_entry_structure_n_subentries (const GstStructure * entry) +{ + if (G_UNLIKELY (!gst_structure_id_has_field_typed (entry, + gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY))) + return 0; + else + return gst_value_array_get_size ((gst_structure_id_get_value (entry, + gst_toc_fields[GST_TOC_SUBENTRIES]))); +} + +static const GstStructure * +gst_toc_entry_structure_nth_subentry (const GstStructure * entry, guint nth) +{ + guint count; + const GValue *array; + + count = gst_toc_entry_structure_n_subentries (entry); + + if (count < nth) + return NULL; + + if (G_UNLIKELY (!gst_structure_id_has_field_typed (entry, + gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY))) + return NULL; + else { + array = + gst_value_array_get_value (gst_structure_id_get_value (entry, + gst_toc_fields[GST_TOC_SUBENTRIES]), nth); + return gst_value_get_structure (array); + } +} + +static GstTocEntry * +gst_toc_entry_from_structure (const GstStructure * entry, guint level) +{ + GstTocEntry *ret, *subentry; + const GValue *val; + const GstTagList *entry_tags; + const GstStructure *subentry_struct; + gint count, i; + const gchar *uid; + guint chapters_count = 0, editions_count = 0; + + g_return_val_if_fail (entry != NULL, NULL); + g_return_val_if_fail (gst_structure_id_has_field_typed (entry, + gst_toc_fields[GST_TOC_UID], G_TYPE_STRING), NULL); + g_return_val_if_fail (gst_structure_id_has_field_typed (entry, + gst_toc_fields[GST_TOC_TYPE], GST_TYPE_TOC_ENTRY_TYPE), NULL); + + val = gst_structure_id_get_value (entry, gst_toc_fields[GST_TOC_UID]); + uid = g_value_get_string (val); + + ret = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, uid); + + gst_structure_get_enum (entry, GST_TOC_ENTRY_TYPE_FIELD, + GST_TYPE_TOC_ENTRY_TYPE, (gint *) & (ret->type)); + + if (gst_structure_id_has_field_typed (entry, + gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY)) { + count = gst_toc_entry_structure_n_subentries (entry); + + for (i = 0; i < count; ++i) { + subentry_struct = gst_toc_entry_structure_nth_subentry (entry, i); + subentry = gst_toc_entry_from_structure (subentry_struct, level + 1); + + /* skip empty editions */ + if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION + && subentry->subentries == NULL)) { + g_warning + ("Empty edition found while deserializing TOC from GstStructure, skipping"); + continue; + } + + if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION) + ++editions_count; + else + ++chapters_count; + + /* check for mixed content */ + if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) { + g_critical + ("Mixed editions and chapters in the TOC contents, the TOC is broken"); + gst_toc_entry_free (subentry); + gst_toc_entry_free (ret); + return NULL; + } + + if (G_UNLIKELY (subentry == NULL)) { + gst_toc_entry_free (ret); + return NULL; + } + + ret->subentries = g_list_prepend (ret->subentries, subentry); + } + + ret->subentries = g_list_reverse (ret->subentries); + } + + if (gst_structure_id_has_field_typed (entry, + gst_toc_fields[GST_TOC_TAGS], GST_TYPE_STRUCTURE)) { + val = gst_structure_id_get_value (entry, gst_toc_fields[GST_TOC_TAGS]); + + if (G_LIKELY (GST_IS_TAG_LIST (gst_value_get_structure (val)))) { + entry_tags = GST_TAG_LIST (gst_value_get_structure (val)); + ret->tags = gst_tag_list_copy (entry_tags); + } + } + + if (gst_structure_id_has_field_typed (entry, + gst_toc_fields[GST_TOC_INFO], GST_TYPE_STRUCTURE)) { + val = gst_structure_id_get_value (entry, gst_toc_fields[GST_TOC_INFO]); + + if (G_LIKELY (GST_IS_STRUCTURE (gst_value_get_structure (val)))) + ret->info = gst_structure_copy (gst_value_get_structure (val)); + } + + return ret; +} + +GstToc * +_gst_toc_from_structure (const GstStructure * toc) +{ + GstToc *ret; + GstTocEntry *subentry; + const GstStructure *subentry_struct; + const GValue *val; + const GstTagList *entry_tags; + guint count, i; + guint editions_count = 0, chapters_count = 0; + + g_return_val_if_fail (toc != NULL, NULL); + + ret = gst_toc_new (); + + if (gst_structure_id_has_field_typed (toc, + gst_toc_fields[GST_TOC_SUBENTRIES], GST_TYPE_ARRAY)) { + count = gst_toc_entry_structure_n_subentries (toc); + + for (i = 0; i < count; ++i) { + subentry_struct = gst_toc_entry_structure_nth_subentry (toc, i); + subentry = gst_toc_entry_from_structure (subentry_struct, 0); + + /* skip empty editions */ + if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION + && subentry->subentries == NULL)) { + g_warning + ("Empty edition found while deserializing TOC from GstStructure, skipping"); + continue; + } + + /* check for success */ + if (G_UNLIKELY (subentry == NULL)) { + g_critical ("Couldn't serialize deserializing TOC from GstStructure"); + gst_toc_free (ret); + return NULL; + } + + if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION) + ++editions_count; + else + ++chapters_count; + + /* check for mixed content */ + if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) { + g_critical + ("Mixed editions and chapters in the TOC contents, the TOC is broken"); + gst_toc_entry_free (subentry); + gst_toc_free (ret); + return NULL; + } + + ret->entries = g_list_prepend (ret->entries, subentry); + } + + ret->entries = g_list_reverse (ret->entries); + } + + if (gst_structure_id_has_field_typed (toc, + gst_toc_fields[GST_TOC_TAGS], GST_TYPE_STRUCTURE)) { + val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_TAGS]); + + if (G_LIKELY (GST_IS_TAG_LIST (gst_value_get_structure (val)))) { + entry_tags = GST_TAG_LIST (gst_value_get_structure (val)); + ret->tags = gst_tag_list_copy (entry_tags); + } + } + + if (gst_structure_id_has_field_typed (toc, + gst_toc_fields[GST_TOC_INFO], GST_TYPE_STRUCTURE)) { + val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_INFO]); + + if (G_LIKELY (GST_IS_STRUCTURE (gst_value_get_structure (val)))) + ret->info = gst_structure_copy (gst_value_get_structure (val)); + } + + if (G_UNLIKELY (ret->entries == NULL)) { + gst_toc_free (ret); + return NULL; + } + + return ret; +} + +static GstStructure * +gst_toc_entry_to_structure (const GstTocEntry * entry, guint level) +{ + GstStructure *ret, *subentry_struct; + GstTocEntry *subentry; + GList *cur; + GValue subentries_val = { 0 }; + GValue entry_val = { 0 }; + guint chapters_count = 0, editions_count = 0; + + g_return_val_if_fail (entry != NULL, NULL); + + ret = + gst_toc_entry_structure_new (entry->type, entry->uid, entry->tags, + entry->info); + + g_value_init (&subentries_val, GST_TYPE_ARRAY); + g_value_init (&entry_val, GST_TYPE_STRUCTURE); + + cur = entry->subentries; + while (cur != NULL) { + subentry = cur->data; + + if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION) + ++editions_count; + else + ++chapters_count; + + /* check for mixed content */ + if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) { + g_critical + ("Mixed editions and chapters in the TOC contents, the TOC is broken"); + gst_structure_free (ret); + g_value_unset (&entry_val); + g_value_unset (&subentries_val); + return NULL; + } + + /* skip empty editions */ + if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION + && subentry->subentries == NULL)) { + g_warning + ("Empty edition found while serializing TOC to GstStructure, skipping"); + cur = cur->next; + continue; + } + + subentry_struct = gst_toc_entry_to_structure (subentry, level + 1); + + /* check for success */ + if (G_UNLIKELY (subentry_struct == NULL)) { + gst_structure_free (ret); + g_value_unset (&subentries_val); + g_value_unset (&entry_val); + return NULL; + } + + /* skip empty editions */ + if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION + && subentry->subentries == NULL)) { + g_warning + ("Empty edition found while serializing TOC to GstStructure, skipping"); + cur = cur->next; + continue; + } + + gst_value_set_structure (&entry_val, subentry_struct); + gst_value_array_append_value (&subentries_val, &entry_val); + gst_structure_free (subentry_struct); + + cur = cur->next; + } + + gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_SUBENTRIES], + &subentries_val); + + g_value_unset (&subentries_val); + g_value_unset (&entry_val); + return ret; +} + +GstStructure * +_gst_toc_to_structure (const GstToc * toc) +{ + GValue val = { 0 }; + GValue subentries_val = { 0 }; + GstStructure *ret, *subentry_struct; + GstTocEntry *subentry; + GList *cur; + guint editions_count = 0, chapters_count = 0; + + g_return_val_if_fail (toc != NULL, NULL); + g_return_val_if_fail (toc->entries != NULL, NULL); + + ret = gst_toc_structure_new (toc->tags, toc->info); + + g_value_init (&val, GST_TYPE_STRUCTURE); + g_value_init (&subentries_val, GST_TYPE_ARRAY); + cur = toc->entries; + + while (cur != NULL) { + subentry = cur->data; + + if (subentry->type == GST_TOC_ENTRY_TYPE_EDITION) + ++editions_count; + else + ++chapters_count; + + /* check for mixed content */ + if (G_UNLIKELY (chapters_count > 0 && editions_count > 0)) { + g_critical + ("Mixed editions and chapters in the TOC contents, the TOC is broken"); + gst_structure_free (ret); + g_value_unset (&val); + g_value_unset (&subentries_val); + return NULL; + } + + /* skip empty editions */ + if (G_UNLIKELY (subentry->type == GST_TOC_ENTRY_TYPE_EDITION + && subentry->subentries == NULL)) { + g_warning + ("Empty edition found while serializing TOC to GstStructure, skipping"); + cur = cur->next; + continue; + } + + subentry_struct = gst_toc_entry_to_structure (subentry, 0); + + /* check for success */ + if (G_UNLIKELY (subentry_struct == NULL)) { + g_critical ("Couldn't serialize TOC to GstStructure"); + gst_structure_free (ret); + g_value_unset (&val); + g_value_unset (&subentries_val); + return NULL; + } + + gst_value_set_structure (&val, subentry_struct); + gst_value_array_append_value (&subentries_val, &val); + gst_structure_free (subentry_struct); + + cur = cur->next; + } + + gst_structure_id_set_value (ret, gst_toc_fields[GST_TOC_SUBENTRIES], + &subentries_val); + + g_value_unset (&val); + g_value_unset (&subentries_val); + return ret; +} + +static gboolean +gst_toc_check_entry_for_uid (const GstTocEntry * entry, const gchar * uid) +{ + GList *cur; + + g_return_val_if_fail (entry != NULL, FALSE); + g_return_val_if_fail (uid != NULL, FALSE); + + if (g_strcmp0 (entry->uid, uid) == 0) + return TRUE; + + cur = entry->subentries; + while (cur != NULL) { + if (gst_toc_check_entry_for_uid (cur->data, uid)) + return TRUE; + cur = cur->next; + } + + return FALSE; +} + +/** + * gst_toc_find_entry: + * @toc: #GstToc to search in. + * @uid: UID to find #GstTocEntry with. + * + * Find #GstTocEntry with given @uid in the @toc. + * + * Returns: #GstTocEntry with specified @uid from the @toc, or NULL if not found. + * + * Since: 0.10.37 + */ +GstTocEntry * +gst_toc_find_entry (const GstToc * toc, const gchar * uid) +{ + GList *cur; + + g_return_val_if_fail (toc != NULL, NULL); + g_return_val_if_fail (uid != NULL, NULL); + + cur = toc->entries; + while (cur != NULL) { + if (gst_toc_check_entry_for_uid (cur->data, uid)) + return cur->data; + cur = cur->next; + } + + return NULL; +} + +/** + * gst_toc_entry_copy: + * @entry: #GstTocEntry to copy. + * + * Copy #GstTocEntry with all subentries (deep copy). + * + * Returns: newly allocated #GstTocEntry in case of success, NULL otherwise; + * free it when done with gst_toc_entry_free(). + * + * Since: 0.10.37 + */ +GstTocEntry * +gst_toc_entry_copy (const GstTocEntry * entry) +{ + GstTocEntry *ret, *sub; + GList *cur; + + g_return_val_if_fail (entry != NULL, NULL); + + ret = gst_toc_entry_new (entry->type, entry->uid); + + if (GST_IS_STRUCTURE (entry->info)) + ret->info = gst_structure_copy (entry->info); + + if (GST_IS_TAG_LIST (entry->tags)) + ret->tags = gst_tag_list_copy (entry->tags); + + cur = entry->pads; + while (cur != NULL) { + if (GST_IS_PAD (cur->data)) + ret->pads = g_list_prepend (ret->pads, gst_object_ref (cur->data)); + cur = cur->next; + } + ret->pads = g_list_reverse (ret->pads); + + cur = entry->subentries; + while (cur != NULL) { + sub = gst_toc_entry_copy (cur->data); + + if (sub != NULL) + ret->subentries = g_list_prepend (ret->subentries, sub); + + cur = cur->next; + } + ret->subentries = g_list_reverse (ret->subentries); + + return ret; +} + +/** + * gst_toc_copy: + * @toc: #GstToc to copy. + * + * Copy #GstToc with all subentries (deep copy). + * + * Returns: newly allocated #GstToc in case of success, NULL otherwise; + * free it when done with gst_toc_free(). + * + * Since: 0.10.37 + */ +GstToc * +gst_toc_copy (const GstToc * toc) +{ + GstToc *ret; + GstTocEntry *entry; + GList *cur; + + g_return_val_if_fail (toc != NULL, NULL); + + ret = gst_toc_new (); + + if (GST_IS_STRUCTURE (toc->info)) + ret->info = gst_structure_copy (toc->info); + + if (GST_IS_TAG_LIST (toc->tags)) + ret->tags = gst_tag_list_copy (toc->tags); + + cur = toc->entries; + while (cur != NULL) { + entry = gst_toc_entry_copy (cur->data); + + if (entry != NULL) + ret->entries = g_list_prepend (ret->entries, entry); + + cur = cur->next; + } + ret->entries = g_list_reverse (ret->entries); + + return ret; +} + +/** + * gst_toc_entry_set_start_stop: + * @entry: #GstTocEntry to set values. + * @start: start value to set. + * @stop: stop value to set. + * + * Set @start and @stop values for the @entry. + * + * Since: 0.10.37 + */ +void +gst_toc_entry_set_start_stop (GstTocEntry * entry, gint64 start, gint64 stop) +{ + const GValue *val; + GstStructure *structure = NULL; + + g_return_if_fail (entry != NULL); + g_return_if_fail (GST_IS_STRUCTURE (entry->info)); + + if (gst_structure_id_has_field_typed (entry->info, + gst_toc_fields[GST_TOC_TIME], GST_TYPE_STRUCTURE)) { + val = + gst_structure_id_get_value (entry->info, gst_toc_fields[GST_TOC_TIME]); + structure = gst_structure_copy (gst_value_get_structure (val)); + } + + if (structure == NULL) + structure = gst_structure_id_empty_new (gst_toc_fields[GST_TOC_TIMENAME]); + + gst_structure_id_set (structure, gst_toc_fields[GST_TOC_TIME_START], + G_TYPE_INT64, start, gst_toc_fields[GST_TOC_TIME_STOP], G_TYPE_INT64, + stop, NULL); + + gst_structure_id_set (entry->info, gst_toc_fields[GST_TOC_TIME], + GST_TYPE_STRUCTURE, structure, NULL); + + gst_structure_free (structure); +} + +/** + * gst_toc_entry_get_start_stop: + * @entry: #GstTocEntry to get values from. + * @start: (out): the storage for the start value, leave #NULL if not need. + * @stop: (out): the storage for the stop value, leave #NULL if not need. + * + * Get start and stop values from the @entry and write them into appropriate storages. + * + * Returns: TRUE if all non-NULL storage pointers were filled with appropriate values, + * FALSE otherwise. + * + * Since: 0.10.37 + */ +gboolean +gst_toc_entry_get_start_stop (const GstTocEntry * entry, gint64 * start, + gint64 * stop) +{ + gboolean ret = TRUE; + const GValue *val; + const GstStructure *structure; + + g_return_val_if_fail (entry != NULL, FALSE); + g_return_val_if_fail (GST_IS_STRUCTURE (entry->info), FALSE); + + if (!gst_structure_id_has_field_typed (entry->info, + gst_toc_fields[GST_TOC_TIME], GST_TYPE_STRUCTURE)) + return FALSE; + + val = gst_structure_id_get_value (entry->info, gst_toc_fields[GST_TOC_TIME]); + structure = gst_value_get_structure (val); + + if (start != NULL) { + if (gst_structure_id_has_field_typed (structure, + gst_toc_fields[GST_TOC_TIME_START], G_TYPE_INT64)) + *start = + g_value_get_int64 (gst_structure_id_get_value (structure, + gst_toc_fields[GST_TOC_TIME_START])); + else + ret = FALSE; + } + + if (stop != NULL) { + if (gst_structure_id_has_field_typed (structure, + gst_toc_fields[GST_TOC_TIME_STOP], G_TYPE_INT64)) + *stop = + g_value_get_int64 (gst_structure_id_get_value (structure, + gst_toc_fields[GST_TOC_TIME_STOP])); + else + ret = FALSE; + } + + return ret; +} + +gboolean +_gst_toc_structure_get_updated (const GstStructure * toc) +{ + const GValue *val; + + g_return_val_if_fail (GST_IS_STRUCTURE (toc), FALSE); + + if (G_LIKELY (gst_structure_id_has_field_typed (toc, + gst_toc_fields[GST_TOC_UPDATED], G_TYPE_BOOLEAN))) { + val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_UPDATED]); + return g_value_get_boolean (val); + } + + return FALSE; +} + +void +_gst_toc_structure_set_updated (GstStructure * toc, gboolean updated) +{ + GValue val = { 0 }; + + g_return_if_fail (toc != NULL); + + g_value_init (&val, G_TYPE_BOOLEAN); + g_value_set_boolean (&val, updated); + gst_structure_id_set_value (toc, gst_toc_fields[GST_TOC_UPDATED], &val); + g_value_unset (&val); +} + +gchar * +_gst_toc_structure_get_extend_uid (const GstStructure * toc) +{ + const GValue *val; + + g_return_val_if_fail (GST_IS_STRUCTURE (toc), NULL); + + if (G_LIKELY (gst_structure_id_has_field_typed (toc, + gst_toc_fields[GST_TOC_EXTENDUID], G_TYPE_STRING))) { + val = gst_structure_id_get_value (toc, gst_toc_fields[GST_TOC_EXTENDUID]); + return g_strdup (g_value_get_string (val)); + } + + return NULL; +} + +void +_gst_toc_structure_set_extend_uid (GstStructure * toc, const gchar * extend_uid) +{ + GValue val = { 0 }; + + g_return_if_fail (toc != NULL); + g_return_if_fail (extend_uid != NULL); + + g_value_init (&val, G_TYPE_STRING); + g_value_set_string (&val, extend_uid); + gst_structure_id_set_value (toc, gst_toc_fields[GST_TOC_EXTENDUID], &val); + g_value_unset (&val); +} diff --git a/gst/gsttoc.h b/gst/gsttoc.h new file mode 100644 index 0000000..a817efe --- /dev/null +++ b/gst/gsttoc.h @@ -0,0 +1,111 @@ +/* GStreamer + * (c) 2010, 2012 Alexander Saprykin + * + * gsttoc.h: generic TOC API declaration + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GST_TOC_H__ +#define __GST_TOC_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +typedef struct _GstTocEntry GstTocEntry; +typedef struct _GstToc GstToc; + +/** + * GstTocEntryType: + * @GST_TOC_ENTRY_TYPE_CHAPTER: a chapter type entry. + * @GST_TOC_ENTRY_TYPE_EDITION: an edition entry (angle or alternative in other terms). + * + * The different types of TOC entry. + */ +typedef enum { + GST_TOC_ENTRY_TYPE_CHAPTER = 0, + GST_TOC_ENTRY_TYPE_EDITION = 1 +} GstTocEntryType; + +/** + * GstTocEntry: + * @uid: unique (for a whole TOC) id of the entry. This value should be persistent and + * should not be changed while updating TOC. @uid should be handled as "opaque" value + * without meaning (e.g. applications should not assume the /editionX/chapterY/chapter/Z structure, + * other demuxers could do something else), it should help to track updates of certain entries. + * @type: #GstTocEntryType of this entry. + * @subentries: list of #GstTocEntry children. + * @pads: list of #GstPad objects, related to this #GstTocEntry. + * @tags: tags related to this entry. + * @info: extra information related to this entry. + * + * Definition of TOC entry structure. + */ +struct _GstTocEntry { + gchar *uid; + GstTocEntryType type; + GList *subentries; + GList *pads; + GstTagList *tags; + GstStructure *info; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +/* FIXME: pad member should be GstPad type, but that's + * impossible due to recursive includes */ + +/** + * GstToc: + * @entries: list of #GstTocEntry entries of the TOC. + * @tags: tags related to the whole TOC. + * @info: extra information related to the TOC. + * + * Definition of TOC structure. + */ +struct _GstToc { + GList *entries; + GstTagList *tags; + GstStructure *info; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +/* functions to create new structures */ +GstToc * gst_toc_new (void); +GstTocEntry * gst_toc_entry_new (GstTocEntryType type, const gchar *uid); +GstTocEntry * gst_toc_entry_new_with_pad (GstTocEntryType type, const gchar *uid, gpointer pad); + +/* functions to free structures */ +void gst_toc_entry_free (GstTocEntry *entry); +void gst_toc_free (GstToc *toc); + +GstTocEntry * gst_toc_find_entry (const GstToc *toc, const gchar *uid); +GstTocEntry * gst_toc_entry_copy (const GstTocEntry *entry); +GstToc * gst_toc_copy (const GstToc *toc); + +void gst_toc_entry_set_start_stop (GstTocEntry *entry, gint64 start, gint64 stop); +gboolean gst_toc_entry_get_start_stop (const GstTocEntry *entry, gint64 *start, gint64 *stop); + +G_END_DECLS + +#endif /* __GST_TOC_H__ */ + -- 2.7.4