GStreamer Basic tutorial 13 Playback Speed

"GStreamer 基本教程13: 播放速度"

Posted by Stephen on March 25, 2022

前言

本文是GStreamer学习笔记,也可以看成是对原文的意译。

这些教程描述了理解其余教程所需的GStreamer主题。

GStreamer教程:

基础教程 : GStreamer 介绍

基础教程 1: Hello world!

基础教程 2: GStreamer 概念

基础教程 3: 动态管道

基础教程 4: 时间管理

基础教程 5: GUI工具包集成

基础教程 6: 媒体格式和pad功能

基础教程 7: 多线程和Pad可用性

基础教程 8: 管道短路操作

基础教程 9: 媒体信息收集

基础教程 10: GStreamer工具

基础教程 11: 调试工具

基础教程 12: 流媒体

基础教程 13: 播放速度

基础教程 14: 有用的元素

基础教程 16: 特定平台元素

环境

系统环境

Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.4 LTS
Release:	18.04
Codename:	bionic
Linux version :       5.3.0-46-generic ( buildd@lcy01-amd64-013 ) 
Gcc version:         7.5.0  ( Ubuntu 7.5.0-3ubuntu1~18.04 )

软件信息

version : 	
     GStreamer-1.0

正文

1. 目标

快进、倒放和慢动作都是统称为特技模式的技术,它们都有共同点,可以修改正常的播放速率。本教程展示了如何实现这些效果并将帧步进添加到交易中。特别是,它表明:

  • 如何改变播放速率,比正常快和慢,向前和向后。
  • 如何逐帧推进视频

2. 介绍

快进是以高于其正常(预期)速度的速度播放媒体的技术;而慢动作使用的速度低于预期的速度。反向播放做同样的事情,但向后,从流的末尾到开头。

所有这些技术所做的就是改变播放速率,这是一个等于 1.0 的变量,用于正常播放,大于 1.0(绝对值)用于快速模式,低于 1.0(绝对值)用于慢速模式,正向播放和反向播放的负数。

GStreamer 提供了两种改变播放速率的机制:Step Events 和 Seek Events。除了更改后续播放速率(仅更改为正值)之外,步骤事件还允许跳过给定数量的媒体。此外,Seek Events 允许跳转到流中的任何位置并设置正负播放速率。

基础教程 4: 时间管理搜索事件中已经显示,使用辅助函数来隐藏它们的复杂性。本教程进一步解释了如何使用这些事件。

由于创建它们所需的参数数量减少,步进事件是更改播放速率的更方便的方法;但是,它们也有一些缺点,因此本教程中使用了 Seek Events。步骤事件只影响接收器(在管道的末端),所以它们只有在管道的其余部分可以支持以不同的速度运行时才会起作用,Seek 事件一直通过管道,所以每个元素都可以对它们做出反应. Step 事件的好处是它们的动作要快得多。Step 事件也无法改变播放方向。

为了使用这些事件,它们被创建然后传递到管道,它们在管道中向上游传播,直到它们到达可以处理它们的元素。如果一个事件被传递到一个 bin 元素上,比如playbin,它只会将该事件提供给它的所有接收器,这将导致执行多次搜索。常用的方法是通过视频接收器或音频接收器属性检索一个播放素材的接收器,并将事件直接馈送到接收器中。

帧步进是一种允许逐帧播放视频的技术。它是通过暂停管道,然后每次发送 Step Events 跳过一帧来实现的。

3. 特技模型播发器

