C語言多媒體框架GStreamer使用教程深講
之前參與的萬能視頻播放器項(xiàng)目采用了多媒體GStreamer開源框架,在最上層業(yè)務(wù)層通過連接各個插件形成一個pipeline來完成相應(yīng)的業(yè)務(wù)需求。但之前沒接觸過GStreamer框架,所以從項(xiàng)目的前期預(yù)研開始,就從GStreamer最基本的概念開始熟悉,逐步解決項(xiàng)目中遇到的多個問題。本文根據(jù)此次項(xiàng)目實(shí)踐,對GStreamer多媒體框架做一個相對全面的總結(jié)。
1、GStreamer簡介
GStreamer是GNOME桌面環(huán)境下用來創(chuàng)建流媒體應(yīng)用的多媒體框架,其基本設(shè)計(jì)思想來自于俄勒岡(Oregon)研究生學(xué)院有關(guān)視頻管道的創(chuàng)意,同時也借鑒了DirectShow的設(shè)計(jì)思想。
GStreamer是用c語言實(shí)現(xiàn)的,使用了面向?qū)ο蟮乃季S。GStreamer框架是基于插件和管道的,所有的插件都能夠被鏈接到任意的已經(jīng)定義的數(shù)據(jù)流管道中,數(shù)據(jù)通過管道機(jī)制進(jìn)行統(tǒng)一交換。GStreamer的很多優(yōu)點(diǎn)來源于其框架的模塊化,使得新的插件能夠無縫合并。
GStreamer能夠處理任意類型的數(shù)據(jù)流,其目標(biāo)是要簡化音/視頻應(yīng)用程序的開發(fā),其最顯著的用途是在構(gòu)建音視頻播放器、編輯音視頻文件、音視頻格式轉(zhuǎn)換和流媒體服務(wù)上,GStreamer已經(jīng)能夠被用來處理像 MP3、Ogg、MPEG1、MPEG2、AVI、Quicktime 等多種格式的多媒體數(shù)據(jù)。
GStreamer核心庫函數(shù)是一個處理插件、數(shù)據(jù)流和媒體操作的框架。另外,其還提供了一套API,用于程序員使用其它插件來編寫他所需要的應(yīng)用程序時使用。但是,由于追求模塊化和高效率,使得GStreamer在整個框架上變的復(fù)雜,也同時因?yàn)閺?fù)雜度的提高,使得開發(fā)一個新的應(yīng)用程序顯得不是那么的簡單。
2、GStreamer基本概念
2.1、元件(Element)
元件是GStreamer的核心,是具有一定功能的基本單元,可將其描述為一個具有特定屬性的黑盒子。其在代碼里面的類型是GstElement,可以理解為Gstreamer里面的基類。Gstreamer默認(rèn)安裝了很多有用的元件,按照功能上的差異,element分為以下幾類:
(1)source element 數(shù)據(jù)源元件,只有輸出端,用來產(chǎn)生供管道消費(fèi)的數(shù)據(jù),例如,音頻捕捉單元,它從聲卡讀取原始音頻數(shù)據(jù),供其它模塊用;
(2)filter(/filter-like) element 中間元件,包括過濾器、轉(zhuǎn)換器、復(fù)用器、解復(fù)用器、編解碼器等,其既有輸入端又有輸出端,從輸入端獲得相應(yīng)數(shù)據(jù),經(jīng)過處理之后傳遞給輸出端,有的element可能有一個source pad多個sink pads(demux),有的可能有多個source pads一個sink pad(mux),有的有一個source pad一個sink pad,例如,音頻編碼單元,它從外界獲得音頻數(shù)據(jù)之后,根據(jù)壓縮算法編碼后,給其它模塊使用;
(3)sink elements 接收器元件,只有輸入端,僅有消費(fèi)數(shù)據(jù)的能力,是整條媒體管道的終端,例如,音頻回放單元,負(fù)責(zé)將接收到的數(shù)據(jù)寫到聲卡上;
2.2、箱柜(Bin)
由多個基本單元組成的一個高級的功能單元,是裝載元件的容器,可以通過改變一個Bin的狀態(tài)來改變其內(nèi)部所有元件的狀態(tài),Bin可以發(fā)送總線消息(bus message)給其子集元件。
Bin和pipeline的區(qū)別就是pipeline肯定是bin,但bin不一定是pipeline,bin就像一個盒子,里面放了什么東西,功能具體是怎么實(shí)現(xiàn)的,用戶可以不關(guān)心,bin是元件的集合,而pipeline更強(qiáng)調(diào)應(yīng)用的可執(zhí)行性。
2.3、管道(Pipeline)
最高等級的Bin,是一種允許對所包含的元件進(jìn)行安排(scheduling)的普通容器。頂層(toplevel)箱柜必須為一個管道,因此每個GStreamer應(yīng)用程序都至少需要一個管道。當(dāng)應(yīng)用程序啟動后,管道會自動運(yùn)行在后臺線程中,下面是一個典型的pipeline示例:
2.4、襯墊(Pad)
不同Elements之間的鏈接點(diǎn),數(shù)據(jù)流在元件之間流動就是依靠Pads。Pads有處理特殊數(shù)據(jù)的能力,也就是其支持特定媒體類型的能力,一個Pads能夠限制數(shù)據(jù)流類型的通過,鏈接成功的條件是,兩個Pads允許通過的數(shù)據(jù)類型一致時才能建立(數(shù)據(jù)類型協(xié)商)。
Pads按照數(shù)據(jù)導(dǎo)向,可分為source pads(element的輸出),sink pads(element 的輸入),按照時效性可分為,永久型(always)、隨機(jī)型(sometimes)、請求型(on request),三種時效性的意義顧名思義: 永久型的襯墊一直會存在,隨機(jī)型的襯墊只在某種特定的條件下才存在(會隨機(jī)消失的襯墊也屬于隨機(jī)型),請求型的襯墊只在應(yīng)用程序明確發(fā)出請求時才出現(xiàn)。
Pads通過GstCaps對象進(jìn)行描述,一個GstCaps對象包括一個或者多個GstStructure對象,一個GstStructure描述一種媒體類型,其結(jié)構(gòu)中只包含功能集中規(guī)定的固定值。
2.5、能力集(Caps)
Pad的屬性描述,例如:
SRC template: 'src'
Availability: Always
Capabilities:
audio/x-raw-float
rate: [ 8000, 50000 ]
channels: [ 1, 2 ]
endianness: 1234
width: 32
buffer-frames: 0
SINK template: 'sink'
Availability: Always
Capabilities:
audio/x-vorbis
2.6、幽靈pad(ghost pad)
bin本身沒有pad,所以就沒有辦法把兩個bin鏈接起來。但可以用bin中的一個元件的pad構(gòu)造一個代理pad,這樣bin就有一個代理pad了。這個pad實(shí)際指向被代理的那個單元的pad,示例如下:
2.7、Bus
Bus采用自己的線程機(jī)制,負(fù)責(zé)pipeline線程和應(yīng)用程序程序之間的通信。每個pipeline缺省創(chuàng)建一個Bus,應(yīng)用程序在總線上設(shè)置一個類似于對象的信號處理的消息處理器,當(dāng)主循環(huán)運(yùn)行的時候,總線將會輪詢這個消息處理器是否有新的消息,當(dāng)消息被采集到后,總線將呼叫相應(yīng)的回調(diào)函數(shù)來完成相關(guān)操作。
應(yīng)用程序有兩種方法使用Bus,第一種是使用 GLib/Gtk+ main loop及gst_bus_add_watch () or gst_bus_add_signal_watch()事件回調(diào)函數(shù)機(jī)制,第二種是程序通過gst_bus_peek () /gst_bus_poll ()主動檢查Bus中的消息;
2.8、緩沖區(qū)(Buffer)
管道的數(shù)據(jù)流由一組緩沖區(qū)和事件組成,緩沖區(qū)包括實(shí)際的管道數(shù)據(jù),事件包括控制信息,如尋找信息和流的終止信號。所有這些數(shù)據(jù)流在運(yùn)行的時候自動的流過管道。
緩沖區(qū)包含了你創(chuàng)建的管道里的數(shù)據(jù)流,通常一個源元件會創(chuàng)建一個新的緩沖區(qū),同時元件還將會把緩沖區(qū)的數(shù)據(jù)傳遞給下一個元件。一個緩沖區(qū)主要由以下一個組成:
(1)指向某塊內(nèi)存的指針;
(2)內(nèi)存的大小;
(3)緩沖區(qū)的時間戳;
(4)一個引用計(jì)數(shù),指出了緩沖區(qū)所使用的元件數(shù)。沒有元件可引用的時候,這個引用將用于銷毀緩沖區(qū)。
buffer的創(chuàng)建有2種方式,一種是由當(dāng)前的element自己創(chuàng)建,然后把這個buffer傳遞給下一個element;另外一種方式就是dwonstream-allocated buffers,就是由下一個element來創(chuàng)建要求大小的buffer,并提供buffer操作函數(shù),當(dāng)前element通過調(diào)用buffer操作函數(shù)將數(shù)據(jù)寫入這個buffer中完成buffer數(shù)據(jù)傳遞。其區(qū)別在于buffer的創(chuàng)建是在數(shù)據(jù)傳輸?shù)脑炊薳lement創(chuàng)建還是在數(shù)據(jù)接收端element來創(chuàng)建。
2.9、插件(Plugin)
元件必須封裝在插件中才能被使用,一個插件是一塊可以加載的代碼,通常被稱為共享對象文件(shared object file)或動態(tài)鏈接庫(dynamically linked library),一個插件中可以包含一個或若干element。
3、GStreamer基本架構(gòu)
GStreamer core、Plugins以及依賴的第三方開源庫的架構(gòu)關(guān)系,如下圖所示,
Gstreamer的組成結(jié)構(gòu)如下圖所示:
4、GStreamer通信機(jī)制
Gstreamer的通信機(jī)制示意圖及解釋如下:
4.1、Message
pipeline用來主動向外報告自己的運(yùn)行狀態(tài)。這些Message被發(fā)送到一個消息隊(duì)列,也就是pipeline的Bus,應(yīng)用程序可以從Bus中獲取Message,并作出自定義的反應(yīng)。Message是GST提供的,屬于異步操作;
4.2、Event
pipeline中插件之間進(jìn)行通信的機(jī)制,分為下行事件,上行事件和雙向事件。也可以由應(yīng)用程序直接向某一個插件發(fā)送事件,但起作用的前提是:該插件定義了該事件的響應(yīng)操作。 通過事件可以控制整個pipeline的運(yùn)行狀態(tài)。
下行事件是由source插件向sink插件方向傳輸,例如,
GST_EVENT_EOS (流的終止信號)
GST_EVENT_NEWSEGMENT
上行事件是由sink插件向source插件方向傳輸,用于改變管道中數(shù)據(jù)流的狀態(tài),例如:
GST_EVENT_QOS
GST_EVENT_SEEK(查找)
雙向事件,例如:
GST_EVENT_FLUSH_START
GST_EVENT_FLUSH_STOP
4.3、Signal
應(yīng)用程序控制某一插件的運(yùn)行狀態(tài),signal可以看做Glib對象的一個屬性,是由Gobject系統(tǒng)提供的,屬于同步操作,與linux中的系統(tǒng)信號有差別。通過信號可以讓某個插件做一些對插件本身變量的操作,比如增加或者刪除一些維護(hù)信息等等。
4.4、Probe
應(yīng)用程序可以通過探針Probe來探測某個插件的pad中流過的數(shù)據(jù),比如:在audioconert 插件的src pad 加一個探針,每當(dāng)有buf到達(dá)時,就調(diào)用callback_have_data(),這里這個函數(shù)只是打印一下buf的大小,統(tǒng)計(jì)一下buf流過的個數(shù);
//main GstPad *m_pad_concert_src = gst_element_get_static_pad(m_gst_convert, "src"); gst_pad_add_buffer_probe(m_pad_concert_src, G_CALLBACK(callback_have_data), NULL); gst_object_unref(m_pad_concert_src); /*******Callback handler when probe date received***********/ static gboolean callback_have_data(GstPad *padsrc, GstBuffer *buffer, gpointer data) { gint iBufSize = 0; gchar* pBuffer = NULL; iBufSize = GST_BUFFER_SIZE(buffer); pBuffer = (gchar*)GST_BUFFER_DATA(buffer); static gint numBuf = 0; g_print("\rBUF %d Size=%d ", numBuf++, iBufSize); return TRUE; }
4.5、Quary
應(yīng)用程序可以查詢pipline當(dāng)前的運(yùn)行狀態(tài),比如:以下代碼用來查詢當(dāng)前播放的位置,和總的播放時間。
GstFormat m_format = GST_FORMAT_TIME; gint64 m_position , m_length; if( gst_element_query_position(pipeline, &m_format,&m_position) && gst_element_query_duration(pipeline, &m_format, &m_length)) { g_print("Current: %"GST_TIME_FORMAT" Total: %" GST_TIME_FORMAT "\r", GST_TIME_ARGS(m_position),GST_TIME_ARGS(m_length)); }
5、GStreamer元件狀態(tài)
一個元件在被創(chuàng)建后,它不會執(zhí)行任何操作,通過改變元件的狀態(tài),才能使它做某些事情。元件有四種狀態(tài),每種狀態(tài)都有其特定的意義,具體如下:
GST_STATE_NULL 默認(rèn)狀態(tài):沒有分配任何資源,沒有載入插件,不能處理數(shù)據(jù);
GST_STATE_READY 預(yù)備狀態(tài):分配或載入所有與流無關(guān)的資源(非硬件資源),所有數(shù)據(jù)流的位置信息應(yīng)該自動置0,如果數(shù)據(jù)流先前被打開過,它應(yīng)該被關(guān)閉,并且其位置信息、特性信息應(yīng)該被重新置為初始狀態(tài);
GST_STATE_PAUSED 暫停狀態(tài):準(zhǔn)備好全部資源,接受數(shù)據(jù)流,只是sink element暫停,收到數(shù)據(jù)不處理,只是block;
GST_STATE_PLAYING 播放狀態(tài):準(zhǔn)備好全部資源,接受并處理數(shù)據(jù)流;其實(shí)這個狀態(tài)除了當(dāng)前運(yùn)行時鐘外,其它與PAUSED狀態(tài)一樣,可以通過gst_element_set_state()來改變一個元件的狀態(tài),當(dāng)元件處于GST_STATE_PLAYING狀態(tài),管道會開始自動處理數(shù)據(jù)。
6、GStreamer中的幾個關(guān)鍵概念
6.1、識別流的MIME類型
元件通過caps來描述其能處理的媒體格式,元件之間交互數(shù)據(jù)流通過caps協(xié)商,caps是一個mime類型或者一些特性集的組合。
一個加載進(jìn)系統(tǒng)的元件必須提供其源襯墊和接收襯墊支持的mime類型。通過Gstreamer注冊中心可以知道目前注冊的不同的元件,以及他們所期望得到的與他們能夠產(chǎn)生的媒體類型,下圖顯示了管道中每個Pads所處理的MIME類型。
6.2、媒體流類型檢測(typefind)
通常當(dāng)加載一個新的媒體流時,媒體的類型并不明了。這意味著選擇一條管道來對媒體流進(jìn)行解碼之前,首先需要檢測媒體流的類型。 GStreamer 使用了類型檢測 (typefinding) 來達(dá)到此目的。類型檢測是構(gòu)建管道所必經(jīng)的步驟。
首先它會一直讀取數(shù)據(jù)流,在此期間,它會把數(shù)據(jù)提供給所有的實(shí)現(xiàn)了類型檢測器 (typefinder) 的插件,當(dāng)其中任何一個類型檢測器識別出數(shù)據(jù)流,這個類型檢測器元件將會發(fā)送一個信號,并開始像一個關(guān)卡 (passthrough)模塊一樣工作。如果數(shù)據(jù)流的類型沒有被任何類型檢測器識別出來,管道會發(fā)送一個錯誤信息,并終止所有正在處理該數(shù)據(jù)流的動作。一旦類型檢測元件找到一個類型,應(yīng)用程序?qū)褂迷撛鳛楣艿赖囊徊糠謥斫獯a媒體流。
6.3、數(shù)據(jù)探測
探測是襯墊監(jiān)聽器的形象比喻,從技術(shù)上,探針僅僅是一個可以依附于襯墊的回調(diào)信號。這些信號默認(rèn)是沒有被發(fā)射(fired)的(不然的話會降低性能),但是可以通過附加探針調(diào)用gst_pad_add_data_probe() 或類似的函數(shù)被激活,這些函數(shù)附加了信號處理器,并激活實(shí)際信號的發(fā)射。
同樣地,你可以用 gst_pad_remove_data_probe () 或相關(guān)函數(shù)來刪除信號處理器,也可以只是監(jiān)聽時間或緩沖區(qū)。 探針在管道的線程context運(yùn)行,所以回調(diào)不應(yīng)該阻塞,而且通常不能有異常的阻塞,否則會降低管道的性能,如果出現(xiàn)這樣的缺陷,會導(dǎo)致死鎖甚至崩潰。
6.4、插件加載流程
如下圖所示,基于插件的程序,其工作原理本質(zhì)上都是通過讀取動態(tài)庫實(shí)現(xiàn)的,只需要每個動態(tài)庫中實(shí)現(xiàn)某一個特定的接口就可以了,比如XX_init等,這里就是plugin_init。里面會有個像注冊表一樣的數(shù)據(jù)結(jié)構(gòu)會存儲所有的插件的信息。
7、GStreamer開發(fā)示例-MP3文件播放器
利用GStreamer框架提供的組件,來實(shí)現(xiàn)一個簡單的MP3播放器。數(shù)據(jù)源元件負(fù)責(zé)從磁盤上讀取數(shù)據(jù),過濾器元件負(fù)責(zé)對數(shù)據(jù)進(jìn)行解碼,而接受器元件則負(fù)責(zé)將解碼后的數(shù)據(jù)寫入聲卡,示例代碼和注釋如下:
#include <gst/gst.h> #include <glib.h> //定義消息處理函數(shù), static gboolean bus_call(GstBus *bus,GstMessage *msg,gpointer data) { GMainLoop *loop = (GMainLoop *) data;//主循環(huán)的指針,接受EOS消息時退出 switch (GST_MESSAGE_TYPE(msg)) { case GST_MESSAGE_EOS: g_print("End of stream\n"); g_main_loop_quit(loop); break; case GST_MESSAGE_ERROR: { gchar *debug; GError *error; gst_message_parse_error(msg,&error,&debug); g_free(debug); g_printerr("ERROR:%s\n",error->message); g_error_free(error); g_main_loop_quit(loop); break; } default: break; } return TRUE; } int main(int argc,char *argv[]) { GMainLoop *loop; GstElement *pipeline,*source,*decoder,*sink;//定義組件 GstBus *bus; gst_init(&argc,&argv); //初始化gstreamer loop = g_main_loop_new(NULL,FALSE);//創(chuàng)建主循環(huán),在執(zhí)行 g_main_loop_run后正式開始循環(huán) if(argc != 2) { g_printerr("Usage:%s <mp3 filename>\n",argv[0]); return -1; } //創(chuàng)建管道和元件 pipeline = gst_pipeline_new("audio-player"); //管道用來容納元件 source = gst_element_factory_make("filesrc","file-source");//數(shù)據(jù)源元件 decoder = gst_element_factory_make("mad","mad-decoder");//過濾器元件 sink = gst_element_factory_make("autoaudiosink","audio-output");//接收器元件 if(!pipeline||!source||!decoder||!sink){ g_printerr("One element could not be created.Exiting.\n"); return -1; } //設(shè)置 source的location 參數(shù),即文件地址. g_object_set(G_OBJECT(source),"location",argv[1],NULL); //得到管道的消息總線 bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); //添加消息監(jiān)視器 gst_bus_add_watch(bus,bus_call,loop); gst_object_unref(bus); //把元件添加到管道中。管道是一個特殊的組件,可以更好的讓數(shù)據(jù)流動 gst_bin_add_many(GST_BIN(pipeline),source,decoder,sink,NULL); //通過襯墊依次連接元件 gst_element_link_many(source,decoder,sink,NULL); //啟動管道,開始播放 gst_element_set_state(pipeline,GST_STATE_PLAYING); g_print("Running\n"); //開始循環(huán) g_main_loop_run(loop); g_print("Returned,stopping playback\n"); //終止管道,釋放資源 gst_element_set_state(pipeline,GST_STATE_NULL); gst_object_unref(GST_OBJECT(pipeline)); return 0; }
編譯運(yùn)行
gcc-Wall$(pkg-config--cflags--libsgstreamer-0.10)-gtest2.c-otest2
./test2/home/phinecos/test.mp3
8、最后
本文總結(jié)了多媒體框架GStreamer一些基本概念及流程,希望能給使用GStreamer開源庫的朋友提供一個借鑒或參考。
到此這篇關(guān)于C語言多媒體框架GStreamer使用教程深講的文章就介紹到這了,更多相關(guān)C語言GStreamer內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言實(shí)現(xiàn)找出二叉樹中某個值的所有路徑的方法
這篇文章主要介紹了C語言實(shí)現(xiàn)找出二叉樹中某個值的所有路徑的方法,針對數(shù)據(jù)結(jié)構(gòu)中二叉樹的實(shí)用操作技巧,需要的朋友可以參考下2014-09-09二分法求多項(xiàng)式在-10 10間值的實(shí)現(xiàn)代碼
以下實(shí)例是介紹了二分法求多項(xiàng)式在-10 10間值的實(shí)現(xiàn)代碼。需要的朋友參考下2013-05-05C++實(shí)現(xiàn)冒泡排序(BubbleSort)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)冒泡排序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-04-04C語言數(shù)據(jù)結(jié)構(gòu)深入探索順序表
順序表,全名順序存儲結(jié)構(gòu),是線性表的一種,線性表用于存儲邏輯關(guān)系為“一對一”的數(shù)據(jù),順序表自然也不例外,不僅如此,順序表對數(shù)據(jù)的物理存儲結(jié)構(gòu)也有要求,跟隨下文來具體了解吧2022-03-03C語言 pthread_create() 函數(shù)講解
這篇文章主要介紹了C語言 pthread_create() 函數(shù)講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08