diff options
author | Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> | 2016-06-05 23:32:50 +0200 |
---|---|---|
committer | Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> | 2016-06-05 23:36:01 +0200 |
commit | c26379435d2baa57505f928c7b0cd263a37fa36f (patch) | |
tree | 96d7932cdfde65c5a8b8415a2ddf069732c819c1 /sdk-basic-tutorial-media-information-gathering.md | |
parent | 6cdcdcd2d9fc6e12803d1a3151084fbf9e631819 (diff) |
Make naming consistent
Diffstat (limited to 'sdk-basic-tutorial-media-information-gathering.md')
-rw-r--r-- | sdk-basic-tutorial-media-information-gathering.md | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/sdk-basic-tutorial-media-information-gathering.md b/sdk-basic-tutorial-media-information-gathering.md new file mode 100644 index 0000000..04fa816 --- /dev/null +++ b/sdk-basic-tutorial-media-information-gathering.md @@ -0,0 +1,548 @@ +# Basic tutorial 9: Media information gathering + +# Goal + +Sometimes you might want to quickly find out what kind of media a file +(or URI) contains, or if you will be able to play the media at all. You +can build a pipeline, set it to run, and watch the bus messages, but +GStreamer has a utility that does just that for you. This tutorial +shows: + + - How to recover information regarding a URI + + - How to find out if a URI is playable + +# Introduction + +`GstDiscoverer` is a utility object found in the `pbutils` library +(Plug-in Base utilities) that accepts a URI or list of URIs, and returns +information about them. It can work in synchronous or asynchronous +modes. + +In synchronous mode, there is only a single function to call, +`gst_discoverer_discover_uri()`, which blocks until the information is +ready. Due to this blocking, it is usually less interesting for +GUI-based applications and the asynchronous mode is used, as described +in this tutorial. + +The recovered information includes codec descriptions, stream topology +(number of streams and sub-streams) and available metadata (like the +audio language). + +![](images/icons/grey_arrow_down.gif)As an example, this is the result +of discovering http://docs.gstreamer.com/media/sintel\_trailer-480p.webm +(Click to expand) + + Duration: 0:00:52.250000000 + Tags: + video codec: On2 VP8 + language code: en + container format: Matroska + application name: ffmpeg2theora-0.24 + encoder: Xiph.Org libVorbis I 20090709 + encoder version: 0 + audio codec: Vorbis + nominal bitrate: 80000 + bitrate: 80000 + Seekable: yes + Stream information: + container: WebM + audio: Vorbis + Tags: + language code: en + container format: Matroska + audio codec: Vorbis + application name: ffmpeg2theora-0.24 + encoder: Xiph.Org libVorbis I 20090709 + encoder version: 0 + nominal bitrate: 80000 + bitrate: 80000 + video: VP8 + Tags: + video codec: VP8 video + container format: Matroska + +The following code tries to discover the URI provided through the +command line, and outputs the retrieved information (If no URI is +provided it uses a default one). + +This is a simplified version of what the `gst-discoverer-1.0` tool does +([Basic tutorial 10: GStreamer +tools](Basic%2Btutorial%2B10%253A%2BGStreamer%2Btools.html)), which is +an application that only displays data, but does not perform any +playback. + +# The GStreamer Discoverer + +Copy this code into a text file named `basic-tutorial-9.c` (or find it +in the SDK installation). + +**basic-tutorial-9.c** + +``` lang=c +#include <string.h> +#include <gst/gst.h> +#include <gst/pbutils/pbutils.h> + +/* Structure to contain all our information, so we can pass it around */ +typedef struct _CustomData { + GstDiscoverer *discoverer; + GMainLoop *loop; +} CustomData; + +/* Print a tag in a human-readable format (name: value) */ +static void print_tag_foreach (const GstTagList *tags, const gchar *tag, gpointer user_data) { + GValue val = { 0, }; + gchar *str; + gint depth = GPOINTER_TO_INT (user_data); + + gst_tag_list_copy_value (&val, tags, tag); + + if (G_VALUE_HOLDS_STRING (&val)) + str = g_value_dup_string (&val); + else + str = gst_value_serialize (&val); + + g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str); + g_free (str); + + g_value_unset (&val); +} + +/* Print information regarding a stream */ +static void print_stream_info (GstDiscovererStreamInfo *info, gint depth) { + gchar *desc = NULL; + GstCaps *caps; + const GstTagList *tags; + + caps = gst_discoverer_stream_info_get_caps (info); + + if (caps) { + if (gst_caps_is_fixed (caps)) + desc = gst_pb_utils_get_codec_description (caps); + else + desc = gst_caps_to_string (caps); + gst_caps_unref (caps); + } + + g_print ("%*s%s: %s\n", 2 * depth, " ", gst_discoverer_stream_info_get_stream_type_nick (info), (desc ? desc : "")); + + if (desc) { + g_free (desc); + desc = NULL; + } + + tags = gst_discoverer_stream_info_get_tags (info); + if (tags) { + g_print ("%*sTags:\n", 2 * (depth + 1), " "); + gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (depth + 2)); + } +} + +/* Print information regarding a stream and its substreams, if any */ +static void print_topology (GstDiscovererStreamInfo *info, gint depth) { + GstDiscovererStreamInfo *next; + + if (!info) + return; + + print_stream_info (info, depth); + + next = gst_discoverer_stream_info_get_next (info); + if (next) { + print_topology (next, depth + 1); + gst_discoverer_stream_info_unref (next); + } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) { + GList *tmp, *streams; + + streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info)); + for (tmp = streams; tmp; tmp = tmp->next) { + GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data; + print_topology (tmpinf, depth + 1); + } + gst_discoverer_stream_info_list_free (streams); + } +} + +/* This function is called every time the discoverer has information regarding + * one of the URIs we provided.*/ +static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) { + GstDiscovererResult result; + const gchar *uri; + const GstTagList *tags; + GstDiscovererStreamInfo *sinfo; + + uri = gst_discoverer_info_get_uri (info); + result = gst_discoverer_info_get_result (info); + switch (result) { + case GST_DISCOVERER_URI_INVALID: + g_print ("Invalid URI '%s'\n", uri); + break; + case GST_DISCOVERER_ERROR: + g_print ("Discoverer error: %s\n", err->message); + break; + case GST_DISCOVERER_TIMEOUT: + g_print ("Timeout\n"); + break; + case GST_DISCOVERER_BUSY: + g_print ("Busy\n"); + break; + case GST_DISCOVERER_MISSING_PLUGINS:{ + const GstStructure *s; + gchar *str; + + s = gst_discoverer_info_get_misc (info); + str = gst_structure_to_string (s); + + g_print ("Missing plugins: %s\n", str); + g_free (str); + break; + } + case GST_DISCOVERER_OK: + g_print ("Discovered '%s'\n", uri); + break; + } + + if (result != GST_DISCOVERER_OK) { + g_printerr ("This URI cannot be played\n"); + return; + } + + /* If we got no error, show the retrieved information */ + + g_print ("\nDuration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_discoverer_info_get_duration (info))); + + tags = gst_discoverer_info_get_tags (info); + if (tags) { + g_print ("Tags:\n"); + gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1)); + } + + g_print ("Seekable: %s\n", (gst_discoverer_info_get_seekable (info) ? "yes" : "no")); + + g_print ("\n"); + + sinfo = gst_discoverer_info_get_stream_info (info); + if (!sinfo) + return; + + g_print ("Stream information:\n"); + + print_topology (sinfo, 1); + + gst_discoverer_stream_info_unref (sinfo); + + g_print ("\n"); +} + +/* This function is called when the discoverer has finished examining + * all the URIs we provided.*/ +static void on_finished_cb (GstDiscoverer *discoverer, CustomData *data) { + g_print ("Finished discovering\n"); + + g_main_loop_quit (data->loop); +} + +int main (int argc, char **argv) { + CustomData data; + GError *err = NULL; + gchar *uri = "http://docs.gstreamer.com/media/sintel_trailer-480p.webm"; + + /* if a URI was provided, use it instead of the default one */ + if (argc > 1) { + uri = argv[1]; + } + + /* Initialize cumstom data structure */ + memset (&data, 0, sizeof (data)); + + /* Initialize GStreamer */ + gst_init (&argc, &argv); + + g_print ("Discovering '%s'\n", uri); + + /* Instantiate the Discoverer */ + data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err); + if (!data.discoverer) { + g_print ("Error creating discoverer instance: %s\n", err->message); + g_clear_error (&err); + return -1; + } + + /* Connect to the interesting signals */ + g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data); + g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data); + + /* Start the discoverer process (nothing to do yet) */ + gst_discoverer_start (data.discoverer); + + /* Add a request to process asynchronously the URI passed through the command line */ + if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) { + g_print ("Failed to start discovering URI '%s'\n", uri); + g_object_unref (data.discoverer); + return -1; + } + + /* Create a GLib Main Loop and set it to run, so we can wait for the signals */ + data.loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (data.loop); + + /* Stop the discoverer process */ + gst_discoverer_stop (data.discoverer); + + /* Free resources */ + g_object_unref (data.discoverer); + g_main_loop_unref (data.loop); + + return 0; +} +``` + +<table> +<tbody> +<tr class="odd"> +<td><img src="images/icons/emoticons/information.png" width="16" height="16" /></td> +<td><div id="expander-2049220294" class="expand-container"> +<div id="expander-control-2049220294" class="expand-control"> +<span class="expand-control-icon"><img src="images/icons/grey_arrow_down.gif" class="expand-control-image" /></span><span class="expand-control-text">Need help? (Click to expand)</span> +</div> +<div id="expander-content-2049220294" class="expand-content"> +<p>If you need help to compile this code, refer to the <strong>Building the tutorials</strong> section for your platform: <a href="Installing%2Bon%2BLinux.html#InstallingonLinux-Build">Linux</a>, <a href="Installing%2Bon%2BMac%2BOS%2BX.html#InstallingonMacOSX-Build">Mac OS X</a> or <a href="Installing%2Bon%2BWindows.html#InstallingonWindows-Build">Windows</a>, or use this specific command on Linux:</p> +<div class="panel" style="border-width: 1px;"> +<div class="panelContent"> +<p><code>gcc basic-tutorial-9.c -o basic-tutorial-9 `pkg-config --cflags --libs gstreamer-pbutils-1.0 gstreamer-1.0`</code></p> +</div> +</div> +<p>If you need help to run this code, refer to the <strong>Running the tutorials</strong> section for your platform: <a href="Installing%2Bon%2BLinux.html#InstallingonLinux-Run">Linux</a>, <a href="Installing%2Bon%2BMac%2BOS%2BX.html#InstallingonMacOSX-Run">Mac OS X</a> or <a href="Installing%2Bon%2BWindows.html#InstallingonWindows-Run">Windows</a></p> +<p><span>This tutorial opens the URI passed as the first parameter in the command line (or a default URI if none is provided) and outputs information about it on the screen. If the media is located on the Internet, the application might take a bit to react depending on your connection speed.</span></p> +<p>Required libraries: <code>gstreamer-pbutils-1.0 gstreamer-1.0</code></p> +</div> +</div></td> +</tr> +</tbody> +</table> + +# Walkthrough + +These are the main steps to use the `GstDiscoverer`: + +``` lang=c +/* Instantiate the Discoverer */ +data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err); +if (!data.discoverer) { + g_print ("Error creating discoverer instance: %s\n", err->message); + g_clear_error (&err); + return -1; +} +``` + +`gst_discoverer_new()` creates a new Discoverer object. The first +parameter is the timeout per file, in nanoseconds (use the +`GST_SECOND` macro for simplicity). + +``` lang=c +/* Connect to the interesting signals */ +g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data); +g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data); +``` + +Connect to the interesting signals, as usual. We discuss them in the +snippet for their callbacks. + +``` lang=c +/* Start the discoverer process (nothing to do yet) */ +gst_discoverer_start (data.discoverer); +``` + +`gst_discoverer_start()` launches the discovering process, but we have +not provided any URI to discover yet. This is done +next: + +``` lang=c +/* Add a request to process asynchronously the URI passed through the command line */ +if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) { + g_print ("Failed to start discovering URI '%s'\n", uri); + g_object_unref (data.discoverer); + return -1; +} +``` + +`gst_discoverer_discover_uri_async()` enqueues the provided URI for +discovery. Multiple URIs can be enqueued with this function. As the +discovery process for each of them finishes, the registered callback +functions will be fired +up. + +``` lang=c +/* Create a GLib Main Loop and set it to run, so we can wait for the signals */ +data.loop = g_main_loop_new (NULL, FALSE); +g_main_loop_run (data.loop); +``` + +The usual GLib main loop is instantiated and executed. We will get out +of it when `g_main_loop_quit()` is called from the +`on_finished_cb` callback. + +``` lang=c +/* Stop the discoverer process */ +gst_discoverer_stop (data.discoverer); +``` + +Once we are done with the discoverer, we stop it with +`gst_discoverer_stop()` and unref it with `g_object_unref()`. + +Let's review now the callbacks we have +registered: + +``` lang=c +/* This function is called every time the discoverer has information regarding + * one of the URIs we provided.*/ +static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) { + GstDiscovererResult result; + const gchar *uri; + const GstTagList *tags; + GstDiscovererStreamInfo *sinfo; + + uri = gst_discoverer_info_get_uri (info); + result = gst_discoverer_info_get_result (info); +``` + +We got here because the Discoverer has finished working on one URI, and +provides us a `GstDiscovererInfo` structure with all the information. + +The first step is to retrieve the particular URI this call refers to (in +case we had multiple discover process running, which is not the case in +this example) with `gst_discoverer_info_get_uri()` and the discovery +result with `gst_discoverer_info_get_result()`. + +``` lang=c +switch (result) { + case GST_DISCOVERER_URI_INVALID: + g_print ("Invalid URI '%s'\n", uri); + break; + case GST_DISCOVERER_ERROR: + g_print ("Discoverer error: %s\n", err->message); + break; + case GST_DISCOVERER_TIMEOUT: + g_print ("Timeout\n"); + break; + case GST_DISCOVERER_BUSY: + g_print ("Busy\n"); + break; + case GST_DISCOVERER_MISSING_PLUGINS:{ + const GstStructure *s; + gchar *str; + + s = gst_discoverer_info_get_misc (info); + str = gst_structure_to_string (s); + + g_print ("Missing plugins: %s\n", str); + g_free (str); + break; + } + case GST_DISCOVERER_OK: + g_print ("Discovered '%s'\n", uri); + break; +} + +if (result != GST_DISCOVERER_OK) { + g_printerr ("This URI cannot be played\n"); + return; +} +``` + +As the code shows, any result other than `GST_DISCOVERER_OK` means that +there has been some kind of problem, and this URI cannot be played. The +reasons can vary, but the enum values are quite explicit +(`GST_DISCOVERER_BUSY` can only happen when in synchronous mode, which +is not used in this example). + +If no error happened, information can be retrieved from the +`GstDiscovererInfo` structure with the different +`gst_discoverer_info_get_*` methods (like, +`gst_discoverer_info_get_duration()`, for example). + +Bits of information which are made of lists, like tags and stream info, +needs some extra parsing: + +``` lang=c +tags = gst_discoverer_info_get_tags (info); +if (tags) { + g_print ("Tags:\n"); + gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1)); +} +``` + +Tags are metadata (labels) attached to the media. They can be examined +with `gst_tag_list_foreach()`, which will call `print_tag_foreach` for +each tag found (the list could also be traversed manually, for example, +or a specific tag could be searched for with +`gst_tag_list_get_string()`). The code for `print_tag_foreach` is pretty +much self-explicative. + +``` lang=c +sinfo = gst_discoverer_info_get_stream_info (info); +if (!sinfo) + return; + +g_print ("Stream information:\n"); + +print_topology (sinfo, 1); + +gst_discoverer_stream_info_unref (sinfo); +``` + +`gst_discoverer_info_get_stream_info()` returns +a `GstDiscovererStreamInfo` structure that is parsed in +the `print_topology` function, and then discarded +with `gst_discoverer_stream_info_unref()`. + +``` lang=c +/* Print information regarding a stream and its substreams, if any */ +static void print_topology (GstDiscovererStreamInfo *info, gint depth) { + GstDiscovererStreamInfo *next; + + if (!info) + return; + + print_stream_info (info, depth); + + next = gst_discoverer_stream_info_get_next (info); + if (next) { + print_topology (next, depth + 1); + gst_discoverer_stream_info_unref (next); + } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) { + GList *tmp, *streams; + + streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info)); + for (tmp = streams; tmp; tmp = tmp->next) { + GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data; + print_topology (tmpinf, depth + 1); + } + gst_discoverer_stream_info_list_free (streams); + } +} +``` + +The `print_stream_info` function's code is also pretty much +self-explicative: it prints the stream's capabilities and then the +associated caps, using `print_tag_foreach` too. + +Then, `print_topology` looks for the next element to display. If +`gst_discoverer_stream_info_get_next()` returns a non-NULL stream info, +it refers to our descendant and that should be displayed. Otherwise, if +we are a container, recursively call `print_topology` on each of our +children obatined with `gst_discoverer_container_info_get_streams()`. +Otherwise, we are a final stream, and do not need to recurse (This part +of the Discoverer API is admittedly a bit obscure). + +# Conclusion + +This tutorial has shown: + + - How to recover information regarding a URI using the `GstDiscoverer` + + - How to find out if a URI is playable by looking at the return code + obtained with `gst_discoverer_info_get_result()`. + +It has been a pleasure having you here, and see you soon\! |