1. 程式人生 > >Onvif對接Rtsp實時媒體流(基於live555)

Onvif對接Rtsp實時媒體流(基於live555)

Onvif(Open Network Video Interface Forum,開放型網路視訊介面論壇),是安迅士聯合博世及索尼公司共同成立的一個國際開放型網路視訊產品標準網路介面開發論壇,以公開、開放的原則共同制定的開放型行業標準。

Onvif標準網路視訊裝置之間的資訊交換定義通用協議,包括實時視訊、音訊、元資料和控制資訊等。網路視訊產品由此所能提供的多種可能性,使終端使用者,整合商,顧問和生產廠商能夠輕鬆地從中獲益,並獲得高性價比、更靈活的解決方案、市場擴張的機會以及更低的風險。

gSOAP一種跨平臺的C C++軟體開發工具包。生成C/C++RPC程式碼,XML資料繫結,對SOAP Web

服務和其他應用形成高效的具體架構解析器。

WSDL(網路服務描述語言)是一個用來描述Web服務和說明如何與Web服務通訊的XML語言。

本軟體是用gSOAP實現了onvif中定義的裝置發現和Webservice服務。在使用gSOAP時首先是根據wsdl檔案生成相應的標頭檔案,然後根據標頭檔案裡申明的功能函式,在原始檔中實現這些功能函式。


1生成onvif依賴檔案

下載gSOAP
http://www.cs.fsu.edu/~engelen/soap.html
當前使用的是2.8.14版本。解壓後,在bin目錄中有需要的兩個工具wsdl2h這個工具根據wsdl檔案生成一個頭檔案,這個標頭檔案中申明瞭很多功能函式,通過實現這些功能函式或者自定義函式來實現onvif功能。
生成onvif標頭檔案
wsdl2h –o onvif.h –c –s –t ./typemap.dat
http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl
http://www.onvif.org/onvif/ver10/event/wsdl/event.wsdl
http://www.onvif.org/onvif/ver10/display.wsdl
http://www.onvif.org/onvif/ver10/deviceio.wsdl
http://www.onvif.org/onvif/ver20/imaging/wsdl/imaging.wsdl
http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl
http://www.onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl
http://www.onvif.org/onvif/ver10/receiver.wsdl
http://www.onvif.org/onvif/ver10/recording.wsdl
http://www.onvif.org/onvif/ver10/search.wsdl
http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl
http://www.onvif.org/onvif/ver10/replay.wsdl
http://www.onvif.org/onvif/ver20/analytics/wsdl/analytics.wsdl
http://www.onvif.org/onvif/ver10/analyticsdevice.wsdl
http://www.onvif.org/onvif/ver10/schema/onvif.xsd
http://www.onvif.org/ver10/actionengine.wsdl
 
生成框架
soapcpp2-2 onvif.h -x -I./gsoap-2.8/gsoap/import -I./gsoap-2.8/gsoap
將wsdd.nsmap改名為wsdd.h,刪除其它所有nsmap檔案,它們的內容是一樣的,刪除soapClientLib.c,soapServerLib.c這兩個檔案。
從gsoap裡拷貝所需檔案到當前目錄,這些檔案包括dom.c、soapC.c、stdsoap2.c、md5evp.c、smdevp.c、mecevp.c、threads.c、wsaapi.c、wsseapi.c、duration.c
在生成框架時,2.8.14版本的gSOAP會報錯,只需要註釋掉./third-party/gsoap-2.8/gsoap/import/wsa5.h 278行函式的申明即可。
 
生成原始檔

在上面生成的onvif.h裡申明瞭很多函式,需要在原始檔中實現這些函式。

2 ONVIF

目錄及概述


1)global_init:初始化網路、記憶體、onvif服務、webserver及相關全域性變數
2)webserver_thread: 解析soap協議並提供相應web服務
3)hello_thread: 以一定的時間間隔傳送“HELLO”訊息
4)probe_thread:是用來應答客戶端傳送的“Probe“訊息
 
