番外篇從此開(kāi)始忙厌,番外用于講一些HEXA開(kāi)發(fā)過(guò)程中的一些在日志中不便展開(kāi)的一些點(diǎn)點(diǎn)滴滴。
前言
- 如題,本篇要講的是Gstreamer的簡(jiǎn)單使用卸奉,最好是看官方文檔,除非看英文文檔對(duì)你來(lái)說(shuō)太痛苦颖御,那就來(lái)看這篇好了榄棵。
- 本篇雖然是講編程凝颇,不講命令行,但是在編程或編寫(xiě)命令行之前的準(zhǔn)備工作都是類(lèi)似的疹鳄,可以作為參考拧略。
Gstreamer簡(jiǎn)介
它的來(lái)歷我還沒(méi)有了解,目前我只知道它是一個(gè)在Linux環(huán)境上很容易制作流媒體應(yīng)用的程序框架瘪弓。
單說(shuō)Gstreamer的話(huà)垫蛆,它只是個(gè)框架,如果把框架比喻為骨架的話(huà)腺怯,插件就是它的血肉了袱饭,框架+插件就可以構(gòu)成一個(gè)處理流媒體的流水線(xiàn),基于Gstreamer的應(yīng)用可以通過(guò)對(duì)流水線(xiàn)的控制實(shí)現(xiàn)各種流媒體處理的功能呛占。
應(yīng)用開(kāi)發(fā)者只要在豐富的插件中找到適合自己應(yīng)用的虑乖,再把他們連成流水線(xiàn),立刻就可以構(gòu)建好一個(gè)流媒體處理模塊晾虑。關(guān)于使這個(gè)過(guò)程變得容易的原因疹味,一方面是插件非常豐富,大多數(shù)情況下帜篇,沒(méi)有必要自己制作插件糙捺;另一方面原因是插件的連接非常方便,在五花八門(mén)的插件中笙隙,有很簡(jiǎn)單的方法可以確定插件之間是否可以連接洪灯。
Gstreamer編程模型
我理解的Gstreamer編程要分為以下幾步
- 構(gòu)想流水線(xiàn)
在編程之前,首先要在腦中構(gòu)造一個(gè)流程竟痰,即輸入是什么婴渡,大概要經(jīng)過(guò)哪些處理,最后輸出什么凯亮。
輸入要想好輸入的形式或設(shè)備边臼,比如處理視頻的話(huà),可以是攝像頭假消、某種格式的視頻文件或是網(wǎng)絡(luò)流等柠并,音頻可以是話(huà)筒或某種格式的音頻文件等。
處理過(guò)程可以大致考慮一下富拗,應(yīng)該需要哪種編解碼器或是格式轉(zhuǎn)換器臼予,需要哪種muxer或demuxer。
輸出也是要想好形式或設(shè)備啃沪,比如視頻的話(huà)粘拾,可以是顯示器、液晶屏创千、某種格式的文件或是網(wǎng)絡(luò)流等缰雇,音頻可以是揚(yáng)聲器或某種格式的音頻文件等入偷。 - 插件選型
要在插件庫(kù)中找出能夠滿(mǎn)足構(gòu)想的那些插件,并確認(rèn)它們能夠連接起來(lái)械哟,構(gòu)成我們想要的流水線(xiàn)疏之。那就需要確認(rèn)以下問(wèn)題- 當(dāng)前開(kāi)發(fā)環(huán)境下哪些插件是可用的?
- 怎么知道那些從名字上看似乎可用的插件是不是能滿(mǎn)足需求暇咆?
- 怎么知道這些看上去能用的插件能不能接起來(lái)锋爪?
- 寫(xiě)代碼
把構(gòu)想中的對(duì)象和代碼對(duì)應(yīng)起來(lái),實(shí)現(xiàn)流水線(xiàn)爸业。
經(jīng)過(guò)上述過(guò)程后其骄,基本的編程就算完成了。下面以我寫(xiě)的simple.c為例扯旷,用上述過(guò)程復(fù)盤(pán)一下拯爽。
構(gòu)想流水線(xiàn)
官方例子
下圖來(lái)自官方文檔,看起來(lái)這是一個(gè)簡(jiǎn)單的ogg視頻文件播放器薄霜。整個(gè)大方框是一個(gè)pipeline,大方框中的小方框(例如file-source)是構(gòu)成pipeline的一個(gè)個(gè)插件中的元件纸兔,有src/sink字樣的藍(lán)色小方框是用來(lái)對(duì)接元件的東西惰瓜,其中src表示輸出,sink表示輸入汉矿。
單從字面上也不難理解這些元件
- file-source
即以文件作為源崎坊,這里具體就是某個(gè)ogg視頻文件了。 - ogg-demuxer
demuxer即分離器洲拇,從最后的結(jié)果看奈揍,它是把ogg文件中音/視頻分開(kāi)了。 - vorbis-decoder
很明顯赋续,這應(yīng)該是一個(gè)音頻解碼器 - audio-sink
看圖標(biāo)男翰,這應(yīng)該就是揚(yáng)聲器的驅(qū)動(dòng)之類(lèi)的了 - theora-decoder
很明顯+1,這應(yīng)該是一個(gè)視頻解碼器 - video-sink
看圖標(biāo)+1纽乱,這應(yīng)該就是顯示器的驅(qū)動(dòng)之類(lèi)的了
上面這個(gè)例子的輸入是ogg視頻文件蛾绎,中間要demuxer分離音視頻,然后分別解碼鸦列,最后輸出給音視頻設(shè)備播放租冠。其中的每個(gè)元件都是可以替換的,比如想把輸入源換成rtmp流薯嗤,只要把file-source換成rtmp-src元件就行了顽爹;再比如想做一個(gè)編碼器只要把整個(gè)圖左右翻轉(zhuǎn)一下,播放設(shè)備sink換成采集設(shè)備src骆姐,xxx-decoder換成xxx-encoder镜粤,xxx-demuxer換成xxx-encoder就可以了捏题。
我的流水線(xiàn)
- 輸入
視頻方面是機(jī)器人攝像頭采集的原始圖像,音頻方面反正還沒(méi)有合適的采集設(shè)備繁仁,先不考慮 - 輸出
rtmp網(wǎng)絡(luò)流 - 處理
在上面的輸入和輸出之間肯定要有個(gè)編碼器
后面加音頻的話(huà)涉馅,音視頻需要合成為一個(gè)流,所以必然需要一個(gè)合成器(muxer)
攝像頭==>編碼器==>合成器==>rtmp流黄虱,我的流水線(xiàn)就這么簡(jiǎn)單稚矿,然后我們來(lái)插件選型。
插件選型
- 當(dāng)前開(kāi)發(fā)環(huán)境下哪些插件是可用的捻浦?
這很簡(jiǎn)單晤揣,只要使用Gstreamer的命令行工具gst-inspect,如果是1.x版本在終端中輸入以下命令就能列出所有插件的名字
gst-inspect-1.0
很多插件......
rtmp: rtmpsink: RTMP output sink
rtmp: rtmpsrc: RTMP Source
很多插件......
這里我只列了兩個(gè)比較好認(rèn)的朱灿,rtmp
是插件名字昧识,rtmpsink
和rtmpsrc
是這個(gè)插件的2個(gè)元件類(lèi)型,第二個(gè)冒號(hào)后面是對(duì)這個(gè)元件的說(shuō)明盗扒。
- 怎么知道那些從名字上看似乎可用的插件是不是能滿(mǎn)足需求跪楞?
這個(gè)問(wèn)題隱含一個(gè)已知,我們要先大概知道一些名字侣灶,然后才能從名字上過(guò)濾掉大量的無(wú)關(guān)插件甸祭,比如關(guān)于攝像頭,需要知道v4l褥影、uvc等池户。
首先,找攝像頭信號(hào)源凡怎,可以用正則表達(dá)式搜索video.*source
校焦,然后再用搜索引擎大概了解一下就能初步選出來(lái)了。把那些比較像的找出來(lái)后统倒,只要再用用搜索引擎寨典,很容易確定其中的video4linux2: v4l2src: Video (video4linux2) Source
是大概率可用的。
其次房匆,找輸出插件凝赛,正則表達(dá)式搜rtmp.*sink
,和找輸入類(lèi)似的坛缕,rtmp: rtmpsink: RTMP output sink
看起來(lái)比較像墓猎,先就是它了。
最后赚楚,要把中間那些插件找到毙沾,先找編碼器,正則表達(dá)式搜索video.*encoder
宠页,在我的機(jī)器人上有3個(gè)插件中的6個(gè)元件備選左胞,如果在開(kāi)發(fā)環(huán)境里裝了Gstreamer的libav類(lèi)插件寇仓,就搜出更多了。
此處篩選可以基于這樣一個(gè)原則烤宙,基于已知的知識(shí)找出最像的那個(gè)作為備選就好遍烦。
因?yàn)楣俜浇o我推薦過(guò)libimxvpu
,所以我就用名字叫imxvpu的插件就行了躺枕,它有4個(gè)元件服猪,分別是imxvpuenc_mjpeg
,imxvpuenc_mpeg4
拐云,imxvpuenc_h264
罢猪,imxvpuenc_h263
,看起來(lái)就是選一種編碼格式就行了叉瘩,先隨便選一個(gè)膳帕,就先選h264
吧。如果沒(méi)有些先驗(yàn)只是薇缅,關(guān)于合成器(mux)就不是那么好找了危彩,如果知道rtmp和flash有關(guān),這一步可以先確定一個(gè)泳桦,不知道也沒(méi)關(guān)系汤徽,就直接進(jìn)行下一步好了。 - 怎么知道這些看上去能用的插件能不能接起來(lái)蓬痒?
- 為什么會(huì)有不能連接的情況泻骤?
所有插件不是一個(gè)人或一隊(duì)人寫(xiě)的漆羔,這就好像一個(gè)國(guó)際公司的兩個(gè)部門(mén)要對(duì)接一樣梧奢,兩個(gè)部門(mén)至少要各出一個(gè)人作為接口與對(duì)方部門(mén)交流,如果這倆部門(mén)處在不同的國(guó)家演痒,那么這兩個(gè)接口很可能講著不同的語(yǔ)言亲轨,他們之間就無(wú)法有效傳達(dá)信息了。
因此鸟顺,兩個(gè)元件要想連接惦蚊,必須知道講話(huà)的接口講得是什么語(yǔ)言,聽(tīng)話(huà)的接口聽(tīng)得懂什么語(yǔ)言讯嫂,如果語(yǔ)言一致蹦锋,就是能連接的。 - 怎么看元件能“講或聽(tīng)得懂哪種語(yǔ)言”欧芽?
我們可以用gst-inspect-1.0 <元件名>
進(jìn)一步看一下元件詳細(xì)信息莉掂,以便對(duì)元件有進(jìn)一步了解,下面看一下rtmpsink
的信息千扔。
- 為什么會(huì)有不能連接的情況泻骤?
# gst-inspect-1.0 rtmpsink
Factory Details:
Rank primary (256)
Long-name RTMP output sink
Klass Sink/Network
Description Sends FLV content to a server via RTMP
Author Jan Schmidt <thaytan@noraisin.net>
Plugin Details:
Name rtmp
Description RTMP source and sink
Filename /usr/lib/arm-linux-gnueabihf/gstreamer-1.0/libgstrtmp.so
Version 1.2.4
License LGPL
Source module gst-plugins-bad
Source release date 2014-04-18
Binary package GStreamer Bad Plugins (Ubuntu)
Origin URL https://launchpad.net/distros/ubuntu/+source/gst-plugins-bad1.0
GObject
+----GInitiallyUnowned
+----GstObject
+----GstElement
+----GstBaseSink
+----GstRTMPSink
...
Pad Templates:
SINK template: 'sink'
Availability: Always
Capabilities:
video/x-flv
...
Element Properties:
...
location : RTMP url
flags: readable, writable
String. Default: null
稍微解讀一下
- Factory Details和Plugin Details都是一些更詳盡的描述憎妙,比如
Source module gst-plugins-bad
是說(shuō)這個(gè)插件屬于gst-plugins-bad插件庫(kù)的库正,如果你想看代碼,可以去github搜這個(gè)名字厘唾。/usr/lib/arm-linux-gnueabihf/gstreamer-1.0/libgstrtmp.so
是這個(gè)插件庫(kù)文件的位置褥符。 - Pad Templates
Pad概念后面再討論,總之這里是描述這個(gè)元件可以“講什么語(yǔ)言”或“聽(tīng)什么語(yǔ)言”的抚垃。這里有個(gè)SINK template: 'sink'
是說(shuō)它有“聽(tīng)”的能力喷楣,如果能“說(shuō)”,就會(huì)有SRC template
了讯柔。
Capabilities
是對(duì)SINK template: 'sink'
的能力的具體描述抡蛙,即video/x-flv
,可以理解為一種視頻格式魂迄,也就是說(shuō)這個(gè)元件能接收video/x-flv
格式的視頻輸入粗截,如果對(duì)方的SRC template
能輸出這個(gè)格式,那么就可以連接捣炬。
這里的flv
字眼說(shuō)明熊昌,這個(gè)元件前面的合成器可以搜索mux、flv或flash找到哦湿酸。 - Element Properties
這里是屬性列表婿屹,是插件自己定制的,所以別的插件可能有不同的屬性列表推溃。location
屬性的值就是rtmp流推送的地址昂利。如果是官方例子中的file-source,應(yīng)該也會(huì)有location
屬性铁坎,它代表著要播放的視頻文件路徑蜂奸,即同樣的屬性名在不同插件中的意思可能不同,屬性名和含義完全是插件設(shè)計(jì)者決定的硬萍。
至此扩所,判斷插件能否對(duì)接的方法有了,即對(duì)比SINK template
和SRC template
的Capabilities
朴乖,如果有重疊的部分祖屏,就是可以對(duì)接的。
我完成插件選型后設(shè)計(jì)的流水線(xiàn)如下买羞,下一步就是用代碼實(shí)現(xiàn)它了袁勺。
寫(xiě)代碼
最簡(jiǎn)單的實(shí)現(xiàn),只要遵循以下套路就能滿(mǎn)足大多數(shù)需求了畜普。
#include <gst/gst.h>
#include <glib.h>
int main (int argc,char *argv[])
{
GMainLoop *loop;
GstElement *pipeline, *元件0, *元件1, ..., *元件n;
/* Initialisation */
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
/* 創(chuàng)建流水線(xiàn) */
pipeline = gst_pipeline_new ("自己起的流水線(xiàn)名");
/* 創(chuàng)建元件 */
元件0 = gst_element_factory_make ("元件名0", "自己起的元件實(shí)例名0");
元件1 = gst_element_factory_make ("元件名1", "自己起的元件實(shí)例名1");
//其他元件...
/* 如果需要給一些原件設(shè)置屬性 */
g_object_set (G_OBJECT (元件0), "屬性名", 屬性值, NULL);
/* 把元件與流水線(xiàn)綁定 */
gst_bin_add_many (GST_BIN (pipeline), 元件0, 元件1, ..., 元件n, NULL);
/* 連接元件期丰,也可以用gst_element_link_many一句連接 */
if (gst_element_link (元件0, 元件1)){
g_print ("link success %d\n", __LINE__);
}
else{
return -1;
}
if (gst_element_link (元件1, 元件2)){
g_print ("link success %d\n", __LINE__);
}
else{
return -1;
}
//......
if (gst_element_link (元件n-1, 元件n)){
g_print ("link success %d\n", __LINE__);
}
else{
return -1;
}
/* 啟動(dòng)播放*/
gst_element_set_state (pipeline, GST_STATE_PLAYING);
/* 運(yùn)行主循環(huán),ctrl+c可以退出這個(gè)循環(huán) */
g_main_loop_run (loop);
/* 釋放內(nèi)存之類(lèi)的收尾動(dòng)作 */
//略...
return 0;
}
套路是不是很簡(jiǎn)單,也就是以下幾個(gè)步驟
- 創(chuàng)建流水線(xiàn)
pipeline
咐汞,想好名字就行了盖呼; - 創(chuàng)建元件
元件0
~元件n
,第一個(gè)參數(shù)是元件類(lèi)型化撕,第二個(gè)參數(shù)是給這個(gè)元件的實(shí)例起的名字几晤,兩者就像類(lèi)和對(duì)象、數(shù)據(jù)類(lèi)型和變量的關(guān)系一樣植阴; - 對(duì)一些需要配置的元件設(shè)置屬性(
g_object_set
)蟹瘾; -
gst_bin_add_many
相當(dāng)于把元件裝配到流水線(xiàn)上,準(zhǔn)備連接掠手; - 按順序連接元件(
gst_element_link
或gst_element_link_many
)憾朴; - 設(shè)置流水線(xiàn)狀態(tài)為
GST_STATE_PLAYING
,這個(gè)狀態(tài)就意味著流水線(xiàn)開(kāi)始播放了喷鸽; - 用
g_main_loop_run
運(yùn)行一個(gè)死循環(huán)众雷,只要流水線(xiàn)正常運(yùn)行,這個(gè)循環(huán)就不會(huì)退出做祝,如果用戶(hù)ctrl+c或者播放出錯(cuò)了砾省,這個(gè)循環(huán)就退出了; - 在循環(huán)退出后進(jìn)行收尾工作混槐。
我的代碼比這個(gè)套路多了一點(diǎn)點(diǎn)東西编兄,不過(guò)要是沒(méi)有多這一點(diǎn)點(diǎn),也是可以運(yùn)作的声登。
上面套路中涉及的概念和數(shù)據(jù)類(lèi)型如下表狠鸳,其中GstElementFactory
并沒(méi)有出現(xiàn)。這是因?yàn)?code>gst_element_factory_make把使用GstElementFactory
的過(guò)程封裝了悯嗓,官方講elements的文檔里有對(duì)此說(shuō)明件舵。
概念 | 數(shù)據(jù)類(lèi)型 |
---|---|
元件類(lèi)型 | GstElementFactory |
元件實(shí)例 | GstElement |
流水線(xiàn) | GstElement |
工廠(factory)?元件(element)?這些名字是不是很形象?其設(shè)計(jì)思想就是乒验,先請(qǐng)不同的工廠制造出各種元件往毡,然后把元件組裝成一條流水線(xiàn),這條流水線(xiàn)上流動(dòng)的就是流媒體的不同形態(tài)洪乍,包括文件或內(nèi)存等不同形式眯杏、編/解碼前/后等不同狀態(tài)。
總結(jié)
Gstreamer的編程模型還有很多其他的機(jī)制來(lái)滿(mǎn)足更復(fù)雜的需求壳澳。例如岂贩,當(dāng)兩個(gè)元件有多種方式對(duì)接時(shí),如何指定用哪種方式對(duì)接巷波,再比如萎津,如何對(duì)流水線(xiàn)甚至單個(gè)元件進(jìn)行更細(xì)致的控制等卸伞,這些東西也許會(huì)繼續(xù)出文章說(shuō)明。
下一篇 也許會(huì)有锉屈。荤傲。。