basic-tutorial-13.c

   #include <string.h>
   #include <stdio.h>
   #include <gst/gst.h>
   
   typedef struct _CustomData
   {
     GstElement *pipeline;
     GstElement *video_sink;
     GMainLoop *loop;
   
     gboolean playing;             /* Playing or Paused */
     gdouble rate;                 /* Current playback rate (can be negative) */
   } CustomData;
   
   /* Send seek event to change rate */
   static void
   send_seek_event (CustomData * data)
   {
     gint64 position;
     GstEvent *seek_event;
   
     /* Obtain the current position, needed for the seek event */
     if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
       g_printerr ("Unable to retrieve current position.\n");
       return;
     }
   
     /* Create the seek event */
     if (data->rate > 0) {
       seek_event =
           gst_event_new_seek (data->rate, GST_FORMAT_TIME,
           GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
           position, GST_SEEK_TYPE_END, 0);
     } else {
       seek_event =
           gst_event_new_seek (data->rate, GST_FORMAT_TIME,
           GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, 0,
           GST_SEEK_TYPE_SET, position);
     }
   
     if (data->video_sink == NULL) {
       /* If we have not done so, obtain the sink through which we will send the seek events */
       g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
     }
   
     /* Send the event */
     gst_element_send_event (data->video_sink, seek_event);
   
     g_print ("Current rate: %g\n", data->rate);
   }
   
   /* Process keyboard input */
   static gboolean
   handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
   {
     gchar *str = NULL;
   
     if (g_io_channel_read_line (source, &str, NULL, NULL,
             NULL) != G_IO_STATUS_NORMAL) {
       return TRUE;
     }
   
     switch (g_ascii_tolower (str[0])) {
       case 'p':
         data->playing = !data->playing;
         gst_element_set_state (data->pipeline,
             data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
         g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
         break;
       case 's':
         if (g_ascii_isupper (str[0])) {
           data->rate *= 2.0;
         } else {
           data->rate /= 2.0;
         }
         send_seek_event (data);
         break;
       case 'd':
         data->rate *= -1.0;
         send_seek_event (data);
         break;
       case 'n':
         if (data->video_sink == NULL) {
           /* If we have not done so, obtain the sink through which we will send the step events */
           g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
         }
   
         gst_element_send_event (data->video_sink,
             gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS (data->rate), TRUE,
                 FALSE));
         g_print ("Stepping one frame\n");
         break;
       case 'q':
         g_main_loop_quit (data->loop);
         break;
       default:
         break;
     }
   
     g_free (str);
   
     return TRUE;
   }
   
   int
   main (int argc, char *argv[])
   {
     CustomData data;
     GstStateChangeReturn ret;
     GIOChannel *io_stdin;
   
     /* Initialize GStreamer */
     gst_init (&argc, &argv);
   
     /* Initialize our data structure */
     memset (&data, 0, sizeof (data));
   
     /* Print usage map */
     g_print ("USAGE: Choose one of the following options, then press enter:\n"
         " 'P' to toggle between PAUSE and PLAY\n"
         " 'S' to increase playback speed, 's' to decrease playback speed\n"
         " 'D' to toggle playback direction\n"
         " 'N' to move to next frame (in the current direction, better in PAUSE)\n"
         " 'Q' to quit\n");
   
     /* Build the pipeline */
     data.pipeline =
         gst_parse_launch
         ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
         NULL);
   
     /* Add a keyboard watch so we get notified of keystrokes */
   #ifdef G_OS_WIN32
     io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
   #else
     io_stdin = g_io_channel_unix_new (fileno (stdin));
   #endif
     g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc) handle_keyboard, &data);
   
     /* Start playing */
     ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
     if (ret == GST_STATE_CHANGE_FAILURE) {
       g_printerr ("Unable to set the pipeline to the playing state.\n");
       gst_object_unref (data.pipeline);
       return -1;
     }
     data.playing = TRUE;
     data.rate = 1.0;
   
     /* Create a GLib Main Loop and set it to run */
     data.loop = g_main_loop_new (NULL, FALSE);
     g_main_loop_run (data.loop);
   
     /* Free resources */
     g_main_loop_unref (data.loop);
     g_io_channel_unref (io_stdin);
     gst_element_set_state (data.pipeline, GST_STATE_NULL);
     if (data.video_sink != NULL)
       gst_object_unref (data.video_sink);
     gst_object_unref (data.pipeline);
     return 0;
   }

