简介

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

GStreamer查询机制

GStreamer提供了GstQuery的查询机制,用于查询Element或Pad的相应信息。例如:查询当前的播放速率,产生的延迟,是否支持跳转等。可查看GstQuery文档了解所支持的类型。

要查询所需的信息,首先需要构造一个查询的类型,然后使用Element或Pad的查询接口获取数据,最终再解析相应结果。 下面的例子介绍了如何使用GstQuery查询Pipeline的总时间:

   GstQuery *query = gst_query_new_duration (GST_FORMAT_TIME);
gboolean res = gst_element_query (pipeline, query);
if (res) {
gint64 duration;
gst_query_parse_duration (query, NULL, &duration);
g_print ("duration = %"GST_TIME_FORMAT, GST_TIME_ARGS (duration));
} else {
g_print ("duration query failed...");
}
gst_query_unref (query);

示例代码

在本示例中,我们通过查询Pipeline是否支持跳转(seeking),如果支持跳转(有些媒体不支持跳转,例如实时视频),我们会在播放10秒后跳转到其他位置。
在以前的示例中,我们在Pipeline开始执行后,只等待ERROR和EOS消息,然后退出。本例中,我们会在消息等在中设置等待超时时间,超时后,我们会去查询当前播放的时间,用于显示,这与播放器的进度条类似。

#include <gst/gst.h>

/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
GstElement *playbin; /* Our one and only element */
gboolean playing; /* Are we in the PLAYING state? */
gboolean terminate; /* Should we terminate execution? */
gboolean seek_enabled; /* Is seeking enabled for this media? */
gboolean seek_done; /* Have we performed the seek already? */
gint64 duration; /* How long does this media last, in nanoseconds */
} CustomData; /* Forward definition of the message processing function */
static void handle_message (CustomData *data, GstMessage *msg); int main(int argc, char *argv[]) {
CustomData data;
GstBus *bus;
GstMessage *msg;
GstStateChangeReturn ret; data.playing = FALSE;
data.terminate = FALSE;
data.seek_enabled = FALSE;
data.seek_done = FALSE;
data.duration = GST_CLOCK_TIME_NONE; /* Initialize GStreamer */
gst_init (&argc, &argv); /* Create the elements */
data.playbin = gst_element_factory_make ("playbin", "playbin"); if (!data.playbin) {
g_printerr ("Not all elements could be created.\n");
return -;
} /* Set the URI to play */
g_object_set (data.playbin, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL); /* Start playing */
ret = gst_element_set_state (data.playbin, 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.playbin);
return -;
} /* Listen to the bus */
bus = gst_element_get_bus (data.playbin);
do {
msg = gst_bus_timed_pop_filtered (bus, * GST_MSECOND,
GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION_CHANGED); /* Parse message */
if (msg != NULL) {
handle_message (&data, msg);
} else {
/* We got no message, this means the timeout expired */
if (data.playing) {
gint64 current = -; /* Query the current position of the stream */
if (!gst_element_query_position (data.playbin, GST_FORMAT_TIME, &current)) {
g_printerr ("Could not query current position.\n");
} /* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
if (!gst_element_query_duration (data.playbin, GST_FORMAT_TIME, &data.duration)) {
g_printerr ("Could not query current duration.\n");
}
} /* Print current position and total duration */
g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration)); /* If seeking is enabled, we have not done it yet, and the time is right, seek */
if (data.seek_enabled && !data.seek_done && current > * GST_SECOND) {
g_print ("\nReached 10s, performing seek...\n");
gst_element_seek_simple (data.playbin, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, * GST_SECOND);
data.seek_done = TRUE;
}
}
}
} while (!data.terminate); /* Free resources */
gst_object_unref (bus);
gst_element_set_state (data.playbin, GST_STATE_NULL);
gst_object_unref (data.playbin);
return ;
} static void handle_message (CustomData *data, GstMessage *msg) {
GError *err;
gchar *debug_info; switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
g_clear_error (&err);
g_free (debug_info);
data->terminate = TRUE;
break;
case GST_MESSAGE_EOS:
g_print ("End-Of-Stream reached.\n");
data->terminate = TRUE;
break;
case GST_MESSAGE_DURATION_CHANGED:
/* The duration has changed, mark the current one as invalid */
data->duration = GST_CLOCK_TIME_NONE;
break;
case GST_MESSAGE_STATE_CHANGED: {
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
g_print ("Pipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); /* Remember whether we are in the PLAYING state or not */
data->playing = (new_state == GST_STATE_PLAYING); if (data->playing) {
/* We just moved to PLAYING. Check if seeking is possible */
GstQuery *query;
gint64 start, end;
query = gst_query_new_seeking (GST_FORMAT_TIME);
if (gst_element_query (data->playbin, query)) {
gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);
if (data->seek_enabled) {
g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
GST_TIME_ARGS (start), GST_TIME_ARGS (end));
} else {
g_print ("Seeking is DISABLED for this stream.\n");
}
}
else {
g_printerr ("Seeking query failed.");
}
gst_query_unref (query);
}
}
} break;
default:
/* We should not reach here */
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}

