摘要

  在常见的媒体播放器中,通常可以看到快进,快退,慢放等功能,这部分功能被称为“特技模式(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时间的构造及发送示例如下:

  1. GstEvent *event;
  2. gboolean result;
  3. ...
  4. // construct a seek event to play the media from second 2 to 5, flush
  5. // the pipeline to decrease latency.
  6. event = gst_event_new_seek (1.0,
  7. GST_FORMAT_TIME,
  8. GST_SEEK_FLAG_FLUSH,
  9. GST_SEEK_TYPE_SET, * GST_SECOND,
  10. GST_SEEK_TYPE_SET, * GST_SECOND);
  11. ...
  12. result = gst_element_send_event (video_sink, event);
  13. if (!result)
  14. g_warning ("seek failed");
  15. ...

示例代码

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

  1. #include <string.h>
  2. #include <stdio.h>
  3. #include <gst/gst.h>
  4.  
  5. typedef struct _CustomData
  6. {
  7. GstElement *pipeline;
  8. GstElement *video_sink;
  9. GMainLoop *loop;
  10.  
  11. gboolean playing; /* Playing or Paused */
  12. gdouble rate; /* Current playback rate (can be negative) */
  13. } CustomData;
  14.  
  15. /* Send seek event to change rate */
  16. static void
  17. send_seek_event (CustomData * data)
  18. {
  19. gint64 position;
  20. GstEvent *seek_event;
  21.  
  22. /* Obtain the current position, needed for the seek event */
  23. if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
  24. g_printerr ("Unable to retrieve current position.\n");
  25. return;
  26. }
  27.  
  28. /* Create the seek event */
  29. if (data->rate > ) {
  30. seek_event =
  31. gst_event_new_seek (data->rate, GST_FORMAT_TIME,
  32. GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
  33. position, GST_SEEK_TYPE_END, );
  34. } else {
  35. seek_event =
  36. gst_event_new_seek (data->rate, GST_FORMAT_TIME,
  37. GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, ,
  38. GST_SEEK_TYPE_SET, position);
  39. }
  40.  
  41. if (data->video_sink == NULL) {
  42. /* If we have not done so, obtain the sink through which we will send the seek events */
  43. g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
  44. }
  45.  
  46. /* Send the event */
  47. gst_element_send_event (data->video_sink, seek_event);
  48.  
  49. g_print ("Current rate: %g\n", data->rate);
  50. }
  51.  
  52. /* Process keyboard input */
  53. static gboolean
  54. handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
  55. {
  56. gchar *str = NULL;
  57.  
  58. if (g_io_channel_read_line (source, &str, NULL, NULL,
  59. NULL) != G_IO_STATUS_NORMAL) {
  60. return TRUE;
  61. }
  62.  
  63. switch (g_ascii_tolower (str[])) {
  64. case 'p':
  65. data->playing = !data->playing;
  66. gst_element_set_state (data->pipeline,
  67. data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
  68. g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
  69. break;
  70. case 's':
  71. if (g_ascii_isupper (str[])) {
  72. data->rate *= 2.0;
  73. } else {
  74. data->rate /= 2.0;
  75. }
  76. send_seek_event (data);
  77. break;
  78. case 'd':
  79. data->rate *= -1.0;
  80. send_seek_event (data);
  81. break;
  82. case 'n':
  83. if (data->video_sink == NULL) {
  84. /* If we have not done so, obtain the sink through which we will send the step events */
  85. g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
  86. }
  87.  
  88. gst_element_send_event (data->video_sink,
  89. gst_event_new_step (GST_FORMAT_BUFFERS, , ABS (data->rate), TRUE,
  90. FALSE));
  91. g_print ("Stepping one frame\n");
  92. break;
  93. case 'q':
  94. g_main_loop_quit (data->loop);
  95. break;
  96. default:
  97. break;
  98. }
  99.  
  100. g_free (str);
  101.  
  102. return TRUE;
  103. }
  104.  
  105. int
  106. main (int argc, char *argv[])
  107. {
  108. CustomData data;
  109. GstStateChangeReturn ret;
  110. GIOChannel *io_stdin;
  111.  
  112. /* Initialize GStreamer */
  113. gst_init (&argc, &argv);
  114.  
  115. /* Initialize our data structure */
  116. memset (&data, , sizeof (data));
  117.  
  118. /* Print usage map */
  119. g_print ("USAGE: Choose one of the following options, then press enter:\n"
  120. " 'P' to toggle between PAUSE and PLAY\n"
  121. " 'S' to increase playback speed, 's' to decrease playback speed\n"
  122. " 'D' to toggle playback direction\n"
  123. " 'N' to move to next frame (in the current direction, better in PAUSE)\n"
  124. " 'Q' to quit\n");
  125.  
  126. /* Build the pipeline */
  127. data.pipeline =
  128. gst_parse_launch
  129. ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
  130. NULL);
  131.  
  132. /* Add a keyboard watch so we get notified of keystrokes */
  133. #ifdef G_OS_WIN32
  134. io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
  135. #else
  136. io_stdin = g_io_channel_unix_new (fileno (stdin));
  137. #endif
  138. g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc) handle_keyboard, &data);
  139.  
  140. /* Start playing */
  141. ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
  142. if (ret == GST_STATE_CHANGE_FAILURE) {
  143. g_printerr ("Unable to set the pipeline to the playing state.\n");
  144. gst_object_unref (data.pipeline);
  145. return -;
  146. }
  147. data.playing = TRUE;
  148. data.rate = 1.0;
  149.  
  150. /* Create a GLib Main Loop and set it to run */
  151. data.loop = g_main_loop_new (NULL, FALSE);
  152. g_main_loop_run (data.loop);
  153.  
  154. /* Free resources */
  155. g_main_loop_unref (data.loop);
  156. g_io_channel_unref (io_stdin);
  157. gst_element_set_state (data.pipeline, GST_STATE_NULL);
  158. if (data.video_sink != NULL)
  159. gst_object_unref (data.video_sink);
  160. gst_object_unref (data.pipeline);
  161. return ;
  162. }

  通过下面的命令编译即可得到可执行文件,在终端输入相应指令可修改播放速率。

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

