目标

直接播放Internet上的文件而不在本地保存就被称为流播放。我们在前面教程里已经这样做过了,使用了http://的URL。本教程展示的是在播放流的时候需要记住的几个点,特别是:

如何设置缓冲

如何从打断中恢复(因为失去了时钟)

介绍

当在播放流的时候,一旦从网络上取到媒体数据块就会进行解码和放入显示队列。这意味着如果网络来的数据延迟了,那么显示队列就可能没有数据,播放就会停下来。

解决这个问题的办法是建立缓冲,这就是说,在开始播放前允许队列里已经存储了一些数据。这样的话,播放虽然晚了一点开始,但如果网络有什么延时,那么还有一定的缓冲数据可以播放。

这个方案已经在GStreamer里面实现了,但前面的教程中没有涉及到这个方面。有些element,像在playbin2里面用到的queue2和multiqueue,都可以建立自己的缓冲然后根据缓冲的等级发送消息到总线上。一个应用如果希望能更好的适应各种网络环境,那么就该关注这些消息,当缓冲等级低到一定程度时就要暂停播放。

为了在多个sink中同步,我们使用了一个全局的时钟。这个时钟是GStreamer在所有的可以提供时钟的element中选出来的。在某些情况下,例如,一个RTP资源切换流或者更换输出设备,那么时钟就可能丢失。这时就需要重新建立一个时钟,这个过程在本教程会解释一下。

当时钟丢失的时候,应用会从总线上得到一个消息。要建立一个新的时钟,应用仅仅把pipeline设置到PAUSED状态然后重新置成PLAYING即可。

一个适应网络的例子

[objc] view
plain
 copy

  1. <span style="font-size:14px;">#include <gst/gst.h>
  2. #include <string.h>
  3. typedef struct _CustomData {
  4. gboolean is_live;
  5. GstElement *pipeline;
  6. GMainLoop *loop;
  7. } CustomData;
  8. static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) {
  9. switch (GST_MESSAGE_TYPE (msg)) {
  10. case GST_MESSAGE_ERROR: {
  11. GError *err;
  12. gchar *debug;
  13. gst_message_parse_error (msg, &err, &debug);
  14. g_print ("Error: %s\n", err->message);
  15. g_error_free (err);
  16. g_free (debug);
  17. gst_element_set_state (data->pipeline, GST_STATE_READY);
  18. g_main_loop_quit (data->loop);
  19. break;
  20. }
  21. case GST_MESSAGE_EOS:
  22. /* end-of-stream */
  23. gst_element_set_state (data->pipeline, GST_STATE_READY);
  24. g_main_loop_quit (data->loop);
  25. break;
  26. case GST_MESSAGE_BUFFERING: {
  27. ;
  28. /* If the stream is live, we do not care about buffering. */
  29. if (data->is_live) break;
  30. gst_message_parse_buffering (msg, &percent);
  31. g_print ("Buffering (%3d%%)\r", percent);
  32. /* Wait until buffering is complete before start/resume playing */
  33. 00)
  34. gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
  35. else
  36. gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
  37. break;
  38. }
  39. case GST_MESSAGE_CLOCK_LOST:
  40. /* Get a new clock */
  41. gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
  42. gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
  43. break;
  44. default:
  45. /* Unhandled message */
  46. break;
  47. }
  48. }
  49. int main(int argc, charchar *argv[]) {
  50. GstElement *pipeline;
  51. GstBus *bus;
  52. GstStateChangeReturn ret;
  53. GMainLoop *main_loop;
  54. CustomData data;
  55. /* Initialize GStreamer */
  56. gst_init (&argc, &argv);
  57. /* Initialize our data structure */
  58. , sizeof (data));
  59. /* Build the pipeline */
  60. pipeline = gst_parse_launch ("playbin2 uri=http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);
  61. bus = gst_element_get_bus (pipeline);
  62. /* Start playing */
  63. ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  64. if (ret == GST_STATE_CHANGE_FAILURE) {
  65. g_printerr ("Unable to set the pipeline to the playing state.\n");
  66. gst_object_unref (pipeline);
  67. ;
  68. } else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
  69. data.is_live = TRUE;
  70. }
  71. main_loop = g_main_loop_new (NULL, FALSE);
  72. data.loop = main_loop;
  73. data.pipeline = pipeline;
  74. gst_bus_add_signal_watch (bus);
  75. g_signal_connect (bus, "message", G_CALLBACK (cb_message), &data);
  76. g_main_loop_run (main_loop);
  77. /* Free resources */
  78. g_main_loop_unref (main_loop);
  79. gst_object_unref (bus);
  80. gst_element_set_state (pipeline, GST_STATE_NULL);
  81. gst_object_unref (pipeline);
  82. ;
  83. }</span>

