Gstreamer基础教程10 - Streaming
摘要
我们把直接从网络播放一个媒体文件的方式称为在线播放(Online Streaming),我们已经在以往的例子中体验了GStreamer的在线播放功能,当我们指定播放URI为 http:// 时,GStreamer内部会自动通过网络获取媒体数据。在今天的示例中,我们将进一步了解如何处理由网络问题导致的视频缓冲及时钟丢失的问题。
在线播放
在我们进行在线播放时,我们会将收到的媒体数据立即进行解码并送入显示队列显示。当网络不理想时,我们通常不能及时的接收数据,显示队列中的数据会被耗尽而不能得到及时的补充,这会导致播放出现卡顿。
一种通用的处理方式是创建一个缓冲队列,在队列的数据量达到一定阀值时才进行播放,这样会导致起播时间会有一定的延迟,但会使后续的播放更加流畅,避免了因部分数据无法及时到达造成的停顿。
GStreamer框架已经实现了缓冲队列,但在以往的示例中我们并没有使用其相关的功能。某些Element(例如playbin中使用的queue2及multiqueue)可以创建缓冲队列,并在超过/低于指定的数据阀值时产生相应的信号。应用程序可以监听此类信号,在数据不足时(buffer值小于100%)主动暂停播放,在数据充足时恢复播放。
为了达到多个Sink的同步(例如音视频同步),我们需要使用一个全局的参考时钟,GStreamer会在播放时自动选取一个时钟。在某些网络在线播放的情况下原有时钟会失效,我们需要重新选取一个参考时钟。例如,RTP Source切换流或者改变输出设备。
在参考时钟丢失时,GStreamer框架会产生相应的事件,应用层需要对其作出响应,由于GStreamer在进入PLAYING状态时会自动选取参考时钟,所以我们只需在收到时钟丢失事件时将Pipeline的状态切换到PUASED,再切换到PLAYING即可。
示例代码
#include <gst/gst.h>
#include <string.h> typedef struct _CustomData {
gboolean is_live;
GstElement *pipeline;
GMainLoop *loop;
} CustomData; static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) { switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR: {
GError *err;
gchar *debug; gst_message_parse_error (msg, &err, &debug);
g_print ("Error: %s\n", err->message);
g_error_free (err);
g_free (debug); gst_element_set_state (data->pipeline, GST_STATE_READY);
g_main_loop_quit (data->loop);
break;
}
case GST_MESSAGE_EOS:
/* end-of-stream */
gst_element_set_state (data->pipeline, GST_STATE_READY);
g_main_loop_quit (data->loop);
break;
case GST_MESSAGE_BUFFERING: {
gint percent = ; /* If the stream is live, we do not care about buffering. */
if (data->is_live) break; gst_message_parse_buffering (msg, &percent);
g_print ("Buffering (%3d%%)\r", percent);
/* Wait until buffering is complete before start/resume playing */
if (percent < )
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
else
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
}
case GST_MESSAGE_CLOCK_LOST:
/* Get a new clock */
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
default:
/* Unhandled message */
break;
}
} int main(int argc, char *argv[]) {
GstElement *pipeline;
GstBus *bus;
GstStateChangeReturn ret;
GMainLoop *main_loop;
CustomData data; /* Initialize GStreamer */
gst_init (&argc, &argv); /* Initialize our data structure */
memset (&data, , sizeof (data)); /* Build the pipeline */
pipeline = gst_parse_launch ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
bus = gst_element_get_bus (pipeline); /* Start playing */
ret = gst_element_set_state (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 (pipeline);
return -;
} else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
data.is_live = TRUE;
} main_loop = g_main_loop_new (NULL, FALSE);
data.loop = main_loop;
data.pipeline = pipeline; gst_bus_add_signal_watch (bus);
g_signal_connect (bus, "message", G_CALLBACK (cb_message), &data); g_main_loop_run (main_loop); /* Free resources */
g_main_loop_unref (main_loop);
gst_object_unref (bus);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
return ;
}
将代码保存为basic-tutorial-10.c,执行下列命令编译可得到运行程序。
gcc basic-tutorial-.c -o basic-tutorial- `pkg-config --cflags --libs gstreamer-1.0`
由于通过网络获取数据,视频显示窗口可能会有短暂的等待时间,在终端的buffering达到100%时才会开始播放。
源码分析
GStreamer Pipeline相关的处理与以往示例相同,我们只关注在线播放相关的处理。
/* Start playing */
ret = gst_element_set_state (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 (pipeline);
return -;
} else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
data.is_live = TRUE;
}
对于实时的媒体流,我们无法将其设置为PAUSED状态,所以在通过gst_element_set_state 将Pipeline设置成PAUSED状态时,我们会收到GST_STATE_CHANGE_NO_PREROLL。正常情况会返回GST_STATE_CHANGE_SUCCESS 。由于GStreamer的状态会依次从NULL, READY, PAUSED转换为PLAYING,所以我们将状态设置为PLAYING时,也会收到NO_PREROLL返回值。
这里设置is_live标识是因为我们不对其进行缓冲处理。
case GST_MESSAGE_BUFFERING: {
gint percent = ;
/* If the stream is live, we do not care about buffering. */
if (data->is_live) break;
gst_message_parse_buffering (msg, &percent);
g_print ("Buffering (%3d%%)\r", percent);
/* Wait until buffering is complete before start/resume playing */
if (percent < )
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
else
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
}
在非实时流的情况下,如果缓存队列的数据不足,我们会收到GST_MESSAGE_BUFFERING事件,收到此事件时,我们可以通过gst_message_parse_buffering()得到缓冲进度,如果进度小于100%我们就暂停播放,在缓冲完成后我们再恢复播放。如果使用playbin,我们可以直接通过buffer-size或buffer-duration属性去修改缓冲区大小。
case GST_MESSAGE_CLOCK_LOST:
/* Get a new clock */
gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
break;
针对于时钟丢失的这种情况,我们只需在收到GST_MESSAGE_CLOCK_LOST事件时,改变Pipline的状态,由GStreamer自动选取参考时钟即可。
总结
通过本文,我们了解了如何应对两种简单的网络播放问题:
- 通过缓冲消息来控制播放状态。
- 在时钟丢失时重新选择时钟。
通过使用缓冲队列,可以使得网络播放更加流畅。
引用
https://gstreamer.freedesktop.org/documentation/tutorials/basic/streaming.html?gi-language=c
Gstreamer基础教程10 - Streaming的更多相关文章
- 【GStreamer开发】GStreamer基础教程10——GStreamer工具
目标 GStreamer提供了一系列方便使用的工具.这篇教程里不牵涉任何代码,但还是会讲一些有用的内容: 如何在命令行下建立一个pipeline--完全不使用C 如何找出一个element的Capab ...
- 【GStreamer开发】GStreamer基础教程14——常用的element
目标 本教程给出了一系列开发中常用的element.它们包括大杂烩般的eleemnt(比如playbin2)以及一些调试时很有用的element. 简单来说,下面用gst-launch这个工具给出一个 ...
- GStreamer基础教程02 - 基本概念
摘要 在 Gstreamer基础教程01 - Hello World中,我们介绍了如何快速的通过一个字符串创建一个简单的pipeline.为了能够更好的控制pipline中的element,我们需要单 ...
- GStreamer基础教程09 - Appsrc及Appsink
摘要 在我们前面的文章中,我们的Pipline都是使用GStreamer自带的插件去产生/消费数据.在实际的情况中,我们的数据源可能没有相应的gstreamer插件,但我们又需要将数据发送到GStre ...
- GStreamer基础教程12 - 常用命令工具
摘要 GStreamer提供了不同的命令行工具用于快速的查看信息以及验证Pipeline的是否能够正确运行,在平时的开发过程中,我们也优先使用GStreamer的命令行工具验证,再将Pipeline集 ...
- 【GStreamer开发】GStreamer基础教程13——播放速度
目标 快进,倒放和慢放是trick模式的共同技巧,它们有一个共同点就是它们都修改了播放的速度.本教程会展示如何来获得这些效果和如何进行逐帧的跳跃.主要内容是: 如何来变换播放的速度,变快或者变慢,前进 ...
- 【GStreamer开发】GStreamer基础教程08——pipeline的快捷访问
目标 GStreamer建立的pipeline不需要完全关闭.有多种方法可以让数据在任何时候送到pipeline中或者从pipeline中取出.本教程会展示: 如何把外部数据送到pipeline中 如 ...
- 【GStreamer开发】GStreamer基础教程07——多线程和Pad的有效性
目标 GStreamer会自动处理多线程这部分,但在有些情况下,你需要手动对线程做解耦.本教程会教你怎样才能做到这一点,另外也展示了Pad的有效性.主要内容包括: 如何针对部分的pipeline建立一 ...
- 【GStreamer开发】GStreamer基础教程05——集成GUI工具
目标 本教程展示了如何在GStreamer集成一个GUI(比如:GTK+).最基本的原则是GStreamer处理多媒体的播放而GUI处理和用户的交互. 在这个教程里面,我们可以学到: 如何告诉GStr ...
随机推荐
- 转:int整数除以int整数一定得到的是int整数(易忽视)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/u014053368/article/de ...
- Java匹马行天下之Java帝国的崛起(大结局)
Java匹马行天下之Java帝国的崛起大结局 前言: [博客*缘] 网络真情伴, 博客友谊连. 笑中藏泪暖中寒. 回想那些悲喜, 苦涩也缠绵. 往事难回首, 新篇染旧言. 世间多少梦能全. 感谢相牵, ...
- python的元组存储的实质和多元赋值
python中有一种赋值机制即多元赋值,采用这种方式赋值时,等号两边的对象都是元组并且元组的小括号是可选的.通常形式为 x, y, z = 1, 2, 'a string' 等同于 (x, y, z) ...
- 前端深入之css篇|link和@import到底有什么区别?
写在前面 在真正的前端开发中,我们很少去写行内样式和内嵌样式,通常都是去引用外部样式. 而在我们学习之初的外部样式表都是用link引入的,但是当后来我们学习的逐渐深入,发现@import也可以引入样式 ...
- python 处理excel踩过的坑——data_only,公式全部丢失
用openpyxl读取excel的load_workbook有个data_only参数. yb_wb = load_workbook(u"D:\\Desktop\\xxx.xlsx" ...
- 一道题考你对__autoreleasing和__block的理解
考虑下面的代码,有哪些问题,如何把他改成正确的形式? @interface TestObj : NSObject @end @implementation TestObj - (void)method ...
- 基于Tcp穿越的Windows远程桌面(远程桌面管理工具)
基于Tcp穿越的Windows远程桌面(远程桌面管理工具) 1.<C# WinForm 跨线程访问控件(实用简洁写法)> 2.<基于.NET环境,C#语言 实现 ...
- 启动一个Activity的几种方式
在Android中我们可以通过下面两种方式来启动一个新的Activity,注意这里是怎么启动,而非 启动模式!!分为显示启动和隐式启动! 1. 显式启动:通过包名来启动,写法如下: ①最常见的: st ...
- java中&和&&
&和&&都可以用作逻辑与的运算符,表示逻辑与(and) &&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如: If(x==33 ...
- 从干将莫邪的故事说起--java比较操作注意要点
故事背景 <搜神记>: 楚干将.莫邪为楚王作剑,三年乃成.王怒,欲杀之.剑有雌雄.其妻重身当产.夫语妻曰:“吾为王作剑,三年乃成.王怒,往必杀我.汝若生子是男,大,告之曰:‘出户望南山,松 ...