裝置發現功能
過程描述:
1)       Onvif裝置在啟動後,組播“HELLO”訊息,告知裝置已經上線。
2)       onvif客戶端監聽組播訊息,當收到“HELLO”訊息後,單播“PROBE”訊息到onvif裝置端。
3)       裝置端收到“PROBE”訊息後檢查訊息中裝置型別欄位是否匹配,如果匹配則單播“PROBEMATCH”訊息到客戶端,在這個訊息中包含裝置端的web服務地址。
實現函式:
a) intsoap_send___wsdd__Hello(struct soap *soap, constchar*soap_endpoint, constchar*soap_action, structwsdd__HelloType*wsdd__Hello);
這個函式將由Hello執行緒直接呼叫,用來序列化“Hello“訊息,然後組播。
b)int__wsdd__Probe(struct soap* soap, structwsdd__ProbeType*wsdd__Probe)
這個函式用來響應客戶端的“Probe”訊息
 
 
RTSP視訊對接
2.3.1流程描述
1)裝置發現成功後,客戶端傳送獲取裝置端能力集等請求
2)客戶端傳送獲取音視訊源資訊及音視訊編解碼資訊
3)客戶端傳送獲取流媒體URL資訊
實現函式
GetCapabilities
int__tds__GetCapabilities(struct soap* soap, struct _tds__GetCapabilities*tds__GetCapabilities, struct _tds__GetCapabilitiesResponse*tds__GetCapabilitiesResponse)
客戶端通過webservice獲取裝置支援的功能時,webservice會呼叫這個函式,這個函式會告知客戶端這個裝置是否支援image、video、audio等,在GetCapabilities函式中我們想要對接RTSP視訊,必須設定Media 和一些必要引數
 
GetServices
int__tds__GetServices(struct soap* soap, struct _tds__GetServices*tds__GetServices, struct _tds__GetServicesResponse*tds__ GetServicesResponse)
 
GetVideoSources
int__tds__GetVideoSources(struct soap* soap, struct _tds__ GetVideoSources *tds__ GetVideoSouces, struct _tds__GetVideoSourcesResponse*tds__ GetVideoSourcesResponse)
 
GetProfiles
 int__tds__GetProfiles(struct soap* soap, struct _tds__ GetProfiles *tds__ GetProfiles, struct _tds__GetProfilesResponse*tds__ GetProfilesResponse)
除了基本資訊,還需要填充兩大項VideoSourceConfiguration和VideoEncoderConfiguration,一個用於描述視訊源的資訊,另外一個描述視訊的編碼資訊
 
GetVideoSourceConfiguration
int__tds__GetVideoSourceConfiguration(struct soap* soap, struct _tds__ GetVideoSourceConfiguration *tds__ GetVideoSourceConfiguration, struct _tds__GeVideoSourceConfigurationResponse*tds__ GeVideoSourceConfigurationResponse)
 
GetVideoEncoderConfigurationOptions
int __tds__GetVideoEncoderConfiguration(struct soap* soap, struct _tds__ GetVideoEncoderConfiguration *tds__ GetVideEncoderConfiguration, struct _tds__GetVideoEncoderConfigurationResponse*tds__ GetVideoEncoderConfigurationResponse)
 
GetStreamUri
int__trt__GetStreamUri(struct soap* soap, struct _trt__GetStreamUri*trt__GetStreamUri, struct _trt__GetStreamUriResponse*trt__GetStreamUriResponse)
當客戶端通過GetCapabilities請求獲知裝置支援流媒體是,便會發起GetStreamUri請求,webservice返回當前流媒體的地址,這樣,客戶端便能播放實時流媒體。
GetSnapshotUri
int__trt__GetSnapshotUri(struct soap* soap, struct _trt__GetSnapshotUri*trt__GetSnapshotUri, struct _trt__GetSnapshotUriResponse*trt__GetSnapshotUriResponse)
 
 

編譯指令碼

(略......)

第三方依賴庫


日誌管理
為了便於除錯和維護,在這個程式中增加了日誌系統,包括時間戳、日誌等級、所在檔案、所在函式、行數以及日誌內容。
使用方法:
# exportPRINT_LOG_LEVEL=6

然後執行onvif程式,便會在終端輸出日誌


記憶體管理
在這個onvif程式中,經常從系統heap中分配空間,使用之後,經常忘記釋放。這個功能便是記錄呼叫malloc分配空間的地方。
void ycs_init_memory();
初始化記憶體記錄連結串列。
void* ycs_malloc(int size, constchar*file, constchar*func, constint line);
這個函式將傳入的檔名、函式名、行號和分配大小記錄到連結串列,然後返回記憶體地址。
void ycs_free(void*ptr, constchar*file, constchar*func, constint line);
這個函式根據ptr的值在連結串列中查詢對應分配的空間,然後釋放該空間。
void ycs_print_meminfo();
這個函式將輸出連結串列中所有malloc分配空間的資訊。

