跳转至

Streaming

目标

直接从 Internet 播放媒体而不将其存储在本地称为流式处理。在整个教程中,每当我们使用以 http:// 开头的 URI 时,我们都会这样做。本教程介绍了在流式传输时需要记住的几个其他要点。特别:

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

介绍

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

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

事实证明,此解决方案已在 GStreamer 中实现,但前面的教程并未从中受益。一些元素,比如 playbin 中的 queue2 和 multiqueue,能够构建这个缓冲区并发布有关缓冲区级别(队列状态)的总线消息。因此,希望具有更多网络弹性的应用程序应该侦听这些消息,并在缓冲区级别不够高时(通常,每当缓冲区级别低于 100% 时)暂停播放。

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

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

网络弹性示例

将此代码复制到名为 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://gstreamer.freedesktop.org/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;
}

需要帮助?

如果您在编译此代码时需要帮助,请参阅针对您的平台构建教程部分:Linux、Mac OS X 或 Windows,或在 Linux 上使用以下特定命令:

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

如果您需要运行此代码的帮助,请参阅适用于您的平台的运行教程部分:Linux、Mac OS X 或 Windows 的

本教程将打开一个窗口并显示一部电影,并附带音频。媒体是从 Internet 获取的,因此该窗口可能需要几秒钟才能显示,具体取决于您的连接速度。在控制台窗口中,您应该会看到一条缓冲消息,并且只有在缓冲达到 100% 时才应开始播放。如果您的连接速度足够快并且不需要缓冲,则此百分比可能根本不会改变。

必需安装的库:gstreamer-1.0

代码走读

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

/* 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 来指示这是实时流。即使我们尝试将管道设置为 PLAYING,我们也会收到 NO_PREROLL 返回代码,因为状态变化是逐渐发生的(从 NULL 到 READY,到 PAUSED,然后到 PLAYING)。

我们关心直播流,因为我们想为它们禁用缓冲,因此我们记下了 is_live 变量中 gst_element_set_state() 的结果。

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

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() 解析缓冲消息以检索缓冲级别。

然后,我们在控制台上打印缓冲级别,如果低于 100%,则将管道设置为 PAUSED。否则,我们将管道设置为 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;

对于第二个网络问题,即 clock 丢失,我们只需将 pipeline 设置为 PAUSED 并返回 PLAYING,因此选择一个新的 clock,等待接收到新的媒体块(如有必要)。

总结

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

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

处理这些消息可以提高应用程序对网络问题的响应,从而提高整体播放的流畅度。

很高兴您来到这里,很快再见!

评论