源码分析

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

  1. /* Process keyboard input */
  2. static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
  3. gchar *str = NULL;
  4.  
  5. if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) {
  6. return TRUE;
  7. }
  8.  
  9. switch (g_ascii_tolower (str[])) {
  10. case 'p':
  11. data->playing = !data->playing;
  12. gst_element_set_state (data->pipeline, data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
  13. g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
  14. break;

  在终端输入P时,使用gst_element_set_state ()设置播放状态。

  1. case 's':
  2. if (g_ascii_isupper (str[])) {
  3. data->rate *= 2.0;
  4. } else {
  5. data->rate /= 2.0;
  6. }
  7. send_seek_event (data);
  8. break;
  9. case 'd':
  10. data->rate *= -1.0;
  11. send_seek_event (data);
  12. break;

  通过S和s增加和降低播放速度,d用于改变播放方向(倒放),这里在修改rate后,调用send_seek_event实现真正的处理。

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

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

  1. /* Create the seek event */
  2. if (data->rate > ) {
  3. seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
  4. GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_END, );
  5. } else {
  6. seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
  7. GST_SEEK_TYPE_SET, , GST_SEEK_TYPE_SET, position);
  8. }

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

  1. if (data->video_sink == NULL) {
  2. /* If we have not done so, obtain the sink through which we will send the seek events */
  3. g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
  4. }
  5. /* Send the event */
  6. gst_element_send_event (data->video_sink, seek_event);

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

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

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

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

  

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

总结

通过本文我们掌握了:

  • 如何通过gst_event_new_seek()构造SeekEvent,通过gst_element_send_event()发送到sink改变速率。
  • 如何通过gst_event_new_step()实现单帧播放。

引用

https://gstreamer.freedesktop.org/documentation/tutorials/basic/playback-speed.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/additional/design/seeking.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/additional/design/framestep.html?gi-language=c

作者:John.Leng
本文版权归作者所有,欢迎转载。商业转载请联系作者获得授权,非商业转载请在文章页面明显位置给出原文连接.