3效果圖
 
 

 

1 live555

1.1 工作模組
UsageEnvironment  
該類庫是對系統環境的抽象,包括UsageEnvironment 和TaskScheduler。UsageEnvironment  主要用於訊息的輸入輸出和使用者功能,TaskScheduler實現事件的非同步處理,事件處理函式的註冊等。它通過維護一個非同步讀取源實現如訊息到達等事件的處理,通過使用DelayQueue實現其他註冊函式的延時排程。另外,還有一個HashTable類定義了一個通用的hash表,其它程式碼要用 到這個表。我們在使用時可以自定義該類的抽象類的子類,就可以再特定的環境下執行如嵌入式或者GUI等。不需要進行太多的修改。
groupsock
該類是對網路介面的封裝,用於收發資料包。groupsock主要是面向多播資料的收發,它也同時支援單播資料的接收。
liveMedia
該類是live555的核心模組,各種媒體的封裝和資料的傳送。其中基類為Medium,其他的類都派生自該類。如MediaSession,RTP會話類,一個 session又可以包含多個subsession。還有比較重要的兩個派生類Source和Sink,Source抽象了需要傳送的資料,Sink則抽象資料的傳送者,資料的流動可以經過多個source和sink,兩者又通過session聯絡在一起。我們在開發的過程中,可以通過繼承這些類,實現自己需要的相關功能。
BasicUsageEnvironment
該類主要針對簡單控制檯的應用程式,利用select實現事件的獲取和處理。
1.2 工作流程
 live555首先會建立一個RTSP服務(具體的實現可以參看mediaServer裡的服務)。在服務建立過程中,會先呼叫setUpOurSocket建立tcp的連線,並監聽對應傳入的port,用於等待client請求的rtsp協議的互動,然後會把連線處理控制代碼已經socket控制代碼都傳入TaskScheduler當中,等待事件觸發。

 
                                                  (流程圖)
