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
本文版權歸做者全部,歡迎轉載。商業轉載請聯繫做者得到受權,非商業轉載請在文章頁面明顯位置給出原文鏈接.
相關文章
相關標籤/搜索