GStreamer基础教程07 - 播放速率控制的更多相关文章

  1. 【GStreamer开发】GStreamer基础教程13——播放速度

    目标 快进,倒放和慢放是trick模式的共同技巧,它们有一个共同点就是它们都修改了播放的速度.本教程会展示如何来获得这些效果和如何进行逐帧的跳跃.主要内容是: 如何来变换播放的速度,变快或者变慢,前进 ...

  2. 【GStreamer开发】GStreamer基础教程07——多线程和Pad的有效性

    目标 GStreamer会自动处理多线程这部分,但在有些情况下,你需要手动对线程做解耦.本教程会教你怎样才能做到这一点,另外也展示了Pad的有效性.主要内容包括: 如何针对部分的pipeline建立一 ...

  3. GStreamer基础教程05 - 播放时间控制

    简介 在多媒体应用中,我们通常需要查询媒体文件的总时间.当前播放位置,以及跳转到指定的时间点.GStreamer提供了相应的接口来实现此功能,在本文中,我们将通过示例了解如何查询时间信息,以及如何进行 ...

  4. 【GStreamer开发】GStreamer基础教程14——常用的element

    目标 本教程给出了一系列开发中常用的element.它们包括大杂烩般的eleemnt(比如playbin2)以及一些调试时很有用的element. 简单来说,下面用gst-launch这个工具给出一个 ...

  5. 【GStreamer开发】GStreamer基础教程10——GStreamer工具

    目标 GStreamer提供了一系列方便使用的工具.这篇教程里不牵涉任何代码,但还是会讲一些有用的内容: 如何在命令行下建立一个pipeline--完全不使用C 如何找出一个element的Capab ...

  6. 【GStreamer开发】GStreamer基础教程08——pipeline的快捷访问

    目标 GStreamer建立的pipeline不需要完全关闭.有多种方法可以让数据在任何时候送到pipeline中或者从pipeline中取出.本教程会展示: 如何把外部数据送到pipeline中 如 ...

  7. GStreamer基础教程02 - 基本概念

    摘要 在 Gstreamer基础教程01 - Hello World中,我们介绍了如何快速的通过一个字符串创建一个简单的pipeline.为了能够更好的控制pipline中的element,我们需要单 ...

  8. GStreamer基础教程09 - Appsrc及Appsink

    摘要 在我们前面的文章中,我们的Pipline都是使用GStreamer自带的插件去产生/消费数据.在实际的情况中,我们的数据源可能没有相应的gstreamer插件,但我们又需要将数据发送到GStre ...

  9. 【GStreamer开发】GStreamer基础教程05——集成GUI工具

    目标 本教程展示了如何在GStreamer集成一个GUI(比如:GTK+).最基本的原则是GStreamer处理多媒体的播放而GUI处理和用户的交互. 在这个教程里面,我们可以学到: 如何告诉GStr ...

随机推荐

  1. 【HDU - 3533】Escape(bfs)

    Escape  Descriptions: 一个人从(0,0)跑到(n,m),只有k点能量,一秒消耗一点,在图中有k个炮塔,给出炮塔的射击方向c,射击间隔t,子弹速度v,坐标x,y问这个人能不能安全到 ...

  2. 实验吧--web--天下武功唯快不破

    ---恢复内容开始--- 英文翻译过来嘛,就是:天下武功无快不破嘛.(出题者还是挺切题的) 看看前端源码: 注意这里 please post what you find with parameter: ...

  3. sql注入篇2

    一.前言 上一篇:sql注入篇1 二.基于回显的注入类型判断 1.有结果的注入 例如下图: (sqlllab less-1)可以看到有正常结果返回,对于的利用方式就是老套路了,先order by查询出 ...

  4. 「Sqlserver」数据分析师有理由爱Sqlserver之九-无利益关系推荐Sqlserver书单

    在前面系列文章的讲述下,部分读者有兴趣进入Sqlserver的世界的话,笔者不太可能在自媒体的载体上给予全方位的带领,最合适的方式是通过系统的书籍来学习,此篇给大家梳理下笔者曾经看过的自觉不错值得推荐 ...

  5. 《C# 语言学习笔记》——C# 简介

    1 什么是.NET Framework .NET Framework 是Microsoft为开发应用程序而创建的一个富有革命性的新平台. 1.1 .NET Framework 的内容 .NET Fra ...

  6. TCP传输协议如何进行拥塞控制?

    拥塞控制 拥塞现象是指到达通信子网中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象.这种现象跟公路网中 ...

  7. 【Spring】The matching wildcard is strict……

    applicationContext.xml 文件抛出了这个异常信息. 解决方法: 需要在 namespace 后加上对应的 schemaLocation,如下所示: <?xml version ...

  8. 【iOS】the executable was signed with invalid entitlements

    又遇到了这个问题,貌似之前遇到过,如图所示: 原因:开发证书里没添加手机. PS: Xcode7 除外,据说已经不需要证书了,这里用的是 6.4

  9. 【Android】Mac Android adb 配置

    打开终端,输入下面命令: touch .bash_profile open -e .bash_profile 即新建 “.bash_profile” 文件,并会弹出 “.bash_profile” 文 ...

  10. maven添加oracle驱动包

    问题描述 项目用到了oracle,但由于oracle商业版权问题,maven在中心资源库直接下载jar包是要收费的 解决方法 第一步: 下载ojdbc6.jar 第二步: 将下载的jar放入项目的li ...