【視訊開發】RTSP SERVER(基於live555)詳細設計
/*
*本文基於LIVE555的嵌入式的RTSP流媒體伺服器一個設計文件,箇中細節現剖於此,有需者可參考指正,同時也方便後期自己查閱。(本版本是基於2011年的live555)
*/
RTSP SERVER(基於live555)詳細設計
這個server的最終情況如下:
效能:D1資料時:
1.8路全開udp
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1175root 20 0 65712 23m 3112 R 29.0 34.5 285:13.05 dvrapp_SN6108
2.8路全開tcp
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1175root 20 0 65612 23m 3112 R 31.0 34.4 289:03.86 dvrapp_SN6108
檔案:
靜態庫大小:Live555.a 1,585KB
檔案個數: 150左右
目錄
1.程式碼移植
將live555程式碼移植到我司嵌入式平臺上。
1.1程式碼獲取
本次移植使用的版本是2011.12.23.
1.2檔案初步裁剪
Live555為跨平臺庫,本移移植旨在arm linux上執行,所以需先裁剪掉其它無關檔案。
a.刪除之前先生成用於linux的makefile.進入live資料夾,執行./genMakefiles linux.此時生成了用於linux的Makefile.
b.刪除冗餘檔案和資料夾。
每個資料夾下只保留*.cpp,*.hh, *.c, *.h, Makefile.其餘全部刪除。
刪除資料夾:WindowsAudioInputDevice和mediaServer
1.3修改Makefile
Makefile變數主要做以下修改:
C_COMPILER = arm-hismall-linux-gcc
CPLUSPLUS_COMPILER = arm-hismall-linux-g++
LINK = arm-hismall-linux-g++-o
LINK_OPTS = -L.-lpthread
C_FLAGS = $(COMPILE_OPTS) $(CFLAGSARM)
CPLUSPLUS_FLAGS = $(COMPILE_OPTS)$(CFLAGSARM)
1.4將live555生成的靜態庫連結到我司的可執行程式中
如:dvrapp_sn6108
裁剪到此時的Live555編譯時會生成所有的庫,不可能將所有庫鏈進可執行程式中。我司只用到了視訊:H264,音訊 G711.a.其餘不用。故只需將這幾個有關庫連結進即可。
我司連結靜態庫到可執行程式的做法是,先成一個xx.a,然後在生成可執行程式時,連結所有.a檔案。所以我們這裡只需將需要的.o檔案裝進live555.a即可。
具體做法:在live555資料夾下新建資料夾liveLib用於存放其它檔案生成的.a. 此資料夾用於生成live555.a最終庫。(將所有.a先打散成.o,再合成一個live555.a).每個資料夾下在生成.a時都拷貝一份.a到liveLib中。最後將生成的live555.a拷貝到master\LIB\ARM\SN6108中。可執行程式連結時在此資料夾下可找到live555.a。
2.功能新增
Live555原始碼的功能要用到我司具體專案中還需做一定的修改,不是拿來就能用的。
原本對於開源專案,尤其是c++專案,最好不要改動原有的類,所以修改應該是繼承父類,在子類中修改。這樣有利於程式碼的升級,和維護。但是由於考慮到c++的繼承的層數以及虛擬函式的執行時繫結對效能的影響,以及編譯出文件的大小,所以本修改中只對一部分類作了繼承,另一些直接在原有類中新增新方法。
2.1 資料的輸入
資料從ringbuffer到server模組的傳輸,使用了通知機制,即當ringbuf有資料時,資料發出通道,告知某一通道有資料可用,則server在需要的時候會來這個通道來取資料。
資料輸入框圖
通知機制主要由以下函式實現。
void signalNewFrameData(int mediaType, int chanel,int trans_mode,int buf_len)
signalNewFrameData為一個全域性函式,可被外部執行緒呼叫(注意,整個live555是一個單執行緒程式)。signalnewFrameData會呼叫virtual void triggerEvent(EventTriggerId eventTriggerId, void*clientData = NULL), 這個函式共兩個引數,一個是觸發的事件id,另一個是此id對應的事件處理函式所在的類例項指標,這裡具體是各個輸入的videoSource和audioSource類例項指標。
資料的寫入ringbuffer由以下函式實現:
在write_unicast_data_live裡(只用於單播),每當一個數據到來後,先判斷是音訊還是視訊,然後再裝入各自對應的ringbuffer,接著呼叫 signalnewFrameData通知相應的server. 通知時刻落在server剛好需要資料的時刻區間間的概率較小,大部分情況是(經實驗證明了的):server正在處理其它資料; 或已經取完資料,正在等下次取資料時刻的到來(此時可能正停留在Eventloop 裡的sigleStep的select中)。所以通知後都會把事件記入一個bitmask型別的變數fTriggersAwaitingHandling(最多可累計掛入32個待處理事件)中, 然後在select結束後,再處理每個TriggerNum所對應的事件(呼叫Source中的deliverFrame將資料向後傳送)。處理完一個事件,則將fTriggersAwaitingHandling中對應的bitmask位清0,singleStep每一次迴圈中TriggerEvent只處理一個事件(如有未處理完事件,等下一迴圈再處理)。
2.2實時視訊(H264)的輸入和輸出
Live555提供的示例裡面有直接讀檔案的類和使用方法,但沒有實時輸入的類及其實現。
Live555中資料流基本路線是:
SourceàFilter1àFilter2…àSink
Filter可能有多個,也可能一個也沒有。對於h264,filter有兩個,對於音訊g711.a,實現中則沒有Filter.
本設計具體實現視訊實時輸入方法如下:
1.視訊輸入
在live555中,輸入為Source類,輸出為 Sink類。中間處理環節類稱為Filter.
SNDeviceSource類繼承於FramedSource,用於實時輸入h264視訊。該類的實現參考了DeviceSource。
在SNDeviceSource::deliverFrame()中實現資料的輸入。Memmove 將資料拷貝到fTo.(最後優化後已經改為傳指標了,沒有了記憶體拷貝).
2 .Filter
對於H264視訊Source不是直接到Sink, 而是經過如下:
SNDeviceSourceàH264VideoStreamDiscreteFrameràH264FUAFragmenteràH264VideoRTPSink
中間兩個類稱為Filter,是對視訊資料的進一步處理。
H264VideoStreamDiscreteFramer繼承於H264VideoStreamFramer, H264VideoStreamFramer主要是提取sps, pps。H264VideoStreamDiscreteFramer 主要是用於輸入 離散的NAL單元,H264FUAFragmenter主要是對H264nal進行分片打包,根據rfc3984中FU-A規則進行分片打包。
3 .視訊輸出
H264VideoRTPSink為原有類, 實現了h264 rtp包的輸出.
視訊的打包和傳送操作都在H264VideoRTPSink的父類multiFramedRTPSink中.其打包傳送的流程如下:
sendNextàbuildandSendPacketàpackFrameàgetnextFrameà…àafterGettingFrameàsendPacketIfnessaryàscheduleDelayedTask àsendNextà。。。
buildandSendPacket會將rtp包頭打好(timestamps and sequence num先預留,等取到幀資料後再填充)。
packFrame即將幀資料往rtp包頭後面掛。所以其任務就是要取得幀資料,所以呼叫getnextFrame來從上一遊來獲取幀資料。對於h264來說,它的上一級由2.12可看出是H264FUAFragmenter, 這個類會將幀資料分好片(<1448位元組), 交給multiFramedRTP Sink。好,獲取到幀資料後,就是afterGettingFrame了,這裡面主要做一些檢查工作(檢查資料是否正確,buffer是否溢位等各種檢查)和補充工作(填充前面所說的timestamps和rtp sequence number)。之後便是sendPakcetIfnessary, 這裡面會使用tcp或udp將資料傳送出去。傳送完後,需將下一次任務準備一下,即呼叫scheduleDelayedTask將sendNext放入延遲佇列中,等