目标

直接播放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. 本地存储API

    一.定义 随着互联网的快速发展,基于网页的应用越来越普遍,同时也变得越来越复杂,为了满足各种各样的需求,会经常在本地存储大量的数据,HTML5规范提出了相关解决方案 本地存储设置读取方便,容量较大,s ...

  2. noi.ac #46 最长上升子序列

    \(des\) 长度为 \(n\) 的序列 \(A\),从中删去恰好 \(k\) 个元素(右边的元素往左边移动),记 \(cnt\) 为新 序列中 \(Ai = i\) 的元素个数(即权值与下标相同的 ...

  3. (8)打鸡儿教你Vue.js

    监听属性 监听属性 watch 通过 watch 来响应数据的变化 <div id = "app"> <p style = "font-size:25p ...

  4. 编译安装和二进制安装mysql

    二进制安装mysql-5.6.46 mysql二进制安装,已经编译成二进制了,只需要做一些配置即可 [root@localhost ~]$ yum install autoconf libaio -y ...

  5. 前端项目, 每次运行都需要输入 sudo 的解决方法

    前端项目, 每次运行都需要输入 sudo 的解决方法 node一直提示的sudo问题根本原因为: node 的所有者, 项目的所有者, 不同; 解决方法为: 将项目的所有者更改为 chown -R ` ...

  6. ORA-01578: ORACLE 数据块损坏 (文件号 13, 块号 2415081) ORA-01110: 数据文件XXXXXX

    1.使用DBV检查数据文件,在cmd执行:dbv file='E:\APP\ADMINISTRATOR\ORADATA\ORCL\USERS01.DBF' blocksize=8192:然后等待检测结 ...

  7. return关键字

    注意:如果一个函数的返回值类型是具体的数据类型,那么该函数就必须要保证在任意情况下都保证有返回值(除了返回值类型是void)     return  关键字的作用: 1   返回数据给函数的调用者. ...

  8. 【转】JDK5.0中JVM堆模型、GC垃圾收集详细解析

    基本概念 堆/Heap JVM管理的内存叫堆:在32Bit操作系统上有4G的限制,一般来说Windows下为2G,而Linux下为3G:64Bit的就没有这个限制.JVM初始分配的内存由-Xms指定, ...

  9. argmin ,argmax函数

    在数学中,ARG MAX(或ARGMAX)代表最大值,即给定参数的点集,给定表达式的值达到其最大值: 换一种说法, 是f(x)具有最大值M的x的值的集合.例如,如果f(x)是1- | x |,那么它在 ...

  10. storcli64和smartctl定位硬盘的故障信息

    storcli64可对LSIRAID卡基本操作进行管理,本文主要是对LSIRAID卡常使用到的命令进行介绍 https://www.cnblogs.com/wangl-blog/archive/201 ...