将源码保存为basic-tutorial-5.c,执行下列命令可得到编译结果:
gcc basic-tutorial-5.c -o basic-tutorial-5 `pkg-config --cflags --libs gstreamer-1.0`

源码分析

示例前部分内容与其他示例类似,构造Pipeline并使其进入PLAYING状态。之后开始监听Bus上的消息。

msg = gst_bus_timed_pop_filtered (bus,  * GST_MSECOND,
GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION_CHANGED);

与以前的示例相比,我们在gst_bus_timed_pop_filtered ()中加入了超时时间(100毫秒),这使得此函数如果在100毫秒内没有收到任何消息就会返回超时(msg == NULL),我们会在超时中去更新当前时间,如果返回相应消息(msg != NULL),我们在handle_message中处理相应消息。

GStreamer内部有统一的时间类型(GstClockTime),时间计算方式为:GstClockTime = 数值 x 时间单位。GStreamer提供了3种时间单位(宏定义):GST_SECOND(秒),GST_MSECOND(毫秒),GST_NSECOND(纳秒)。例如:
10秒: 10 * GST_SECOND
100毫秒:100 * GST_MSECOND
100纳秒:100 * GST_NSECOND

刷新播放时间

/* We got no message, this means the timeout expired */
if (data.playing) {
/* Query the current position of the stream */
if (!gst_element_query_position (data.pipeline, GST_FORMAT_TIME, &current)) {
g_printerr ("Could not query current position.\n");
}
/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
if (!gst_element_query_duration (data.pipeline, GST_FORMAT_TIME, &data.duration)) {
g_printerr ("Could not query current duration.\n");
}
}

我们首先判断Pipeline的状态,仅在PLAYING状态时才更新当前时间,在非PLAYING状态时查询可能失败。这部分逻辑每秒大概会执行10次,频率足够用于界面的刷新。这里我们只将查询到的时间输出到终端。
GstElement封装了相应的接口分别用于查询当前时间(gst_element_query_position)和总时间(gst_element_query_duration )。

/* Print current position and total duration */
g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));

这里使用GST_TIME_FORMAT 和GST_TIME_ARGS 帮助我们方便地将GstClockTime的值转换为: ”时:分:秒“格式的字符串输出。

/* If seeking is enabled, we have not done it yet, and the time is right, seek */
if (data.seek_enabled && !data.seek_done && current > * GST_SECOND) {
g_print ("\nReached 10s, performing seek...\n");
gst_element_seek_simple (data.pipeline, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, * GST_SECOND);
data.seek_done = TRUE;
}

我们同时在超时处理中判断是否需要进行seek操作(在播放到10s时,自动跳转到30s),这里我们直接在Pipeline对象上使用gst_element_seek_simple()来执行跳转操作。
gst_element_seek_simple所需要的参数为:

  • element : 需要执行seek操作的Element,这里是Pipeline。
  • format:执行seek的类型,这里使用GST_FORMAT_TIME表示我们基于时间的方式进行跳转。其他支持的类型可以查看 GstFormat
  • seek_flags :通过标识指定seek的行为。 常用的标识如下,其他支持的flag详见GstSeekFlags
    • GST_SEEK_FLAG_FLUSH:在执行seek前,清除Pipeline中所有buffer中缓存的数据。这可能导致Pipeline在填充的新数据被显示之前出现短暂的等待,但能提高应用更快的响应速度。如果不指定这个标志,Pipeline中的所有缓存数据会依次输出,然后才会播放跳转的位置,会导致一定的延迟。
    • GST_SEEK_FLAG_KEY_UNIT:对于大多数的视频,如果跳转的位置不是关键帧,需要依次解码该帧所依赖的帧(I帧及P帧)后,才能解码此非关键帧。使用这个标识后,seek会自动从最近的I帧开始播放。这个标识降低了seek的精度,提高了seek的效率。
    • GST_SEEK_FLAG_ACCURATE:一些媒体文件没有提供足够的索引信息,在这种文件中执行seek操作会非常耗时,针对这类文件,GStreamer通过内部计算得到需要跳转的位置,大部分的计算结果都是正确的。如果seek的位置不能达到所需精度时,可以增加此标识。但需要注意的是,使用此标识可能会导致seek耗费更多时间来寻找精确的位置。
  • seek_pos :需要跳转的位置,前面指定了seek的类型为时间,所以这里是30秒。

消息处理

我们在handle_message接口中处理所有Pipeline上的消息,ERROR和EOS与以前示例处理方式相同,此例中新增了以下内容:

case GST_MESSAGE_DURATION_CHANGED:
/* The duration has changed, mark the current one as invalid */
data->duration = GST_CLOCK_TIME_NONE;
break;

在文件的总时间发生变化时,我们会收到此消息,这里简单的将总长度标记为非法值,在下次更新时间时进行查询。