1、初始化
BasicTaskschedular
BasicUsageEnvironment
RTSPServer
|--------new RTSPServer
|------setupOurSocket建立監聽客戶端連線用的socket
|------turnOnBackgroundhandling(socket)將監聽socket加入計劃任務,等待連線
2、接受客戶端連線 incomingConnectionHandlerRTSP
在RTSPServer將監聽socket加入計劃任務後,排程機制會不斷查詢,如果收到連線請求,就會呼叫該回掉函式
(Tips:在Live555中監聽任務的執行都是通過相應的靜態全域性回掉函式,在回掉函式內部再強制轉換到相應的類函式)
在incomingConnectionHandlerRTSP中會clientSocket = accept()得到連線的socket、地址,設定引數等
之後建立一個客戶連線管理createNewClientConnection()
|----new RTSPClientConnection
|---setBackgroundHandling將連線的socket加入計劃任務
(Tips:turnonBackgroundHandling只讓socket可讀,內部會呼叫setBackgroundHandling進行設定,如需其他屬性,如可寫、異常等都需要通過setBackgroundHandling設定)
3、響應請求過程
3.1 一般過程
incomingRequestHandler
|---readSocket() 讀取資料
|---handleRequestBytes() 解析資料並做相應的處理
|---parseRTSPRequestString() 解析請求
|---handleCmd_XXX() 根據不同的RTSP協議命令去處理
|---send(fResponseBuffer) 構造好迴應欄位後,傳送迴應
3.2具體過程
3.2.1 handleCmd_DESCRIBE
handleCmd_DESCRIBE
|---urlTotalSuffix 提取streamName
|---authenticationOK 驗證使用者,這裡只保留了介面,未進行實現
|---fOurServer.lookupServerMediaSession(streamName)
這個用來獲取ServerMediaSession,一般不同型別的伺服器會有不同的實現策略,如Live555MediaServer只用來流化本地檔案,所以有了繼承類DynamicRTSPServer,重寫lookupServerMediaSession方法,本來找不到session例項會返回NULL,而在這裡將查詢當前目錄下對應的本地檔案,然後去建立相應的session,這也就是為什麼要將檔案與程式放在同一目錄下的原因。
這裡的ServerMediaSession在建立時會同時新增ServerMediasubsession,因為是伺服器,所以一定知道自己應該建立些什麼subsession
|---sdpDescription =session->generateSDPDescription() 獲取SDP描述資訊
|---snprintf() 組建回覆字串
3.2.1.1 generateSDPDescription過程
generateSDPDescription
|---foreach subsession sdpLines =subsession->sdpLines() 獲取每個subsessiond的sdp描述
|---sdpLines() 過程
|---onDemandServerMediasubsession 為點播式流媒體服務建立的中間繼承類,Live555Mediasever中的subsession都繼承自此類
|---FrameSource createNewStreamSource 建立一個數據源的Souce
|---RTPSink createNewRTPSink 建立RTPSink
|---setSDPLinesFromRTPSink(source, sink)從臨時的source與sink中獲取sdp
(Tips:createNewStreamSource與createNewRTPSink都是抽象方法,需要子類去實現,之後會從setSDPLinesFromRTPSink(source, sink)中得到sdp資訊,從這裡也可以看出sdp資訊由RTPSink獲得。Live555伺服器的機制是sink從source中獲取資料,建立sink的時候會將source作為引數傳入,因為伺服器知道建立了什麼型別的subSession,所以對一般的媒體資訊sink都會具備,唯一需要獲取的是getAuxSDPLine(),OnDemandServerMediasubsession預設的處理方式是從RTPSink中的auxSDPLine獲取,如果沒有就返回NULL,對Live555MediaServer來說,傳輸H264檔案時是無法得到sps、pps資訊的,必須通過讀取檔案,所以在H264subsession中就選擇建立Source和Sink讀取一段資訊後解析獲得)
3.2.2 handleCmd_SETUP
handleCmd_SETUP
|---sessionID 找一個唯一的sessionID,用於標識當前的subsession
|---fourServer.createNewClientSession(sessionID)
|---clientSession.handleCmd_SETUP() 轉到RTSPClientSession中去處理
|---fOurServerMediaSubsession = sms =fourSrever.lookupServerMediasession
|---建立streamState結構
|---fStreamStates = new struct streamState[fNumStreamStates]
|---foreach subSessionfstreamState[i].subsession = subsession 將subsession裝進streamState結構
|---trackID,subsession->trackID 通過trackID尋找對應的subSession
|---parseTransportHeader 解析Transport引數
|---subsession->getStreamParameters 在這裡將建立真正的FrameSource與RTPSink
|---FrameSource *mediaSource = createNewStreamSource()
|---rtoGroupsock = new GroupSock(serverRTPPort)
|---rtcpGroupsock = new Groupsock(serverRTPPort)
|---rtpSink = creatNewRTPSink(rtpGroupsock, mediaSource)
|---streamToken = new StreamState(rtpSink, mediaSource, rtpsock,rtcpsock)
|---fDestinationHashTable->add(sessionId, destination)
3.2.3handleCmd_PLAY
handleCmd_PLAY
|---在RTSPClientSession中,處理PLAY、PAUSE、TEARDOWN用同一個函式介面handle_CmdwithinSession,只是在裡面又進行了區分
|---找到對應的subsession->startStream()這裡將啟動流傳輸資料
|---streamState.startPlaying() 開始傳輸
|---如果沒有fRTCPInstance就建立一個RTCPInstance::createNew(fRTPSink)
|---fRTPgs->addDestination()
|---fRTCPgs->addDestination()
|---fRTPSink->startPlaying()
|---MediaSink::continuePlay() 到這裡就是各個子類實現了,純虛擬函式
|---MuliFramedRTPSink::continuePlaying()這裡針對的是H264檔案的解析
|---buildAndSendPacket()
|---準備RTP包頭
|---packFrame 打包幀資料
|---分兩種情況:一是上一次沒打包完,還有資料;二是一個全新的幀
|---對情況二,會呼叫fSource->getNextFrame()裡面加入的回撥函式afterGettingFrame,實際是呼叫sink->afterGettingFrame1
|---最終會依情況打包
|---sendPacketIfNecessary()
在sendPacketIfNecessary中會呼叫fRTPInterface.sendPacket()傳送資料,之後安排一個延時任務回撥sendNext函式,在sendNext中又會呼叫buildAndSendPacket,從而形成一個迴路來不斷髮送資料,知道檢測到fNoFrameLeft

