前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GStreamer基础教程07 - 播放速率控制

GStreamer基础教程07 - 播放速率控制

原创
作者头像
不会飞的小鸟
修改2019-08-26 10:04:20
2K0
修改2019-08-26 10:04:20
举报
文章被收录于专栏:只为你下只为你下

  在常见的媒体播放器中,通常可以看到快进,快退,慢放等功能,这部分功能被称为“特技模式(Trick Mode)”,这些模式有个共同点:都通过修改播放的速率来达到相应的目的。 本文将介绍如何通过GStreamer去实现快进,快退,慢放以及单帧播放。

  

  GStreamer Seek与Step事件

  

  快进(Fast-Forward),快退(Fast-Rewind)和慢放(Slow-Motion)都是通过修改播放的速率来达到相应的目的。在GStreamer中,将1倍速作为正常的播放速率,将大于1倍速的2倍,4倍,8倍等倍速称为快进,慢放则是播放速率的绝对值小于1倍速,当播放速率小于0时,则进行倒放。

  

  在GStreamer中,我们通过seek与step事件来控制Element的播放速率及区域。Step事件允许跳过指定的区域并设置后续的播放速率(此速率必须大于0)。Seek事件允许跳转到播放文件中的的任何位置,并且播放速率可以大于0或小于0.

  

  在播放时间控制中,我们使用gst_element_seek_simple 来快速的跳转到指定的位置,此函数是对seek事件的封装。实际使用时,我们首先需要构造一个seek event,设置seek的绝对起始位置和停止位置,停止位置可以设置为0,这样会执行seek的播放速率直到结束。同时可以支持按buffer的方式进行seek,以及设置不同的标志指定seek的行为。

  

  Step事件相较于Seek事件需要更少的参数,更易用于修改播放速率,但是不够灵活。Step事件只会作用于最终的sink,Seek事件则可以作用于Pipeline中所有的Element。Step操作的效率高于Seek。

  

  在GStreamer中,单帧播放(Frame Stepping)与快进相同,也是通过事件实现。单帧播放通常在暂停的状态下,构造并发送step event每次播放一帧。

  

  需要注意的是,seek event需要直接作用于sink element(eg: audio sink或video sink),如果直接将seek event作用于Pipeline,Pipeline会自动将事件转发给所有的sink,如果有多个sink,就会造成多次seek。通常是先获取Pipeline中的video-sink或audio-sink,然后发送seek event到指定的sink,完成seek的操作。 Seek时间的构造及发送示例如下:

  

  复制代码

  

  GstEvent *event;

  

  gboolean result;

  

  ...

  

  // construct a seek event to play the media from second 2 to 5, flush

  

  // the pipeline to decrease latency.

  

  event = gst_event_new_seek (1.0,

  

  GST_FORMAT_TIME,

  

  GST_SEEK_FLAG_FLUSH,

  

  GST_SEEK_TYPE_SET, 2 * GST_SECOND,

  

  GST_SEEK_TYPE_SET, 5 * GST_SECOND);

  

  ...

  

  result = gst_element_send_event (video_sink, event);

  

  if (!result)

  

  g_warning ("seek failed");

  

  ...

  

  复制代码

  

  示例代码

  

  下面通过一个完整的示例,来查看GStreamer是如何通过seek和step达到相应的播放速度。

  

  复制代码

  

  #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 www.51kunlunyule.com 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 www.kunlunyulegw.com 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 (www.tongyayule.com 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 *www.dongfangyuld.com/

  

  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-7.c -o basic-tutorial-7 `pkg-config --cflags --libs gstreamer-1.0`

  

  源码分析

  

  本例中,Pipeline的创建与其他示例相同,通过playbin播放文件,采用GLib的I/O接口来处理键盘输入。

  

  复制代码

  

  /* Process keyboard input */

  

  static gboolean handle_keyboard (www.jintianxuesha.com 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 (www.fengshen157.com data->pipeline, data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);

  

  g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");

  

  break;

  

  复制代码

  

  在终端输入P时,使用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;

  

  }

  

  复制代码

  

  这个函数会构造一个SeekEvent发送到Pipeline以调节速率。因为Seek Event会跳转到指定的位置,但我们在此例汇总只想改变速率,不跳转到其他位置,所以首先通过gst_element_query_position ()获取当前的播放位置。

  

  复制代码

  

  /* 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);

  

  }

  

  复制代码

  

  通过gst_event_new_seek()创建SeekEvent,设置新的rate,flag,起始位置,结束位置。需要注意的是,起始位置需要小于结束位置。

  

  复制代码

  

  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);

  

  复制代码

  

  正如上文提到的,为了避免Pipeline执行多次的seek,我们在此处获取video-sink,并向其发送SeekEvent。我们直到执行Seek时才获取video-sink是因为实际的sink有可能会根据不同的媒体类型,在PLAYING状态时才创建。

  

  以上部分内容就是速率的修改,关于单帧播放的情况,实现方式更加简单:

  

  复制代码

  

  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;

  

  复制代码

  

  我们通过gst_event_new_step()创建了StepEvent,并指定只跳转一帧,且不改变当前速率。单帧播放通常都是先暂停,然后再进行单帧播放。

  

  以上就是通过GStreamer实现播放速率的控制,实际中,有些Element对倒放支持不是很好,不能达到理想的效果。

  

  总结

  

  通过本文我们掌握了:

  

  如何通过gst_event_new_seek()构造SeekEvent,通过gst_element_send_event()发送到sink改变速率。

  

  如何通过gst_event_new_step()实现单帧播放。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档