工作流程

这个例子中唯一特殊的是对特定消息的相互作用, 因此,初始化代码非常简单清晰。唯一新加的一点就是对实时流的检测:

[objc] view
plain
 copy

  1. <span style="font-size:14px;">  /* Start playing */
  2. ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  3. if (ret == GST_STATE_CHANGE_FAILURE) {
  4. g_printerr ("Unable to set the pipeline to the playing state.\n");
  5. gst_object_unref (pipeline);
  6. ;
  7. } else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
  8. data.is_live = TRUE;
  9. }</span>

实时流是不能暂停的,所以在PAUSED状态的行为和PLAYING状态是一样的。在设置实时流到PAUSED成功后,会返回GST_STATE_CHANGE_NO_PREROLL,而不是平常的GST_STATE_CHANGE_SUCCESS。因为状态的变化是渐变的(从NULL到READY,从PAUSED到PLAYING),所以我们把pipeline设置到PLAYING状态,也会收到NO_PROROLL这个返回值。

我们关注实时流是因为我们希望可以关闭缓冲,所以我们一直在关注gst_element_set_state()的返回值并根据这个值来设置is_live变量。

现在我们看一下消息解析的回调函数里的关键部分:

[objc] view
plain
 copy

  1. case GST_MESSAGE_BUFFERING: {
  2. ;
  3. /* If the stream is live, we do not care about buffering. */
  4. if (data->is_live) break;
  5. gst_message_parse_buffering (msg, &percent);
  6. g_print ("Buffering (%3d%%)\r", percent);
  7. /* Wait until buffering is complete before start/resume playing */
  8. 00)
  9. gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
  10. else
  11. gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
  12. break;
  13. }

首先,如果是一个实时源,就不用关心这个缓冲消息。

我们使用gst_message_parse_buffering()来解析缓冲消息,从而获得缓冲等级。

其次,我们在缓冲等级小于100%时把pipeline设置成PAUSED状态,并把消息在调试区域打印出来;反之,我们就把pipeline设置成PLAYING状态。

在启动的时候,我们会看见在播放之前缓冲等级上升到100%,这就是我们希望达到的。如果在后面,网络变慢了或者失去响应,我们的缓冲也耗光了,我们会收到新的缓冲消息告诉我们缓冲等级已经低于100%,我们就把pipeline设置成PAUSED知道重新获得足够的数据。

[objc] view
plain
 copy

  1. case GST_MESSAGE_CLOCK_LOST:
  2. /* Get a new clock */
  3. gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
  4. gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
  5. break;

对于丢失时钟这个网络问题,我们简单地把pipeline设置成PAUSED状态然后在切换到PLAYING,这样一个新的时钟会被选择上,等待收到新的媒体数据。

