Implement our own theme, yay!
[platform/upstream/gstreamer.git] / sdk-basic-tutorial-media-information-gathering.md
1 # Basic tutorial 9: Media information gathering
2
3 ## Goal
4
5 Sometimes you might want to quickly find out what kind of media a file
6 (or URI) contains, or if you will be able to play the media at all. You
7 can build a pipeline, set it to run, and watch the bus messages, but
8 GStreamer has a utility that does just that for you. This tutorial
9 shows:
10
11   - How to recover information regarding a URI
12
13   - How to find out if a URI is playable
14
15 ## Introduction
16
17 `GstDiscoverer` is a utility object found in the `pbutils` library
18 (Plug-in Base utilities) that accepts a URI or list of URIs, and returns
19 information about them. It can work in synchronous or asynchronous
20 modes.
21
22 In synchronous mode, there is only a single function to call,
23 `gst_discoverer_discover_uri()`, which blocks until the information is
24 ready. Due to this blocking, it is usually less interesting for
25 GUI-based applications and the asynchronous mode is used, as described
26 in this tutorial.
27
28 The recovered information includes codec descriptions, stream topology
29 (number of streams and sub-streams) and available metadata (like the
30 audio language).
31
32 As an example, this is the result
33 of discovering https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel\_trailer-480p.webm
34
35     Duration: 0:00:52.250000000
36     Tags:
37       video codec: On2 VP8
38       language code: en
39       container format: Matroska
40       application name: ffmpeg2theora-0.24
41       encoder: Xiph.Org libVorbis I 20090709
42       encoder version: 0
43       audio codec: Vorbis
44       nominal bitrate: 80000
45       bitrate: 80000
46     Seekable: yes
47     Stream information:
48       container: WebM
49         audio: Vorbis
50           Tags:
51             language code: en
52             container format: Matroska
53             audio codec: Vorbis
54             application name: ffmpeg2theora-0.24
55             encoder: Xiph.Org libVorbis I 20090709
56             encoder version: 0
57             nominal bitrate: 80000
58             bitrate: 80000
59         video: VP8
60           Tags:
61             video codec: VP8 video
62             container format: Matroska
63
64 The following code tries to discover the URI provided through the
65 command line, and outputs the retrieved information (If no URI is
66 provided it uses a default one).
67
68 This is a simplified version of what the `gst-discoverer-1.0` tool does
69 ([](sdk-basic-tutorial-gstreamer-tools.md)), which is
70 an application that only displays data, but does not perform any
71 playback.
72
73 ## The GStreamer Discoverer
74
75 Copy this code into a text file named `basic-tutorial-9.c` (or find it
76 in the SDK installation).
77
78 **basic-tutorial-9.c**
79
80 ``` c
81 #include <string.h>
82 #include <gst/gst.h>
83 #include <gst/pbutils/pbutils.h>
84
85 /* Structure to contain all our information, so we can pass it around */
86 typedef struct _CustomData {
87   GstDiscoverer *discoverer;
88   GMainLoop *loop;
89 } CustomData;
90
91 /* Print a tag in a human-readable format (name: value) */
92 static void print_tag_foreach (const GstTagList *tags, const gchar *tag, gpointer user_data) {
93   GValue val = { 0, };
94   gchar *str;
95   gint depth = GPOINTER_TO_INT (user_data);
96
97   gst_tag_list_copy_value (&val, tags, tag);
98
99   if (G_VALUE_HOLDS_STRING (&val))
100     str = g_value_dup_string (&val);
101   else
102     str = gst_value_serialize (&val);
103
104   g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);
105   g_free (str);
106
107   g_value_unset (&val);
108 }
109
110 /* Print information regarding a stream */
111 static void print_stream_info (GstDiscovererStreamInfo *info, gint depth) {
112   gchar *desc = NULL;
113   GstCaps *caps;
114   const GstTagList *tags;
115
116   caps = gst_discoverer_stream_info_get_caps (info);
117
118   if (caps) {
119     if (gst_caps_is_fixed (caps))
120       desc = gst_pb_utils_get_codec_description (caps);
121     else
122       desc = gst_caps_to_string (caps);
123     gst_caps_unref (caps);
124   }
125
126   g_print ("%*s%s: %s\n", 2 * depth, " ", gst_discoverer_stream_info_get_stream_type_nick (info), (desc ? desc : ""));
127
128   if (desc) {
129     g_free (desc);
130     desc = NULL;
131   }
132
133   tags = gst_discoverer_stream_info_get_tags (info);
134   if (tags) {
135     g_print ("%*sTags:\n", 2 * (depth + 1), " ");
136     gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (depth + 2));
137   }
138 }
139
140 /* Print information regarding a stream and its substreams, if any */
141 static void print_topology (GstDiscovererStreamInfo *info, gint depth) {
142   GstDiscovererStreamInfo *next;
143
144   if (!info)
145     return;
146
147   print_stream_info (info, depth);
148
149   next = gst_discoverer_stream_info_get_next (info);
150   if (next) {
151     print_topology (next, depth + 1);
152     gst_discoverer_stream_info_unref (next);
153   } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
154     GList *tmp, *streams;
155
156     streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info));
157     for (tmp = streams; tmp; tmp = tmp->next) {
158       GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
159       print_topology (tmpinf, depth + 1);
160     }
161     gst_discoverer_stream_info_list_free (streams);
162   }
163 }
164
165 /* This function is called every time the discoverer has information regarding
166  * one of the URIs we provided.*/
167 static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) {
168   GstDiscovererResult result;
169   const gchar *uri;
170   const GstTagList *tags;
171   GstDiscovererStreamInfo *sinfo;
172
173   uri = gst_discoverer_info_get_uri (info);
174   result = gst_discoverer_info_get_result (info);
175   switch (result) {
176     case GST_DISCOVERER_URI_INVALID:
177       g_print ("Invalid URI '%s'\n", uri);
178       break;
179     case GST_DISCOVERER_ERROR:
180       g_print ("Discoverer error: %s\n", err->message);
181       break;
182     case GST_DISCOVERER_TIMEOUT:
183       g_print ("Timeout\n");
184       break;
185     case GST_DISCOVERER_BUSY:
186       g_print ("Busy\n");
187       break;
188     case GST_DISCOVERER_MISSING_PLUGINS:{
189       const GstStructure *s;
190       gchar *str;
191
192       s = gst_discoverer_info_get_misc (info);
193       str = gst_structure_to_string (s);
194
195       g_print ("Missing plugins: %s\n", str);
196       g_free (str);
197       break;
198     }
199     case GST_DISCOVERER_OK:
200       g_print ("Discovered '%s'\n", uri);
201       break;
202   }
203
204   if (result != GST_DISCOVERER_OK) {
205     g_printerr ("This URI cannot be played\n");
206     return;
207   }
208
209   /* If we got no error, show the retrieved information */
210
211   g_print ("\nDuration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_discoverer_info_get_duration (info)));
212
213   tags = gst_discoverer_info_get_tags (info);
214   if (tags) {
215     g_print ("Tags:\n");
216     gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));
217   }
218
219   g_print ("Seekable: %s\n", (gst_discoverer_info_get_seekable (info) ? "yes" : "no"));
220
221   g_print ("\n");
222
223   sinfo = gst_discoverer_info_get_stream_info (info);
224   if (!sinfo)
225     return;
226
227   g_print ("Stream information:\n");
228
229   print_topology (sinfo, 1);
230
231   gst_discoverer_stream_info_unref (sinfo);
232
233   g_print ("\n");
234 }
235
236 /* This function is called when the discoverer has finished examining
237  * all the URIs we provided.*/
238 static void on_finished_cb (GstDiscoverer *discoverer, CustomData *data) {
239   g_print ("Finished discovering\n");
240
241   g_main_loop_quit (data->loop);
242 }
243
244 int main (int argc, char **argv) {
245   CustomData data;
246   GError *err = NULL;
247   gchar *uri = "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm";
248
249   /* if a URI was provided, use it instead of the default one */
250   if (argc > 1) {
251     uri = argv[1];
252   }
253
254   /* Initialize cumstom data structure */
255   memset (&data, 0, sizeof (data));
256
257   /* Initialize GStreamer */
258   gst_init (&argc, &argv);
259
260   g_print ("Discovering '%s'\n", uri);
261
262   /* Instantiate the Discoverer */
263   data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err);
264   if (!data.discoverer) {
265     g_print ("Error creating discoverer instance: %s\n", err->message);
266     g_clear_error (&err);
267     return -1;
268   }
269
270   /* Connect to the interesting signals */
271   g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data);
272   g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data);
273
274   /* Start the discoverer process (nothing to do yet) */
275   gst_discoverer_start (data.discoverer);
276
277   /* Add a request to process asynchronously the URI passed through the command line */
278   if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) {
279     g_print ("Failed to start discovering URI '%s'\n", uri);
280     g_object_unref (data.discoverer);
281     return -1;
282   }
283
284   /* Create a GLib Main Loop and set it to run, so we can wait for the signals */
285   data.loop = g_main_loop_new (NULL, FALSE);
286   g_main_loop_run (data.loop);
287
288   /* Stop the discoverer process */
289   gst_discoverer_stop (data.discoverer);
290
291   /* Free resources */
292   g_object_unref (data.discoverer);
293   g_main_loop_unref (data.loop);
294
295   return 0;
296 }
297 ```
298
299
300 > ![Information](images/icons/emoticons/information.png)
301 > Need help?
302 >
303 > If you need help to compile this code, refer to the **Building the tutorials**  section for your platform: [Linux](sdk-installing-on-linux.md#InstallingonLinux-Build), [Mac OS X](sdk-installing-on-mac-osx.md#InstallingonMacOSX-Build) or [Windows](sdk-installing-on-windows.md#InstallingonWindows-Build), or use this specific command on Linux:
304 >
305 > ``gcc basic-tutorial-9.c -o basic-tutorial-9 `pkg-config --cflags --libs gstreamer-1.0 gstreamer-pbutils-1.0` ``
306 >
307 >If you need help to run this code, refer to the **Running the tutorials** section for your platform: [Linux](sdk-installing-on-linux.md#InstallingonLinux-Run), [Mac OS X](sdk-installing-on-mac-osx.md#InstallingonMacOSX-Run) or [Windows](sdk-installing-on-windows.md#InstallingonWindows-Run).
308 >
309 > 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.
310 >
311 > Required libraries: `gstreamer-pbutils-1.0` `gstreamer-1.0`
312
313
314 ## Walkthrough
315
316 These are the main steps to use the `GstDiscoverer`:
317
318 ``` c
319 /* Instantiate the Discoverer */
320 data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err);
321 if (!data.discoverer) {
322   g_print ("Error creating discoverer instance: %s\n", err->message);
323   g_clear_error (&err);
324   return -1;
325 }
326 ```
327
328 `gst_discoverer_new()` creates a new Discoverer object. The first
329 parameter is the timeout per file, in nanoseconds (use the
330 `GST_SECOND` macro for simplicity).
331
332 ``` c
333 /* Connect to the interesting signals */
334 g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data);
335 g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data);
336 ```
337
338 Connect to the interesting signals, as usual. We discuss them in the
339 snippet for their callbacks.
340
341 ``` c
342 /* Start the discoverer process (nothing to do yet) */
343 gst_discoverer_start (data.discoverer);
344 ```
345
346 `gst_discoverer_start()` launches the discovering process, but we have
347 not provided any URI to discover yet. This is done
348 next:
349
350 ``` c
351 /* Add a request to process asynchronously the URI passed through the command line */
352 if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) {
353   g_print ("Failed to start discovering URI '%s'\n", uri);
354   g_object_unref (data.discoverer);
355   return -1;
356 }
357 ```
358
359 `gst_discoverer_discover_uri_async()` enqueues the provided URI for
360 discovery. Multiple URIs can be enqueued with this function. As the
361 discovery process for each of them finishes, the registered callback
362 functions will be fired
363 up.
364
365 ``` c
366 /* Create a GLib Main Loop and set it to run, so we can wait for the signals */
367 data.loop = g_main_loop_new (NULL, FALSE);
368 g_main_loop_run (data.loop);
369 ```
370
371 The usual GLib main loop is instantiated and executed. We will get out
372 of it when `g_main_loop_quit()` is called from the
373 `on_finished_cb` callback.
374
375 ``` c
376 /* Stop the discoverer process */
377 gst_discoverer_stop (data.discoverer);
378 ```
379
380 Once we are done with the discoverer, we stop it with
381 `gst_discoverer_stop()` and unref it with `g_object_unref()`.
382
383 Let's review now the callbacks we have
384 registered:
385
386 ``` c
387 /* This function is called every time the discoverer has information regarding
388  * one of the URIs we provided.*/
389 static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) {
390   GstDiscovererResult result;
391   const gchar *uri;
392   const GstTagList *tags;
393   GstDiscovererStreamInfo *sinfo;
394
395   uri = gst_discoverer_info_get_uri (info);
396   result = gst_discoverer_info_get_result (info);
397 ```
398
399 We got here because the Discoverer has finished working on one URI, and
400 provides us a `GstDiscovererInfo` structure with all the information.
401
402 The first step is to retrieve the particular URI this call refers to (in
403 case we had multiple discover process running, which is not the case in
404 this example) with `gst_discoverer_info_get_uri()` and the discovery
405 result with `gst_discoverer_info_get_result()`.
406
407 ``` c
408 switch (result) {
409   case GST_DISCOVERER_URI_INVALID:
410     g_print ("Invalid URI '%s'\n", uri);
411     break;
412   case GST_DISCOVERER_ERROR:
413     g_print ("Discoverer error: %s\n", err->message);
414     break;
415   case GST_DISCOVERER_TIMEOUT:
416     g_print ("Timeout\n");
417     break;
418   case GST_DISCOVERER_BUSY:
419     g_print ("Busy\n");
420     break;
421   case GST_DISCOVERER_MISSING_PLUGINS:{
422     const GstStructure *s;
423     gchar *str;
424
425     s = gst_discoverer_info_get_misc (info);
426     str = gst_structure_to_string (s);
427
428     g_print ("Missing plugins: %s\n", str);
429     g_free (str);
430     break;
431   }
432   case GST_DISCOVERER_OK:
433     g_print ("Discovered '%s'\n", uri);
434     break;
435 }
436
437 if (result != GST_DISCOVERER_OK) {
438   g_printerr ("This URI cannot be played\n");
439   return;
440 }
441 ```
442
443 As the code shows, any result other than `GST_DISCOVERER_OK` means that
444 there has been some kind of problem, and this URI cannot be played. The
445 reasons can vary, but the enum values are quite explicit
446 (`GST_DISCOVERER_BUSY` can only happen when in synchronous mode, which
447 is not used in this example).
448
449 If no error happened, information can be retrieved from the
450 `GstDiscovererInfo` structure with the different
451 `gst_discoverer_info_get_*` methods (like,
452 `gst_discoverer_info_get_duration()`, for example).
453
454 Bits of information which are made of lists, like tags and stream info,
455 needs some extra parsing:
456
457 ``` c
458 tags = gst_discoverer_info_get_tags (info);
459 if (tags) {
460   g_print ("Tags:\n");
461   gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));
462 }
463 ```
464
465 Tags are metadata (labels) attached to the media. They can be examined
466 with `gst_tag_list_foreach()`, which will call `print_tag_foreach` for
467 each tag found (the list could also be traversed manually, for example,
468 or a specific tag could be searched for with
469 `gst_tag_list_get_string()`). The code for `print_tag_foreach` is pretty
470 much self-explicative.
471
472 ``` c
473 sinfo = gst_discoverer_info_get_stream_info (info);
474 if (!sinfo)
475   return;
476
477 g_print ("Stream information:\n");
478
479 print_topology (sinfo, 1);
480
481 gst_discoverer_stream_info_unref (sinfo);
482 ```
483
484 `gst_discoverer_info_get_stream_info()` returns
485 a `GstDiscovererStreamInfo` structure that is parsed in
486 the `print_topology` function, and then discarded
487 with `gst_discoverer_stream_info_unref()`.
488
489 ``` c
490 /* Print information regarding a stream and its substreams, if any */
491 static void print_topology (GstDiscovererStreamInfo *info, gint depth) {
492   GstDiscovererStreamInfo *next;
493
494   if (!info)
495     return;
496
497   print_stream_info (info, depth);
498
499   next = gst_discoverer_stream_info_get_next (info);
500   if (next) {
501     print_topology (next, depth + 1);
502     gst_discoverer_stream_info_unref (next);
503   } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
504     GList *tmp, *streams;
505
506     streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info));
507     for (tmp = streams; tmp; tmp = tmp->next) {
508       GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
509       print_topology (tmpinf, depth + 1);
510     }
511     gst_discoverer_stream_info_list_free (streams);
512   }
513 }
514 ```
515
516 The `print_stream_info` function's code is also pretty much
517 self-explicative: it prints the stream's capabilities and then the
518 associated caps, using `print_tag_foreach` too.
519
520 Then, `print_topology` looks for the next element to display. If
521 `gst_discoverer_stream_info_get_next()` returns a non-NULL stream info,
522 it refers to our descendant and that should be displayed. Otherwise, if
523 we are a container, recursively call `print_topology` on each of our
524 children obatined with `gst_discoverer_container_info_get_streams()`.
525 Otherwise, we are a final stream, and do not need to recurse (This part
526 of the Discoverer API is admittedly a bit obscure).
527
528 ## Conclusion
529
530 This tutorial has shown:
531
532   - How to recover information regarding a URI using the `GstDiscoverer`
533
534   - How to find out if a URI is playable by looking at the return code
535     obtained with `gst_discoverer_info_get_result()`.
536
537 It has been a pleasure having you here, and see you soon!