摘要
我們把直接從網路播放一個媒體檔案的方式稱為在線播放(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/本文著作權歸作者所有,歡迎轉載,商業轉載請聯系作者獲得授權,非商業轉載請在文章頁面明顯位置給出原文連接.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/3821.html
標籤:其他