编译和运行

   gcc basic-tutorial-13.c -o basic-tutorial-13 `pkg-config --cflags --libs gstreamer-1.0`
   ./basic-tutorial-13

4. 演练

在 main 函数的初始化代码中没有什么新的东西:一个 playbin管道被实例化,一个 I/O 监视被安装来跟踪击键和一个 GLib 主循环被执行。

然后,在键盘处理函数中:

   /* Process keyboard input */
   static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
     gchar *str = NULL;
   
     if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) {
       return TRUE;
     }
   
     switch (g_ascii_tolower (str[0])) {
     case 'p':
       data->playing = !data->playing;
       gst_element_set_state (data->pipeline, data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
       g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
       break;

暂停/播放切换的处理方式与gst_element_set_state()之前的教程相同。

   case 's':
     if (g_ascii_isupper (str[0])) {
       data->rate *= 2.0;
     } else {
       data->rate /= 2.0;
     }
     send_seek_event (data);
     break;
   case 'd':
     data->rate *= -1.0;
     send_seek_event (data);
     break;

使用“S”和“s”将当前播放速率加倍或减半,使用“d”反转当前播放方向。在这两种情况下, 都会更新并调用rate变量。send_seek_event让我们回顾一下这个函数。

   /* Send seek event to change rate */
   static void send_seek_event (CustomData *data) {
     gint64 position;
     GstEvent *seek_event;
   
     /* Obtain the current position, needed for the seek event */
     if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
       g_printerr ("Unable to retrieve current position.\n");
       return;
     }

此函数创建一个新的 Seek Event 并将其发送到管道以更新速率。首先,用 恢复当前位置 gst_element_query_position()。这是必需的,因为 Seek 事件会跳转到流中的另一个位置,并且由于我们实际上不想移动,所以我们跳转到当前位置。使用 Step Event 会更简单,但这个事件目前还不能完全发挥作用,如介绍中所述。

   /* Create the seek event */
   if (data->rate > 0) {
     seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
         GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_END, 0);
   } else {
     seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
         GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, position);
   }

Seek 事件是用gst_event_new_seek(). 它的参数基本上是新的速率、新的开始位置和新的停止位置。无论播放方向如何,开始位置都必须小于停止位置,因此对两个播放方向进行不同的处理。

   if (data->video_sink == NULL) {
     /* If we have not done so, obtain the sink through which we will send the seek events */
     g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
   }

正如简介中所解释的,为避免执行多次搜索,事件仅发送到一个接收器,在本例中为视频接收器。它是playbin通过video-sink属性获得的。PLAYING此时读取它而不是在初始化时读取,因为实际接收器可能会根据媒体内容而改变,并且直到管道被读取并且某些媒体已被读取时才会知道。

   /* Send the event */
   gst_element_send_event (data->video_sink, seek_event);

新事件最终被发送到选定的接收器 gst_element_send_event()

回到键盘处理程序,我们仍然错过了帧步进代码,这真的很简单:

   case 'n':
     if (data->video_sink == NULL) {
       /* If we have not done so, obtain the sink through which we will send the step events */
       g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
     }
   
     gst_element_send_event (data->video_sink,
         gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS (data->rate), TRUE, FALSE));
     g_print ("Stepping one frame\n");
     break;

创建一个新的 Step Event gst_event_new_step(),其参数基本上指定要跳过的数量(示例中为 1 帧)和新的速率(我们不会更改)。

视频接收器被抓取playbin,以防我们还没有它,就像以前一样。

这样我们就完成了。在测试本教程时,请记住,向后播放在许多元素中并不是最佳的。

更改播放速率可能仅适用于本地文件。如果无法修改,请尝试将第playbin114 行中传递给的 URI 更改为本地 URI,以file:///

5. 结论

本教程学习了:

  • 如何使用 Seek 事件更改播放速率, gst_event_new_seek()使用gst_element_send_event().
  • 如何使用创建的步骤事件逐帧推进视频gst_event_new_step()

后记