文/王鋒 陸凱
把采集的yuv格式通過ffmpeg編解碼庫編碼成h264格式,再通過網(wǎng)絡傳輸?shù)绞覂炔シ沤K端,在室內機終端設備再通過ffmpeg解碼器轉換為yuv420p格式,最終轉換為RGB格式,并在Linux系統(tǒng)的ARM平臺上利用QT圖形化界面顯示。最終實現(xiàn)了數(shù)字可視對講系統(tǒng)功能實現(xiàn)的整個流程。
Gstreamer是一個基于管道Pipeline的多媒體應用框架,采用C語言編程,但是通過gObject,將各插件封裝成面向對象編程的工具。元件 Element是Gstreamer最重要和基本的對象類,通過插件Plugin的形式提供,多個元件Elements可以組合為箱柜bin,并進一步聚合形成一個管道Pipeline完成一個多媒體應用處理。目前是嵌入式Linux最為常用的處理多媒體應用框架。我們主要是在ffmpeg多媒體編解碼的過程中加入Gstreamer 的應用框架。
Gstreamer框架中使用gst-launch命令進行流媒體播放,我們在開發(fā)過程中,主要使用gst-launch 在終端編譯和運行一條pipeline用于播放多媒體。gst-launch-0.10 或gst-launch-1.0一般ubuntu系統(tǒng)自帶,相關插件包可通過wget下載opky安裝。由于是基于嵌入式ARM芯片的流媒體開發(fā),還需交叉編譯相關gstreamer動態(tài)庫移植到下位機平臺,如glib庫、gstreamer插 件 庫libgstqt5videosink.so、qt5lib庫libQt5GLib-2.0.so、libQt5GStreamer-1.0.so等。 開發(fā)過程中調用的gst代碼有gstffmpeg-0.11.2、gst-libav-1.14.4等。
由于需要做攝像頭的視頻采集,所以首先在內核中添加視頻采集模塊Video4Linux2,它是一種內核設備驅動,主要為Linux 下的應用程序編程提供視頻設備接口函數(shù),同時,由于我們是基于GStreamer 框架開發(fā),故在v4l2攝像頭采集中加入GStreamer 插件的方式進行開發(fā)。其中 Video4Linux2插件是一個用于捕捉和播放視頻的API和驅動框架,支持一般的攝像頭設備。
v4l2本身不僅僅是支持視頻采集功能,它還支持其他的視頻功能,元件v4l2src屬于Video4Linux2插件,用于讀取Video4Linux2設備的視頻幀,這里即為攝像頭。v4l2src是使用v4l2接口的視頻源插件,只是用來做視頻采集的,支持多種格式的視頻采集,例如rgb格式和yuv格式。
在Linux系統(tǒng)中V4L2驅動的攝像頭數(shù)據(jù)采集我們采用內存映射方式(mmap)進行圖像采集。數(shù)據(jù)的采集從/dev/video0設備文件。
視頻采集過程如下:
(1)創(chuàng)建一條名為pipe的新管道 pipe=gst_pipeline_new("pipe");管 道 在GStreamer框架中是用來容納和管理元件的。
(2)調用gst_element_factory_make函數(shù),創(chuàng)建v4l2src、jpegenc、f ilesink插件,分別作為輸入數(shù)據(jù)源元件、過濾器元件、輸出數(shù)據(jù)源元件,并調用g_object_set設置元件的屬性,輸入源v4l2src的device屬性設置一下,指定采集設備的名稱:并對輸入源指定幀數(shù)量,最后創(chuàng)建f ilesink插件后設置文件的保存路徑。如圖1所示。
(3)判斷管道與元件創(chuàng)建無問題,調用gst_bin_add_many()函數(shù)添加已創(chuàng)建好的三個元件到pipe管道中,并按順序連接起來,可以更好的讓數(shù)據(jù)流動。如圖2所示。
(4)調用gst_pipeline_get_bus()獲取管道的消息總線,并添加消息總線監(jiān)視器,釋放線資源。如圖3所示。
(5) 在管道創(chuàng)建完成并添加消息監(jiān)視器后,切換管道狀態(tài)PLAYING狀態(tài),來啟動整個管道的數(shù)據(jù)傳輸處理流程,處理完成停止管道并釋放占用的資源。在創(chuàng)建管道之前先創(chuàng)建了一個 loop=g_main_loop_new(NULL,FALSE);循環(huán)體,g_main_loop_run()則是進入主循環(huán)在這里我們調用啟動它。有事件時,它就處理事件,沒事件時就睡眠狀態(tài)。如圖4所示。
V4l2攝像頭視頻采集完成后生成的yuv格式數(shù)據(jù)量很大,便于傳輸我們需要把YUV422的像素數(shù)據(jù)編碼為H.264的壓縮編碼數(shù)據(jù),傳輸完成后再進行數(shù)據(jù)的解碼。在此采取Gstreamer管道的方式進行Ffmpeg編解碼,所以需要安裝ffmpeg庫、x264庫之外,還需安裝Gstreamer ffmpeg插件等。
1.2.1 插件的初始化