case GST_MESSAGE_STATE_CHANGED: {
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
g_print ("Pipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); /* Remember whether we are in the PLAYING state or not */
data->playing = (new_state == GST_STATE_PLAYING);

跳转和时间查询操作仅在PUASED和PLAYING状态时才能得到正确的结果,因为所有的Element只能在这2个状态才能接收处理seek和query的指令。这里会保存播放的状态便于后续使用,并且在进入PLAYING状态时查询当前所播放的文件/流是否支持跳转操作:

if (data->playing) {
/* We just moved to PLAYING. Check if seeking is possible */
GstQuery *query;
gint64 start, end;
query = gst_query_new_seeking (GST_FORMAT_TIME);
if (gst_element_query (data->pipeline, query)) {
gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);
if (data->seek_enabled) {
g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
GST_TIME_ARGS (start), GST_TIME_ARGS (end));
} else {
g_print ("Seeking is DISABLED for this stream.\n");
}
}
else {
g_printerr ("Seeking query failed.");
}
gst_query_unref (query);
}

这里的查询步骤与文章开始介绍的方式相同:

  • 首先,通过gst_query_new_seeking()构造一个跳转的查询对象,使用GST_FORMAT_TIME作为参数,表明我们需要知道当前的文件是否支持通过时间进行跳转。我们同样可以使用GST_FORMAT_BYTES作为参数,用于查询是否可以根据文件的偏移量来就行跳转,但这种使用方式不常见。
  • 接着,将查询对象传入gst_element_query()查询,并得到结果。
  • 最后,通过gst_query_parse_seeking()解析是否支持跳转及所支持的范围。

一定需要记住在使用完后释放查询对象

总结

在本教程中,我们学习了:

  • 如何通过GstQuery查询Pipeline上的信息。
  • 如何通过gst_element_query_position()和gst_element_query_duration() 查询当前时间和总时间。
  • 如何通过gst_element_seek_simple()操作跳转到任意位置。
  • 在哪些状态可以执行查询和跳转操作。

后续我们将介绍如何获取媒体文件中的元数据(Metadata)。

引用

https://gstreamer.freedesktop.org/documentation/tutorials/basic/time-management.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/additional/design/query.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/gstreamer/gstquery.html?gi-language=c

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

GStreamer基础教程05 - 播放时间控制的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

  7. GStreamer基础教程12 - 常用命令工具

    摘要 GStreamer提供了不同的命令行工具用于快速的查看信息以及验证Pipeline的是否能够正确运行,在平时的开发过程中,我们也优先使用GStreamer的命令行工具验证,再将Pipeline集 ...

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

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

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

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

随机推荐

  1. Debug监视器(监视运行期程序通过API函数OutputDebugString输出的字符串)

    http://download.csdn.net/detail/zswang/207199

  2. 静态编译 Qt 5.6

    源码下载 Qt git地址:git://code.qt.io/qt/qt5.git 推荐用 SourceTree 客户端来克隆源码 准备工作 系统 Windows 7 或者 Windows 10 编译 ...

  3. ansible(三)

    一.setup模块(收集信息 ) 1.ansible中的setup模块可以收集到的信息 ansible web -m setup ansible_all_ipv4_addresses # ipv4的所 ...

  4. 3013C语言_输入输出

    第三章 输入输出 3.1输入输出概念及其实现 (1)数据从计算机内部向外部输出设备(如显示器.打印机等)输送的操作称为 “输出”,数据从计算机外部向输入设备(如键盘.鼠标.扫描仪等)送入的操作称为 “ ...

  5. 转:Mongoose使用操作

    一般我们不直接用MongoDB的函数来操作MongoDB数据库 Mongose就是一套操作MongoDB数据库的接口.   连接数据库 // mongoose 链接var mongoose = req ...

  6. 零基础搭建appium自动化环境

    目录 1.关键概念 2.安装过程 2.1.安装nodejs 2.2.安装appium 2.3.安装Android SDK 2.4.安装模拟器 2.5.安装Python3 2.6.安装appium Cl ...

  7. 系统学习 Java IO (九)----缓冲流 BufferedInputStream/BufferedOutputStream

    目录:系统学习 Java IO---- 目录,概览 BufferedInputStream BufferedInputStream 类为输入流提供缓冲. 缓冲可以加快IO的速度. BufferedIn ...

  8. maven导入jar包于本地库中

    在使用Maven的过程中,经常碰到有些jar包在中央仓库没有的情况.如果公司有私服,那么就把jar包安装到私服上.如果没有私服,那就把jar包安装到本地Maven仓库. 默认情况下,Maven本地库被 ...

  9. 02-三种Bean装配机制(三)

    在前两篇中分别介绍了自动化装配机制和通过JavaConfig类进行装配,接下来介绍下通过XML装配bean,其实就目前趋势来看,这种方式用的越来越少(不是我说的,是作者说的,喜欢这种方式的别喷我

  10. VirtualBox中安装CentOS使得在ssh和外网都能正常链接

    一.网卡模式选择 网卡1:Host-only 用于主宿机互访,这是主宿机通过192.168.56.X这一网络通信,主机是否能上网不影响双方通信 网卡2:NAT 宿机用这一网卡通过主机上网 二.具体配置 ...