摘要

  在常见的媒体播放器中,通常可以看到快进,快退,慢放等功能,这部分功能被称为“特技模式(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 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_WIN32io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
#elseio_stdin = g_io_channel_unix_new (fileno (stdin));
#endifg_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-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 (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;

  在终端输入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()实现单帧播放。

引用

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
出处:http://www.cnblogs.com/xleng/
本文版权归作者所有,欢迎转载。商业转载请联系作者获得授权,非商业转载请在文章页面明显位置给出原文连接.

转载于:https://www.cnblogs.com/xleng/p/11401802.html

GStreamer基础教程07 - 播放速率控制相关推荐

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

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

  2. GStreamer基础教程10——GStreamer工具

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

  3. 【GStreamer学习】之GStreamer基础教程

    目标 没有什么比在屏幕上打印出"Hello World"更能获得对软件库的第一印象了! 但是由于我们正在学习多媒体框架,所以我们将输出"Hello World!" ...

  4. Gstreamer基础教程13:Playback Speed

    文章目录 1.Goal 2.介绍 3. A Trick mode player 3.1 Compile 3.2 Code 4.Analyze 5.讨论 1.Goal 快进.倒放和慢动作都是所谓的技巧模 ...

  5. Gstreamer基础教程10: Gstreamer 工具

    文章目录 1. Goal 2. 介绍 3. gst-lanuch-1.0 3.1 Elements 3.2 Properties 3.3 Named elements 3.4 Pads 3.5 Cap ...

  6. Gstreamer基础教程10:GStreamer tools

    文章目录 目标 一.Introduction(简介) 二.gst-launch-1.0 1.Elements 2.Properties(属性) 3.Named elements(元素重命名) 4.Pa ...

  7. GStreamer基础教程04 - 动态连接Pipeline

    摘要 在以前的文章中,我们了解到了2种播放文件的方式:一种是在知道了文件的类型及编码方式后,手动创建所需Element并构造Pipeline:另一种是直接使用playbin,由playbin内部动态创 ...

  8. GStreamer基础教程01 - Hello World

    摘要 在面对一个新的软件库时,第一步通常实现一个"hello world"程序,来了解库的用法.对于GStreamer,我们可以实现一个极简的播放器,来了解GStreamer的使用 ...

  9. GStreamer基础教程02——GStreamer概念

    上一个教程演示了如何自动生成一个pipeline.这次我们打算用一个个element来手动搭建一个pipeline.我们这个教程会演示: 1. 什么是GStreamer的element以及如何建立一个 ...

最新文章

  1. mysql修改binlog格式_mysql binlog格式...
  2. SqlServer的NEWID()函数巧用,生成GUID作为唯一标识用
  3. ios下获取所有实体/虚拟网卡的信息,并以此判断设备所处的网络状态
  4. 前端学习(2244)vue值造轮子之用户分析
  5. python3安装步骤-超详细的小白python3.X安装教程|Python安装
  6. Struts2 页面url请求怎样找action
  7. 「AtCoder Grand018A」Getting Difference(GCD)
  8. UVALive 6181
  9. 联想昭阳E46A不能上网
  10. 7种常见的PPT设计元素
  11. Mybatis的联合查询
  12. Excel2013 单元格锁定
  13. 洛谷----P3717 [AHOI2017初中组]cover
  14. python查询水果价格_C语言查询水果价格
  15. 一个功能齐全的IOS音乐播放器应用源码
  16. 虚拟主机服务器错误500解决方法
  17. matlab+nbiot,基于STM32L4+BC28(全网通) NBIOT开发板原理图教程源码等开源分享
  18. Java编程之四大名著
  19. Web全栈~18.jQuery
  20. 2019年3月计算机一级考试试题,2019年3月计算机一级考试MsOffice操作试题(五)

热门文章

  1. Java中的Unsafe在安全领域的一些应用总结和复现
  2. Python中的元类及元类实现的单例模式
  3. No grammar constraints (DTD or XML schema).....两种解决方法
  4. 获取硬盘总容量,柱面数,磁道数,扇区数
  5. [Redux/Mobx] redux和flux的区别是什么?
  6. [react] react怎么提高列表渲染的性能?
  7. 前端学习(3290):react hook state-hook传入对象+1操作
  8. Taro+react开发(36)每一个节点要一个view包裹
  9. 前端学习(3120):react-hello-react的setstate的使用
  10. [html] 举例说明html的修饰元素有哪些?