GStreamer基础教程07 - 播放速率控制
摘要
在常见的媒体播放器中,通常可以看到快进,快退,慢放等功能,这部分功能被称为“特技模式(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, * GST_SECOND,
- GST_SEEK_TYPE_SET, * 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 > ) {
- 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, );
- } else {
- seek_event =
- gst_event_new_seek (data->rate, GST_FORMAT_TIME,
- GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, ,
- 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[])) {
- 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[])) {
- 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, , 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, , 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_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 (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 -;
- }
- 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 ;
- }
通过下面的命令编译即可得到可执行文件,在终端输入相应指令可修改播放速率。
- gcc basic-tutorial-.c -o basic-tutorial- `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[])) {
- 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[])) {
- 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 > ) {
- 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, );
- } else {
- seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
- GST_SEEK_TYPE_SET, , 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, , 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
GStreamer基础教程07 - 播放速率控制的更多相关文章
- 【GStreamer开发】GStreamer基础教程13——播放速度
目标 快进,倒放和慢放是trick模式的共同技巧,它们有一个共同点就是它们都修改了播放的速度.本教程会展示如何来获得这些效果和如何进行逐帧的跳跃.主要内容是: 如何来变换播放的速度,变快或者变慢,前进 ...
- 【GStreamer开发】GStreamer基础教程07——多线程和Pad的有效性
目标 GStreamer会自动处理多线程这部分,但在有些情况下,你需要手动对线程做解耦.本教程会教你怎样才能做到这一点,另外也展示了Pad的有效性.主要内容包括: 如何针对部分的pipeline建立一 ...
- GStreamer基础教程05 - 播放时间控制
简介 在多媒体应用中,我们通常需要查询媒体文件的总时间.当前播放位置,以及跳转到指定的时间点.GStreamer提供了相应的接口来实现此功能,在本文中,我们将通过示例了解如何查询时间信息,以及如何进行 ...
- 【GStreamer开发】GStreamer基础教程14——常用的element
目标 本教程给出了一系列开发中常用的element.它们包括大杂烩般的eleemnt(比如playbin2)以及一些调试时很有用的element. 简单来说,下面用gst-launch这个工具给出一个 ...
- 【GStreamer开发】GStreamer基础教程10——GStreamer工具
目标 GStreamer提供了一系列方便使用的工具.这篇教程里不牵涉任何代码,但还是会讲一些有用的内容: 如何在命令行下建立一个pipeline--完全不使用C 如何找出一个element的Capab ...
- 【GStreamer开发】GStreamer基础教程08——pipeline的快捷访问
目标 GStreamer建立的pipeline不需要完全关闭.有多种方法可以让数据在任何时候送到pipeline中或者从pipeline中取出.本教程会展示: 如何把外部数据送到pipeline中 如 ...
- GStreamer基础教程02 - 基本概念
摘要 在 Gstreamer基础教程01 - Hello World中,我们介绍了如何快速的通过一个字符串创建一个简单的pipeline.为了能够更好的控制pipline中的element,我们需要单 ...
- GStreamer基础教程09 - Appsrc及Appsink
摘要 在我们前面的文章中,我们的Pipline都是使用GStreamer自带的插件去产生/消费数据.在实际的情况中,我们的数据源可能没有相应的gstreamer插件,但我们又需要将数据发送到GStre ...
- 【GStreamer开发】GStreamer基础教程05——集成GUI工具
目标 本教程展示了如何在GStreamer集成一个GUI(比如:GTK+).最基本的原则是GStreamer处理多媒体的播放而GUI处理和用户的交互. 在这个教程里面,我们可以学到: 如何告诉GStr ...
随机推荐
- apache CXF Service 简单使用
cxf介绍 框架官网:cxf.apache.org 支持多种协议: SOAP1.1,1.2 XML/HTTP CORBA(Common Object Request Broker Architectu ...
- 在安装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 ...
- Java EE.Servlet.会话管理
一次会话是从客户打开浏览器开始到关闭浏览器结束.记录会话信息的技术称为会话跟踪.常见的会话跟踪技术有Cookie.URL重写和隐藏表单域. 1.Cookie Cookie是一小块可以嵌入到HTTP请求 ...
- docker跨主机通信扁平化网络的设计与实现
端口映射.ovs. fannel,weave 1.使用网桥实现跨主机容器连接 使用Open vSwitch实现跨主机容器连接
- 转 java - java反射机制创建对象
转 https://blog.csdn.net/nch_ren/article/details/78814080 1.创建service实现类 package com.springstudy.refl ...
- <<Modern CMake>> 翻译 2.3 与代码通信
<<Modern CMake>> 翻译 2.3 与代码通信 配置文件 CMake 允许您使用代码通过 configure_file 存取 CMake 变量. 此命令复制一个文件 ...
- 【MySQL】ON DUPLICATE KEY UPDATE
之前没用过这个操作,甚至没见过--最近接触到,而且还挺有用. 作用:若 KEY 不重复,则插入记录:否则更新记录. 单条操作: INSERT INTO table(a, b, c) VALUES (1 ...
- spring boot 学习笔记(一)之前端文件配置
一.叙述 spring boot 由于是内置的tomcat ,因此其在独立运行的时候,是不需要单独安装 tomcat,这使其前端文件(CSS.JS.html)等放置的位置与war中的不同. 二.常见配 ...
- Java 性能优化(一)
Java 性能调优(一) 1.衡量程序性能的标准 (1) 程序响应速度: (2) 内存占有情况: 2.程序调优措施 (1) 设计调优 设计调优处于所有调优手段 的上层,需要在软件开发之前进行.在软件开 ...
- WebSocket的实现与应用
WebSocket的实现与应用 前言 说到websocket,就不得不提http协议的连接特点特点与交互模型. 首先,http协议的特点是无状态连接.即http的前一次连接与后一次连接是相互独立的. ...