摘要

  在常见的媒体播放器中,通常可以看到快进,快退,慢放等功能,这部分功能被称为“特技模式(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. apache CXF Service 简单使用

    cxf介绍 框架官网:cxf.apache.org 支持多种协议: SOAP1.1,1.2 XML/HTTP CORBA(Common Object Request Broker Architectu ...

  2. 在安装Openstack的keystone认证服务时,出现The request you have made requires authentication. (HTTP 401) (Request-ID: req-f94bebba-f0c5-4a92-85问题的处理

      创建openstack的keystone认证服务器报错: The request you have made requires authentication. (HTTP 401) (Reques ...

  3. Java EE.Servlet.会话管理

    一次会话是从客户打开浏览器开始到关闭浏览器结束.记录会话信息的技术称为会话跟踪.常见的会话跟踪技术有Cookie.URL重写和隐藏表单域. 1.Cookie Cookie是一小块可以嵌入到HTTP请求 ...

  4. docker跨主机通信扁平化网络的设计与实现

    端口映射.ovs. fannel,weave 1.使用网桥实现跨主机容器连接 使用Open vSwitch实现跨主机容器连接

  5. 转 java - java反射机制创建对象

    转 https://blog.csdn.net/nch_ren/article/details/78814080 1.创建service实现类 package com.springstudy.refl ...

  6. <<Modern CMake>> 翻译 2.3 与代码通信

    <<Modern CMake>> 翻译 2.3 与代码通信 配置文件 CMake 允许您使用代码通过 configure_file 存取 CMake 变量. 此命令复制一个文件 ...

  7. 【MySQL】ON DUPLICATE KEY UPDATE

    之前没用过这个操作,甚至没见过--最近接触到,而且还挺有用. 作用:若 KEY 不重复,则插入记录:否则更新记录. 单条操作: INSERT INTO table(a, b, c) VALUES (1 ...

  8. spring boot 学习笔记(一)之前端文件配置

    一.叙述 spring boot 由于是内置的tomcat ,因此其在独立运行的时候,是不需要单独安装 tomcat,这使其前端文件(CSS.JS.html)等放置的位置与war中的不同. 二.常见配 ...

  9. Java 性能优化(一)

    Java 性能调优(一) 1.衡量程序性能的标准 (1) 程序响应速度: (2) 内存占有情况: 2.程序调优措施 (1) 设计调优 设计调优处于所有调优手段 的上层,需要在软件开发之前进行.在软件开 ...

  10. WebSocket的实现与应用

    WebSocket的实现与应用 前言 说到websocket,就不得不提http协议的连接特点特点与交互模型. 首先,http协议的特点是无状态连接.即http的前一次连接与后一次连接是相互独立的. ...