Gstreamer基础教程10 - Streaming

摘要

  咱们把直接从网络播放一个媒体文件的方式称为在线播放(Online Streaming),咱们已经在以往的例子中体验了GStreamer的在线播放功能,当咱们指定播放URI为 http:// 时,GStreamer内部会自动经过网络获取媒体数据。在今天的示例中,咱们将进一步了解如何处理由网络问题致使的视频缓冲及时钟丢失的问题。html

在线播放

  在咱们进行在线播放时,咱们会将收到的媒体数据当即进行解码并送入显示队列显示。当网络不理想时,咱们一般不能及时的接收数据,显示队列中的数据会被耗尽而不能获得及时的补充,这会致使播放出现卡顿。
  一种通用的处理方式是建立一个缓冲队列,在队列的数据量达到必定阀值时才进行播放,这样会致使起播时间会有必定的延迟,但会使后续的播放更加流畅,避免了因部分数据没法及时到达形成的停顿。
  GStreamer框架已经实现了缓冲队列,但在以往的示例中咱们并无使用其相关的功能。某些Element(例如playbin中使用的queue2及multiqueue)能够建立缓冲队列,并在超过/低于指定的数据阀值时产生相应的信号。应用程序能够监听此类信号,在数据不足时(buffer值小于100%)主动暂停播放,在数据充足时恢复播放。
  为了达到多个Sink的同步(例如音视频同步),咱们须要使用一个全局的参考时钟,GStreamer会在播放时自动选取一个时钟。在某些网络在线播放的状况下原有时钟会失效,咱们须要从新选取一个参考时钟。例如,RTP Source切换流或者改变输出设备。
  在参考时钟丢失时,GStreamer框架会产生相应的事件,应用层须要对其做出响应,因为GStreamer在进入PLAYING状态时会自动选取参考时钟,因此咱们只需在收到时钟丢失事件时将Pipeline的状态切换到PUASED,再切换到PLAYING便可。web

示例代码

#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;
}

  将代码保存为basic-tutorial-10.c,执行下列命令编译可获得运行程序。缓存

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

  因为经过网络获取数据,视频显示窗口可能会有短暂的等待时间,在终端的buffering达到100%时才会开始播放。网络

源码分析

  GStreamer 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;
}

  对于实时的媒体流,咱们没法将其设置为PAUSED状态,因此在经过gst_element_set_state 将Pipeline设置成PAUSED状态时,咱们会收到GST_STATE_CHANGE_NO_PREROLL。正常状况会返回GST_STATE_CHANGE_SUCCESS 。因为GStreamer的状态会依次从NULL, READY, PAUSED转换为PLAYING,因此咱们将状态设置为PLAYING时,也会收到NO_PREROLL返回值。
  这里设置is_live标识是由于咱们不对其进行缓冲处理。oop

 

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_BUFFERING事件,收到此事件时,咱们能够经过gst_message_parse_buffering()获得缓冲进度,若是进度小于100%咱们就暂停播放,在缓冲完成后咱们再恢复播放。若是使用playbin,咱们能够直接经过buffer-size或buffer-duration属性去修改缓冲区大小。源码分析

 

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;

  针对于时钟丢失的这种状况,咱们只需在收到GST_MESSAGE_CLOCK_LOST事件时,改变Pipline的状态,由GStreamer自动选取参考时钟便可。ui

总结

经过本文,咱们了解了如何应对两种简单的网络播放问题:spa

  • 经过缓冲消息来控制播放状态。
  • 在时钟丢失时从新选择时钟。

经过使用缓冲队列,可使得网络播放更加流畅。debug

 

引用

https://gstreamer.freedesktop.org/documentation/tutorials/basic/streaming.html?gi-language=c

 

做者: John.Leng
本文版权归做者全部,欢迎转载。商业转载请联系做者得到受权,非商业转载请在文章页面明显位置给出原文链接.
相关文章
相关标签/搜索