From 63256d271df3e83fb46a989143b68f21171f3d8b Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 14 Mar 2012 20:01:51 +0400 Subject: 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 60d4b11a02..a9852de903 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 8e6c54280c..2323d3f64e 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 35ef29035d..afe276fe8e 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 c36a72d99d..78937e613e 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 0000000000..2553d24549 --- /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 0000000000..a817efecfe --- /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__ */ + -- cgit v1.2.3-70-g09d2 From 2cb82d42fb221034cf086cdbb60310c3dd495f7c Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 14 Mar 2012 20:40:32 +0400 Subject: Add new TOC and TOC select events --- gst/gstevent.c | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ gst/gstevent.h | 16 +++++++++ gst/gstquark.c | 2 +- gst/gstquark.h | 4 ++- 4 files changed, 131 insertions(+), 2 deletions(-) diff --git a/gst/gstevent.c b/gst/gstevent.c index 8c323aa148..498ef9574d 100644 --- a/gst/gstevent.c +++ b/gst/gstevent.c @@ -114,6 +114,7 @@ static GstEventQuarks event_quarks[] = { {GST_EVENT_EOS, "eos", 0}, {GST_EVENT_NEWSEGMENT, "newsegment", 0}, {GST_EVENT_TAG, "tag", 0}, + {GST_EVENT_TOC, "toc", 0}, {GST_EVENT_BUFFERSIZE, "buffersize", 0}, {GST_EVENT_SINK_MESSAGE, "sink-message", 0}, {GST_EVENT_QOS, "qos", 0}, @@ -121,6 +122,7 @@ static GstEventQuarks event_quarks[] = { {GST_EVENT_NAVIGATION, "navigation", 0}, {GST_EVENT_LATENCY, "latency", 0}, {GST_EVENT_STEP, "step", 0}, + {GST_EVENT_TOC_SELECT, "toc-select", 0}, {GST_EVENT_CUSTOM_UPSTREAM, "custom-upstream", 0}, {GST_EVENT_CUSTOM_DOWNSTREAM, "custom-downstream", 0}, {GST_EVENT_CUSTOM_DOWNSTREAM_OOB, "custom-downstream-oob", 0}, @@ -1303,3 +1305,112 @@ gst_event_parse_sink_message (GstEvent * event, GstMessage ** msg) GST_MESSAGE (gst_value_dup_mini_object (gst_structure_id_get_value (event->structure, GST_QUARK (MESSAGE)))); } + +/** + * gst_event_new_toc: + * @toc: #GstToc structure. + * @updated: whether @toc was updated or not. + * + * Generate a TOC event from the given @toc. The purpose of the TOC event is to + * inform elements that some kind of the TOC was found. + * + * Returns: a new #GstEvent. + * + * Since: 0.10.37 + */ +GstEvent * +gst_event_new_toc (GstToc * toc, gboolean updated) +{ + GstStructure *toc_struct; + + g_return_val_if_fail (toc != NULL, NULL); + + GST_CAT_INFO (GST_CAT_EVENT, "creating toc event"); + + toc_struct = _gst_toc_to_structure (toc); + + if (G_LIKELY (toc_struct != NULL)) { + _gst_toc_structure_set_updated (toc_struct, updated); + return gst_event_new_custom (GST_EVENT_TOC, toc_struct); + } else + return NULL; +} + +/** + * gst_event_parse_toc: + * @event: a TOC event. + * @toc: (out): pointer to #GstToc structure. + * @updated: (out): pointer to store TOC updated flag. + * + * Parse a TOC @event and store the results in the given @toc and @updated locations. + * + * Since: 0.10.37 + */ +void +gst_event_parse_toc (GstEvent * event, GstToc ** toc, gboolean * updated) +{ + const GstStructure *structure; + + g_return_if_fail (event != NULL); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_TOC); + g_return_if_fail (toc != NULL); + + structure = gst_event_get_structure (event); + *toc = _gst_toc_from_structure (structure); + + if (updated != NULL) + *updated = _gst_toc_structure_get_updated (structure); +} + +/** + * gst_event_new_toc_select: + * @uid: UID in the TOC to start playback from. + * + * Generate a TOC select event with the given @uid. The purpose of the + * TOC select event is to start playback based on the TOC's entry with the + * given @uid. + * + * Returns: a new #GstEvent. + * + * Since: 0.10.37 + */ +GstEvent * +gst_event_new_toc_select (const gchar * uid) +{ + GstStructure *structure; + + g_return_val_if_fail (uid != NULL, NULL); + + GST_CAT_INFO (GST_CAT_EVENT, "creating toc select event for UID: %s", uid); + + structure = gst_structure_id_new (GST_QUARK (EVENT_TOC_SELECT), + GST_QUARK (UID), G_TYPE_STRING, uid, NULL); + + return gst_event_new_custom (GST_EVENT_TOC_SELECT, structure); +} + +/** + * gst_event_parse_toc_select: + * @event: a TOC select event. + * @uid: (out): storage for the selection UID. + * + * Parse a TOC select @event and store the results in the given @uid location. + * + * Since: 0.10.37 + */ +void +gst_event_parse_toc_select (GstEvent * event, gchar ** uid) +{ + const GstStructure *structure; + const GValue *val; + + g_return_if_fail (event != NULL); + g_return_if_fail (GST_EVENT_TYPE (event) == GST_EVENT_TOC_SELECT); + + structure = gst_event_get_structure (event); + val = gst_structure_id_get_value (structure, GST_QUARK (UID)); + + if (uid != NULL) + *uid = g_strdup (g_value_get_string (val)); + +} diff --git a/gst/gstevent.h b/gst/gstevent.h index 20e8ef015e..2321be5f10 100644 --- a/gst/gstevent.h +++ b/gst/gstevent.h @@ -31,6 +31,7 @@ #include #include #include +#include G_BEGIN_DECLS @@ -94,6 +95,8 @@ typedef enum { * send messages that should be emitted in sync with * rendering. * Since: 0.10.26 + * @GST_EVENT_TOC: An event which indicates that a new table of contents (TOC) + was found or updated. Since: 0.10.37 * @GST_EVENT_QOS: A quality message. Used to indicate to upstream elements * that the downstream elements should adjust their processing * rate. @@ -106,6 +109,8 @@ typedef enum { * Since: 0.10.12 * @GST_EVENT_STEP: A request for stepping through the media. Sinks will usually * execute the step operation. Since: 0.10.24 + * @GST_EVENT_TOC_SELECT: A request for a new playback position based on TOC + * entry's UID. Since 0.10.37 * @GST_EVENT_CUSTOM_UPSTREAM: Upstream custom event * @GST_EVENT_CUSTOM_DOWNSTREAM: Downstream custom event that travels in the * data flow. @@ -134,12 +139,14 @@ typedef enum { GST_EVENT_TAG = GST_EVENT_MAKE_TYPE (7, FLAG(DOWNSTREAM) | FLAG(SERIALIZED)), GST_EVENT_BUFFERSIZE = GST_EVENT_MAKE_TYPE (8, FLAG(DOWNSTREAM) | FLAG(SERIALIZED)), GST_EVENT_SINK_MESSAGE = GST_EVENT_MAKE_TYPE (9, FLAG(DOWNSTREAM) | FLAG(SERIALIZED)), + GST_EVENT_TOC = GST_EVENT_MAKE_TYPE (10, FLAG(DOWNSTREAM) | FLAG(SERIALIZED)), /* upstream events */ GST_EVENT_QOS = GST_EVENT_MAKE_TYPE (15, FLAG(UPSTREAM)), GST_EVENT_SEEK = GST_EVENT_MAKE_TYPE (16, FLAG(UPSTREAM)), GST_EVENT_NAVIGATION = GST_EVENT_MAKE_TYPE (17, FLAG(UPSTREAM)), GST_EVENT_LATENCY = GST_EVENT_MAKE_TYPE (18, FLAG(UPSTREAM)), GST_EVENT_STEP = GST_EVENT_MAKE_TYPE (19, FLAG(UPSTREAM)), + GST_EVENT_TOC_SELECT = GST_EVENT_MAKE_TYPE (20, FLAG(UPSTREAM)), /* custom events start here */ GST_EVENT_CUSTOM_UPSTREAM = GST_EVENT_MAKE_TYPE (32, FLAG(UPSTREAM)), @@ -487,6 +494,11 @@ void gst_event_parse_new_segment_full (GstEvent *event, GstEvent* gst_event_new_tag (GstTagList *taglist) G_GNUC_MALLOC; void gst_event_parse_tag (GstEvent *event, GstTagList **taglist); +/* TOC event */ +GstEvent* gst_event_new_toc (GstToc *toc, gboolean updated); +void gst_event_parse_toc (GstEvent *event, GstToc **toc, gboolean *updated); + + /* buffer */ GstEvent * gst_event_new_buffer_size (GstFormat format, gint64 minsize, gint64 maxsize, gboolean async) G_GNUC_MALLOC; @@ -524,6 +536,10 @@ GstEvent* gst_event_new_step (GstFormat format, guint64 amoun void gst_event_parse_step (GstEvent *event, GstFormat *format, guint64 *amount, gdouble *rate, gboolean *flush, gboolean *intermediate); +/* TOC select event */ +GstEvent* gst_event_new_toc_select (const gchar *uid); +void gst_event_parse_toc_select (GstEvent *event, gchar **uid); + G_END_DECLS #endif /* __GST_EVENT_H__ */ diff --git a/gst/gstquark.c b/gst/gstquark.c index 91a201271d..f8b54da076 100644 --- a/gst/gstquark.c +++ b/gst/gstquark.c @@ -50,7 +50,7 @@ static const gchar *_quark_strings[] = { "intermediate", "GstMessageStepStart", "active", "eos", "sink-message", "message", "GstMessageQOS", "running-time", "stream-time", "jitter", "quality", "processed", "dropped", "buffering-ranges", "GstMessageProgress", - "code", "text", "percent", "timeout" + "code", "text", "percent", "timeout", "toc-select", "uid" }; GQuark _priv_gst_quark_table[GST_QUARK_MAX]; diff --git a/gst/gstquark.h b/gst/gstquark.h index 6e16ee5d7c..b3cfb459a4 100644 --- a/gst/gstquark.h +++ b/gst/gstquark.h @@ -132,8 +132,10 @@ typedef enum _GstQuarkId GST_QUARK_TEXT = 103, GST_QUARK_PERCENT = 104, GST_QUARK_TIMEOUT = 105, + GST_QUARK_EVENT_TOC_SELECT = 106, + GST_QUARK_UID = 107, - GST_QUARK_MAX = 106 + GST_QUARK_MAX = 108 } GstQuarkId; extern GQuark _priv_gst_quark_table[GST_QUARK_MAX]; -- cgit v1.2.3-70-g09d2 From af85bd8dbf8816b1c2be23450bd064e857ad1b0e Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 14 Mar 2012 20:41:48 +0400 Subject: Add new TOC message --- gst/gstmessage.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ gst/gstmessage.h | 7 +++++++ 2 files changed, 66 insertions(+) diff --git a/gst/gstmessage.c b/gst/gstmessage.c index 423ebc2d88..b7fec38495 100644 --- a/gst/gstmessage.c +++ b/gst/gstmessage.c @@ -113,6 +113,7 @@ static GstMessageQuarks message_quarks[] = { {GST_MESSAGE_STEP_START, "step-start", 0}, {GST_MESSAGE_QOS, "qos", 0}, {GST_MESSAGE_PROGRESS, "progress", 0}, + {GST_MESSAGE_TOC, "toc", 0}, {0, NULL, 0} }; @@ -2154,3 +2155,61 @@ gst_message_parse_progress (GstMessage * message, GstProgressType * type, GST_QUARK (CODE), G_TYPE_STRING, code, GST_QUARK (TEXT), G_TYPE_STRING, text, NULL); } + +/** + * gst_message_new_toc: + * @src: the object originating the message. + * @toc: #GstToc structure for the message. + * @updated: whether TOC was updated or not. + * + * Create a new TOC message. The message is posted by elements + * that discovered or updated a TOC. + * + * Returns: a new TOC message. + * + * MT safe. + * + * Since: 0.10.37 + */ +GstMessage * +gst_message_new_toc (GstObject * src, GstToc * toc, gboolean updated) +{ + GstStructure *toc_struct; + + g_return_val_if_fail (toc != NULL, NULL); + + toc_struct = _gst_toc_to_structure (toc); + + if (G_LIKELY (toc_struct != NULL)) { + _gst_toc_structure_set_updated (toc_struct, updated); + return gst_message_new_custom (GST_MESSAGE_TOC, src, toc_struct); + } else + return NULL; +} + +/** + * gst_message_parse_toc: + * @message: a valid #GstMessage of type GST_MESSAGE_TOC. + * @toc: (out): return location for the TOC. + * @updated: (out): return location for the updated flag. + * + * Extract the TOC from the #GstMessage. The TOC returned in the + * output argument is a copy; the caller must free it with + * gst_toc_free() when done. + * + * MT safe. + * + * Since: 0.10.37 + */ +void +gst_message_parse_toc (GstMessage * message, GstToc ** toc, gboolean * updated) +{ + g_return_if_fail (GST_IS_MESSAGE (message)); + g_return_if_fail (GST_MESSAGE_TYPE (message) == GST_MESSAGE_TOC); + g_return_if_fail (toc != NULL); + + *toc = _gst_toc_from_structure (message->structure); + + if (updated != NULL) + *updated = _gst_toc_structure_get_updated (message->structure); +} diff --git a/gst/gstmessage.h b/gst/gstmessage.h index f0fcc7a4d3..ae5fc19b59 100644 --- a/gst/gstmessage.h +++ b/gst/gstmessage.h @@ -89,6 +89,8 @@ typedef struct _GstMessageClass GstMessageClass; * @GST_MESSAGE_QOS: A buffer was dropped or an element changed its processing * strategy for Quality of Service reasons. Since: 0.10.29 * @GST_MESSAGE_PROGRESS: A progress message. Since: 0.10.33 + * @GST_MESSAGE_TOC: A new table of contents (TOC) was found or previously found TOC + * was updated. Since: 0.10.37 * @GST_MESSAGE_ANY: mask for all of the above messages. * * The different message types that are available. @@ -125,6 +127,7 @@ typedef enum GST_MESSAGE_STEP_START = (1 << 23), GST_MESSAGE_QOS = (1 << 24), GST_MESSAGE_PROGRESS = (1 << 25), + GST_MESSAGE_TOC = (1 << 26), GST_MESSAGE_ANY = ~0 } GstMessageType; @@ -133,6 +136,7 @@ typedef enum #include #include #include +#include /** * GST_MESSAGE_TRACE_NAME: @@ -522,6 +526,9 @@ GstMessage * gst_message_new_progress (GstObject * src, GstProgress void gst_message_parse_progress (GstMessage * message, GstProgressType * type, gchar ** code, gchar ** text); +/* TOC */ +GstMessage * gst_message_new_toc (GstObject *src, GstToc *toc, gboolean updated); +void gst_message_parse_toc (GstMessage *message, GstToc **toc, gboolean *updated); /* custom messages */ GstMessage * gst_message_new_custom (GstMessageType type, -- cgit v1.2.3-70-g09d2 From 105330784c357df741152b0d10e466216bf359e4 Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 14 Mar 2012 20:42:56 +0400 Subject: Add new TOC query --- gst/gstquery.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ gst/gstquery.h | 10 ++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/gst/gstquery.c b/gst/gstquery.c index d9c48f8b91..53b4cf9571 100644 --- a/gst/gstquery.c +++ b/gst/gstquery.c @@ -101,6 +101,7 @@ static GstQueryTypeDefinition standard_definitions[] = { {GST_QUERY_BUFFERING, "buffering", "Buffering status", 0}, {GST_QUERY_CUSTOM, "custom", "Custom query", 0}, {GST_QUERY_URI, "uri", "URI of the source or sink", 0}, + {GST_QUERY_TOC, "toc", "Full table of contents", 0}, {GST_QUERY_NONE, NULL, NULL, 0} }; @@ -1495,3 +1496,94 @@ gst_query_parse_uri (GstQuery * query, gchar ** uri) *uri = g_value_dup_string (gst_structure_id_get_value (query->structure, GST_QUARK (URI))); } + +/** + * gst_query_new_toc: + * + * Constructs a new query TOC query object. Use gst_query_unref() + * when done with it. A TOC query is used to query the full TOC with + * the UID marker for TOC extending (to insert some new entries). + * + * Returns: A #GstQuery. + * + * Since: 0.10.37 + */ +GstQuery * +gst_query_new_toc (void) +{ + GstQuery *query; + + query = gst_query_new (GST_QUERY_TOC, NULL); + + return query; +} + +/** + * gst_query_set_toc: + * @query: a #GstQuery with query type GST_QUERY_TOC. + * @toc: the GstToc to set. + * @extend_uid: UID which can be used for TOC extending (may be NULL), + * 0 means root TOC level. + * + * Answer a TOC query by setting appropriate #GstToc structure. + * + * Since: 0.10.37 + */ +void +gst_query_set_toc (GstQuery * query, GstToc * toc, const gchar * extend_uid) +{ + GstStructure *structure; + + g_return_if_fail (query != NULL); + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_TOC); + g_return_if_fail (toc != NULL); + + structure = _gst_toc_to_structure (toc); + + g_return_if_fail (structure != NULL); + + /* that shouldn't be happen in normal usage */ + if (query->structure != NULL) + gst_structure_free (query->structure); + + if (extend_uid != NULL) + _gst_toc_structure_set_extend_uid (structure, extend_uid); + + query->structure = structure; + gst_structure_set_parent_refcount (query->structure, + &(query->mini_object.refcount)); +} + +/** + * gst_query_parse_toc: + * @query: a #GstQuery. + * @toc: (out): the storage for the received TOC (may be NULL). + * @extend_uid: (out): the storage for the received extend UID marker (may be NULL), + * 0 means root TOC level. + * + * Parse a TOC query, writing the TOC into @toc as a newly + * allocated #GstToc and extend UID into @extend_uid, if the respective parameters + * are non-NULL. Use @extend_uid value to insert new entries into the TOC (@extend_uid will + * act as root entry for newly inserted entries). + * Free @toc with gst_toc_free() and @extend_uid with g_free() after usage. + * + * Since: 0.10.37 + */ +void +gst_query_parse_toc (GstQuery * query, GstToc ** toc, gchar ** extend_uid) +{ + const GstStructure *structure; + + g_return_if_fail (query != NULL); + g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_TOC); + + structure = gst_query_get_structure (query); + + g_return_if_fail (structure != NULL); + + if (toc != NULL) + *toc = _gst_toc_from_structure (structure); + + if (extend_uid != NULL) + *extend_uid = _gst_toc_structure_get_extend_uid (structure); +} diff --git a/gst/gstquery.h b/gst/gstquery.h index 2166537ef2..9a1a0f91c2 100644 --- a/gst/gstquery.h +++ b/gst/gstquery.h @@ -51,6 +51,8 @@ G_BEGIN_DECLS * @GST_QUERY_CUSTOM: a custom application or element defined query. Since * 0.10.22. * @GST_QUERY_URI: query the URI of the source or sink. Since 0.10.22. + * @GST_QUERY_TOC: query the full table of contents (TOC) with the marker + * for an entry which can be used to extend received TOC. Since 0.10.37. * * Standard predefined Query types */ @@ -69,7 +71,8 @@ typedef enum { GST_QUERY_FORMATS, GST_QUERY_BUFFERING, GST_QUERY_CUSTOM, - GST_QUERY_URI + GST_QUERY_URI, + GST_QUERY_TOC } GstQueryType; /** @@ -336,6 +339,11 @@ GstQuery * gst_query_new_uri (void) G_GNUC_MALLOC; void gst_query_parse_uri (GstQuery *query, gchar **uri); void gst_query_set_uri (GstQuery *query, const gchar *uri); +/* TOC query */ +GstQuery * gst_query_new_toc (void); +void gst_query_set_toc (GstQuery *query, GstToc *toc, const gchar *extend_uid); +void gst_query_parse_toc (GstQuery *query, GstToc **toc, gchar **extend_uid); + G_END_DECLS #endif /* __GST_QUERY_H__ */ -- cgit v1.2.3-70-g09d2 From 566025b9935ad591811550aef8a1db7069fdf93e Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 14 Mar 2012 20:45:35 +0400 Subject: Add new GstTocSetter interface --- gst/Makefile.am | 2 + gst/gst.h | 1 + gst/gsttocsetter.c | 362 +++++++++++++++++++++++++++++++++++++++++++++++++++++ gst/gsttocsetter.h | 67 ++++++++++ 4 files changed, 432 insertions(+) create mode 100644 gst/gsttocsetter.c create mode 100644 gst/gsttocsetter.h diff --git a/gst/Makefile.am b/gst/Makefile.am index a9852de903..0da40a0e45 100644 --- a/gst/Makefile.am +++ b/gst/Makefile.am @@ -99,6 +99,7 @@ libgstreamer_@GST_MAJORMINOR@_la_SOURCES = \ gsttask.c \ gsttaskpool.c \ gsttoc.c \ + gsttocsetter.c \ $(GST_TRACE_SRC) \ gsttypefind.c \ gsttypefindfactory.c \ @@ -190,6 +191,7 @@ gst_headers = \ gsttask.h \ gsttaskpool.h \ gsttoc.h \ + gsttocsetter.h \ gsttrace.h \ gsttypefind.h \ gsttypefindfactory.h \ diff --git a/gst/gst.h b/gst/gst.h index afe276fe8e..422cbfb317 100644 --- a/gst/gst.h +++ b/gst/gst.h @@ -69,6 +69,7 @@ #include #include #include +#include #include #include #include diff --git a/gst/gsttocsetter.c b/gst/gsttocsetter.c new file mode 100644 index 0000000000..bee9b34ff1 --- /dev/null +++ b/gst/gsttocsetter.c @@ -0,0 +1,362 @@ +/* GStreamer + * Copyright (C) 2010, 2012 Alexander Saprykin + * + * gsttocsetter.c: interface for TOC setting on elements + * + * 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:gsttocsetter + * @short_description: Element interface that allows setting and retrieval + * of the TOC + * + * Element interface that allows setting of the TOC. + * + * Elements that support some kind of chapters or editions (or tracks like in + * the FLAC cue sheet) will implement this interface. + * + * If you just want to retrieve the TOC in your application then all you + * need to do is watch for TOC messages on your pipeline's bus (or you can + * perform TOC query). This interface is only for setting TOC data, not for + * extracting it. To set TOC from the application, find proper tocsetter element + * and set TOC using gst_toc_setter_set_toc(). + * + * Elements implementing the #GstTocSetter interface can extend existing TOC + * by getting extend UID for that (you can use gst_toc_find_entry() to retrieve it) + * with any TOC entries received from downstream. + */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gst_private.h" +#include "gsttocsetter.h" +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_toc_interface_debug); +#define GST_CAT_DEFAULT tag_toc_interface_debug + +static GQuark gst_toc_key; + +typedef struct +{ + GstToc *toc; + GStaticMutex lock; +} GstTocData; + +GType +gst_toc_setter_get_type (void) +{ + static volatile gsize toc_setter_type = 0; + + if (g_once_init_enter (&toc_setter_type)) { + GType _type; + static const GTypeInfo toc_setter_info = { + sizeof (GstTocSetterIFace), /* class_size */ + NULL, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + 0, + 0, + NULL + }; + + GST_DEBUG_CATEGORY_INIT (gst_toc_interface_debug, "GstTocInterface", 0, + "interfaces for the TOC"); + + _type = g_type_register_static (G_TYPE_INTERFACE, "GstTocSetter", + &toc_setter_info, 0); + + g_type_interface_add_prerequisite (_type, GST_TYPE_ELEMENT); + + gst_toc_key = g_quark_from_static_string ("GST_TOC_SETTER"); + g_once_init_leave (&toc_setter_type, _type); + } + + return toc_setter_type; +} + +static void +gst_toc_data_free (gpointer p) +{ + GstTocData *data = (GstTocData *) p; + + if (data->toc) + gst_toc_free (data->toc); + + g_static_mutex_free (&data->lock); + + g_slice_free (GstTocData, data); +} + +static GstTocData * +gst_toc_setter_get_data (GstTocSetter * setter) +{ + GstTocData *data; + + data = g_object_get_qdata (G_OBJECT (setter), gst_toc_key); + if (!data) { + static GStaticMutex create_mutex = G_STATIC_MUTEX_INIT; + + /* make sure no other thread is creating a GstTocData at the same time */ + g_static_mutex_lock (&create_mutex); + data = g_object_get_qdata (G_OBJECT (setter), gst_toc_key); + if (!data) { + data = g_slice_new (GstTocData); + g_static_mutex_init (&data->lock); + data->toc = NULL; + g_object_set_qdata_full (G_OBJECT (setter), gst_toc_key, data, + gst_toc_data_free); + } + g_static_mutex_unlock (&create_mutex); + } + + return data; +} + +/** + * gst_toc_setter_reset_toc: + * @setter: a #GstTocSetter. + * + * Reset the internal TOC. Elements should call this from within the + * state-change handler. + * + * Since: 0.10.37 + */ +void +gst_toc_setter_reset_toc (GstTocSetter * setter) +{ + GstTocData *data; + + g_return_if_fail (GST_IS_TOC_SETTER (setter)); + + data = gst_toc_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + if (data->toc) { + gst_toc_free (data->toc); + data->toc = NULL; + } + g_static_mutex_unlock (&data->lock); +} + +/** + * gst_toc_setter_get_toc: + * @setter: a #GstTocSetter. + * + * Return current TOC the setter uses. The TOC should not be + * modified or freed. + * + * This function is not thread-safe. Use gst_toc_setter_get_toc_copy() instead. + * + * Returns: a current snapshot of the TOC used in the setter + * or NULL if none is used. + * + * Since: 0.10.37 + */ +const GstToc * +gst_toc_setter_get_toc (GstTocSetter * setter) +{ + g_return_val_if_fail (GST_IS_TOC_SETTER (setter), NULL); + + return gst_toc_setter_get_data (setter)->toc; +} + +/** + * gst_toc_setter_get_toc_copy: + * @setter: a #GstTocSetter. + * + * Return current TOC the setter uses. The difference between this + * function and gst_toc_setter_get_toc() is that this function returns deep + * copy of the TOC, so you can modify it in any way. This function is thread-safe. + * Free it when done with gst_toc_free(). + * + * Returns: a copy of the current snapshot of the TOC used in the setter + * or NULL if none is used. + * + * Since: 0.10.37 + */ +GstToc * +gst_toc_setter_get_toc_copy (GstTocSetter * setter) +{ + GstTocData *data; + GstToc *ret = NULL; + + g_return_val_if_fail (GST_IS_TOC_SETTER (setter), NULL); + + data = gst_toc_setter_get_data (setter); + g_static_mutex_lock (&data->lock); + + if (data->toc != NULL) + ret = gst_toc_copy (data->toc); + + g_static_mutex_unlock (&data->lock); + + return ret; +} + +/** + * gst_toc_setter_set_toc: + * @setter: a #GstTocSetter. + * @toc: a #GstToc to set. + * + * Set the given TOC on the setter. Previously setted TOC will be + * freed before setting a new one. + * + * Since: 0.10.37 + */ +void +gst_toc_setter_set_toc (GstTocSetter * setter, const GstToc * toc) +{ + GstTocData *data; + + g_return_if_fail (GST_IS_TOC_SETTER (setter)); + + data = gst_toc_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + if (data->toc) + gst_toc_free (data->toc); + + data->toc = gst_toc_copy (toc); + + g_static_mutex_unlock (&data->lock); +} + +/** + * gst_toc_setter_get_toc_entry: + * @setter: a #GstTocSetter. + * @uid: UID to find entry with. + * + * Return #GstTocEntry (if any) with given @uid. Returned entry should + * not be modified or freed. + * + * This function is not thread-safe. Use gst_toc_setter_get_toc_entry_copy() instead. + * + * Returns: a TOC entry with given @uid from the TOC in the setter + * or NULL if none entry with such @uid was found. + * + * Since: 0.10.37 + */ +const GstTocEntry * +gst_toc_setter_get_toc_entry (GstTocSetter * setter, const gchar * uid) +{ + GstTocData *data; + const GstTocEntry *ret; + + g_return_val_if_fail (GST_IS_TOC_SETTER (setter), NULL); + g_return_val_if_fail (uid != NULL, NULL); + + data = gst_toc_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + + ret = gst_toc_find_entry (data->toc, uid); + + g_static_mutex_unlock (&data->lock); + + return ret; +} + +/** + * gst_toc_setter_get_toc_entry_copy: + * @setter: a #GstTocSetter. + * @uid: UID to find entry with. + * + * Return #GstTocEntry (if any) with given @uid. It perform a deep copying, + * so you can modify returned value. Free it when done with gst_toc_entry_free(). + * This function is thread-safe. + * + * Returns: a TOC entry with given @uid from the TOC in the setter + * or NULL if none entry with such @uid was found. + * + * Since: 0.10.37 + */ +GstTocEntry * +gst_toc_setter_get_toc_entry_copy (GstTocSetter * setter, const gchar * uid) +{ + GstTocData *data; + GstTocEntry *ret = NULL; + const GstTocEntry *search; + + g_return_val_if_fail (GST_IS_TOC_SETTER (setter), NULL); + g_return_val_if_fail (uid != NULL, NULL); + + data = gst_toc_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + + search = gst_toc_find_entry (data->toc, uid); + if (search != NULL) + ret = gst_toc_entry_copy (search); + + g_static_mutex_unlock (&data->lock); + + return ret; +} + +/** + * gst_toc_setter_add_toc_entry: + * @setter: a #GstTocSetter. + * @parent_uid: UID of the parent entry to append given @entry. Use 0 for the TOC root level. + * @entry: #GstTocEntry to append. + * + * Try to find entry with given @parent_uid and append an @entry to that #GstTocEntry. + * + * Returns: TRUE if entry with @parent_uid was found, FALSE otherwise. + * + * Since: 0.10.37 + */ +gboolean +gst_toc_setter_add_toc_entry (GstTocSetter * setter, const gchar * parent_uid, + const GstTocEntry * entry) +{ + GstTocData *data; + GstTocEntry *parent; + GstTocEntry *copy_entry; + gboolean ret = FALSE; + + g_return_val_if_fail (GST_IS_TOC_SETTER (setter), FALSE); + g_return_val_if_fail (parent_uid != NULL, FALSE); + g_return_val_if_fail (entry != NULL, FALSE); + + data = gst_toc_setter_get_data (setter); + + g_static_mutex_lock (&data->lock); + + copy_entry = gst_toc_entry_copy (entry); + + if (g_strcmp0 (parent_uid, "0") == 0) + data->toc->entries = g_list_append (data->toc->entries, copy_entry); + else { + parent = gst_toc_find_entry (data->toc, parent_uid); + + if (parent != NULL) { + parent->subentries = g_list_append (parent->subentries, copy_entry); + ret = TRUE; + } + } + + g_static_mutex_unlock (&data->lock); + + return ret; +} diff --git a/gst/gsttocsetter.h b/gst/gsttocsetter.h new file mode 100644 index 0000000000..2174e0d00f --- /dev/null +++ b/gst/gsttocsetter.h @@ -0,0 +1,67 @@ +/* GStreamer + * Copyright (C) 2010, 2012 Alexander Saprykin + * + * gsttocsetter.h: Interfaces for TOC + * + * 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_SETTER_H__ +#define __GST_TOC_SETTER_H__ + +#include + +G_BEGIN_DECLS +#define GST_TYPE_TOC_SETTER (gst_toc_setter_get_type ()) +#define GST_TOC_SETTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_TOC_SETTER, GstTocSetter)) +#define GST_IS_TOC_SETTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_TOC_SETTER)) +#define GST_TOC_SETTER_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GST_TYPE_TOC_SETTER, GstTocSetterIFace)) +/** + * GstTocSetter: + * + * Opaque #GstTocSetter data structure. + */ +typedef struct _GstTocSetter GstTocSetter; +typedef struct _GstTocSetterIFace GstTocSetterIFace; + +/** + * GstTocSetterIFace: + * @g_iface: parent interface type. + * + * #GstTocSetterIFace interface. + */ + +struct _GstTocSetterIFace +{ + GTypeInterface g_iface; + + /* signals */ + + /* virtual table */ +}; + +GType gst_toc_setter_get_type (void); +void gst_toc_setter_reset_toc (GstTocSetter *setter); +const GstToc * gst_toc_setter_get_toc (GstTocSetter *setter); +GstToc * gst_toc_setter_get_toc_copy (GstTocSetter *setter); +void gst_toc_setter_set_toc (GstTocSetter *setter, const GstToc *toc); +const GstTocEntry * gst_toc_setter_get_toc_entry (GstTocSetter *setter, const gchar *uid); +GstTocEntry * gst_toc_setter_get_toc_entry_copy (GstTocSetter *setter, const gchar *uid); +gboolean gst_toc_setter_add_toc_entry (GstTocSetter *setter, const gchar *parent_uid, const GstTocEntry *entry); + +G_END_DECLS +#endif /* __GST_TOC_SETTER_H__ */ + -- cgit v1.2.3-70-g09d2 From 7fd9bb5cd22df81bb89641fef44762bf434e3735 Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 14 Mar 2012 21:12:22 +0400 Subject: gstchecks: Add unit test for the GstToc --- tests/check/Makefile.am | 3 +- tests/check/gst/gsttoc.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 tests/check/gst/gsttoc.c diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index c2eadcf237..efcacb2932 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -63,7 +63,7 @@ REGISTRY_CHECKS = \ gst/gstevent \ gst/gstghostpad \ gst/gstindex \ - gst/gstinterface \ + gst/gstinterface \ gst/gstplugin \ gst/gstpreset \ gst/gstquery \ @@ -118,6 +118,7 @@ check_PROGRAMS = \ gst/gsttag \ gst/gsttagsetter \ gst/gsttask \ + gst/gsttoc \ gst/gstvalue \ generic/states \ $(PARSE_CHECKS) \ diff --git a/tests/check/gst/gsttoc.c b/tests/check/gst/gsttoc.c new file mode 100644 index 0000000000..ee0b429615 --- /dev/null +++ b/tests/check/gst/gsttoc.c @@ -0,0 +1,338 @@ +/* GStreamer + * + * unit test for GstToc + * + * Copyright (C) 2010, 2012 Alexander Saprykin + * + * 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. + */ + +/* ------- TOC ------- + * / \ + * edition1 edition2 + * | | + * -chapter1 -chapter3 + * -chapter2 | + * -subchapter1 + */ + +#include + +#define ENTRY_ED1 "/edition1" +#define ENTRY_ED2 "/edition2" +#define ENTRY_ED3 "test-edition" + +#define ENTRY_CH1 "/edition1/chapter1" +#define ENTRY_CH2 "/edition1/chapter2" +#define ENTRY_CH3 "/edition2/chapter3" +#define ENTRY_CH4 "/test-chapter" + +#define ENTRY_SUB1 "/edition2/chapter3/subchapter1" + +#define ENTRY_TAG "EntryTag" +#define TOC_TAG "TocTag" + +#define TEST_UID "129537542" +#define INFO_NAME "info" +#define INFO_FIELD "info-test" +#define INFO_TEXT_EN "info-text-entry" +#define INFO_TEXT_TOC "info-text-toc" + +#define CHECK_TOC_ENTRY(entry_c,type_c,uid_c) \ +{ \ + gchar *tag_c; \ + const GValue *val; \ + \ + fail_unless_equals_string (entry_c->uid, uid_c); \ + fail_unless (entry_c->type == type_c); \ + fail_unless (entry_c->tags != NULL); \ + fail_unless (entry_c->pads == NULL); \ + \ + fail_unless (entry_c->info != NULL); \ + val = gst_structure_get_value (entry_c->info, INFO_FIELD); \ + fail_unless (val != NULL); \ + fail_unless_equals_string (g_value_get_string (val), INFO_TEXT_EN); \ + \ + fail_unless (gst_tag_list_get_string (entry_c->tags, \ + GST_TAG_TITLE, &tag_c)); \ + fail_unless_equals_string (tag_c, ENTRY_TAG); \ +} + +#define CHECK_TOC(toc_t) \ +{ \ + GstTocEntry *entry_t, *subentry_t; \ + gchar *tag_t; \ + const GValue *val; \ + /* check TOC */ \ + fail_unless (g_list_length (toc_t->entries) == 2); \ + fail_unless (toc_t->tags != NULL); \ + fail_unless (gst_tag_list_get_string (toc_t->tags, \ + GST_TAG_TITLE, &tag_t)); \ + fail_unless_equals_string (tag_t, TOC_TAG); \ + \ + fail_unless (toc_t->info != NULL); \ + val = gst_structure_get_value (toc_t->info, INFO_FIELD); \ + fail_unless (val != NULL); \ + fail_unless_equals_string (g_value_get_string (val), INFO_TEXT_TOC); \ + \ + /* check edition1 */ \ + entry_t = g_list_nth_data (toc_t->entries, 0); \ + fail_if (entry_t == NULL); \ + fail_unless (g_list_length (entry_t->subentries) == 2); \ + CHECK_TOC_ENTRY (entry_t, GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED1); \ + /* check chapter1 */ \ + subentry_t = g_list_nth_data (entry_t->subentries, 0); \ + fail_if (subentry_t == NULL); \ + fail_unless (g_list_length (subentry_t->subentries) == 0); \ + CHECK_TOC_ENTRY (subentry_t, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH1); \ + /* check chapter2 */ \ + subentry_t = g_list_nth_data (entry_t->subentries, 1); \ + fail_if (subentry_t == NULL); \ + fail_unless (g_list_length (subentry_t->subentries) == 0); \ + CHECK_TOC_ENTRY (subentry_t, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH2); \ + /* check edition2 */ \ + entry_t = g_list_nth_data (toc_t->entries, 1); \ + fail_if (entry_t == NULL); \ + fail_unless (g_list_length (entry_t->subentries) == 1); \ + CHECK_TOC_ENTRY (entry_t, GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED2); \ + /* check chapter3 */ \ + subentry_t = g_list_nth_data (entry_t->subentries, 0); \ + fail_if (subentry_t == NULL); \ + fail_unless (g_list_length (subentry_t->subentries) == 1); \ + CHECK_TOC_ENTRY (subentry_t, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH3); \ + /* check subchapter1 */ \ + subentry_t = g_list_nth_data (subentry_t->subentries, 0); \ + fail_if (subentry_t == NULL); \ + fail_unless (g_list_length (subentry_t->subentries) == 0); \ + CHECK_TOC_ENTRY (subentry_t, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_SUB1); \ +} + +GST_START_TEST (test_serializing) +{ + GstToc *toc, *test_toc = NULL; + GstTocEntry *ed, *ch, *subch; + GstEvent *event; + GstMessage *message; + GstQuery *query; + gboolean updated; + gchar *uid; + gint64 start = -1, stop = -1; + + toc = gst_toc_new (); + fail_if (toc == NULL); + gst_tag_list_add (toc->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + TOC_TAG, NULL); + toc->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_TOC, + NULL); + + /* create edition1 */ + ed = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED1); + fail_if (ed == NULL); + gst_tag_list_add (ed->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + ed->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + CHECK_TOC_ENTRY (ed, GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED1); + + /* append chapter1 to edition1 */ + ch = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH1); + fail_if (ch == NULL); + gst_tag_list_add (ch->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + ch->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + CHECK_TOC_ENTRY (ch, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH1); + + ed->subentries = g_list_append (ed->subentries, ch); + fail_unless (g_list_length (ed->subentries) == 1); + + /* append chapter2 to edition1 */ + ch = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH2); + fail_if (ch == NULL); + gst_tag_list_add (ch->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + ch->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + CHECK_TOC_ENTRY (ch, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH2); + + /* append edition1 to the TOC */ + toc->entries = g_list_append (toc->entries, ed); + fail_unless (g_list_length (toc->entries) == 1); + + /* test gst_toc_entry_find() */ + ed = NULL; + ed = gst_toc_find_entry (toc, ENTRY_ED1); + + fail_if (ed == NULL); + + ed->subentries = g_list_append (ed->subentries, ch); + fail_unless (g_list_length (ed->subentries) == 2); + + /* test info GstStructure */ + gst_toc_entry_set_start_stop (ch, 100, 1000); + fail_if (!gst_toc_entry_get_start_stop (ch, &start, &stop)); + fail_unless (start == 100); + fail_unless (stop == 1000); + + /* create edition2 */ + ed = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED2); + fail_if (ed == NULL); + gst_tag_list_add (ed->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + ed->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + CHECK_TOC_ENTRY (ed, GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED2); + + /* create chapter3 */ + ch = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH3); + fail_if (ch == NULL); + gst_tag_list_add (ch->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + ch->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + CHECK_TOC_ENTRY (ch, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH3); + + /* create subchapter1 */ + subch = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_SUB1); + fail_if (subch == NULL); + gst_tag_list_add (subch->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + subch->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + CHECK_TOC_ENTRY (subch, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_SUB1); + + /* append subchapter1 to chapter3 */ + ch->subentries = g_list_append (ch->subentries, subch); + fail_unless (g_list_length (ch->subentries) == 1); + + /* append chapter3 to edition2 */ + ed->subentries = g_list_append (ed->subentries, ch); + fail_unless (g_list_length (ed->subentries) == 1); + + /* finally append edition2 to the TOC */ + toc->entries = g_list_append (toc->entries, ed); + fail_unless (g_list_length (toc->entries) == 2); + + /* test gst_toc_copy() */ + test_toc = gst_toc_copy (toc); + fail_if (test_toc == NULL); + CHECK_TOC (test_toc); + gst_toc_free (test_toc); + test_toc = NULL; + + /* check TOC event handling */ + event = gst_event_new_toc (toc, TRUE); + fail_if (event == NULL); + fail_if (event->structure == NULL); + fail_unless (event->type == GST_EVENT_TOC); + ASSERT_MINI_OBJECT_REFCOUNT (GST_MINI_OBJECT (event), "GstEvent", 1); + + gst_event_parse_toc (event, &test_toc, &updated); + fail_unless (updated == TRUE); + fail_if (test_toc == NULL); + CHECK_TOC (test_toc); + gst_toc_free (test_toc); + gst_event_unref (event); + updated = FALSE; + test_toc = NULL; + + /* check TOC message handling */ + message = gst_message_new_toc (NULL, toc, TRUE); + fail_if (message == NULL); + fail_if (event->structure == NULL); + fail_unless (message->type == GST_MESSAGE_TOC); + ASSERT_MINI_OBJECT_REFCOUNT (GST_MINI_OBJECT (message), "GstMessage", 1); + + gst_message_parse_toc (message, &test_toc, &updated); + fail_unless (updated == TRUE); + fail_if (test_toc == NULL); + CHECK_TOC (test_toc); + gst_toc_free (test_toc); + gst_message_unref (message); + test_toc = NULL; + + /* check TOC select event handling */ + event = gst_event_new_toc_select (TEST_UID); + fail_if (event == NULL); + fail_if (event->structure == NULL); + fail_unless (event->type == GST_EVENT_TOC_SELECT); + ASSERT_MINI_OBJECT_REFCOUNT (GST_MINI_OBJECT (event), "GstEvent", 1); + + gst_event_parse_toc_select (event, &uid); + fail_unless_equals_string (uid, TEST_UID); + gst_event_unref (event); + g_free (uid); + + /* check TOC query handling */ + query = gst_query_new_toc (); + fail_if (query == NULL); + gst_query_set_toc (query, toc, TEST_UID); + fail_if (query->structure == NULL); + fail_unless (query->type == GST_QUERY_TOC); + ASSERT_MINI_OBJECT_REFCOUNT (GST_MINI_OBJECT (query), "GstQuery", 1); + + gst_query_parse_toc (query, &test_toc, &uid); + fail_unless_equals_string (uid, TEST_UID); + fail_if (test_toc == NULL); + CHECK_TOC (test_toc); + gst_toc_free (test_toc); + gst_query_unref (query); + g_free (uid); + + /* that's wrong code, we should fail */ + ch = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH4); + toc->entries = g_list_prepend (toc->entries, ch); + ASSERT_CRITICAL (message = gst_message_new_toc (NULL, toc, TRUE)); + + /* and yet another one */ + toc->entries = g_list_remove (toc->entries, ch); + gst_toc_entry_free (ch); + ed = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED3); + ch = (GstTocEntry *) (toc->entries->data); + ch->subentries = g_list_prepend (ch->subentries, ed); + ASSERT_WARNING (message = gst_message_new_toc (NULL, toc, TRUE)); + + gst_toc_free (toc); +} + +GST_END_TEST; + +static Suite * +gst_toc_suite (void) +{ + Suite *s = suite_create ("GstToc"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_serializing); + + return s; +} + +GST_CHECK_MAIN (gst_toc); -- cgit v1.2.3-70-g09d2 From 9b80982c534047e6eb97d59fa1981b6d99f4e942 Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 14 Mar 2012 21:13:22 +0400 Subject: gstchecks: Add unit test for the GstTocSetter --- tests/check/Makefile.am | 1 + tests/check/gst/gsttocsetter.c | 401 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 tests/check/gst/gsttocsetter.c diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index efcacb2932..c91bece8c8 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -119,6 +119,7 @@ check_PROGRAMS = \ gst/gsttagsetter \ gst/gsttask \ gst/gsttoc \ + gst/gsttocsetter \ gst/gstvalue \ generic/states \ $(PARSE_CHECKS) \ diff --git a/tests/check/gst/gsttocsetter.c b/tests/check/gst/gsttocsetter.c new file mode 100644 index 0000000000..7b44171a60 --- /dev/null +++ b/tests/check/gst/gsttocsetter.c @@ -0,0 +1,401 @@ +/* GStreamer GstTocSetter interface unit tests + * Copyright (C) 2010, 2012 Alexander Saprykin + * + * 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. + */ + +#include +#include +#include + +#define ENTRY_ED1 "/edition1" +#define ENTRY_ED2 "/edition2" +#define ENTRY_ED3 "test-edition" + +#define ENTRY_CH1 "/edition1/chapter1" +#define ENTRY_CH2 "/edition1/chapter2" +#define ENTRY_CH3 "/edition2/chapter3" +#define ENTRY_CH4 "/test-chapter" + +#define ENTRY_SUB1 "/edition2/chapter3/subchapter1" + +#define ENTRY_TAG "EntryTag" +#define TOC_TAG "TocTag" +#define INFO_NAME "info" +#define INFO_FIELD "info-test" +#define INFO_TEXT_EN "info-text-entry" +#define INFO_TEXT_TOC "info-text-toc" + +#define CHECK_TOC_ENTRY(entry_c,type_c,uid_c) \ +{ \ + gchar *tag_c; \ + const GValue *val; \ + \ + fail_unless_equals_string (entry_c->uid, uid_c); \ + fail_unless (entry_c->type == type_c); \ + fail_unless (entry_c->tags != NULL); \ + fail_unless (entry_c->pads == NULL); \ + \ + fail_unless (entry_c->info != NULL); \ + val = gst_structure_get_value (entry_c->info, INFO_FIELD); \ + fail_unless (val != NULL); \ + fail_unless_equals_string (g_value_get_string (val), INFO_TEXT_EN); \ + \ + fail_unless (gst_tag_list_get_string (entry_c->tags, \ + GST_TAG_TITLE, &tag_c)); \ + fail_unless_equals_string (tag_c, ENTRY_TAG); \ +} + +#define CHECK_TOC(toc_t) \ +{ \ + GstTocEntry *entry_t, *subentry_t; \ + gchar *tag_t; \ + const GValue *val; \ + /* check TOC */ \ + fail_unless (g_list_length (toc_t->entries) == 2); \ + fail_unless (toc_t->tags != NULL); \ + fail_unless (gst_tag_list_get_string (toc_t->tags, \ + GST_TAG_TITLE, &tag_t)); \ + fail_unless_equals_string (tag_t, TOC_TAG); \ + \ + fail_unless (toc_t->info != NULL); \ + val = gst_structure_get_value (toc_t->info, INFO_FIELD); \ + fail_unless (val != NULL); \ + fail_unless_equals_string (g_value_get_string (val), INFO_TEXT_TOC); \ + \ + /* check edition1 */ \ + entry_t = g_list_nth_data (toc_t->entries, 0); \ + fail_if (entry_t == NULL); \ + fail_unless (g_list_length (entry_t->subentries) == 2); \ + CHECK_TOC_ENTRY (entry_t, GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED1); \ + /* check chapter1 */ \ + subentry_t = g_list_nth_data (entry_t->subentries, 0); \ + fail_if (subentry_t == NULL); \ + fail_unless (g_list_length (subentry_t->subentries) == 0); \ + CHECK_TOC_ENTRY (subentry_t, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH1); \ + /* check chapter2 */ \ + subentry_t = g_list_nth_data (entry_t->subentries, 1); \ + fail_if (subentry_t == NULL); \ + fail_unless (g_list_length (subentry_t->subentries) == 0); \ + CHECK_TOC_ENTRY (subentry_t, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH2); \ + /* check edition2 */ \ + entry_t = g_list_nth_data (toc_t->entries, 1); \ + fail_if (entry_t == NULL); \ + fail_unless (g_list_length (entry_t->subentries) == 1); \ + CHECK_TOC_ENTRY (entry_t, GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED2); \ + /* check chapter3 */ \ + subentry_t = g_list_nth_data (entry_t->subentries, 0); \ + fail_if (subentry_t == NULL); \ + fail_unless (g_list_length (subentry_t->subentries) == 1); \ + CHECK_TOC_ENTRY (subentry_t, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH3); \ + /* check subchapter1 */ \ + subentry_t = g_list_nth_data (subentry_t->subentries, 0); \ + fail_if (subentry_t == NULL); \ + fail_unless (g_list_length (subentry_t->subentries) == 0); \ + CHECK_TOC_ENTRY (subentry_t, GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_SUB1); \ +} + +/* some minimal GstTocSetter object */ +#define GST_TYPE_DUMMY_ENC gst_dummy_enc_get_type() + +typedef GstElement GstDummyEnc; +typedef GstElementClass GstDummyEncClass; + +static void gst_dummy_enc_add_interfaces (GType enc_type); + +GType gst_dummy_enc_get_type (void); +GST_BOILERPLATE_FULL (GstDummyEnc, gst_dummy_enc, GstElement, + GST_TYPE_ELEMENT, gst_dummy_enc_add_interfaces); + +static void +gst_dummy_enc_add_interfaces (GType enc_type) +{ + static const GInterfaceInfo toc_setter_info = { NULL, NULL, NULL }; + + g_type_add_interface_static (enc_type, GST_TYPE_TOC_SETTER, &toc_setter_info); +} + +static void +gst_dummy_enc_base_init (gpointer g_class) +{ +} + +static void +gst_dummy_enc_class_init (GstDummyEncClass * klass) +{ +} + +static void +gst_dummy_enc_init (GstDummyEnc * enc, GstDummyEncClass * klass) +{ +} + +static GstToc * +create_toc (void) +{ + GstToc *toc; + GstTocEntry *ed, *ch, *subch; + + toc = gst_toc_new (); + gst_tag_list_add (toc->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + TOC_TAG, NULL); + toc->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_TOC, + NULL); + + /* create edition1 */ + ed = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED1); + gst_tag_list_add (ed->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + ed->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + /* append chapter1 to edition1 */ + ch = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH1); + gst_tag_list_add (ch->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + ch->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + ed->subentries = g_list_append (ed->subentries, ch); + + /* append chapter2 to edition1 */ + ch = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH2); + gst_tag_list_add (ch->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + ch->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + ed->subentries = g_list_append (ed->subentries, ch); + + /* append edition1 to the TOC */ + toc->entries = g_list_append (toc->entries, ed); + + /* create edition2 */ + ed = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, ENTRY_ED2); + gst_tag_list_add (ed->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + ed->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + /* create chapter3 */ + ch = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_CH3); + gst_tag_list_add (ch->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + ch->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + /* create subchapter1 */ + subch = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_CHAPTER, ENTRY_SUB1); + gst_tag_list_add (subch->tags, GST_TAG_MERGE_APPEND, GST_TAG_TITLE, + ENTRY_TAG, NULL); + subch->info = + gst_structure_new (INFO_NAME, INFO_FIELD, G_TYPE_STRING, INFO_TEXT_EN, + NULL); + + /* append subchapter1 to chapter3 */ + ch->subentries = g_list_append (ch->subentries, subch); + + /* append chapter3 to edition2 */ + ed->subentries = g_list_append (ed->subentries, ch); + + /* finally append edition2 to the TOC */ + toc->entries = g_list_append (toc->entries, ed); + + return toc; +} + +GST_START_TEST (test_set) +{ + GstToc *toc; + GstTocEntry *entry, *ed; + GstTocSetter *setter; + GstElement *enc; + + enc = g_object_new (GST_TYPE_DUMMY_ENC, NULL); + fail_unless (enc != NULL); + + setter = GST_TOC_SETTER (enc); + + toc = create_toc (); + fail_unless (toc != NULL); + + gst_toc_setter_set_toc (setter, toc); + + gst_toc_free (toc); + toc = gst_toc_setter_get_toc_copy (setter); + + CHECK_TOC (toc); + + /* test entry adding into the root TOC */ + entry = g_list_last (toc->entries)->data; + toc->entries = g_list_remove (toc->entries, entry); + + gst_toc_setter_set_toc (setter, toc); + gst_toc_setter_add_toc_entry (setter, "0", entry); + + gst_toc_free (toc); + toc = gst_toc_setter_get_toc_copy (setter); + + CHECK_TOC (toc); + + /* test entry adding into the arbitrary entry */ + entry = gst_toc_find_entry (toc, ENTRY_CH2); + fail_if (entry == NULL); + + ed = toc->entries->data; + ed->subentries = g_list_remove (ed->subentries, entry); + + gst_toc_setter_add_toc_entry (setter, ed->uid, entry); + + CHECK_TOC (toc); + + gst_toc_free (toc); + gst_toc_setter_reset_toc (setter); + toc = gst_toc_setter_get_toc_copy (setter); + + fail_unless (toc == NULL); + + g_object_unref (enc); +} + +GST_END_TEST static int spin_and_wait = 1; +static int threads_running = 0; + +#define THREADS_TEST_SECONDS 1.5 + +static gpointer +test_threads_thread_func1 (gpointer data) +{ + GstToc *toc; + GstTocSetter *setter = GST_TOC_SETTER (data); + GTimer *timer; + + toc = create_toc (); + timer = g_timer_new (); + + g_atomic_int_inc (&threads_running); + while (g_atomic_int_get (&spin_and_wait)) + g_usleep (0); + + GST_INFO ("Go!"); + g_timer_start (timer); + + while (g_timer_elapsed (timer, NULL) < THREADS_TEST_SECONDS) + gst_toc_setter_set_toc (setter, toc); + + gst_toc_free (toc); + g_timer_destroy (timer); + GST_INFO ("Done"); + + return NULL; +} + +static gpointer +test_threads_thread_func2 (gpointer data) +{ + GstToc *toc; + GstTocSetter *setter = GST_TOC_SETTER (data); + GTimer *timer; + + toc = create_toc (); + timer = g_timer_new (); + + g_atomic_int_inc (&threads_running); + while (g_atomic_int_get (&spin_and_wait)) + g_usleep (0); + + GST_INFO ("Go!"); + g_timer_start (timer); + + while (g_timer_elapsed (timer, NULL) < THREADS_TEST_SECONDS) + gst_toc_setter_set_toc (setter, toc); + + gst_toc_free (toc); + g_timer_destroy (timer); + GST_INFO ("Done"); + + return NULL; +} + +static gpointer +test_threads_thread_func3 (gpointer data) +{ + GstTocSetter *setter = GST_TOC_SETTER (data); + GTimer *timer; + + timer = g_timer_new (); + + g_atomic_int_inc (&threads_running); + while (g_atomic_int_get (&spin_and_wait)) + g_usleep (0); + + GST_INFO ("Go!"); + g_timer_start (timer); + + while (g_timer_elapsed (timer, NULL) < THREADS_TEST_SECONDS) { + gst_toc_setter_reset_toc (setter); + } + + g_timer_destroy (timer); + GST_INFO ("Done"); + + return NULL; +} + +GST_START_TEST (test_threads) +{ + GstTocSetter *setter; + GThread *threads[3]; + + setter = GST_TOC_SETTER (g_object_new (GST_TYPE_DUMMY_ENC, NULL)); + + spin_and_wait = TRUE; + threads[0] = g_thread_create (test_threads_thread_func1, setter, TRUE, NULL); + threads[1] = g_thread_create (test_threads_thread_func2, setter, TRUE, NULL); + threads[2] = g_thread_create (test_threads_thread_func3, setter, TRUE, NULL); + + while (g_atomic_int_get (&threads_running) < 3) + g_usleep (10); + + g_atomic_int_set (&spin_and_wait, FALSE); + + g_thread_join (threads[0]); + g_thread_join (threads[1]); + g_thread_join (threads[2]); + + g_object_unref (G_OBJECT (setter)); +} + +GST_END_TEST static Suite * +gst_toc_setter_suite (void) +{ + Suite *s = suite_create ("GstTocSetter"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_set); + tcase_add_test (tc_chain, test_threads); + + return s; +} + +GST_CHECK_MAIN (gst_toc_setter); -- cgit v1.2.3-70-g09d2 From dbe99015112d66b1452ae9b12db5675b6ece150d Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 14 Mar 2012 21:14:23 +0400 Subject: docs: Add GstToc and GstTocSetter sections with related functions --- docs/gst/gstreamer-docs.sgml | 2 ++ docs/gst/gstreamer-sections.txt | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/docs/gst/gstreamer-docs.sgml b/docs/gst/gstreamer-docs.sgml index ca969a141a..deda41e720 100644 --- a/docs/gst/gstreamer-docs.sgml +++ b/docs/gst/gstreamer-docs.sgml @@ -99,6 +99,8 @@ Windows. It is released under the GNU Library General Public License + + diff --git a/docs/gst/gstreamer-sections.txt b/docs/gst/gstreamer-sections.txt index 58be88de8d..c52d5f14c3 100644 --- a/docs/gst/gstreamer-sections.txt +++ b/docs/gst/gstreamer-sections.txt @@ -847,6 +847,12 @@ gst_event_parse_step gst_event_new_sink_message gst_event_parse_sink_message + +gst_event_new_toc +gst_event_parse_toc + +gst_event_new_toc_select +gst_event_parse_toc_select GstEventClass GST_EVENT @@ -1294,6 +1300,8 @@ gst_message_set_qos_stats gst_message_parse_qos gst_message_parse_qos_values gst_message_parse_qos_stats +gst_message_new_toc +gst_message_parse_toc GstStructureChangeType gst_message_new_structure_change @@ -2078,6 +2086,10 @@ gst_query_parse_nth_buffering_range gst_query_new_uri gst_query_parse_uri gst_query_set_uri + +gst_query_new_toc +gst_query_parse_toc +gst_query_set_toc GstQueryClass GST_QUERY @@ -2537,6 +2549,51 @@ gst_task_state_get_type +
+gsttoc +GstToc +GstToc +GstTocEntry +GstTocEntryType +gst_toc_entry_new +gst_toc_entry_new_with_pad +gst_toc_entry_free +gst_toc_new +gst_toc_free +gst_toc_entry_copy +gst_toc_copy +gst_toc_find_entry +gst_toc_entry_get_start_stop +gst_toc_entry_set_start_stop + +GST_TYPE_TOC_ENTRY_TYPE + +gst_toc_entry_type_get_type +
+ + +
+gsttocsetter +GstTocSetter +GstTocSetter +GstTocSetterIFace +gst_toc_setter_get_toc +gst_toc_setter_get_toc_copy +gst_toc_setter_reset_toc +gst_toc_setter_set_toc +gst_toc_setter_get_toc_entry +gst_toc_setter_get_toc_entry_copy +gst_toc_setter_add_toc_entry + +GST_IS_TOC_SETTER +GST_TOC_SETTER +GST_TOC_SETTER_GET_IFACE +GST_TYPE_TOC_SETTER + +gst_toc_setter_get_type +
+ +
gsttrace GstTrace -- cgit v1.2.3-70-g09d2 From 7d88015650ca0624a47c635b4cb5fb66ca5ad697 Mon Sep 17 00:00:00 2001 From: Alexander Saprykin Date: Wed, 28 Mar 2012 23:15:41 +0400 Subject: docs: add overview of GstToc usage --- docs/design/Makefile.am | 1 + docs/design/part-toc.txt | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 docs/design/part-toc.txt diff --git a/docs/design/Makefile.am b/docs/design/Makefile.am index b313c9d366..087a3b03a6 100644 --- a/docs/design/Makefile.am +++ b/docs/design/Makefile.am @@ -44,6 +44,7 @@ EXTRA_DIST = \ part-stream-status.txt \ part-streams.txt \ part-synchronisation.txt \ + part-toc.txt \ part-TODO.txt \ part-trickmodes.txt diff --git a/docs/design/part-toc.txt b/docs/design/part-toc.txt new file mode 100644 index 0000000000..73231dacf2 --- /dev/null +++ b/docs/design/part-toc.txt @@ -0,0 +1,89 @@ +Implementing GstToc support in GStreamer elements + +1. General info about GstToc structure + +GstToc introduces a general way to handle chapters within multimedia +formats. GstToc can be represented as tree structure with arbitrary +hierarchy. Tree item can be either of two types: chapter or edition. +Chapter acts like a part of the media data, for example audio track +in CUE sheet, or part of the movie. Edition acts like some kind of +alternative way to process media content, for example DVD angles. +GstToc has one limitation on tree structure: on the same level of +hierarchy there couldn't be items of different type, i.e. you shouldn't +have editions and chapters mixed together. Here is an example of right TOC: + + ------- TOC ------- + / \ + edition1 edition2 + | | + -chapter1 -chapter3 + -chapter2 + +Here are two editions, the first contains two chapters, and the second +has only one chapter. And here is an example of invalid TOC: + + ------- TOC ------- + / \ + edition1 chapter1 + | + -chapter1 + -chapter2 + +Here you have edition1 and chapter1 mixed on the same level of hierarchy, +and such TOC will be considered broken. + +GstToc has 'entries' field of GList type which consists of children items. +Each item is of type GstTocEntry. Also GstToc has list of tags and +GstStructure called 'info'. Please, use GstToc.info and GstTocEntry.info +fields this 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. + +Let's look at GstTocEntry a bit closer. One of the most important fields +is 'uid', which must be unique for each item within the TOC. This is used +to identify each item inside TOC, especially when element receives TOC +select event with UID to seek on. Field 'subentries' of type GList contains +children items of type GstTocEntry. Thus you can achieve arbitrary hierarchy +level. Field 'type' can be either GST_TOC_ENTRY_TYPE_CHAPTER or +GST_TOC_ENTRY_TYPE_EDITION which corresponds to chapter or edition type of +item respectively. Field 'pads' of type GList contains list of GStreamer +pads related to the item. It can be used for example to link a TOC with +specific pad. Field 'tags' is a list of tags related to the item. And field +'info' is similar to GstToc.info described above. + +So, a little more about managing GstToc. Use gst_toc_new() and gst_toc_free() +to create/free it. GstTocEntry can be created using gst_toc_entry_new() and +gst_toc_entry_new_with_pad(). The latter method used to create GstTocEntry +linked to particular pad. While building GstToc you can set start and stop +timestamps for each item using gst_toc_entry_set_start_stop(). +The best way to process already created GstToc is to recursively go through +the 'entries' and 'subentries' fields. + +2. Working with GstQuery + +GstQuery with GstToc can be created using gst_query_new_toc(). Use +gst_query_set_toc() to set TOC into the query and parse it with +gst_query_parse_toc(). The 'extend_uid' parameter (0 for root level) in two +last methods should be used for TOC extending: get GstTocEntry with +gst_toc_find_entry() by given UID and use it to add your own chapters/editions. +The common action on such query is to set TOC for it. + +3. Working with GstMessage + +GstMessage with GstToc can be created using gst_message_new_toc() and parsed +with gst_message_parse_toc(). The 'updated' parameter in these methods indicates +whether the TOC was just discovered (set to false) or TOC was already found and +have been updated (set to true). The common usage for such message is to post it +to pipeline in case you have discovered TOC data within your element. + +4. Working with GstEvent + +GstToc supports select event through GstEvent infrastructure. The idea is the +following: when you receive TOC select event, parse it with +gst_event_parse_toc_select() and seek stream (if it is not streamable) for +specified TOC UID (you can use gst_toc_find_entry() to find entry in TOC by UID). +To create TOC select event use gst_event_new_toc_select(). The common action on +such event is to seek to specified UID within your element. + -- cgit v1.2.3-70-g09d2 From ea9cc8c871eab95e6fdc268122c13d2706b3b63d Mon Sep 17 00:00:00 2001 From: Stefan Sauer Date: Thu, 22 Mar 2012 08:36:02 +0100 Subject: gst-launch: add -c, --toc to print the toc Print the nested chapter and edition structure of the chapters message. --- tools/gst-launch.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tools/gst-launch.c b/tools/gst-launch.c index f3f2d07900..8877015907 100644 --- a/tools/gst-launch.c +++ b/tools/gst-launch.c @@ -69,6 +69,7 @@ static GstElement *pipeline; static EventLoopResult caught_error = ELR_NO_ERROR; static gboolean quiet = FALSE; static gboolean tags = FALSE; +static gboolean toc = FALSE; static gboolean messages = FALSE; static gboolean is_live = FALSE; static gboolean waiting_eos = FALSE; @@ -507,6 +508,35 @@ print_tag (const GstTagList * list, const gchar * tag, gpointer unused) } } +static void +print_toc_entry (gpointer data, gpointer user_data) +{ + GstTocEntry *entry = (GstTocEntry *) data; + const guint max_indent = 40; + const gchar spc[max_indent + 1] = " "; + const gchar *entry_types[] = { "chapter", "edition" }; + guint indent = MIN (GPOINTER_TO_UINT (user_data), max_indent); + gint64 start, stop; + + gst_toc_entry_get_start_stop (entry, &start, &stop); + + PRINT ("%s%s:", &spc[max_indent - indent], entry_types[entry->type]); + if (GST_CLOCK_TIME_IS_VALID (start)) { + PRINT (" start: %" GST_TIME_FORMAT, GST_TIME_ARGS (start)); + } + if (GST_CLOCK_TIME_IS_VALID (stop)) { + PRINT (" stop: %" GST_TIME_FORMAT, GST_TIME_ARGS (stop)); + } + PRINT ("\n"); + indent += 2; + + /* TODO: print tags */ + + /* loop over sub-toc entries */ + g_list_foreach (entry->subentries, print_toc_entry, + GUINT_TO_POINTER (indent)); +} + #ifndef DISABLE_FAULT_HANDLER /* we only use sighandler here because the registers are not important */ static void @@ -709,6 +739,28 @@ event_loop (GstElement * pipeline, gboolean blocking, GstState target_state) gst_tag_list_free (tag_list); } break; + case GST_MESSAGE_TOC: + if (toc) { + GstToc *toc_msg; + gboolean updated; + + if (GST_IS_ELEMENT (GST_MESSAGE_SRC (message))) { + PRINT (_("FOUND TOC : found by element \"%s\".\n"), + GST_MESSAGE_SRC_NAME (message)); + } else if (GST_IS_OBJECT (GST_MESSAGE_SRC (message))) { + PRINT (_("FOUND TOC : found by object \"%s\".\n"), + GST_MESSAGE_SRC_NAME (message)); + } else { + PRINT (_("FOUND TOC\n")); + } + + gst_message_parse_toc (message, &toc_msg, &updated); + /* recursively loop over toc entries */ + g_list_foreach (toc_msg->entries, print_toc_entry, + GUINT_TO_POINTER (0)); + gst_toc_free (toc_msg); + } + break; case GST_MESSAGE_INFO:{ GError *gerror; gchar *debug; @@ -929,6 +981,8 @@ main (int argc, char *argv[]) GOptionEntry options[] = { {"tags", 't', 0, G_OPTION_ARG_NONE, &tags, N_("Output tags (also known as metadata)"), NULL}, + {"toc", 'c', 0, G_OPTION_ARG_NONE, &toc, + N_("Ouput TOC (chapters and editions)"), NULL}, {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, N_("Output status information and property notifications"), NULL}, {"quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, -- cgit v1.2.3-70-g09d2