Update theme submodule
[platform/upstream/gstreamer.git] / tutorial-basic-time-management.md
1 #  Basic tutorial 4: Time management
2
3 ## Goal
4
5 This tutorial shows how to use GStreamer time-related facilities. In
6 particular:
7
8   - How to query the pipeline for information like stream position or
9     duration.
10
11   - How to seek (jump) to a different position (time instant) inside the
12     stream.
13
14 ## Introduction
15
16 `GstQuery` is a mechanism that allows asking an element or pad for a
17 piece of information. In this example we ask the pipeline if seeking is
18 allowed (some sources, like live streams, do not allow seeking). If it
19 is allowed, then, once the movie has been running for ten seconds, we
20 skip to a different position using a seek.
21
22 In the previous tutorials, once we had the pipeline setup and running,
23 our main function just sat and waited to receive an ERROR or an EOS
24 through the bus. Here we modify this function to periodically wake up
25 and query the pipeline for the stream position, so we can print it on
26 screen. This is similar to what a media player would do, updating the
27 User Interface on a periodic basis.
28
29 Finally, the stream duration is queried and updated whenever it changes.
30
31 ## Seeking example
32
33 Copy this code into a text file named `basic-tutorial-4.c` (or find it
34 in the SDK installation).
35
36 **basic-tutorial-4.c**
37
38 ``` c
39 #include <gst/gst.h>
40
41 /* Structure to contain all our information, so we can pass it around */
42 typedef struct _CustomData {
43   GstElement *playbin;  /* Our one and only element */
44   gboolean playing;      /* Are we in the PLAYING state? */
45   gboolean terminate;    /* Should we terminate execution? */
46   gboolean seek_enabled; /* Is seeking enabled for this media? */
47   gboolean seek_done;    /* Have we performed the seek already? */
48   gint64 duration;       /* How long does this media last, in nanoseconds */
49 } CustomData;
50
51 /* Forward definition of the message processing function */
52 static void handle_message (CustomData *data, GstMessage *msg);
53
54 int main(int argc, char *argv[]) {
55   CustomData data;
56   GstBus *bus;
57   GstMessage *msg;
58   GstStateChangeReturn ret;
59
60   data.playing = FALSE;
61   data.terminate = FALSE;
62   data.seek_enabled = FALSE;
63   data.seek_done = FALSE;
64   data.duration = GST_CLOCK_TIME_NONE;
65
66   /* Initialize GStreamer */
67   gst_init (&argc, &argv);
68
69   /* Create the elements */
70   data.playbin = gst_element_factory_make ("playbin", "playbin");
71
72   if (!data.playbin) {
73     g_printerr ("Not all elements could be created.\n");
74     return -1;
75   }
76
77   /* Set the URI to play */
78   g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
79
80   /* Start playing */
81   ret = gst_element_set_state (data.playbin, GST_STATE_PLAYING);
82   if (ret == GST_STATE_CHANGE_FAILURE) {
83     g_printerr ("Unable to set the pipeline to the playing state.\n");
84     gst_object_unref (data.playbin);
85     return -1;
86   }
87
88   /* Listen to the bus */
89   bus = gst_element_get_bus (data.playbin);
90   do {
91     msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,
92         GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION);
93
94     /* Parse message */
95     if (msg != NULL) {
96       handle_message (&data, msg);
97     } else {
98       /* We got no message, this means the timeout expired */
99       if (data.playing) {
100         gint64 current = -1;
101
102         /* Query the current position of the stream */
103         if (!gst_element_query_position (data.playbin, GST_TIME_FORMAT, &current)) {
104           g_printerr ("Could not query current position.\n");
105         }
106
107         /* If we didn't know it yet, query the stream duration */
108         if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
109           if (!gst_element_query_duration (data.playbin, GST_TIME_FORMAT, &data.duration)) {
110             g_printerr ("Could not query current duration.\n");
111           }
112         }
113
114         /* Print current position and total duration */
115         g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
116             GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));
117
118         /* If seeking is enabled, we have not done it yet, and the time is right, seek */
119         if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) {
120           g_print ("\nReached 10s, performing seek...\n");
121           gst_element_seek_simple (data.playbin, GST_FORMAT_TIME,
122               GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
123           data.seek_done = TRUE;
124         }
125       }
126     }
127   } while (!data.terminate);
128
129   /* Free resources */
130   gst_object_unref (bus);
131   gst_element_set_state (data.playbin, GST_STATE_NULL);
132   gst_object_unref (data.playbin);
133   return 0;
134 }
135
136 static void handle_message (CustomData *data, GstMessage *msg) {
137   GError *err;
138   gchar *debug_info;
139
140   switch (GST_MESSAGE_TYPE (msg)) {
141     case GST_MESSAGE_ERROR:
142       gst_message_parse_error (msg, &err, &debug_info);
143       g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
144       g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
145       g_clear_error (&err);
146       g_free (debug_info);
147       data->terminate = TRUE;
148       break;
149     case GST_MESSAGE_EOS:
150       g_print ("End-Of-Stream reached.\n");
151       data->terminate = TRUE;
152       break;
153     case GST_MESSAGE_DURATION:
154       /* The duration has changed, mark the current one as invalid */
155       data->duration = GST_CLOCK_TIME_NONE;
156       break;
157     case GST_MESSAGE_STATE_CHANGED: {
158       GstState old_state, new_state, pending_state;
159       gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
160       if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
161         g_print ("Pipeline state changed from %s to %s:\n",
162             gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
163
164         /* Remember whether we are in the PLAYING state or not */
165         data->playing = (new_state == GST_STATE_PLAYING);
166
167         if (data->playing) {
168           /* We just moved to PLAYING. Check if seeking is possible */
169           GstQuery *query;
170           gint64 start, end;
171           query = gst_query_new_seeking (GST_FORMAT_TIME);
172           if (gst_element_query (data->playbin, query)) {
173             gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);
174             if (data->seek_enabled) {
175               g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
176                   GST_TIME_ARGS (start), GST_TIME_ARGS (end));
177             } else {
178               g_print ("Seeking is DISABLED for this stream.\n");
179             }
180           }
181           else {
182             g_printerr ("Seeking query failed.");
183           }
184           gst_query_unref (query);
185         }
186       }
187     } break;
188     default:
189       /* We should not reach here */
190       g_printerr ("Unexpected message received.\n");
191       break;
192   }
193   gst_message_unref (msg);
194 }
195 ```
196
197 > ![Information](images/icons/emoticons/information.png)
198 > Need help?
199 >
200 > If you need help to compile this code, refer to the **Building the tutorials**  section for your platform: [Linux](installing-on-linux.md#InstallingonLinux-Build), [Mac OS X](installing-on-mac-osx.md#InstallingonMacOSX-Build) or [Windows](installing-on-windows.md#InstallingonWindows-Build), or use this specific command on Linux:
201 >
202 > ``gcc basic-tutorial-4.c -o basic-tutorial-4 `pkg-config --cflags --libs gstreamer-1.0` ``
203 >
204 >If you need help to run this code, refer to the **Running the tutorials** section for your platform: [Linux](installing-on-linux.md#InstallingonLinux-Run), [Mac OS X](installing-on-mac-osx.md#InstallingonMacOSX-Run) or [Windows](installing-on-windows.md#InstallingonWindows-Run).
205 >
206 > This tutorial opens a window and displays a movie, with accompanying audio. The media is fetched from the Internet, so the window might take a few seconds to appear, depending on your connection speed. 10 seconds into the movie it skips to a new position
207 >
208 >Required libraries: `gstreamer-1.0`
209
210 ## Walkthrough
211
212 ```
213 /* Structure to contain all our information, so we can pass it around */
214 typedef struct _CustomData {
215   GstElement *playbin;  /* Our one and only element */
216   gboolean playing;      /* Are we in the PLAYING state? */
217   gboolean terminate;    /* Should we terminate execution? */
218   gboolean seek_enabled; /* Is seeking enabled for this media? */
219   gboolean seek_done;    /* Have we performed the seek already? */
220   gint64 duration;       /* How long does this media last, in nanoseconds */
221 } CustomData;
222
223 /* Forward definition of the message processing function */
224 static void handle_message (CustomData *data, GstMessage *msg);
225 ```
226
227 We start by defining a structure to contain all our information, so we
228 can pass it around to other functions. In particular, in this example we
229 move the message handling code to its own function
230 `handle_message` because it is growing a bit too big.
231
232 We would then build a pipeline composed of a single element, a
233 `playbin`, which we already saw in [Basic tutorial 1: Hello
234 world!](tutorial-basic-hello-world.md). However,
235 `playbin` is in itself a pipeline, and in this case it is the only
236 element in the pipeline, so we use directly the `playbin` element. We
237 will skip the details: the URI of the clip is given to `playbin` via
238 the URI property and the pipeline is set to the playing state.
239
240 ```
241 msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,
242     GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION);
243 ```
244
245 Previously we did not provide a timeout to
246 `gst_bus_timed_pop_filtered()`, meaning that it didn't return until a
247 message was received. Now we use a timeout of 100 milliseconds, so, if
248 no message is received, 10 times per second the function will return
249 with a NULL instead of a `GstMessage`. We are going to use this to
250 update our “UI”. Note that the timeout period is specified in
251 nanoseconds, so usage of the `GST_SECOND` or `GST_MSECOND` macros is
252 highly recommended.
253
254 If we got a message, we process it in the `handle_message`` `function
255 (next subsection), otherwise:
256
257 ### User interface resfreshing
258
259 ```
260 /* We got no message, this means the timeout expired */
261 if (data.playing) {
262 ```
263
264 First off, if we are not in the PLAYING state, we do not want to do
265 anything here, since most queries would fail. Otherwise, it is time to
266 refresh the screen.
267
268 We get here approximately 10 times per second, a good enough refresh
269 rate for our UI. We are going to print on screen the current media
270 position, which we can learn be querying the pipeline. This involves a
271 few steps that will be shown in the next subsection, but, since position
272 and duration are common enough queries, `GstElement` offers easier,
273 ready-made alternatives:
274
275 ```
276 /* Query the current position of the stream */
277 if (!gst_element_query_position (data.pipeline, GST_FORMAT_TIME, &current)) {
278   g_printerr ("Could not query current position.\n");
279 }
280 ```
281
282 `gst_element_query_position()` hides the management of the query object
283 and directly provides us with the result.
284
285 ```
286 /* If we didn't know it yet, query the stream duration */
287 if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
288   if (!gst_element_query_duration (data.pipeline, GST_TIME_FORMAT, &data.duration)) {
289      g_printerr ("Could not query current duration.\n");
290   }
291 }
292 ```
293
294 Now is a good moment to know the length of the stream, with
295 another `GstElement` helper function: `gst_element_query_duration()`
296
297 ```
298 /* Print current position and total duration */
299 g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
300     GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));
301 ```
302
303 Note the usage of the `GST_TIME_FORMAT` and `GST_TIME_ARGS` macros to
304 provide user-friendly representation of GStreamer
305 times.
306
307 ```
308 /* If seeking is enabled, we have not done it yet, and the time is right, seek */
309 if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) {
310   g_print ("\nReached 10s, performing seek...\n");
311   gst_element_seek_simple (data.pipeline, GST_FORMAT_TIME,
312       GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
313   data.seek_done = TRUE;
314 }
315 ```
316
317 Now we perform the seek, “simply” by
318 calling `gst_element_seek_simple()` on the pipeline. A lot of the
319 intricacies of seeking are hidden in this method, which is a good
320 thing!
321
322 Let's review the parameters:
323
324 `GST_FORMAT_TIME` indicates that we are specifying the destination in
325 time, as opposite to bytes (and other more obscure mechanisms).
326
327 Then come the GstSeekFlags, let's review the most common:
328
329 `GST_SEEK_FLAG_FLUSH`: This discards all data currently in the pipeline
330 before doing the seek. Might pause a bit while the pipeline is refilled
331 and the new data starts to show up, but greatly increases the
332 “responsiveness” of the application. If this flag is not provided,
333 “stale” data might be shown for a while until the new position appears
334 at the end of the pipeline.
335
336 `GST_SEEK_FLAG_KEY_UNIT`: Most encoded video streams cannot seek to
337 arbitrary positions, only to certain frames called Key Frames. When this
338 flag is used, the seek will actually move to the closest key frame and
339 start producing data straight away. If this flag is not used, the
340 pipeline will move internally to the closest key frame (it has no other
341 alternative) but data will not be shown until it reaches the requested
342 position. Not providing the flag is more accurate, but might take longer
343 to react.
344
345 `GST_SEEK_FLAG_ACCURATE`: Some media clips do not provide enough
346 indexing information, meaning that seeking to arbitrary positions is
347 time-consuming. In these cases, GStreamer usually estimates the position
348 to seek to, and usually works just fine. If this precision is not good
349 enough for your case (you see seeks not going to the exact time you
350 asked for), then provide this flag. Be warned that it might take longer
351 to calculate the seeking position (very long, on some files).
352
353 And finally we provide the position to seek to. Since we asked
354 for `GST_FORMAT_TIME` , this position is in nanoseconds, so we use
355 the `GST_SECOND` macro for simplicity.
356
357 ### Message Pump
358
359 The `handle_message` function processes all messages received through
360 the pipeline's bus. ERROR and EOS handling is the same as in previous
361 tutorials, so we skip to the interesting part:
362
363 ```
364 case GST_MESSAGE_DURATION:
365   /* The duration has changed, mark the current one as invalid */
366   data->duration = GST_CLOCK_TIME_NONE;
367   break;
368 ```
369
370 This message is posted on the bus whenever the duration of the stream
371 changes. Here we simply mark the current duration as invalid, so it gets
372 re-queried later.
373
374 ```
375 case GST_MESSAGE_STATE_CHANGED: {
376   GstState old_state, new_state, pending_state;
377   gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
378   if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
379     g_print ("Pipeline state changed from %s to %s:\n",
380         gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
381
382     /* Remember whether we are in the PLAYING state or not */
383     data->playing = (new_state == GST_STATE_PLAYING);
384 ```
385
386 Seeks and time queries generally only get a valid reply when in the
387 PAUSED or PLAYING state, since all elements have had a chance to
388 receive information and configure themselves. Here we take note of
389 whether we are in the PLAYING state or not with the `playing`
390 variable.
391
392 Also, if we have just entered the PLAYING state, we do our first query.
393 We ask the pipeline if seeking is allowed on this stream:
394
395 ```
396 if (data->playing) {
397   /* We just moved to PLAYING. Check if seeking is possible */
398   GstQuery *query;
399   gint64 start, end;
400   query = gst_query_new_seeking (GST_FORMAT_TIME);
401   if (gst_element_query (data->pipeline, query)) {
402     gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);
403     if (data->seek_enabled) {
404       g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
405           GST_TIME_ARGS (start), GST_TIME_ARGS (end));
406     } else {
407       g_print ("Seeking is DISABLED for this stream.\n");
408     }
409   }
410   else {
411     g_printerr ("Seeking query failed.");
412   }
413   gst_query_unref (query);
414 }
415 ```
416
417 `gst_query_new_seeking()` creates a new query object of the "seeking"
418 type, with `GST_FORMAT_TIME` format. This indicates that we are
419 interested in seeking by specifying the new time to which we want to
420 move. We could also ask for `GST_FORMAT_BYTES`, and then seek to a
421 particular byte position inside the source file, but this is normally
422 less useful.
423
424 This query object is then passed to the pipeline with
425 `gst_element_query()`. The result is stored in the same query, and can
426 be easily retrieved with `gst_query_parse_seeking()`. It extracts a
427 boolean indicating if seeking is allowed, and the range in which seeking
428 is possible.
429
430 Don't forget to unref the query object when you are done with it.
431
432 And that's it! With this knowledge a media player can be built which
433 periodically updates a slider based on the current stream position and
434 allows seeking by moving the slider!
435
436 ## Conclusion
437
438 This tutorial has shown:
439
440   - How to query the pipeline for information using `GstQuery`
441
442   - How to obtain common information like position and duration
443     using `gst_element_query_position()` and `gst_element_query_duration()`
444
445   - How to seek to an arbitrary position in the stream
446     using `gst_element_seek_simple()`
447
448   - In which states all these operations can be performed.
449
450 The next tutorial shows how to integrate GStreamer with a Graphical User
451 Interface toolkit.
452
453 Remember that attached to this page you should find the complete source
454 code of the tutorial and any accessory files needed to build it.
455
456 It has been a pleasure having you here, and see you soon!