1.3 live555除錯方法

1)調節socket傳送快取
 (略......
2)調節live555快取閥值
(略......
3)更換協議TCPorUDP
(略......
4)新增列印日誌

(略......

1.4 安卓平臺編譯
1、編譯指令碼
(略......
2、編譯指令:
a)  ./genMakefiles android
b)  make


2 流媒體伺服器
本流媒體伺服器是基於live555進行的二次開發,作用是實現裝置端實時音視訊資料的RTSP轉發。通過封裝jni介面啟動流媒體伺服器,獲取音視訊實時資料進行編碼、解析後,分包由live555傳送。

2.1 目錄結構
 


 
2.2 編譯指令碼
 (略......
2.3 jni封裝


功能:啟動RtspServer
Java_com_eques_device_ui_hardware_RTSPJNI_RtspServer(JNIEnv*env,jobject thiz)
引數名    資料型別    描述
        
功能:取實時視訊資料
Java_com_eques_device_ui_hardware_RTSPJNI_ReadVideoData(JNIEnv*env,jobject thiz,jbyteArray DataIn,jint  insize)
引數名    資料型別    描述
DataIn    jbyteArray    H264資料
insize    jint      長度


功能:取實時音訊資料
Java_com_eques_device_ui_hardware_RTSPJNI_ReadAudioData(JNIEnv*env,jobject thiz,jbyteArray DataIn,jint  insize)
引數名    資料型別    描述
DataIn    jbyteArray    PCM資料
insize    jint      長度

2.4 H264實時流傳輸
LIVE555預設只支援傳送音視訊檔案,而不支援從媒體裝置獲取的實時碼流。這需要修改LIVE555原始碼以實現H264碼流實時傳送功能。
從實現RTSP服務的相關基類派生出H264碼流直播的類,重寫類的成員方法來實現。。具體實現方法是新增H264LiveVideoServerMediaSubssion:public H264VideoFileServerMediaSubsession類,並重寫createNewStreamSource成員方法。該成員方法的關鍵段程式碼段如下:

(略......


該程式碼段的主要工作是把ByteStreamFileSource替換為使用者自定義的H264FramedLiveSource,用於獲取高清攝像頭上的實時視訊資料。H264FramedLiveSource的成員方法H264FramedLiveSource::doGetNextFrame就實現了從H264編碼輸出端獲取H264格式視訊資料並送到H264orH265VideoRTPSink端的過程。該成員方法的關鍵程式碼段如下:

(略......


這樣,當伺服器端收到客戶端PLAY命令時,不斷呼叫H264FramedLiveSource::doGetNextFrame讀取H264格式視訊資料,封包和傳送出去,實現H264碼流實時傳輸功能。
在live555android.cpp主函式中,只需在建立ServerMediaSession時加入H264LiveVideoServerMediaSubssion,並向RTSPServer中註冊該ServerMediaSession即可。

2.5 AAC實時流傳輸
1、AAC編碼
jni介面獲取的資料為pcm流,需要編碼aac流
需要注意的是pcm到aac編碼前需要做位的儲存轉化程式碼如下:

(略......

 

2、重寫類的成員方法來實現,具體實現方法是新增AudioLiveVideoServerMediaSubssion:public AudioVideoFileServerMediaSubsession類,並重寫createNewStreamSource成員方法。該成員方法的關鍵段程式碼段如下:

 (略......

AudioFramedLiveSource,用於獲取高清攝像頭上的實時視訊資料。AudioFramedLiveSource的成員方法AudioFramedLiveSource::doGetNextFrame就實現了AAC資料的解析分包拷貝到live555快取,關鍵程式碼段如下:

(略......


這樣,當伺服器端收到客戶端PLAY命令時,不斷呼叫AudioFramedLiveSource::doGetNextFrame讀取AAC格式資料,封包和傳送出去,實現AAC碼流實時傳輸功能。
在live555android.cpp主函式中,只需在建立ServerMediaSession時加入AACLiveVideoServerMediaSubssion,並向RTSPServer中註冊該ServerMediaSession即可。

http://download.csdn.net/detail/yuanchunsi/9710732(下載地址)