s/ffmpegcolorspace/videoconvert/ in a few places
[platform/upstream/gstreamer.git] / sdk-playback-tutorial-playbin-usage.md
1 # Playback tutorial 1: Playbin usage
2
3 ## Goal
4
5 We have already worked with the `playbin` element, which is capable of
6 building a complete playback pipeline without much work on our side.
7 This tutorial shows how to further customize `playbin` in case its
8 default values do not suit our particular needs.
9
10 We will learn:
11
12 -   How to find out how many streams a file contains, and how to switch
13     among them.
14
15 -   How to gather information regarding each stream.
16
17 As a side note, even though its name is `playbin`, you can pronounce it
18 “playbin”, since the original `playbin` element is deprecated and nobody
19 should be using it.
20
21 ## Introduction
22
23 More often than not, multiple audio, video and subtitle streams can be
24 found embedded in a single file. The most common case are regular
25 movies, which contain one video and one audio stream (Stereo or 5.1
26 audio tracks are considered a single stream). It is also increasingly
27 common to find movies with one video and multiple audio streams, to
28 account for different languages. In this case, the user selects one
29 audio stream, and the application will only play that one, ignoring the
30 others.
31
32 To be able to select the appropriate stream, the user needs to know
33 certain information about them, for example, their language. This
34 information is embedded in the streams in the form of “metadata”
35 (annexed data), and this tutorial shows how to retrieve it.
36
37 Subtitles can also be embedded in a file, along with audio and video,
38 but they are dealt with in more detail in [Playback tutorial 2: Subtitle
39 management]. Finally, multiple video streams can also be found in a
40 single file, for example, in DVD with multiple angles of the same scene,
41 but they are somewhat rare.
42
43 > ![information] Embedding multiple streams inside a single file is
44 > called “multiplexing” or “muxing”, and such file is then known as a
45 > “container”. Common container formats are Matroska (.mkv), Quicktime
46 > (.qt, .mov, .mp4), Ogg (.ogg) or Webm (.webm).
47 >
48 > Retrieving the individual streams from within the container is called
49 > “demultiplexing” or “demuxing”.
50
51 The following code recovers the amount of streams in the file, their
52 associated metadata, and allows switching the audio stream while the
53 media is playing.
54
55 ## The multilingual player
56
57 Copy this code into a text file named `playback-tutorial-1.c` (or find
58 it in the GStreamer installation).
59
60 **playback-tutorial-1.c**
61
62 ``` c
63 #include <gst/gst.h>
64
65 /* Structure to contain all our information, so we can pass it around */
66 typedef struct _CustomData {
67   GstElement *playbin;  /* Our one and only element */
68
69   gint n_video;          /* Number of embedded video streams */
70   gint n_audio;          /* Number of embedded audio streams */
71   gint n_text;           /* Number of embedded subtitle streams */
72
73   gint current_video;    /* Currently playing video stream */
74   gint current_audio;    /* Currently playing audio stream */
75   gint current_text;     /* Currently playing subtitle stream */
76
77   GMainLoop *main_loop;  /* GLib's Main Loop */
78 } CustomData;
79
80 /* playbin flags */
81 typedef enum {
82   GST_PLAY_FLAG_VIDEO         = (1 << 0), /* We want video output */
83   GST_PLAY_FLAG_AUDIO         = (1 << 1), /* We want audio output */
84   GST_PLAY_FLAG_TEXT          = (1 << 2)  /* We want subtitle output */
85 } GstPlayFlags;
86
87 /* Forward definition for the message and keyboard processing functions */
88 static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data);
89 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data);
90
91 int main(int argc, char *argv[]) {
92   CustomData data;
93   GstBus *bus;
94   GstStateChangeReturn ret;
95   gint flags;
96   GIOChannel *io_stdin;
97
98   /* Initialize GStreamer */
99   gst_init (&argc, &argv);
100
101   /* Create the elements */
102   data.playbin = gst_element_factory_make ("playbin", "playbin");
103
104   if (!data.playbin) {
105     g_printerr ("Not all elements could be created.\n");
106     return -1;
107   }
108
109   /* Set the URI to play */
110   g_object_set (data.playbin, "uri", "http://docs.gstreamer.com/media/sintel_cropped_multilingual.webm", NULL);
111
112   /* Set flags to show Audio and Video but ignore Subtitles */
113   g_object_get (data.playbin, "flags", &flags, NULL);
114   flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO;
115   flags &= ~GST_PLAY_FLAG_TEXT;
116   g_object_set (data.playbin, "flags", flags, NULL);
117
118   /* Set connection speed. This will affect some internal decisions of playbin */
119   g_object_set (data.playbin, "connection-speed", 56, NULL);
120
121   /* Add a bus watch, so we get notified when a message arrives */
122   bus = gst_element_get_bus (data.playbin);
123   gst_bus_add_watch (bus, (GstBusFunc)handle_message, &data);
124
125   /* Add a keyboard watch so we get notified of keystrokes */
126 #ifdef G_OS_WIN32
127   io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
128 #else
129   io_stdin = g_io_channel_unix_new (fileno (stdin));
130 #endif
131   g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);
132
133   /* Start playing */
134   ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
135   if (ret == GST_STATE_CHANGE_FAILURE) {
136     g_printerr ("Unable to set the pipeline to the playing state.\n");
137     gst_object_unref (data.playbin);
138     return -1;
139   }
140
141   /* Create a GLib Main Loop and set it to run */
142   data.main_loop = g_main_loop_new (NULL, FALSE);
143   g_main_loop_run (data.main_loop);
144
145   /* Free resources */
146   g_main_loop_unref (data.main_loop);
147   g_io_channel_unref (io_stdin);
148   gst_object_unref (bus);
149   gst_element_set_state (data.playbin, GST_STATE_NULL);
150   gst_object_unref (data.playbin);
151   return 0;
152 }
153
154 /* Extract some metadata from the streams and print it on the screen */
155 static void analyze_streams (CustomData *data) {
156   gint i;
157   GstTagList *tags;
158   gchar *str;
159   guint rate;
160
161   /* Read some properties */
162   g_object_get (data->playbin, "n-video", &data->n_video, NULL);
163   g_object_get (data->playbin, "n-audio", &data->n_audio, NULL);
164   g_object_get (data->playbin, "n-text", &data->n_text, NULL);
165
166   g_print ("%d video stream(s), %d audio stream(s), %d text stream(s)\n",
167     data->n_video, data->n_audio, data->n_text);
168
169   g_print ("\n");
170   for (i = 0; i < data->n_video; i++) {
171     tags = NULL;
172     /* Retrieve the stream's video tags */
173     g_signal_emit_by_name (data->playbin, "get-video-tags", i, &tags);
174     if (tags) {
175       g_print ("video stream %d:\n", i);
176       gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
177       g_print ("  codec: %s\n", str ? str : "unknown");
178       g_free (str);
179       gst_tag_list_free (tags);
180     }
181   }
182
183   g_print ("\n");
184   for (i = 0; i < data->n_audio; i++) {
185     tags = NULL;
186     /* Retrieve the stream's audio tags */
187     g_signal_emit_by_name (data->playbin, "get-audio-tags", i, &tags);
188     if (tags) {
189       g_print ("audio stream %d:\n", i);
190       if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str)) {
191         g_print ("  codec: %s\n", str);
192         g_free (str);
193       }
194       if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
195         g_print ("  language: %s\n", str);
196         g_free (str);
197       }
198       if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate)) {
199         g_print ("  bitrate: %d\n", rate);
200       }
201       gst_tag_list_free (tags);
202     }
203   }
204
205   g_print ("\n");
206   for (i = 0; i < data->n_text; i++) {
207     tags = NULL;
208     /* Retrieve the stream's subtitle tags */
209     g_signal_emit_by_name (data->playbin, "get-text-tags", i, &tags);
210     if (tags) {
211       g_print ("subtitle stream %d:\n", i);
212       if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
213         g_print ("  language: %s\n", str);
214         g_free (str);
215       }
216       gst_tag_list_free (tags);
217     }
218   }
219
220   g_object_get (data->playbin, "current-video", &data->current_video, NULL);
221   g_object_get (data->playbin, "current-audio", &data->current_audio, NULL);
222   g_object_get (data->playbin, "current-text", &data->current_text, NULL);
223
224   g_print ("\n");
225   g_print ("Currently playing video stream %d, audio stream %d and text stream %d\n",
226     data->current_video, data->current_audio, data->current_text);
227   g_print ("Type any number and hit ENTER to select a different audio stream\n");
228 }
229
230 /* Process messages from GStreamer */
231 static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data) {
232   GError *err;
233   gchar *debug_info;
234
235   switch (GST_MESSAGE_TYPE (msg)) {
236     case GST_MESSAGE_ERROR:
237       gst_message_parse_error (msg, &err, &debug_info);
238       g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
239       g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
240       g_clear_error (&err);
241       g_free (debug_info);
242       g_main_loop_quit (data->main_loop);
243       break;
244     case GST_MESSAGE_EOS:
245       g_print ("End-Of-Stream reached.\n");
246       g_main_loop_quit (data->main_loop);
247       break;
248     case GST_MESSAGE_STATE_CHANGED: {
249       GstState old_state, new_state, pending_state;
250       gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
251       if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
252         if (new_state == GST_STATE_PLAYING) {
253           /* Once we are in the playing state, analyze the streams */
254           analyze_streams (data);
255         }
256       }
257     } break;
258   }
259
260   /* We want to keep receiving messages */
261   return TRUE;
262 }
263
264 /* Process keyboard input */
265 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
266   gchar *str = NULL;
267
268   if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) == G_IO_STATUS_NORMAL) {
269     int index = g_ascii_strtoull (str, NULL, 0);
270     if (index < 0 || index >= data->n_audio) {
271       g_printerr ("Index out of bounds\n");
272     } else {
273       /* If the input was a valid audio stream index, set the current audio stream */
274       g_print ("Setting current audio stream to %d\n", index);
275       g_object_set (data->playbin, "current-audio", index, NULL);
276     }
277   }
278   g_free (str);
279   return TRUE;
280 }
281 ```
282
283 > ![information] If you need help to compile this code, refer to the
284 > **Building the tutorials** section for your platform: [Mac] or
285 > [Windows] or use this specific command on Linux:
286 >
287 > `` gcc playback-tutorial-1.c -o playback-tutorial-1 `pkg-config --cflags --libs gstreamer-1.0` ``
288 >
289 > If you need help to run this code, refer to the **Running the
290 > tutorials** section for your platform: [Mac OS X], [Windows][1], for
291 > [iOS] or for [android].
292 >
293 > This tutorial opens a window and displays a movie, with accompanying
294 > audio. The media is fetched from the Internet, so the window might take
295 > a few seconds to appear, depending on your connection speed. The number
296 > of audio streams is shown in the terminal, and the user can switch from
297 > one to another by entering a number and pressing enter. A small delay is
298 > to be expected.
299 >
300 > Bear in mind that there is no latency management (buffering), so on slow
301 > connections, the movie might stop after a few seconds. See how [Tutorial
302 > 12: Live streaming] solves this issue.
303 >
304 > Required libraries: `gstreamer-1.0`
305
306 ## Walkthrough
307
308 ``` c
309 /* Structure to contain all our information, so we can pass it around */
310 typedef struct _CustomData {
311   GstElement *playbin;  /* Our one and only element */
312
313   gint n_video;          /* Number of embedded video streams */
314   gint n_audio;          /* Number of embedded audio streams */
315   gint n_text;           /* Number of embedded subtitle streams */
316
317   gint current_video;    /* Currently playing video stream */
318   gint current_audio;    /* Currently playing audio stream */
319   gint current_text;     /* Currently playing subtitle stream */
320
321   GMainLoop *main_loop;  /* GLib's Main Loop */
322 } CustomData;
323 ```
324
325 We start, as usual, putting all our variables in a structure, so we can
326 pass it around to functions. For this tutorial, we need the amount of
327 streams of each type, and the currently playing one. Also, we are going
328 to use a different mechanism to wait for messages that allows
329 interactivity, so we need a GLib's main loop object.
330
331 ``` c
332 /* playbin flags */
333 typedef enum {
334   GST_PLAY_FLAG_VIDEO         = (1 << 0), /* We want video output */
335   GST_PLAY_FLAG_AUDIO         = (1 << 1), /* We want audio output */
336   GST_PLAY_FLAG_TEXT          = (1 << 2)  /* We want subtitle output */
337 } GstPlayFlags;
338 ```
339
340 Later we are going to set some of `playbin`'s flags. We would like to
341 have a handy enum that allows manipulating these flags easily, but since
342 `playbin` is a plug-in and not a part of the GStreamer core, this enum
343 is not available to us. The “trick” is simply to declare this enum in
344 our code, as it appears in the `playbin` documentation: `GstPlayFlags`.
345 GObject allows introspection, so the possible values for these flags can
346 be retrieved at runtime without using this trick, but in a far more
347 cumbersome way.
348
349 ``` c
350 /* Forward definition for the message and keyboard processing functions */
351 static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data);
352 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data);
353 ```
354
355 Forward declarations for the two callbacks we will be using.
356 `handle_message` for the GStreamer messages, as we have already seen,
357 and `handle_keyboard` for key strokes, since this tutorial is
358 introducing a limited amount of interactivity.
359
360 We skip over the creation of the pipeline, the instantiation of
361 `playbin` and pointing it to our test media through the `uri`
362 property. `playbin` is in itself a pipeline, and in this case it is the
363 only element in the pipeline, so we skip completely the creation of the
364 pipeline, and use directly the  `playbin` element.
365
366 We focus on some of the other properties of `playbin`, though:
367
368 ``` c
369 /* Set flags to show Audio and Video but ignore Subtitles */
370 g_object_get (data.playbin, "flags", &flags, NULL);
371 flags |= GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO;
372 flags &= ~GST_PLAY_FLAG_TEXT;
373 g_object_set (data.playbin, "flags", flags, NULL);
374 ```
375
376 `playbin`'s behavior can be changed through its `flags` property, which
377 can have any combination of `GstPlayFlags`. The most interesting values
378 are:
379
380 | Flag                      | Description                                                                                                                        |
381 |---------------------------|------------------------------------------------------------------------------------------------------------------------------------|
382 | GST_PLAY_FLAG_VIDEO       | Enable video rendering. If this flag is not set, there will be no video output.                                                    |
383 | GST_PLAY_FLAG_AUDIO       | Enable audio rendering. If this flag is not set, there will be no audio output.                                                    |
384 | GST_PLAY_FLAG_TEXT        | Enable subtitle rendering. If this flag is not set, subtitles will not be shown in the video output.                               |
385 | GST_PLAY_FLAG_VIS         | Enable rendering of visualisations when there is no video stream. Playback tutorial 6: Audio visualization goes into more details. |
386 | GST_PLAY_FLAG_DOWNLOAD    | See Basic tutorial 12: Streaming  and Playback tutorial 4: Progressive streaming.                                                  |
387 | GST_PLAY_FLAG_BUFFERING   | See Basic tutorial 12: Streaming  and Playback tutorial 4: Progressive streaming.                                                  |
388 | GST_PLAY_FLAG_DEINTERLACE | If the video content was interlaced, this flag instructs playbin to deinterlace it before displaying it.                           |
389
390 In our case, for demonstration purposes, we are enabling audio and video
391 and disabling subtitles, leaving the rest of flags to their default
392 values (this is why we read the current value of the flags with
393 `g_object_get()` before overwriting it with `g_object_set()`).
394
395 ``` c
396 /* Set connection speed. This will affect some internal decisions of playbin */
397 g_object_set (data.playbin, "connection-speed", 56, NULL);
398 ```
399
400 This property is not really useful in this example.
401 `connection-speed` informs `playbin` of the maximum speed of our network
402 connection, so, in case multiple versions of the requested media are
403 available in the server, `playbin` chooses the most appropriate. This is
404 mostly used in combination with streaming protocols like `mms` or
405 `rtsp`.
406
407 We have set all these properties one by one, but we could have all of
408 them with a single call to `g_object_set()`:
409
410 ``` c
411 g_object_set (data.playbin, "uri", "http://docs.gstreamer.com/media/sintel_cropped_multilingual.webm", "flags", flags, "connection-speed", 56, NULL);
412 ```
413
414 This is why `g_object_set()` requires a NULL as the last parameter.
415
416 ``` c
417   /* Add a keyboard watch so we get notified of keystrokes */
418 #ifdef _WIN32
419   io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
420 #else
421   io_stdin = g_io_channel_unix_new (fileno (stdin));
422 #endif
423   g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);
424 ```
425
426 These lines connect a callback function to the standard input (the
427 keyboard). The mechanism shown here is specific to GLib, and not really
428 related to GStreamer, so there is no point in going into much depth.
429 Applications normally have their own way of handling user input, and
430 GStreamer has little to do with it besides the Navigation interface
431 discussed briefly in [Tutorial 17: DVD playback].
432
433 ``` c
434 /* Create a GLib Main Loop and set it to run */
435 data.main_loop = g_main_loop_new (NULL, FALSE);
436 g_main_loop_run (data.main_loop);
437 ```
438
439 To allow interactivity, we will no longer poll the GStreamer bus
440 manually. Instead, we create a `GMainLoop`(GLib main loop) and set it
441 running with `g_main_loop_run()`. This function blocks and will not
442 return until `g_main_loop_quit()` is issued. In the meantime, it will
443 call the callbacks we have registered at the appropriate
444 times: `handle_message` when a message appears on the bus, and
445 `handle_keyboard` when the user presses any key.
446
447 There is nothing new in handle\_message, except that when the pipeline
448 moves to the PLAYING state, it will call the `analyze_streams` function:
449
450 ``` c
451 /* Extract some metadata from the streams and print it on the screen */
452 static void analyze_streams (CustomData *data) {
453   gint i;
454   GstTagList *tags;
455   gchar *str;
456   guint rate;
457
458   /* Read some properties */
459   g_object_get (data->playbin, "n-video", &data->n_video, NULL);
460   g_object_get (data->playbin, "n-audio", &data->n_audio, NULL);
461   g_object_get (data->playbin, "n-text", &data->n_text, NULL);
462 ```
463
464 As the comment says, this function just gathers information from the
465 media and prints it on the screen. The number of video, audio and
466 subtitle streams is directly available through the `n-video`,
467 `n-audio` and `n-text` properties.
468
469 ``` c
470 for (i = 0; i < data->n_video; i++) {
471   tags = NULL;
472   /* Retrieve the stream's video tags */
473   g_signal_emit_by_name (data->playbin, "get-video-tags", i, &tags);
474   if (tags) {
475     g_print ("video stream %d:\n", i);
476     gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
477     g_print ("  codec: %s\n", str ? str : "unknown");
478     g_free (str);
479     gst_tag_list_free (tags);
480   }
481 }
482 ```
483
484 Now, for each stream, we want to retrieve its metadata. Metadata is
485 stored as tags in a `GstTagList` structure, which is a list of data
486 pieces identified by a name. The `GstTagList` associated with a stream
487 can be recovered with `g_signal_emit_by_name()`, and then individual
488 tags are extracted with the `gst_tag_list_get_*` functions
489 like `gst_tag_list_get_string()` for example.
490
491 > ![information]
492 > This rather unintuitive way of retrieving the tag list
493 > is called an Action Signal. Action signals are emitted by the
494 > application to a specific element, which then performs an action and
495 > returns a result. They behave like a dynamic function call, in which
496 > methods of a class are identified by their name (the signal's name)
497 > instead of their memory address. These signals are listed In the
498 > documentation along with the regular signals, and are tagged “Action”.
499 > See `playbin`, for example.
500
501 `playbin` defines 3 action signals to retrieve metadata:
502 `get-video-tags`, `get-audio-tags` and `get-text-tags`. The name if the
503 tags is standardized, and the list can be found in the `GstTagList`
504 documentation. In this example we are interested in the
505 `GST_TAG_LANGUAGE_CODE` of the streams and their `GST_TAG_*_CODEC`
506 (audio, video or text).
507
508 ``` c
509 g_object_get (data->playbin, "current-video", &data->current_video, NULL);
510 g_object_get (data->playbin, "current-audio", &data->current_audio, NULL);
511 g_object_get (data->playbin, "current-text", &data->current_text, NULL);
512 ```
513
514 Once we have extracted all the metadata we want, we get the streams that
515 are currently selected through 3 more properties of `playbin`:
516 `current-video`, `current-audio` and `current-text`. 
517
518 It is interesting to always check the currently selected streams and
519 never make any assumption. Multiple internal conditions can make
520 `playbin` behave differently in different executions. Also, the order in
521 which the streams are listed can change from one run to another, so
522 checking the metadata to identify one particular stream becomes crucial.
523
524 ``` c
525 /* Process keyboard input */
526 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
527   gchar *str = NULL;
528
529   if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) == G_IO_STATUS_NORMAL) {
530     int index = g_ascii_strtoull (str, NULL, 0);
531     if (index < 0 || index >= data->n_audio) {
532       g_printerr ("Index out of bounds\n");
533     } else {
534       /* If the input was a valid audio stream index, set the current audio stream */
535       g_print ("Setting current audio stream to %d\n", index);
536       g_object_set (data->playbin, "current-audio", index, NULL);
537     }
538   }
539   g_free (str);
540   return TRUE;
541 }
542 ```
543
544 Finally, we allow the user to switch the running audio stream. This very
545 basic function just reads a string from the standard input (the
546 keyboard), interprets it as a number, and tries to set the
547 `current-audio` property of `playbin` (which previously we have only
548 read).
549
550 Bear in mind that the switch is not immediate. Some of the previously
551 decoded audio will still be flowing through the pipeline, while the new
552 stream becomes active and is decoded. The delay depends on the
553 particular multiplexing of the streams in the container, and the length
554 `playbin` has selected for its internal queues (which depends on the
555 network conditions).
556
557 If you execute the tutorial, you will be able to switch from one
558 language to another while the movie is running by pressing 0, 1 or 2
559 (and ENTER). This concludes this tutorial.
560
561 ## Conclusion
562
563 This tutorial has shown:
564
565 -   A few more of `playbin`'s properties: `flags`, `connection-speed`,
566     `n-video`, `n-audio`, `n-text`, `current-video`, `current-audio` and
567     `current-text`.
568
569 -   How to retrieve the list of tags associated with a stream
570     with `g_signal_emit_by_name()`.
571
572 -   How to retrieve a particular tag from the list with
573     `gst_tag_list_get_string()`or `gst_tag_list_get_uint()`
574
575 -   How to switch the current audio simply by writing to the
576     `current-audio` property.
577
578 The next playback tutorial shows how to handle subtitles, either
579 embedded in the container or in an external file.
580
581 Remember that attached to this page you should find the complete source
582 code of the tutorial and any accessory files needed to build it.
583
584 It has been a pleasure having you here, and see you soon!
585
586   [Playback tutorial 2: Subtitle management]: sdk-playback-tutorial-subtitle-management.md
587   [information]: images/icons/emoticons/information.png
588   [Mac]: sdk-installing-on-mac-osx.md
589   [Windows]: Installing+on+Windows
590   [Mac OS X]: sdk-installing-on-mac-osx.md#building-the-tutorials
591   [1]: sdk-installing-on-windows.md#running-the-tutorials
592   [iOS]: sdk-installing-for-ios-development.md#building-the-tutorials
593   [android]: sdk-installing-for-android-development.md#building-the-tutorials