GStreamer Basic tutorial 12 Streaming

"GStreamer 基本教程12: 流媒体"

Posted by Stephen on March 24, 2022

前言

本文是GStreamer学习笔记,也可以看成是对原文的意译。

这些教程描述了理解其余教程所需的GStreamer主题。

GStreamer教程:

基础教程 : GStreamer 介绍

基础教程 1: Hello world!

基础教程 2: GStreamer 概念

基础教程 3: 动态管道

基础教程 4: 时间管理

基础教程 5: GUI工具包集成

基础教程 6: 媒体格式和pad功能

基础教程 7: 多线程和Pad可用性

基础教程 8: 管道短路操作

基础教程 9: 媒体信息收集

基础教程 10: GStreamer工具

基础教程 11: 调试工具

基础教程 12: 流媒体

基础教程 13: 播放速度

基础教程 14: 有用的元素

基础教程 16: 特定平台元素

环境

系统环境

Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.4 LTS
Release:	18.04
Codename:	bionic
Linux version :       5.3.0-46-generic ( buildd@lcy01-amd64-013 ) 
Gcc version:         7.5.0  ( Ubuntu 7.5.0-3ubuntu1~18.04 )

软件信息

version : 	
     GStreamer-1.0

正文

1. 目标

直接从 Internet 播放媒体而不将其存储在本地称为流媒体。每当我们使用以http://. 本教程显示了流式传输时要牢记的几个附加点。尤其是:

  • 如何启用缓冲(以缓解网络问题)
  • 如何从中断中恢复(丢失时钟)

2. 介绍

流式传输时,媒体块一旦从网络到达,就会被解码并排队等待呈现。这意味着如果一个块被延迟(这在 Internet 上并不少见),演示队列可能会干涸,媒体播放可能会停止。

通用的解决方案是建立一个“缓冲区”,即在开始播放之前允许一定数量的媒体块排队。这样,播放开始会延迟一点,但是,如果某些块延迟,则不会影响再现,因为队列中有更多块在等待。

事实证明,这个解决方案已经在 GStreamer 中实现了,但是之前的教程并没有从中受益。一些元素,如queue2multiqueue内部playbin,能够构建此缓冲区并发布有关缓冲区级别(队列状态)的总线消息。如果缓冲区级别不够高(通常是低于 100% 时),想要拥有更多网络弹性的应用程序应该监听这些消息并暂停播放。

为了实现多个接收器(例如音频和视频接收器)之间的同步,使用了全局时钟。该时钟由 GStreamer 在所有可以提供的元素中选择。在某些情况下,例如 RTP 源切换流或更改输出设备,此时钟可能会丢失,需要选择新的时钟。这主要发生在处理流式传输时,因此本教程中解释了该过程。

当时钟丢失时,应用程序在总线上收到一条消息;要选择一个新的,应用程序只需要将管道设置为 PAUSED然后PLAYING再次设置。

3. 网络弹性示例

basic-tutorial-12.c

   #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 = 0;
   
         /* 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 < 100)
           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, 0, 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 -1;
     } 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 0;
   }

编译运行

   gcc basic-tutorial-12.c -o basic-tutorial-12 `pkg-config --cflags --libs gstreamer-1.0`
   ./basic-tutorial-12

4. 演练

本教程做的唯一特别的事情是对某些消息做出反应;因此,初始化代码非常简单,现在应该是不言自明的了。唯一的新位是实时流的检测:

   /* 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 -1;
   } else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
     data.is_live = TRUE;
   }

直播流无法暂停,因此它们在状态中的行为PAUSED就像在状态中一样PLAYING。将直播流设置为PAUSED成功,但返回GST_STATE_CHANGE_NO_PREROLL,而不是 GST_STATE_CHANGE_SUCCESS表明这是一个直播流。NO_PREROLL即使我们尝试将管道设置为,我们也会收到返回码PLAYING,因为状态更改是逐步发生的(从 NULL 到 READY,再到PAUSED,然后到PLAYING)。

我们关心实时流,因为我们想为它们禁用缓冲,所以我们记下变量gst_element_set_state()中 的结果。is_live

现在让我们回顾一下消息解析回调中有趣的部分:

   case GST_MESSAGE_BUFFERING: {
     gint percent = 0;
   
     /* 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 < 100)
       gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
     else
       gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
     break;
   }

首先,如果这是一个实时源,请忽略缓冲消息。

我们解析缓冲消息gst_message_parse_buffering()以检索缓冲级别。

然后,我们在控制台上打印缓冲级别并将管道设置为PAUSED是否低于 100%。否则,我们将管道设置为 PLAYING.

在启动时,我们会看到缓冲水平在播放开始前上升到 100%,这正是我们想要实现的。如果稍后网络变得缓慢或无响应并且我们的缓冲区耗尽,我们将收到级别低于 100% 的新缓冲消息,因此我们将再次暂停管道,直到建立足够的缓冲区。

   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;

对于第二个网络问题,时钟丢失,我们简单地将管道设置为PAUSED并返回到PLAYING,因此选择了一个新时钟,如果需要,等待接收新的媒体块。

5. 结论

本教程描述了如何通过两个非常简单的预防措施为您的应用程序添加网络弹性:

  • 处理管道发送的缓冲消息
  • 照顾时钟丢失

后记