摘要
在 Gstreamer基礎教程01 - Hello World中,我們介紹了如何快速的通過一個字符串創建一個簡單的pipeline。為了能夠更好的控制pipline中的element,我們需要單獨創建element,然后再構造pipeline,下面將介紹GStreamer的一些基本概念並展示pipeline的另一種構造方式。
基本概念
Element
我們知道element是構建GStreamer pipeline的基礎,element在框架中的類型為GstElement,所有GStreamer提供的解碼器(decoder),編碼器(encoder), 分離器(demuxer), 音視頻輸出設備都是派生自GstElement。
那么element到底是什么呢?
從應用的角度,我們可以將一個element認為是一個功能塊,他實現一個特定的功能,比如:數據讀取,音頻解碼,聲音輸出等。各個功能塊之間可以通過某種特定的數據接口(這種接口稱為pad,將在后續章節講述)進行數據傳輸。每個element有唯一的類型,還有相應的屬性,用於控制element的行為。
為了更直觀的展現element,我們通常用一個框來表示一個element,在element內部使用小框表示pad。
這些功能塊有些可以生成數據,有些只接收數據,有些先接收數據,再生成數據。為了便於區分這些element,我們把他們分為三類:
1. source element
只能生成數據,不能接收數據的element稱為source element。例如用於文件讀取的filesrc等。
對於source element,我們通常用src pad表示element能產生數據,並將其放在element的右邊。source element只有src pad,通過設備、文件、網絡等方式讀取數據后,通過src pad向pipeline發送數據,開始pipeline的處理流程。如下圖:
2. sink element
只能接收數據,不能產生數據的element稱為sink element。例如播放聲音的alsasink等。
對於sink element,我們通常用sink pad表示element能接收處理數據,並將其放在element的左邊。sink element只有sink pad,從sink pad讀取數據后,將數據發送到指定設備或位置,結束pipeline的處理流程。如下圖:
3. filter-like element
既能接收數據,又能生成數據的element稱為filter-like element。例如分離器,解碼器,音量控制器等。
對於filter-like element,既包含位於element左邊的sink pad,又包含位於element右邊的src pad。Element首先從sink pad讀取數據,然后對數據進行處理,最后在src pad產生新的數據。如下圖:
對於這些的element,可能包含多個src pad,也可能包含多個sink pad,例如mp4的demuxer(qtdemux)會將mp4文件中的音頻和視頻的分離到audio src pad和video src pad。而mp4的muxer(mp4mux)則相反,會將audio sink pad和video sink pad的數據合並到一個src pad,再經其他element將數據寫入文件或發送到網絡。demuxer如下圖:
連接element
當我們有很多element時,我們需要將他們按數據的傳輸路徑將其串聯起來,src pad只能聯接到sink pad,這樣才能夠實現相應的功能。
Bin和Pipeline
我們將element串聯起來后就能實現相應的功能,為什么我們還需要bin和pipline呢?我們首先來看看在GStreamer框架中element,bin,pipeline對象之間的繼承關系:
GObject
╰──GInitiallyUnowned
╰──GstObject
╰──GstElement
╰──GstBin
╰──GstPipeline
這里bin和pipeline都是一個element,那么bin和pipeline都在element的基礎上實現了什么功能,解決了什么問題呢?
我們在創建了element多個element后,我們需要對element進行狀態/資源管理,如果每次狀態改變時,都需要依次去操作每個element,這樣每次編寫一個應用都會有大量的重復工作,這時就有了bin。
Bin繼承自element后,實現了容器的功能,可以將多個element添加到bin,當操作bin時,bin會將相應的操作轉發到內部所有的element中,我們可以將bin認為認為是一個新的邏輯element,由bin來管理其內部element的狀態及資源,同事轉發其產生的消息。常見的bin有decodebin,autovideoconvert等。
Bin實現了容器的功能,那pipeline又有什么功能呢?
在多媒體應用中,音視頻同步是一個基本的功能,需要支持這樣的功能,所有的element必須要有一個相同的時鍾,這樣才能保證各個音頻和視頻在同一時間輸出。pipeline就會為其內部所有的element選擇一個相同的時鍾,同時還為應用提供了bus系統,用於消息的接收。
Bus
剛才我們提到pipeline會提供一個bus,這個pipeline上所有的element都可以使用這個bus向應用程序發送消息。Bus主要是為了解決多線程之間消息處理的問題。由於GStreamer內部可能會創建多個線程,如果沒有bus,應用程序可能同時收到從多個線程的消息,如果應用程序在發送線程中通過回調去處理消息,應用程序有可能阻塞播放線程,造成播放卡頓,死鎖等其他問題。為了解決這類問題,GStreamer通常是將多個線程的消息發送到bus系統,由應用程序從bus中取出消息,然后進行處理。Bus在這里扮演了消息隊列的角色,通過bus解耦了GStreamer框架和應用程序對消息的處理,降低了應用程序的復雜度。
Element Hello World
在有上面的知識后,我們通過一個示例來看看element是如何創建及使用的。
#include <gst/gst.h>
int main (int argc, char *argv[]) { GstElement *pipeline, *source, *filter, *sink; GstBus *bus; GstMessage *msg; GstStateChangeReturn ret; /* Initialize GStreamer */ gst_init (&argc, &argv); /* Create the elements */ source = gst_element_factory_make ("videotestsrc", "source"); filter = gst_element_factory_make ("timeoverlay", "filter"); sink = gst_element_factory_make ("autovideosink", "sink"); /* Create the empty pipeline */ pipeline = gst_pipeline_new ("test-pipeline"); if (!pipeline || !source || !filter || !sink) { g_printerr ("Not all elements could be created.\n"); return -1; } /* Build the pipeline */ gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL); if (gst_element_link_many (source, filter, sink, NULL) != TRUE) { g_printerr ("Elements could not be linked.\n"); gst_object_unref (pipeline); return -1; } /* Modify the source's properties */ g_object_set (source, "pattern", 0, NULL); /* 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; } /* Wait until error or EOS */ bus = gst_element_get_bus (pipeline); msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS); /* Parse message */
if (msg != NULL) { GError *err; gchar *debug_info; switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_ERROR: gst_message_parse_error (msg, &err, &debug_info); g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); g_clear_error (&err); g_free (debug_info); break; case GST_MESSAGE_EOS: g_print ("End-Of-Stream reached.\n"); break; default: /* We should not reach here because we only asked for ERRORs and EOS */ g_printerr ("Unexpected message received.\n"); break; } gst_message_unref (msg); } /* Free resources */ gst_object_unref (bus); gst_element_set_state (pipeline, GST_STATE_NULL); gst_object_unref (pipeline); return 0; }
編譯並運行示例,可以看到彈出的窗口中播放着測試視頻,並且還顯示着播放時間。
$ gcc basic-tutorial-2.c -o basic-tutorial-2 `pkg-config --cflags --libs gstreamer-1.0` $ ./basic-tutorial-2
源碼分析
創建Element
/* Create the elements */ source = gst_element_factory_make ("videotestsrc", "source"); filter = gst_element_factory_make ("timeoverlay", "filter"); sink = gst_element_factory_make ("autovideosink", "sink");
在對GStreamer進行初始化后,我們可以通過gst_element_factory_make創建element。第一個參數是element的類型,可以通過這個字符串,找到對應的類型,從而創建element對象。第二個參數指定了創建element的名字,當我們沒有保存創建element的對象指針時,我們可以通過gst_bin_get_by_name從pipeline中取得該element的對象指針。如果第二個參數為NULL,則GStreamer內部會為該element自動生成一個唯一的名字。
我們在當前示例中創建了3個element:videotestsrc,timeoverlay,autovideosink,其作用分別為:
- videotestsrc是一個source element,用於產生視頻數據,通常用於調試。
- timeoverlay是一個filter-like element,可以在視頻數據中疊加一個時間字符串。
- autovideosink上一個sink element,用於自動選擇視頻輸出設備,創建視頻顯示窗口,並顯示其收到的數據。
創建Pipeline
/* Create the empty pipeline */ pipeline = gst_pipeline_new ("test-pipeline");
Pipeline通過gst_pipeline_new創建,參數為pipeline的名字。
我們知道pipeline會提供播放所必須的時鍾以及對消息的處理,所以我們需要把我們創建的element添加到pipeline中。
/* Build the pipeline */ gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL); if (gst_element_link_many (source, filter, sink, NULL) != TRUE) { g_printerr ("Elements could not be linked.\n"); gst_object_unref (pipeline); return -1; }
從上面的講解我們知道pipeline是繼承自bin,所以所有bin的方法都可以應用於pipeline,需要注意的是,我們需要通過相應的宏(這里是GST_BIN)來將子類轉換為父類,宏內部會對其做類型檢查。在這里我們使用gst_bin_add_many將多個element加入到pipeline中,這個函數接受任意多個參數,最后以NULL表示參數列表的結束。如果一次只需要加入一個,可以使用gst_bin_add函數。
在將element加入bin后,我們需要將其連接起來才能完成相應的功能,由於有多個element,所以我們這里使用gst_element_link_many,element會根據參數的順序依次將element連接起來。
需要注意的是,只有被加入到同一個bin的element才能夠被連接在一起,所以我們需要在連接前,將所需要的element加入到pipeline/bin中。
至此,我們已經完成了pipeline的創建,test-pipeline可以表示為:
設置element屬性
/* Modify the source's properties */ g_object_set (source, "pattern", 0, NULL);
大部分的element都有自己的屬性。有的屬性只能被讀取,這種屬性常用於查詢element的狀態。有的屬性同時支持修改,這種屬性常用於控制element的行為。
由於GstElement繼承於GObject,同時GObject對象系統提供了 g_object_get()用於讀取屬性,g_object_set()用於修改屬性,g_object_set()支持以NULL結束的屬性-值的鍵值對,所以可以一次修改element的多個屬性。
我們這里通過g_object_set()來修改videotestsrc的pattern屬性。pattern屬性可以控制測試圖像的類型,可以嘗試將0修改為1,查看輸出結果有何不同。
我們可以通過gst-inspect-1.0 videotestsrc命令來查看pattern所支持的所有值。
設置播放狀態
/* 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; }
在完成pipeline的創建以及屬性的修改后,我們將pipeline的狀態設置為PLAYING,這里與上一文章中的示例相同,只增加來錯誤處理,其他的返回值處理講在后續章節講述。
等待播放結束與釋放資源
這部分內容與上一文章中的示例相同,這里只增加了消息類型的判斷。更多關於消息的內容將在后續章節講述。
由於videotestsrc會持續輸出視頻數據,所以我們在這個例子中不會受到EOS消息,只有當我們關閉視頻窗口時會收到error消息,發送消息的element名和具體的消息內容會通過終端輸出。
總結
在本教程中,我們掌握了:
- GStreamer element,bin,pipeline,bus的基本概念。
- 如何使用gst_element_factory_make ()創建element。
- 如何使用gst_pipeline_new ()創建pipeline。
- 如何使用gst_bin_add_many ()將多個element加入pipeline。
- 如何使用gst_element_link_many ()將多個element連接起來。
在這兩篇文章中,我們介紹了pipeline的兩種創建方式,下一篇文章我們將介紹GStreamer是如何保證element可以正確的連接在一起。
引用
https://gstreamer.freedesktop.org/documentation/tutorials/basic/concepts.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/application-development/basics/elements.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/application-development/basics/bins.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/gstreamer/gstpipeline.html?gi-language=c