1 # Basic tutorial 13: Playback speed
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
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
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.
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.
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.
33 In [](tutorial-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.
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.
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.
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.
59 ## A trick mode player
61 Copy this code into a text file named `basic-tutorial-13.c`.
63 **basic-tutorial-13.c**
70 typedef struct _CustomData {
72 GstElement *video_sink;
75 gboolean playing; /* Playing or Paused */
76 gdouble rate; /* Current playback rate (can be negative) */
79 /* Send seek event to change rate */
80 static void send_seek_event (CustomData *data) {
82 GstFormat format = GST_FORMAT_TIME;
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");
91 /* Create the seek event */
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);
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);
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);
106 gst_element_send_event (data->video_sink, seek_event);
108 g_print ("Current rate: %g\n", data->rate);
111 /* Process keyboard input */
112 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
115 if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) {
119 switch (g_ascii_tolower (str[0])) {
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");
126 if (g_ascii_isupper (str[0])) {
131 send_seek_event (data);
135 send_seek_event (data);
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);
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");
148 g_main_loop_quit (data->loop);
159 int main(int argc, char *argv[]) {
161 GstStateChangeReturn ret;
162 GIOChannel *io_stdin;
164 /* Initialize GStreamer */
165 gst_init (&argc, &argv);
167 /* Initialize our data structure */
168 memset (&data, 0, sizeof (data));
170 /* Print usage map */
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"
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);
182 /* Add a keyboard watch so we get notified of keystrokes */
184 io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
186 io_stdin = g_io_channel_unix_new (fileno (stdin));
188 g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc)handle_keyboard, &data);
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);
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);
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);
216 > ![Information](images/icons/emoticons/information.png)
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:
221 > `` gcc basic-tutorial-13.c -o basic-tutorial-13 `pkg-config --cflags --libs gstreamer-1.0` ``
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).
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.
227 > Required libraries: `gstreamer-1.0`
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.
235 Then, in the keyboard handler function:
238 /* Process keyboard input */
239 static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
242 if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) {
246 switch (g_ascii_tolower (str[0])) {
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");
254 Pause / Playing toggle is handled with `gst_element_set_state()` as in
259 if (g_ascii_isupper (str[0])) {
264 send_seek_event (data);
268 send_seek_event (data);
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.
278 /* Send seek event to change rate */
279 static void send_seek_event (CustomData *data) {
281 GstEvent *seek_event;
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");
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.
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);
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);
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
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);
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.
331 gst_element_send_event (data->video_sink, seek_event);
334 The new Event is finally sent to the selected sink with
335 `gst_element_send_event()`.
337 Back to the keyboard handler, we still miss the frame stepping code,
338 which is really simple:
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);
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");
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).
357 The video sink is grabbed from `playbin` in case we didn’t have it yet,
360 And with this we are done. When testing this tutorial, keep in mind that
361 backward playback is not optimal in many elements.
363 > ![Warning](images/icons/emoticons/warning.png)
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:///`
370 This tutorial has shown:
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()`.
378 It has been a pleasure having you here, and see you soon!