Update theme submodule
[platform/upstream/gstreamer.git] / markdown / tutorials / basic / playback-speed.md
1 # Basic tutorial 13: Playback speed
2
3 ## Goal
4
5 Fast-forward, reverse-playback and slow-motion are all techniques
6 collectively known as *trick modes* and they all have in common that
7 modify the normal playback rate. This tutorial shows how to achieve
8 these effects and adds frame-stepping into the deal. In particular, it
9 shows:
10
11   - How to change the playback rate, faster and slower than normal,
12     forward and backwards.
13   - How to advance a video frame-by-frame
14
15 ## Introduction
16
17 Fast-forward is the technique that plays a media at a speed higher than
18 its normal (intended) speed; whereas slow-motion uses a speed lower than
19 the intended one. Reverse playback does the same thing but backwards,
20 from the end of the stream to the beginning.
21
22 All these techniques do is change the playback rate, which is a variable
23 equal to 1.0 for normal playback, greater than 1.0 (in absolute value)
24 for fast modes, lower than 1.0 (in absolute value) for slow modes,
25 positive for forward playback and negative for reverse playback.
26
27 GStreamer provides two mechanisms to change the playback rate: Step
28 Events and Seek Events. Step Events allow skipping a given amount of
29 media besides changing the subsequent playback rate (only to positive
30 values). Seek Events, additionally, allow jumping to any position in the
31 stream and set positive and negative playback rates.
32
33 In [](tutorials/basic/time-management.md) seek
34 events have already been shown, using a helper function to hide their
35 complexity. This tutorial explains a bit more how to use these events.
36
37 Step Events are a more convenient way of changing the playback rate,
38 due to the reduced number of parameters needed to create them;
39 however, they have some downsides, so Seek Events are used in this
40 tutorial instead. Step events only affect the sink (at the end of the
41 pipeline), so they will only work if the rest of the pipeline can
42 support going at a different speed, Seek events go all the way through
43 the pipeline so every element can react to them. The upside of Step
44 events is that they are much faster to act. Step events are also
45 unable to change the playback direction.
46
47 To use these events, they are created and then passed onto the pipeline,
48 where they propagate upstream until they reach an element that can
49 handle them. If an event is passed onto a bin element like `playbin`,
50 it will simply feed the event to all its sinks, which will result in
51 multiple seeks being performed. The common approach is to retrieve one
52 of `playbin`’s sinks through the `video-sink` or
53 `audio-sink` properties and feed the event directly into the sink.
54
55 Frame stepping is a technique that allows playing a video frame by
56 frame. It is implemented by pausing the pipeline, and then sending Step
57 Events to skip one frame each time.
58
59 ## A trick mode player
60
61 Copy this code into a text file named `basic-tutorial-13.c`.
62
63 **basic-tutorial-13.c**
64
65 ``` c
66 #include <string.h>
67 #include <stdio.h>
68 #include <gst/gst.h>
69
70 typedef struct _CustomData {
71   GstElement *pipeline;
72   GstElement *video_sink;
73   GMainLoop *loop;
74
75   gboolean playing;  /* Playing or Paused */
76   gdouble rate;      /* Current playback rate (can be negative) */
77 } CustomData;
78
79 /* Send seek event to change rate */
80 static void send_seek_event (CustomData *data) {
81   gint64 position;
82   GstFormat format = GST_FORMAT_TIME;
83   GstEvent *seek_event;
84
85   /* Obtain the current position, needed for the seek event */
86   if (!gst_element_query_position (data->pipeline, &format, &position)) {
87     g_printerr ("Unable to retrieve current position.\n");
88     return;
89   }
90
91   /* Create the seek event */
92   if (data->rate > 0) {
93     seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
94         GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_NONE, 0);
95   } else {
96     seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
97         GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, position);
98   }
99
100   if (data->video_sink == NULL) {
101     /* If we have not done so, obtain the sink through which we will send the seek events */
102     g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
103   }
104
105   /* Send the event */
106   gst_element_send_event (data->video_sink, seek_event);
107
108   g_print ("Current rate: %g\n", data->rate);
109 }
110
111 /* Process keyboard input */
112 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
113   gchar *str = NULL;
114
115   if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) {
116     return TRUE;
117   }
118
119   switch (g_ascii_tolower (str[0])) {
120   case 'p':
121     data->playing = !data->playing;
122     gst_element_set_state (data->pipeline, data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
123     g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
124     break;
125   case 's':
126     if (g_ascii_isupper (str[0])) {
127       data->rate *= 2.0;
128     } else {
129       data->rate /= 2.0;
130     }
131     send_seek_event (data);
132     break;
133   case 'd':
134     data->rate *= -1.0;
135     send_seek_event (data);
136     break;
137   case 'n':
138     if (data->video_sink == NULL) {
139       /* If we have not done so, obtain the sink through which we will send the step events */
140       g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
141     }
142
143     gst_element_send_event (data->video_sink,
144         gst_event_new_step (GST_FORMAT_BUFFERS, 1, data->rate, TRUE, FALSE));
145     g_print ("Stepping one frame\n");
146     break;
147   case 'q':
148     g_main_loop_quit (data->loop);
149     break;
150   default:
151     break;
152   }
153
154   g_free (str);
155
156   return TRUE;
157 }
158
159 int main(int argc, char *argv[]) {
160   CustomData data;
161   GstStateChangeReturn ret;
162   GIOChannel *io_stdin;
163
164   /* Initialize GStreamer */
165   gst_init (&argc, &argv);
166
167   /* Initialize our data structure */
168   memset (&data, 0, sizeof (data));
169
170   /* Print usage map */
171   g_print (
172     "USAGE: Choose one of the following options, then press enter:\n"
173     " 'P' to toggle between PAUSE and PLAY\n"
174     " 'S' to increase playback speed, 's' to decrease playback speed\n"
175     " 'D' to toggle playback direction\n"
176     " 'N' to move to next frame (in the current direction, better in PAUSE)\n"
177     " 'Q' to quit\n");
178
179   /* Build the pipeline */
180   data.pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
181
182   /* Add a keyboard watch so we get notified of keystrokes */
183 #ifdef G_OS_WIN32
184   io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
185 #else
186   io_stdin = g_io_channel_unix_new (fileno (stdin));
187 #endif
188   g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);
189
190   /* Start playing */
191   ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
192   if (ret == GST_STATE_CHANGE_FAILURE) {
193     g_printerr ("Unable to set the pipeline to the playing state.\n");
194     gst_object_unref (data.pipeline);
195     return -1;
196   }
197   data.playing = TRUE;
198   data.rate = 1.0;
199
200   /* Create a GLib Main Loop and set it to run */
201   data.loop = g_main_loop_new (NULL, FALSE);
202   g_main_loop_run (data.loop);
203
204   /* Free resources */
205   g_main_loop_unref (data.loop);
206   g_io_channel_unref (io_stdin);
207   gst_element_set_state (data.pipeline, GST_STATE_NULL);
208   if (data.video_sink != NULL)
209     gst_object_unref (data.video_sink);
210   gst_object_unref (data.pipeline);
211   return 0;
212 }
213 ```
214
215
216 > ![Information](images/icons/emoticons/information.png)
217 > Need help?
218 >
219 > 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:
220 >
221 > `` gcc basic-tutorial-13.c -o basic-tutorial-13 `pkg-config --cflags --libs gstreamer-1.0` ``
222 >
223 >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).
224 >
225 > 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. The console shows the available commands, composed of a single upper-case or lower-case letter, which you should input followed by the Enter key.
226 >
227 > Required libraries: `gstreamer-1.0`
228
229 ## Walkthrough
230
231 There is nothing new in the initialization code in the main function:  a
232 `playbin` pipeline is instantiated, an I/O watch is installed to track
233 keystrokes and a GLib main loop is executed.
234
235 Then, in the keyboard handler function:
236
237 ``` c
238 /* Process keyboard input */
239 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
240   gchar *str = NULL;
241
242   if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) {
243     return TRUE;
244   }
245
246   switch (g_ascii_tolower (str[0])) {
247   case 'p':
248     data->playing = !data->playing;
249     gst_element_set_state (data->pipeline, data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
250     g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
251     break;
252 ```
253
254 Pause / Playing toggle is handled with `gst_element_set_state()` as in
255 previous tutorials.
256
257 ``` c
258 case 's':
259   if (g_ascii_isupper (str[0])) {
260     data->rate *= 2.0;
261   } else {
262     data->rate /= 2.0;
263   }
264   send_seek_event (data);
265   break;
266 case 'd':
267   data->rate *= -1.0;
268   send_seek_event (data);
269   break;
270 ```
271
272 Use ‘S’ and ‘s’ to double or halve the current playback rate, and ‘d’ to
273 reverse the current playback direction. In both cases, the
274 `rate` variable is updated and `send_seek_event` is called. Let’s
275 review this function.
276
277 ``` c
278 /* Send seek event to change rate */
279 static void send_seek_event (CustomData *data) {
280   gint64 position;
281   GstEvent *seek_event;
282
283   /* Obtain the current position, needed for the seek event */
284   if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
285     g_printerr ("Unable to retrieve current position.\n");
286     return;
287   }
288 ```
289
290 This function creates a new Seek Event and sends it to the pipeline to
291 update the rate. First, the current position is recovered with
292 `gst_element_query_position()`. This is needed because the Seek Event
293 jumps to another position in the stream, and, since we do not actually
294 want to move, we jump to the current position. Using a Step Event would
295 be simpler, but this event is not currently fully functional, as
296 explained in the Introduction.
297
298 ``` c
299 /* Create the seek event */
300 if (data->rate > 0) {
301   seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
302       GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_NONE, 0);
303 } else {
304   seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
305       GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, position);
306 }
307 ```
308
309 The Seek Event is created with `gst_event_new_seek()`. Its parameters
310 are, basically, the new rate, the new start position and the new stop
311 position. Regardless of the playback direction, the start position must
312 be smaller than the stop position, so the two playback directions are
313 treated differently.
314
315 ``` c
316 if (data->video_sink == NULL) {
317   /* If we have not done so, obtain the sink through which we will send the seek events */
318   g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
319 }
320 ```
321
322 As explained in the Introduction, to avoid performing multiple Seeks,
323 the Event is sent to only one sink, in this case, the video sink. It is
324 obtained from `playbin` through the `video-sink` property. It is read
325 at this time instead at initialization time because the actual sink may
326 change depending on the media contents, and this won’t be known until
327 the pipeline is PLAYING and some media has been read.
328
329 ``` c
330 /* Send the event */
331 gst_element_send_event (data->video_sink, seek_event);
332 ```
333
334 The new Event is finally sent to the selected sink with
335 `gst_element_send_event()`.
336
337 Back to the keyboard handler, we still miss the frame stepping code,
338 which is really simple:
339
340 ``` c
341 case 'n':
342   if (data->video_sink == NULL) {
343     /* If we have not done so, obtain the sink through which we will send the step events */
344     g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
345   }
346
347   gst_element_send_event (data->video_sink,
348       gst_event_new_step (GST_FORMAT_BUFFERS, 1, data->rate, TRUE, FALSE));
349   g_print ("Stepping one frame\n");
350   break;
351 ```
352
353 A new Step Event is created with `gst_event_new_step()`, whose
354 parameters basically specify the amount to skip (1 frame in the example)
355 and the new rate (which we do not change).
356
357 The video sink is grabbed from `playbin` in case we didn’t have it yet,
358 just like before.
359
360 And with this we are done. When testing this tutorial, keep in mind that
361 backward playback is not optimal in many elements.
362
363 > ![Warning](images/icons/emoticons/warning.png)
364 >
365 >Changing the playback rate might only work with local files. If you cannot modify it, try changing the URI passed to `playbin` in line 114 to a local URI, starting with `file:///`
366 </table>
367
368 ## Conclusion
369
370 This tutorial has shown:
371
372   - How to change the playback rate using a Seek Event, created with
373     `gst_event_new_seek()` and fed to the pipeline
374     with `gst_element_send_event()`.
375   - How to advance a video frame-by-frame by using Step Events, created
376     with `gst_event_new_step()`.
377
378 It has been a pleasure having you here, and see you soon!