1. 程式人生 > >Gstreamer基礎教程10 - Streaming

Gstreamer基礎教程10 - Streaming

摘要

  我們把直接從網路播放一個媒體檔案的方式稱為線上播放(Online Streaming),我們已經在以往的例子中體驗了GStreamer的線上播放功能,當我們指定播放URI為 http:// 時,GStreamer內部會自動通過網路獲取媒體資料。在今天的示例中,我們將進一步瞭解如何處理由網路問題導致的視訊緩衝及時鐘丟失的問題。

線上播放

  在我們進行線上播放時,我們會將收到的媒體資料立即進行解碼並送入顯示佇列顯示。當網路不理想時,我們通常不能及時的接收資料,顯示佇列中的資料會被耗盡而不能得到及時的補充,這會導致播放出現卡頓。
  一種通用的處理方式是建立一個緩衝佇列,在佇列的資料量達到一定閥值時才進行播放,這樣會導致起播時間會有一定的延遲,但會使後續的播放更加流暢,避免了因部分資料無法及時到達造成的停頓。

  GStreamer框架已經實現了緩衝佇列,但在以往的示例中我們並沒有使用其相關的功能。某些Element(例如playbin中使用的queue2及multiqueue)可以建立緩衝佇列,並在超過/低於指定的資料閥值時產生相應的訊號。應用程式可以監聽此類訊號,在資料不足時(buffer值小於100%)主動暫停播放,在資料充足時恢復播放。
  為了達到多個Sink的同步(例如音視訊同步),我們需要使用一個全域性的參考時鐘,GStreamer會在播放時自動選取一個時鐘。在某些網路線上播放的情況下原有時鐘會失效,我們需要重新選取一個參考時鐘。例如,RTP Source切換流或者改變輸出裝置。
  在參考時鐘丟失時,GStreamer框架會產生相應的事件,應用層需要對其作出響應,由於GStreamer在進入PLAYING狀態時會自動選取參考時鐘,所以我們只需在收到時鐘丟失事件時將Pipeline的狀態切換到PUASED,再切換到PLAYING即可。

示例程式碼

#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標識是因為我們不對其進行緩衝處理。

 

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自動選取參考時鐘即可。

總結

通過本文,我們瞭解瞭如何應對兩種簡單的網路播放問題:

  • 通過緩衝訊息來控制播放狀態。
  • 在時鐘丟失時重新選擇時鐘。

通過使用緩衝佇列,可以使得網路播放更加流暢。

 

引用

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

 

作者:John.Leng 出處:http://www.cnblogs.com/xleng/ 本文版權歸作者所有,歡迎轉載。商業轉載請聯絡作者獲得授權,非商業轉載請在文章頁面明顯位置給出原文連線.