目标

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

如何来变换播放的速度,变快或者变慢,前进或者后退

如何一帧一帧的播放视频

介绍

快进是以超过正常速度播放媒体的一项技术,反之,慢放是以低于正常速度播放的技术。倒放和播放是一样的,只不过是从后面朝前面播放。

所有这些技术做的都是修改播放速度这件事,如果说正常播放速度是1.0的话,那么超过1.0这个数字就是快进,低于1.0这个数字就是慢放了,正数就是从前朝后放,负数就是从后超前放了。

GStreamer提供了两种来变换播放的速度:Step事件和Seek事件。Step事件可以在改变后面的播放速度的情况下跳过一个指定的间隔(只能向前播放)。Seek事件,就可以跳转到任意一个地方并且可以设置播放速度(正向反向都可以)。

在《GStreamer基础教程04——时间管理》里面已经演示过Seek事件了,使用了一个帮助函数来隐藏起复杂性。本教程会做更详细的解释。

Step事件因为需要的参数比较少,用来改变播放速度更加方便一点。但是,他们在GStreamer的实现还需要再做一点工作,所以这里用了Seek事件。

为了使用这些事件,需要先建立它们然后把它们传给pipeline,它们会向上游传播知道遇到能处理这些事件的element。如果一个事件传给了一个bin element(比如playbin2),它会简单地把事件给到它所有的sink,这可能会导致操作执行很多次。常见的做法是通过video-sink或者audio-sink属性找到一个playbin2的sink,然后直接把事件传给这个sink。

逐帧步进就是一帧一帧的播放视频,它是让pipeline暂停,然后发送Step事件给它,让它每次跳跃一帧。

一个神奇模式的播放器

[objc] view
plain
 copy

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

工作流程

在主函数里面的初始化代码没有任何新的东西:初始化一个playbin2,跟踪按键,运行一个GLib主循环。

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

[objc] view
plain
 copy

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

像前一讲一样用gst_element_set_state()来处理暂停/播放的交替。

[objc] view
plain
 copy

  1. <span style="font-size:14px;">  case 's':
  2. ])) {
  3. .0;
  4. } else {
  5. .0;
  6. }
  7. send_seek_event (data);
  8. break;
  9. case 'd':
  10. .0;
  11. send_seek_event (data);
  12. break;</span>

用‘S’来加倍播放的速度,'s'来把播放速度降低一倍,用'd'来转换播放的方向。在这几种情况的任何一种里面,rate这个变量是需要更新的,然后调用send_seek_event()这个方法。让我们来看一下函数:

[objc] view
plain
 copy

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

这个函数新创建了一个Seek时间,并发送给pipeline来更高播放速度。首先,用gst_element_query_position()方法来记录当前位置。这样做事因为Seek事件会跳转其他位置,如果我们后面不希望进行移动,那么就需要返回到原来的位置。用Step事件会简单一点,但Step事件现在还没完全完成,这个在前面已经介绍过了。

[objc] view
plain
 copy

  1. <span style="font-size:14px;">  /* Create the seek event */
  2. ) {
  3. seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
  4. );
  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, position);
  8. }</span>

我们用gst_event_new_seek()来建立Seek事件。参数基本上就是新的播放速度,新的起始位置和新的结束位置。无论播放的方向,起始位置都要在结束位置之前。所以两个播放的方向分成了两段处理。

[objc] view
plain
 copy

  1. <span style="font-size:14px;">  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. }</span>

正如前面解释的,为了避免对此进行Seek,事件只传给一个sink,在这里,就是视频的sink了。通过playbin2的video-sink属性来获得。这个动作在这里执行而不是在初始化时就执行是因为随着播放媒体的不同这个sink是不同的,所以在pipeline到PLAYING状态并已经读取了一些媒体数据前video-sink是不能确定的。

[objc] view
plain
 copy

  1. <span style="font-size:14px;">  /* Send the event */
  2. gst_element_send_event (data->video_sink, seek_event);</span>

最后,我们用gst_element_send_event()把新建的这个事件传给选中的sink。

回到键盘处理函数来,我们还没有提到帧步进的代码的是在这里实现的:

[objc] view
plain
 copy

  1. <span style="font-size:14px;">  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. gst_element_send_event (data->video_sink,
  7. , data->rate, TRUE, FALSE));
  8. g_print ("Stepping one frame\n");
  9. break;</span>

用gst_event_new_step()来创建一个新的Step事件,参数主要是指定的步长(这里是1帧)和速度(这里我们没改)。

获取一下playbin2的视频sink,就像前面提到过地那样。

大功告成!不过在测试本教程时,请记住回放是很多element不支持的。

