文/王鋒 陸凱
把采集的yuv格式通過(guò)ffmpeg編解碼庫(kù)編碼成h264格式,再通過(guò)網(wǎng)絡(luò)傳輸?shù)绞覂?nèi)播放終端,在室內(nèi)機(jī)終端設(shè)備再通過(guò)ffmpeg解碼器轉(zhuǎn)換為yuv420p格式,最終轉(zhuǎn)換為RGB格式,并在Linux系統(tǒng)的ARM平臺(tái)上利用QT圖形化界面顯示。最終實(shí)現(xiàn)了數(shù)字可視對(duì)講系統(tǒng)功能實(shí)現(xiàn)的整個(gè)流程。
Gstreamer是一個(gè)基于管道Pipeline的多媒體應(yīng)用框架,采用C語(yǔ)言編程,但是通過(guò)gObject,將各插件封裝成面向?qū)ο缶幊痰墓ぞ?。元?Element是Gstreamer最重要和基本的對(duì)象類(lèi),通過(guò)插件Plugin的形式提供,多個(gè)元件Elements可以組合為箱柜bin,并進(jìn)一步聚合形成一個(gè)管道Pipeline完成一個(gè)多媒體應(yīng)用處理。目前是嵌入式Linux最為常用的處理多媒體應(yīng)用框架。我們主要是在ffmpeg多媒體編解碼的過(guò)程中加入Gstreamer 的應(yīng)用框架。
Gstreamer框架中使用gst-launch命令進(jìn)行流媒體播放,我們?cè)陂_(kāi)發(fā)過(guò)程中,主要使用gst-launch 在終端編譯和運(yùn)行一條pipeline用于播放多媒體。gst-launch-0.10 或gst-launch-1.0一般ubuntu系統(tǒng)自帶,相關(guān)插件包可通過(guò)wget下載opky安裝。由于是基于嵌入式ARM芯片的流媒體開(kāi)發(fā),還需交叉編譯相關(guān)gstreamer動(dòng)態(tài)庫(kù)移植到下位機(jī)平臺(tái),如glib庫(kù)、gstreamer插 件 庫(kù)libgstqt5videosink.so、qt5lib庫(kù)libQt5GLib-2.0.so、libQt5GStreamer-1.0.so等。 開(kāi)發(fā)過(guò)程中調(diào)用的gst代碼有g(shù)stffmpeg-0.11.2、gst-libav-1.14.4等。
由于需要做攝像頭的視頻采集,所以首先在內(nèi)核中添加視頻采集模塊Video4Linux2,它是一種內(nèi)核設(shè)備驅(qū)動(dòng),主要為L(zhǎng)inux 下的應(yīng)用程序編程提供視頻設(shè)備接口函數(shù),同時(shí),由于我們是基于GStreamer 框架開(kāi)發(fā),故在v4l2攝像頭采集中加入GStreamer 插件的方式進(jìn)行開(kāi)發(fā)。其中 Video4Linux2插件是一個(gè)用于捕捉和播放視頻的API和驅(qū)動(dòng)框架,支持一般的攝像頭設(shè)備。
v4l2本身不僅僅是支持視頻采集功能,它還支持其他的視頻功能,元件v4l2src屬于Video4Linux2插件,用于讀取Video4Linux2設(shè)備的視頻幀,這里即為攝像頭。v4l2src是使用v4l2接口的視頻源插件,只是用來(lái)做視頻采集的,支持多種格式的視頻采集,例如rgb格式和yuv格式。
在Linux系統(tǒng)中V4L2驅(qū)動(dòng)的攝像頭數(shù)據(jù)采集我們采用內(nèi)存映射方式(mmap)進(jìn)行圖像采集。數(shù)據(jù)的采集從/dev/video0設(shè)備文件。
視頻采集過(guò)程如下:
(1)創(chuàng)建一條名為pipe的新管道 pipe=gst_pipeline_new("pipe");管 道 在GStreamer框架中是用來(lái)容納和管理元件的。
(2)調(diào)用gst_element_factory_make函數(shù),創(chuàng)建v4l2src、jpegenc、f ilesink插件,分別作為輸入數(shù)據(jù)源元件、過(guò)濾器元件、輸出數(shù)據(jù)源元件,并調(diào)用g_object_set設(shè)置元件的屬性,輸入源v4l2src的device屬性設(shè)置一下,指定采集設(shè)備的名稱(chēng):并對(duì)輸入源指定幀數(shù)量,最后創(chuàng)建f ilesink插件后設(shè)置文件的保存路徑。如圖1所示。
(3)判斷管道與元件創(chuàng)建無(wú)問(wèn)題,調(diào)用gst_bin_add_many()函數(shù)添加已創(chuàng)建好的三個(gè)元件到pipe管道中,并按順序連接起來(lái),可以更好的讓數(shù)據(jù)流動(dòng)。如圖2所示。
(4)調(diào)用gst_pipeline_get_bus()獲取管道的消息總線(xiàn),并添加消息總線(xiàn)監(jiān)視器,釋放線(xiàn)資源。如圖3所示。
(5) 在管道創(chuàng)建完成并添加消息監(jiān)視器后,切換管道狀態(tài)PLAYING狀態(tài),來(lái)啟動(dòng)整個(gè)管道的數(shù)據(jù)傳輸處理流程,處理完成停止管道并釋放占用的資源。在創(chuàng)建管道之前先創(chuàng)建了一個(gè) loop=g_main_loop_new(NULL,FALSE);循環(huán)體,g_main_loop_run()則是進(jìn)入主循環(huán)在這里我們調(diào)用啟動(dòng)它。有事件時(shí),它就處理事件,沒(méi)事件時(shí)就睡眠狀態(tài)。如圖4所示。
V4l2攝像頭視頻采集完成后生成的yuv格式數(shù)據(jù)量很大,便于傳輸我們需要把YUV422的像素?cái)?shù)據(jù)編碼為H.264的壓縮編碼數(shù)據(jù),傳輸完成后再進(jìn)行數(shù)據(jù)的解碼。在此采取Gstreamer管道的方式進(jìn)行Ffmpeg編解碼,所以需要安裝ffmpeg庫(kù)、x264庫(kù)之外,還需安裝Gstreamer ffmpeg插件等。
1.2.1 插件的初始化
圖1
圖2
圖3
圖4
圖5
ffpmepg插件的初始化直接就是通過(guò)plugin_init()函數(shù)注冊(cè)到Gstreamer中的,每個(gè)plugin都是在plugin_init()函數(shù)中通過(guò)gst_element_register()函數(shù)將plugin的相應(yīng)信息注冊(cè)到gstreamer中。首先調(diào)用av_register_all()函數(shù)注冊(cè)編碼器,該函數(shù)是所有使用編碼器、復(fù)用器的基礎(chǔ),在所有基于ffmpeg的應(yīng)用程序中幾乎都是第一個(gè)被調(diào)用的。
接著調(diào)用ffmepeg編解碼注冊(cè)函數(shù)gst_ffmpegenc_register (plugin); gst_ffmpegdec_register (plugin)等。通過(guò)gst_element_register()函數(shù)將plugin的相應(yīng)信息注冊(cè)到gstreamer中。通過(guò)該函數(shù),可以創(chuàng)建一個(gè)名稱(chēng)為name、優(yōu)先級(jí)為rank的type類(lèi)型elementfactory,并將elementfactory添加到registry。在我們自己編寫(xiě)的插件中,將元件等級(jí)rank的值設(shè)置為比GST_RANK_PRIMARY大即大于256就可,這樣就將會(huì)優(yōu)先選擇我們編寫(xiě)的plugin。
1.2.2 gst-ffmpeg視頻編碼
(1)pad的定義創(chuàng)建、連接、流動(dòng)設(shè)置。
首先調(diào)用gst_ffmpegenc_init()函數(shù)進(jìn)行g(shù)st-ffmpeg編碼的初始化,主要完成對(duì)pad的定義創(chuàng)建、連接、流動(dòng)設(shè)置。指定元件對(duì)外接口sink pad 和src pad,創(chuàng)建pad 的作用是使數(shù)據(jù)流通過(guò)這些接口流入流出元件,它相當(dāng)于element的接口,是element間傳輸數(shù)據(jù)的通道。
指 定sinkpad接 口 后,調(diào) 用gst_ffmpegenc_getcaps()從sink pad 中獲取Gstcaps信息,這個(gè)函數(shù)的作用就是產(chǎn)生一個(gè)新的caps,并設(shè)置這個(gè)caps。并調(diào)用avcodec_alloc_context()為編解碼器上下文分配空間。并設(shè)置分辨率、像素格式、波特率等幀參數(shù)。 gst_ffmpeg_avcodec_open()初始化一個(gè)視音頻編解碼器的AVCodecContext。Gstcaps代表pad能處理的媒體類(lèi)型,調(diào)用caps插件的作用是協(xié)商caps所支持的格式。
(2)Sinkpad的調(diào)度模式設(shè)置。
指定pad初始化視音頻編解碼完成。調(diào)用Chain鏈條函數(shù)對(duì)sinkpad調(diào)度模式進(jìn)行設(shè)置,我們采用推送模式,推送模式是實(shí)現(xiàn)把src pad產(chǎn)生的數(shù)據(jù)“推送”給下游元件即sink pad。推送模式下,源元件發(fā)起數(shù)據(jù)傳輸,是管道中的驅(qū)動(dòng)力量;下游元件在chain函數(shù)中接收buffer。這樣,就完成了從上游元件到下游元件的buffer傳遞。
緊接著給初始化ffmpeg x264參數(shù)賦值給ffmpegenc指針,賦值參數(shù)有編碼輸出比特率、GOP關(guān)鍵幀的最大間隔幀數(shù)、幀大小、rtp負(fù)載尺寸、運(yùn)動(dòng)偵測(cè)的方式等。參數(shù)賦值完成調(diào)用g_object_set_property()函數(shù)把參數(shù)信息寫(xiě)入object結(jié)構(gòu)體。在創(chuàng)建pad、調(diào)度模式設(shè)計(jì)級(jí)從參數(shù)的配置完成后,調(diào)用gst_element_add_pad(),添加srcpad、sinkpad到元件中。所有流程完成為element間的數(shù)據(jù)傳輸鑒定了基礎(chǔ)。
(3)element的注冊(cè)。
在上面pad添加完成后,下面進(jìn)行element的注冊(cè)。首先調(diào)用gst_ffmpeg_cfg_init()構(gòu)建ffmpeg參數(shù)信息,聲明AVCodec類(lèi)型的結(jié)構(gòu)體指針in_plugin,主要用于存儲(chǔ)編碼器的信息。通過(guò)判斷in_plugin結(jié)構(gòu)體成員如類(lèi)型、ID、name從而判斷是否是初始化的編碼信息,如不符合,調(diào)用av_codec_next()函數(shù)傳入相關(guān)編碼信息。緊接著調(diào)用g_type_register_static()相關(guān)的類(lèi)型GType衍生一個(gè)新的glib靜態(tài)類(lèi)型。g_type_add_interface_static()實(shí)現(xiàn)接口,并通過(guò)gst_element_register()注冊(cè)插件支持的所有element類(lèi)型。
上面完成gst-ffmpeg元件的注冊(cè),采用同樣的方法對(duì)ffmpegmux整流器進(jìn)行注冊(cè)。在對(duì)ffmpegmux的注冊(cè)過(guò)程中,調(diào)用gst_element_add_pad()添加mux的src pad,并配置videopads、audiopads、preload等參數(shù)。
(4)Pipe管道運(yùn)行。
在所有視音頻初始化信息完成后,pad指定配置完成,調(diào)用gst_element_factory_make ()完成element的創(chuàng)建,將多個(gè)不同功能的元件(element)裝進(jìn)一個(gè)箱柜(bin)中來(lái)管理元件,再通過(guò)gst_bin_iterate ()函數(shù)運(yùn)行管道。
1.2.3 gst-ffmpeg視頻解碼
gst-ffmpeg視頻解碼主要是調(diào)用gst_ffmpegdemux_loop()函數(shù)來(lái)完成的。原始數(shù)據(jù)流的獲取我們首先可以通過(guò)libavformat來(lái)實(shí)現(xiàn)對(duì)各種文件的分離,libavformat能夠依次讀取數(shù)據(jù)包,過(guò)濾掉所有那些視頻流中不需要的部分。后gst-ffmpeg調(diào)用ffmpeg的API即 av_read_frame()來(lái)讀取一個(gè)完整的幀數(shù)據(jù) ffmpeg,av_read_frame()的好處是可以從一個(gè)簡(jiǎn)單的包里返回一個(gè)包含所有數(shù)據(jù)的視頻幀。
文件系統(tǒng)的讀操作由ffmpeg調(diào)用gstf ilesrc插件的read操作來(lái)實(shí)現(xiàn) 。一幀數(shù)據(jù)讀出以后,調(diào)用avcodec_send_packet()和avcodec_receive_frame()函數(shù)進(jìn)行解碼,解碼器解碼完,調(diào)用dec->callbacks.FillBufferDone通知gst-omx插件。通知gst-omx插件是采用pad push機(jī)制進(jìn)行完成的,實(shí)質(zhì)上采用異步的方式 ,gst-omx的優(yōu)勢(shì)是在不同的硬件下支持不同的編解碼。完成解碼解碼H264視頻數(shù)據(jù)成YUV2格式。
傳輸視音頻利用tcpclientsink和udpsink插件主動(dòng)發(fā)送編碼后的數(shù)據(jù)到播放端。gst_element_factory_make()函數(shù)創(chuàng)建插件,g_object_set()方法來(lái)設(shè)置插件網(wǎng)絡(luò)屬性。并使用gst-launch-0.10工具完成管道的創(chuàng)建。如圖5所示。
視頻解碼后是YUV空間的,我們采用兩種方式進(jìn)行顯示播放,一種是QT GUI的方式,而QT GUI需要RGB空間的數(shù)據(jù),所以需要用sws_scale轉(zhuǎn)換顏色格式到RGB,采用paintEvent(QPaintEvent *)函數(shù)進(jìn)行繪制事件,視頻顯示可以簡(jiǎn)單的通過(guò)videoout元素來(lái)完成視頻顯示。
另外一種方式是用imxv4l2sink元件進(jìn)行播放,由于我們硬件平臺(tái)采用了Freescale的i.MX,是基于Linux下的開(kāi)發(fā),所以可以采用imxv4l2sink 進(jìn)行視頻的播放。QtGStreamer可以通過(guò)overlay嵌入到其他窗口中。imxv4l2sink元件播放視頻利用GPU加速器。所以顯示速度及效果更加顯著。
Gsteramer流媒體的ffmpeg編解碼開(kāi)發(fā)主要還是基于插件的開(kāi)發(fā),通過(guò)插件的形式把各個(gè)元件連接起來(lái),形成管道的方式便于數(shù)據(jù)的傳輸。首先需要?jiǎng)?chuàng)建基本元件,v4l2src、pipeline,f ilesrc,encoder,imxv4l2sink等,然后把這些元件加入到pipeline中,并鏈接起來(lái),最后改變pipeline的狀態(tài),就可以啟動(dòng)對(duì)媒體數(shù)據(jù)的處理了。