【GStreamer开发】GStreamer基础教程12——流的更多相关文章

  1. Android程序开发0基础教程(一)

    程序猿学英语就上视觉英语网 Android程序开发0基础教程(一)   平台简单介绍   令人激动的Google手机操作系统平台-Android在2007年11月13日正式公布了,这是一个开放源码的操 ...

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

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

  3. Java基础教程(12)--深入理解类

    一.方法的返回值   当我们在程序中调用方法时,虚拟机将会跳转到对应的方法中去执行.当以下几种情况发生时,虚拟机将会回到调用方法的语句并继续向下执行: 执行完方法中所有的语句: 遇到return语句: ...

  4. Java基础教程——转换流

    转换流 通常,Window默认的编码方式是GBK,Java项目一般建议设为UTF-8编码.这时候读取文件可能出现乱码.事实上实际应用中编码格式不匹配的场景非常多. 转换流可以指定编码方式,用于解决乱码 ...

  5. Ruby 基础教程 1-2

    1.数组 创建 arrayname=[] arrayname=["1",12,"23"] 访问 arrayname[index] 更新 arrayname[in ...

  6. Java基础教程——打印流

    打印流 打印流可以把原本输出到控制台的信息输出到文件中.PrintStream是字节打印流(还有个对应的字符打印流是PrintWriter,这里不涉及) System类中有个变量: public fi ...

  7. Java基础教程——缓冲流

    缓冲流 "缓冲流"也叫"包装流",是对基本输入输出流的增强: 字节缓冲流: BufferedInputStream , BufferedOutputStream ...

  8. Java基础教程——字符流

    字符流 字节流服务文本文件时,可能出现中文乱码.因为一个中文字符可能占用多个字节. 针对于非英语系的国家和地区,提供了一套方便读写方式--字符流. java.io.Reader java.io.Wri ...

  9. cocos基础教程(12)点击交互的三种处理

    1.概述 游戏也好,程序也好,只有能与用户交互才有意义.手机上的交互大致可以分为两部分:点击和输入.其中点击更为重要,几乎是游戏中全部的交互.在Cocos2d-x 3.0中,更改了dispatch机制 ...

随机推荐

  1. [golang]Go内嵌静态资源go-bindata的安装及使用

    使用 Go 开发应用的时候,有时会遇到需要读取静态资源的情况.比如开发 Web 应用,程序需要加载模板文件生成输出的 HTML.在程序部署的时候,除了发布应用可执行文件外,还需要发布依赖的静态资源文件 ...

  2. 编译器错误 CS0540

    编译项目报错:包含类型不实现接口,CS0540 原因:试图在非派生自接口的类中实现接口成员. 解决方案: 删除接口成员的实现,或将接口添加到类的基类列表. 下面的两个示例生成 CS0540: 一. / ...

  3. [树链剖分]BZOJ3589动态树

    题目描述 别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求你在这棵树上维护两种事件 事件0: 这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子. 事件1: 小明希望你求出几条树枝上 ...

  4. php手记之05-tp5获取器与修改器

    获取器 命名规范为: getFieldNameAttr 例如,我们需要对状态值进行转换,可以使用: <?php class User extends Model { public functio ...

  5. DNGuard HVM Unpacker(3.71 trial support and x64 fixed)

    DNGuard HVM Unpacker(3.71 trial support and x64 fixed) Gr8 news. Finally got the x64 crash fixed. DN ...

  6. AAR文件简介

    假如我们希望提供一个带有资源文件的第三方库给别人使用,总不能直接把源代码给别人,但是我们知道eclipse打包的时候不能包含res的资源文件,于是Android在发布Android studio的时候 ...

  7. MongoDB允许其它IP地址访问

    网址:https://blog.csdn.net/sl1992/article/details/83964310 文章目录1.允许所有地址访问2.绑定内网IP3.绑定多个IP Linux服务器上安装M ...

  8. .net Core 中DateTime在Linux Docker中与Windows时间不一致

    最近写了一个.net core项目,部署到CentOS并在docker上运行的时候,发现DateTime.Now获取的时间与Windows不一致(定时执行的任务,晚了8个小时),在Windows中可以 ...

  9. JVM 初始化阶段例子

    创建如下Demo package com.example.jvm.classloader; class Parent{ static int a = 3; static { System.out.pr ...

  10. 五、postman-sandbox

    一.在postman中运行一些JavaScript代码的地方 公共库(javascript) 环境变量与全局变量 动态变量 操作cookie 获取和查看请求及响应 读取数据文件 二.api文档 htt ...