圖1

圖2

圖3

圖4

圖5
ffpmepg插件的初始化直接就是通過plugin_init()函數(shù)注冊到Gstreamer中的,每個plugin都是在plugin_init()函數(shù)中通過gst_element_register()函數(shù)將plugin的相應信息注冊到gstreamer中。首先調用av_register_all()函數(shù)注冊編碼器,該函數(shù)是所有使用編碼器、復用器的基礎,在所有基于ffmpeg的應用程序中幾乎都是第一個被調用的。
接著調用ffmepeg編解碼注冊函數(shù)gst_ffmpegenc_register (plugin); gst_ffmpegdec_register (plugin)等。通過gst_element_register()函數(shù)將plugin的相應信息注冊到gstreamer中。通過該函數(shù),可以創(chuàng)建一個名稱為name、優(yōu)先級為rank的type類型elementfactory,并將elementfactory添加到registry。在我們自己編寫的插件中,將元件等級rank的值設置為比GST_RANK_PRIMARY大即大于256就可,這樣就將會優(yōu)先選擇我們編寫的plugin。
1.2.2 gst-ffmpeg視頻編碼
(1)pad的定義創(chuàng)建、連接、流動設置。
首先調用gst_ffmpegenc_init()函數(shù)進行gst-ffmpeg編碼的初始化,主要完成對pad的定義創(chuàng)建、連接、流動設置。指定元件對外接口sink pad 和src pad,創(chuàng)建pad 的作用是使數(shù)據(jù)流通過這些接口流入流出元件,它相當于element的接口,是element間傳輸數(shù)據(jù)的通道。
指 定sinkpad接 口 后,調 用gst_ffmpegenc_getcaps()從sink pad 中獲取Gstcaps信息,這個函數(shù)的作用就是產(chǎn)生一個新的caps,并設置這個caps。并調用avcodec_alloc_context()為編解碼器上下文分配空間。并設置分辨率、像素格式、波特率等幀參數(shù)。 gst_ffmpeg_avcodec_open()初始化一個視音頻編解碼器的AVCodecContext。Gstcaps代表pad能處理的媒體類型,調用caps插件的作用是協(xié)商caps所支持的格式。
(2)Sinkpad的調度模式設置。
指定pad初始化視音頻編解碼完成。調用Chain鏈條函數(shù)對sinkpad調度模式進行設置,我們采用推送模式,推送模式是實現(xiàn)把src pad產(chǎn)生的數(shù)據(jù)“推送”給下游元件即sink pad。推送模式下,源元件發(fā)起數(shù)據(jù)傳輸,是管道中的驅動力量;下游元件在chain函數(shù)中接收buffer。這樣,就完成了從上游元件到下游元件的buffer傳遞。
緊接著給初始化ffmpeg x264參數(shù)賦值給ffmpegenc指針,賦值參數(shù)有編碼輸出比特率、GOP關鍵幀的最大間隔幀數(shù)、幀大小、rtp負載尺寸、運動偵測的方式等。參數(shù)賦值完成調用g_object_set_property()函數(shù)把參數(shù)信息寫入object結構體。在創(chuàng)建pad、調度模式設計級從參數(shù)的配置完成后,調用gst_element_add_pad(),添加srcpad、sinkpad到元件中。所有流程完成為element間的數(shù)據(jù)傳輸鑒定了基礎。
(3)element的注冊。
在上面pad添加完成后,下面進行element的注冊。首先調用gst_ffmpeg_cfg_init()構建ffmpeg參數(shù)信息,聲明AVCodec類型的結構體指針in_plugin,主要用于存儲編碼器的信息。通過判斷in_plugin結構體成員如類型、ID、name從而判斷是否是初始化的編碼信息,如不符合,調用av_codec_next()函數(shù)傳入相關編碼信息。緊接著調用g_type_register_static()相關的類型GType衍生一個新的glib靜態(tài)類型。g_type_add_interface_static()實現(xiàn)接口,并通過gst_element_register()注冊插件支持的所有element類型。
上面完成gst-ffmpeg元件的注冊,采用同樣的方法對ffmpegmux整流器進行注冊。在對ffmpegmux的注冊過程中,調用gst_element_add_pad()添加mux的src pad,并配置videopads、audiopads、preload等參數(shù)。
(4)Pipe管道運行。
在所有視音頻初始化信息完成后,pad指定配置完成,調用gst_element_factory_make ()完成element的創(chuàng)建,將多個不同功能的元件(element)裝進一個箱柜(bin)中來管理元件,再通過gst_bin_iterate ()函數(shù)運行管道。
1.2.3 gst-ffmpeg視頻解碼
gst-ffmpeg視頻解碼主要是調用gst_ffmpegdemux_loop()函數(shù)來完成的。原始數(shù)據(jù)流的獲取我們首先可以通過libavformat來實現(xiàn)對各種文件的分離,libavformat能夠依次讀取數(shù)據(jù)包,過濾掉所有那些視頻流中不需要的部分。后gst-ffmpeg調用ffmpeg的API即 av_read_frame()來讀取一個完整的幀數(shù)據(jù) ffmpeg,av_read_frame()的好處是可以從一個簡單的包里返回一個包含所有數(shù)據(jù)的視頻幀。
文件系統(tǒng)的讀操作由ffmpeg調用gstf ilesrc插件的read操作來實現(xiàn) 。一幀數(shù)據(jù)讀出以后,調用avcodec_send_packet()和avcodec_receive_frame()函數(shù)進行解碼,解碼器解碼完,調用dec->callbacks.FillBufferDone通知gst-omx插件。通知gst-omx插件是采用pad push機制進行完成的,實質上采用異步的方式 ,gst-omx的優(yōu)勢是在不同的硬件下支持不同的編解碼。完成解碼解碼H264視頻數(shù)據(jù)成YUV2格式。
傳輸視音頻利用tcpclientsink和udpsink插件主動發(fā)送編碼后的數(shù)據(jù)到播放端。gst_element_factory_make()函數(shù)創(chuàng)建插件,g_object_set()方法來設置插件網(wǎng)絡屬性。并使用gst-launch-0.10工具完成管道的創(chuàng)建。如圖5所示。
視頻解碼后是YUV空間的,我們采用兩種方式進行顯示播放,一種是QT GUI的方式,而QT GUI需要RGB空間的數(shù)據(jù),所以需要用sws_scale轉換顏色格式到RGB,采用paintEvent(QPaintEvent *)函數(shù)進行繪制事件,視頻顯示可以簡單的通過videoout元素來完成視頻顯示。
另外一種方式是用imxv4l2sink元件進行播放,由于我們硬件平臺采用了Freescale的i.MX,是基于Linux下的開發(fā),所以可以采用imxv4l2sink 進行視頻的播放。QtGStreamer可以通過overlay嵌入到其他窗口中。imxv4l2sink元件播放視頻利用GPU加速器。所以顯示速度及效果更加顯著。
Gsteramer流媒體的ffmpeg編解碼開發(fā)主要還是基于插件的開發(fā),通過插件的形式把各個元件連接起來,形成管道的方式便于數(shù)據(jù)的傳輸。首先需要創(chuàng)建基本元件,v4l2src、pipeline,f ilesrc,encoder,imxv4l2sink等,然后把這些元件加入到pipeline中,并鏈接起來,最后改變pipeline的狀態(tài),就可以啟動對媒體數(shù)據(jù)的處理了。