• 【GStreamer开发】GStreamer基础教程12——流


    目标

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

          如何设置缓冲

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


    介绍

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

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

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

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

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


    一个适应网络的例子

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

    工作流程

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

    [objc] view plain copy
     在CODE上查看代码片派生到我的代码片
    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. ");  
    5.     gst_object_unref (pipeline);  
    6.     return -1;  
    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
     在CODE上查看代码片派生到我的代码片
    1. case GST_MESSAGE_BUFFERING: {  
    2.   gint percent = 0;  
    3.     
    4.   /* If the stream is live, we do not care about buffering. */  
    5.   if (data->is_live) break;  
    6.     
    7.   gst_message_parse_buffering (msg, &percent);  
    8.   g_print ("Buffering (%3d%%) ", percent);  
    9.   /* Wait until buffering is complete before start/resume playing */  
    10.   if (percent < 100)  
    11.     gst_element_set_state (data->pipeline, GST_STATE_PAUSED);  
    12.   else  
    13.     gst_element_set_state (data->pipeline, GST_STATE_PLAYING);  
    14.   break;  
    15. }  
          首先,如果是一个实时源,就不用关心这个缓冲消息。

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

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

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

    [objc] view plain copy
     在CODE上查看代码片派生到我的代码片
    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,这样一个新的时钟会被选择上,等待收到新的媒体数据。

  • 相关阅读:
    【轻松前端之旅】<a>元素妙用
    【轻松前端之旅】HTML的块元素、行内元素和空元素
    每周学算法/读英文/知识点心得分享 9.27
    Linux find 命令详解
    每周学算法/读英文/知识点心得分享 9.19
    Linux命令之nohup 和 重定向
    基于C-W节约算法的车辆路径规划问题的Java实现
    每周学算法/读英文/知识点心得分享 9.6
    每周学算法/读英文/知识点心得分享 8.11
    每周学算法/读英文/知识点心得分享 3.11
  • 原文地址:https://www.cnblogs.com/huty/p/8517309.html
Copyright © 2020-2023  润新知