(对于本地文件来说也可以修改播放速度,如果你要试验这一点,那么只要把传给playbin2的URL改成本地的URL即可。请注意,是用file:///来作为开头的)

【GStreamer开发】GStreamer基础教程13——播放速度的更多相关文章

  1. Android程序开发0基础教程(一)

    程序猿学英语就上视觉英语网 Android程序开发0基础教程(一)   平台简单介绍   令人激动的Google手机操作系统平台-Android在2007年11月13日正式公布了,这是一个开放源码的操 ...

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

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

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

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

  4. GStreamer基础教程13 - 调试Pipeline

    摘要 在很多情况下,我们需要对GStreamer创建的Pipeline进行调试,来了解其运行机制以解决所遇到的问题.为此,GStreamer提供了相应的调试机制,方便我们快速定位问题. 查看调试日志 ...

  5. Java基础教程(13)--包

      为了使类型更易于查找,避免命名冲突和访问控制,我们应该使用包来对自己定义的类型进行管理.这里说的类型可以是类.接口.枚举和注解(枚举和注解的内容会在后续教程中介绍).使用包来管理我们的代码,有以下 ...

  6. cocos基础教程(13)使用Physicals代替Box2D和chipmunk

    1.   概述 游戏中模拟真实的世界是个比较麻烦的事情,通常这种事情都是交给物理引擎来做.首屈一指的是Box2D了,它几乎能模拟所有的物理效果.而chipmunk则是个更轻量的引擎,能够满足简单的物理 ...

  7. iOS开发零基础教程之生成git所需的SSH keys

    在我们github看到了一个不错的第三方库时,可能我们想把他git clone到本地,我们需要复制他的SSH URL,如下图: 复制完地址之后,我们需要打开终端,然后输入命令: git clone + ...

  8. Ruby 基础教程1-3

    1.命令行参数ARGV[] 2.文件读取 file=File.open(filename)    text=file.read  print text file.close 一次读取所有内容耗内存,耗 ...

  9. Chrome扩展开发基础教程(附HelloWorld)

    1 概述 Chrome扩展开发的基础教程,代码基于原生JS+H5,教程内容基于谷歌扩展开发官方文档. 2 环境 Chrome 88.0.4324.96 Chromium 87.0.4280.141 B ...

随机推荐

  1. 交互设计算法基础(3) - Quick Sort

    int pivotIndex, pivot, swapIndex; void swap(int[] arr, int x, int y) { int temp = arr[x]; arr[x] = a ...

  2. linux线程操作

    初始化条件变量 int pthread_cond_init(pthread_cond_t *cv,pthread_cond_attr *cattr); 函数返回值:返回0表示成功,返回其他表示失败. ...

  3. 模板 - 数学 - 多项式 - 快速数论变换/NTT

    Huffman分治的NTT,常数一般.使用的时候把多项式的系数们放进vector里面,然后调用solve就可以得到它们的乘积.注意这里默认最大长度是1e6,可能需要改变. #include<bi ...

  4. ElasticSearch : 基础简介

    1.安装 我用的docker安装,这个用起来比较方便,我是在腾讯云部署的docker,具体的过两天总结一下 安装: docker pull elasticsearch 运行: docker run - ...

  5. 【caffe I/O】数据变换器(图像的预处理部分) 代码注释

    caffe.proto中TransformationParameter部分 // Message that stores parameters used to apply transformation ...

  6. JS中注入eval, Function等系统函数截获动态代码

    正文 现在很多网站都上了各种前端反爬手段,无论手段如何,最重要的是要把包含反爬手段的前端javascript代码加密隐藏起来,然后在运行时实时解密动态执行. 动态执行js代码无非两种方法,即eval和 ...

  7. C字符贪吃蛇

    算法参照Perl字符贪吃蛇,源码: #include <stdio.h> #include <windows.h> #define WIDTH 12 // 宽 #define ...

  8. Understanding Action Filters (C#) 可以用来做权限检查

    比如需要操作某一张表league的数据,multi-tenancy的模式,每一行数据都有一个租户id的字段. 那么在api调用操作的时候,我们需要检查league的id,是否和当前用户所属的租户信息一 ...

  9. 在Eclipse IDE进行Struts开发时提示错误:java.lang.ClassNotFoundException: org.apache.struts2.dispatcher.FilterDispatcher的解决办法

    If you have... included all necessary jars Configured build path correctly added them all in deploym ...

  10. CNS、ENS和PNS的发育过程

    central nervous system (CNS) peripheral nervous system (PNS) enteric nervous system (ENS) 做这部分的科研必须要 ...