summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Sauer <ensonic@users.sf.net>2012-04-02 21:15:09 +0200
committerStefan Sauer <ensonic@users.sf.net>2012-04-02 21:33:10 +0200
commit1074a4e99a473efd5ee690da9ecd797c55cec23a (patch)
treeb83054c4f32247615ed06aa34aef2c574446aad6
parentf3aad8b4306d786848af9c612a03c81bec30d25b (diff)
parentea9cc8c871eab95e6fdc268122c13d2706b3b63d (diff)
Merge branch '0.10'
Conflicts: docs/gst/gstreamer-sections.txt gst/Makefile.am gst/gst.c gst/gst.h gst/gstevent.c gst/gstevent.h gst/gstmessage.h gst/gstquark.c gst/gstquark.h gst/gstquery.c gst/gstquery.h tests/check/Makefile.am
-rw-r--r--docs/design/Makefile.am1
-rw-r--r--docs/design/part-toc.txt89
-rw-r--r--docs/gst/gstreamer-docs.sgml2
-rw-r--r--docs/gst/gstreamer-sections.txt56
-rw-r--r--gst/Makefile.am4
-rw-r--r--gst/gst.c1
-rw-r--r--gst/gst.h2
-rw-r--r--gst/gst_private.h13
-rw-r--r--gst/gstevent.c111
-rw-r--r--gst/gstevent.h15
-rw-r--r--gst/gstmessage.c59
-rw-r--r--gst/gstmessage.h7
-rw-r--r--gst/gstquark.c3
-rw-r--r--gst/gstquark.h5
-rw-r--r--gst/gstquery.c86
-rw-r--r--gst/gstquery.h11
-rw-r--r--gst/gsttoc.c1010
-rw-r--r--gst/gsttoc.h111
-rw-r--r--gst/gsttocsetter.c362
-rw-r--r--gst/gsttocsetter.h67
-rw-r--r--tests/check/Makefile.am2
-rw-r--r--tests/check/gst/gsttoc.c338
-rw-r--r--tests/check/gst/gsttocsetter.c401
-rw-r--r--tools/gst-launch.c54
24 files changed, 2807 insertions, 3 deletions
diff --git a/docs/design/Makefile.am b/docs/design/Makefile.am
index 64875c954e..60bd86bc6c 100644
--- a/docs/design/Makefile.am
+++ b/docs/design/Makefile.am
@@ -42,6 +42,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.
+
diff --git a/docs/gst/gstreamer-docs.sgml b/docs/gst/gstreamer-docs.sgml
index e4b1a273c7..46de797efe 100644
--- a/docs/gst/gstreamer-docs.sgml
+++ b/docs/gst/gstreamer-docs.sgml
@@ -101,6 +101,8 @@ Windows. It is released under the GNU Library General Public License
<xi:include href="xml/gsttagsetter.xml" />
<xi:include href="xml/gsttask.xml" />
<xi:include href="xml/gsttaskpool.xml" />
+ <xi:include href="xml/gsttoc.xml" />
+ <xi:include href="xml/gsttocsetter.xml" />
<xi:include href="xml/gsttypefind.xml" />
<xi:include href="xml/gsttypefindfactory.xml" />
<xi:include href="xml/gsturihandler.xml" />
diff --git a/docs/gst/gstreamer-sections.txt b/docs/gst/gstreamer-sections.txt
index 3ec4319ca4..1b943e821e 100644
--- a/docs/gst/gstreamer-sections.txt
+++ b/docs/gst/gstreamer-sections.txt
@@ -1005,6 +1005,12 @@ gst_event_parse_stream_config_setup_data
gst_event_add_stream_config_header
gst_event_get_n_stream_config_headers
gst_event_parse_nth_stream_config_header
+
+gst_event_new_toc
+gst_event_parse_toc
+
+gst_event_new_toc_select
+gst_event_parse_toc_select
<SUBSECTION Standard>
GstEventClass
GST_EVENT
@@ -1413,6 +1419,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
@@ -2225,6 +2233,9 @@ gst_query_has_scheduling_mode
gst_query_new_drain
+gst_query_new_toc
+gst_query_parse_toc
+gst_query_set_toc
<SUBSECTION Standard>
GstQueryClass
GST_QUERY
@@ -2674,6 +2685,51 @@ gst_task_state_get_type
<SECTION>
+<FILE>gsttoc</FILE>
+<TITLE>GstToc</TITLE>
+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
+<SUBSECTION Standard>
+GST_TYPE_TOC_ENTRY_TYPE
+<SUBSECTION Private>
+gst_toc_entry_type_get_type
+</SECTION>
+
+
+<SECTION>
+<FILE>gsttocsetter</FILE>
+<TITLE>GstTocSetter</TITLE>
+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
+<SUBSECTION Standard>
+GST_IS_TOC_SETTER
+GST_TOC_SETTER
+GST_TOC_SETTER_GET_IFACE
+GST_TYPE_TOC_SETTER
+<SUBSECTION Private>
+gst_toc_setter_get_type
+</SECTION>
+
+
+<SECTION>
<FILE>gsttypefind</FILE>
<TITLE>GstTypeFind</TITLE>
GstTypeFind
diff --git a/gst/Makefile.am b/gst/Makefile.am
index d691c87a1b..51d5acb63b 100644
--- a/gst/Makefile.am
+++ b/gst/Makefile.am
@@ -94,6 +94,8 @@ libgstreamer_@GST_MAJORMINOR@_la_SOURCES = \
gsttagsetter.c \
gsttask.c \
gsttaskpool.c \
+ gsttoc.c \
+ gsttocsetter.c \
$(GST_TRACE_SRC) \
gsttypefind.c \
gsttypefindfactory.c \
@@ -185,6 +187,8 @@ gst_headers = \
gsttagsetter.h \
gsttask.h \
gsttaskpool.h \
+ gsttoc.h \
+ gsttocsetter.h \
gsttypefind.h \
gsttypefindfactory.h \
gsturi.h \
diff --git a/gst/gst.c b/gst/gst.c
index 3165c9fae5..f536abc81c 100644
--- a/gst/gst.c
+++ b/gst/gst.c
@@ -741,6 +741,7 @@ init_post (GOptionContext * context, GOptionGroup * group, gpointer data,
_priv_gst_value_initialize ();
g_type_class_ref (gst_param_spec_fraction_get_type ());
_priv_gst_tag_initialize ();
+ _priv_gst_toc_initialize ();
gst_parse_context_get_type ();
_priv_gst_plugin_initialize ();
diff --git a/gst/gst.h b/gst/gst.h
index 14d360c3c0..3873b5e58f 100644
--- a/gst/gst.h
+++ b/gst/gst.h
@@ -74,6 +74,8 @@
#include <gst/gsttagsetter.h>
#include <gst/gsttask.h>
#include <gst/gsttaskpool.h>
+#include <gst/gsttoc.h>
+#include <gst/gsttocsetter.h>
#include <gst/gsttypefind.h>
#include <gst/gsttypefindfactory.h>
#include <gst/gsturi.h>
diff --git a/gst/gst_private.h b/gst/gst_private.h
index 0b2823f4bb..394ff885f6 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 */
@@ -110,6 +113,16 @@ void _priv_gst_sample_initialize (void);
void _priv_gst_tag_initialize (void);
void _priv_gst_value_initialize (void);
void _priv_gst_debug_init (void);
+void _priv_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);
diff --git a/gst/gstevent.c b/gst/gstevent.c
index a0b97f8a75..ae535221bd 100644
--- a/gst/gstevent.c
+++ b/gst/gstevent.c
@@ -113,6 +113,7 @@ static GstEventQuarks event_quarks[] = {
{GST_EVENT_STREAM_CONFIG, "stream-config", 0},
{GST_EVENT_SEGMENT, "segment", 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_EOS, "eos", 0},
@@ -124,6 +125,7 @@ static GstEventQuarks event_quarks[] = {
{GST_EVENT_LATENCY, "latency", 0},
{GST_EVENT_STEP, "step", 0},
{GST_EVENT_RECONFIGURE, "reconfigure", 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},
@@ -1610,3 +1612,112 @@ gst_event_new_stream_start (void)
{
return gst_event_new_custom (GST_EVENT_STREAM_START, NULL);
}
+
+/**
+ * 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 3a9d3f3122..b8609e992d 100644
--- a/gst/gstevent.h
+++ b/gst/gstevent.h
@@ -100,6 +100,8 @@ typedef enum {
* without a SEGMENT event.
* @GST_EVENT_SEGMENT_DONE: (unimplemented) Marks the end of a segment playback.
* @GST_EVENT_GAP: (unimplemented) Marks a gap in the datastream.
+ * @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.
@@ -114,6 +116,8 @@ typedef enum {
* execute the step operation. Since: 0.10.24
* @GST_EVENT_RECONFIGURE: A request for upstream renegotiating caps and reconfiguring.
* Since: 0.11.0
+ * @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.
@@ -148,6 +152,7 @@ typedef enum {
GST_EVENT_BUFFERSIZE = GST_EVENT_MAKE_TYPE (90, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)),
GST_EVENT_SINK_MESSAGE = GST_EVENT_MAKE_TYPE (100, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY) | FLAG(STICKY_MULTI)),
GST_EVENT_EOS = GST_EVENT_MAKE_TYPE (110, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY)),
+ GST_EVENT_TOC = GST_EVENT_MAKE_TYPE (120, FLAG(DOWNSTREAM) | FLAG(SERIALIZED) | FLAG(STICKY) | FLAG(STICKY_MULTI)),
/* non-sticky downstream serialized */
GST_EVENT_SEGMENT_DONE = GST_EVENT_MAKE_TYPE (150, FLAG(DOWNSTREAM) | FLAG(SERIALIZED)),
@@ -160,6 +165,7 @@ typedef enum {
GST_EVENT_LATENCY = GST_EVENT_MAKE_TYPE (220, FLAG(UPSTREAM)),
GST_EVENT_STEP = GST_EVENT_MAKE_TYPE (230, FLAG(UPSTREAM)),
GST_EVENT_RECONFIGURE = GST_EVENT_MAKE_TYPE (240, FLAG(UPSTREAM)),
+ GST_EVENT_TOC_SELECT = GST_EVENT_MAKE_TYPE (250, FLAG(UPSTREAM)),
/* custom events start here */
GST_EVENT_CUSTOM_UPSTREAM = GST_EVENT_MAKE_TYPE (270, FLAG(UPSTREAM)),
@@ -520,6 +526,11 @@ void gst_event_copy_segment (GstEvent *event, GstSegment *se
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;
@@ -561,6 +572,10 @@ void gst_event_parse_step (GstEvent *event, GstFormat *for
/* renegotiate event */
GstEvent* gst_event_new_reconfigure (void) G_GNUC_MALLOC;
+/* TOC select event */
+GstEvent* gst_event_new_toc_select (const gchar *uid) G_GNUC_MALLOC;
+void gst_event_parse_toc_select (GstEvent *event, gchar **uid);
+
G_END_DECLS
#endif /* __GST_EVENT_H__ */
diff --git a/gst/gstmessage.c b/gst/gstmessage.c
index 7db7570a56..5c1e50a56b 100644
--- a/gst/gstmessage.c
+++ b/gst/gstmessage.c
@@ -104,6 +104,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}
};
@@ -2171,3 +2172,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 4a84360879..aadb646eaa 100644
--- a/gst/gstmessage.h
+++ b/gst/gstmessage.h
@@ -88,6 +88,8 @@ typedef struct _GstMessage GstMessage;
* @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.
@@ -124,6 +126,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 <gst/gsttaglist.h>
#include <gst/gststructure.h>
#include <gst/gstquery.h>
+#include <gst/gsttoc.h>
#define GST_TYPE_MESSAGE (gst_message_get_type())
#define GST_IS_MESSAGE(obj) (GST_IS_MINI_OBJECT_TYPE (obj, GST_TYPE_MESSAGE))
@@ -545,6 +549,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);
G_END_DECLS
diff --git a/gst/gstquark.c b/gst/gstquark.c
index f7182ccfbe..b47e3c6741 100644
--- a/gst/gstquark.c
+++ b/gst/gstquark.c
@@ -56,7 +56,8 @@ static const gchar *_quark_strings[] = {
"GstEventReconfigure", "segment", "GstQueryScheduling", "pull-mode",
"allocator", "GstEventFlushStop", "options", "GstQueryAcceptCaps",
"result", "GstQueryCaps", "filter", "modes", "GstEventStreamConfig",
- "setup-data", "stream-headers", "GstEventGap", "GstQueryDrain", "params"
+ "setup-data", "stream-headers", "GstEventGap", "GstQueryDrain", "params",
+ "toc-select", "uid", "toc"
};
GQuark _priv_gst_quark_table[GST_QUARK_MAX];
diff --git a/gst/gstquark.h b/gst/gstquark.h
index 376a8cbb20..9f2306d2f7 100644
--- a/gst/gstquark.h
+++ b/gst/gstquark.h
@@ -164,7 +164,10 @@ typedef enum _GstQuarkId
GST_QUARK_EVENT_GAP = 135,
GST_QUARK_QUERY_DRAIN = 136,
GST_QUARK_PARAMS = 137,
- GST_QUARK_MAX = 138
+ GST_QUARK_EVENT_TOC_SELECT = 138,
+ GST_QUARK_UID = 139,
+ GST_QUARK_QUERY_TOC = 140,
+ GST_QUARK_MAX = 141
} GstQuarkId;
extern GQuark _priv_gst_quark_table[GST_QUARK_MAX];
diff --git a/gst/gstquery.c b/gst/gstquery.c
index f1ec4831d0..8e755cf5f4 100644
--- a/gst/gstquery.c
+++ b/gst/gstquery.c
@@ -110,6 +110,7 @@ static GstQueryQuarks query_quarks[] = {
{GST_QUERY_ACCEPT_CAPS, "accept-caps", 0},
{GST_QUERY_CAPS, "caps", 0},
{GST_QUERY_DRAIN, "drain", 0},
+ {GST_QUERY_TOC, "toc", 0},
{0, NULL, 0}
};
@@ -2320,3 +2321,88 @@ gst_query_new_drain (void)
return query;
}
+
+/**
+ * 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.
+ */
+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.
+ */
+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.
+ */
+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 81c3e6e0f8..c060ce05a0 100644
--- a/gst/gstquery.h
+++ b/gst/gstquery.h
@@ -33,6 +33,7 @@
#include <gst/gststructure.h>
#include <gst/gstformat.h>
#include <gst/gstpad.h>
+#include <gst/gsttoc.h>
G_BEGIN_DECLS
@@ -101,6 +102,8 @@ typedef enum {
* @GST_QUERY_ACCEPT_CAPS: the accept caps query
* @GST_QUERY_CAPS: the caps query
* @GST_QUERY_DRAIN: wait till all serialized data is consumed downstream
+ * @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
*/
@@ -124,7 +127,8 @@ typedef enum {
GST_QUERY_SCHEDULING = GST_QUERY_MAKE_TYPE (150, FLAG(UPSTREAM)),
GST_QUERY_ACCEPT_CAPS = GST_QUERY_MAKE_TYPE (160, FLAG(BOTH)),
GST_QUERY_CAPS = GST_QUERY_MAKE_TYPE (170, FLAG(BOTH)),
- GST_QUERY_DRAIN = GST_QUERY_MAKE_TYPE (180, FLAG(DOWNSTREAM) | FLAG(SERIALIZED))
+ GST_QUERY_DRAIN = GST_QUERY_MAKE_TYPE (180, FLAG(DOWNSTREAM) | FLAG(SERIALIZED)),
+ GST_QUERY_TOC = GST_QUERY_MAKE_TYPE (190, FLAG(BOTH))
} GstQueryType;
#undef FLAG
@@ -476,6 +480,11 @@ void gst_query_intersect_caps_result (GstQuery *query, GstCaps *fi
/* drain query */
GstQuery * gst_query_new_drain (void) G_GNUC_MALLOC;
+/* 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__ */
diff --git a/gst/gsttoc.c b/gst/gsttoc.c
new file mode 100644
index 0000000000..7753ae34c2
--- /dev/null
+++ b/gst/gsttoc.c
@@ -0,0 +1,1010 @@
+/* GStreamer
+ * (c) 2010, 2012 Alexander Saprykin <xelfium@gmail.com>
+ *
+ * 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
+_priv_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 <xelfium@gmail.com>
+ *
+ * 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 <gst/gstconfig.h>
+#include <gst/gsttaglist.h>
+#include <gst/gstformat.h>
+
+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__ */
+
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 <xelfium@gmail.com>
+ *
+ * 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 <gobject/gvaluecollector.h>
+#include <string.h>
+
+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 <xelfium@gmail.com>
+ *
+ * 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 <gst/gst.h>
+
+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__ */
+
diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am
index 821fe7e6ce..b65737a6ac 100644
--- a/tests/check/Makefile.am
+++ b/tests/check/Makefile.am
@@ -126,6 +126,8 @@ check_PROGRAMS = \
gst/gsttag \
gst/gsttagsetter \
gst/gsttask \
+ gst/gsttoc \
+ gst/gsttocsetter \
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 <xelfium@gmail.com>
+ *
+ * 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 <gst/check/gstcheck.h>
+
+#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);
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 <xelfium@gmail.com>
+ *
+ * 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 <gst/check/gstcheck.h>
+#include <gst/gst.h>
+#include <string.h>
+
+#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);
diff --git a/tools/gst-launch.c b/tools/gst-launch.c
index af0d4b5d1a..9a34139e43 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;
@@ -437,6 +438,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
@@ -612,6 +642,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;
@@ -832,6 +884,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,