1. 程式人生 > >Linux裝置上的Onvif實現18: ONVIF視訊監視功能開發問題總結

Linux裝置上的Onvif實現18: ONVIF視訊監視功能開發問題總結

ONVIF視訊監視功能開發問題總結

       我從去年8月份開始學習ONVIF,經歷了各種困難,有時簡直要暴走發狂,終於能夠達成計劃目標,實現了預訂功能。痛苦已經過去,現在是寫個問題總結的時候了,希望能記錄遇到的問題,以便將來遺忘時參考。

測試的攝像頭有2種品牌3種型號。分別是海康的2款槍機,DS-2CD3312D-I

臺灣升泰科技(AVTECH)的一款家用IPCAVM311

1 自己編寫的服務端收到Probe命令,應答的報文不能被ONVIFTest識別。

該問題比較奇怪,經過比較正常的應答包和錯誤包,發現是Header段缺少<wsadis:RelatesTo>內容。OnvifTest

發出的probe命令帶有MessageID,裝置的應答報文必須帶有該MessageID,只有一致才認為是正確的匹配。缺少<wsadis:RelatesTo>就導致OnvifTest工具軟體無法識別應答命令。

跟蹤服務端soap_serve()函式流程,發現問題出在soap_wsa_reply函式。

main()

soap_serve()

soap_serve_request()

soap_serve___wsdd__Probe()

__wsdd__Probe() wsddapi.c

soap_wsa_reply() wsddapi.c

該函式呼叫了外掛函式soap_lookup_plugin()

導致返回值無效,引起提前結束函式。這就引起沒有填充Header段的RelatesTo結構體。

現在不清楚soap_lookup_plugin()為什麼返回NULL,但是可以把對data的操作搬移到newheader->SOAP_WSA(RelatesTo)之後執行。這樣回答的報文中就包含RelatesTo內容了。修改後的soap_wsa_reply()函式請看部落格原文:

linux裝置上的Onvif實現5:實現Probe命令檢測裝置:

http://gaohtao.blog.163.com/blog/static/58241823201362343648345/

2 客戶端發出Probe命令,攝像頭應答多次,造成記錄的裝置列表中重複。

我使用的Probe命令設定的等待時間是5s,在此期間等待攝像頭應答,如果沒有收到應答包就自動結束。收到應答包後解析出裝置名和IP地址新增到裝置列表中。實際上發現海康的攝像頭只應答一次,升泰科技的攝像頭攝像頭應答3次。3次應答的MessageID完全相同。

傳送的probe MessageID

<wsa:MessageID>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:MessageID>

收到的3次應答MessageID

1 <wsa:MessageID>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:MessageID>

      <wsa:RelatesTo>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:RelatesTo>

2 <wsa:MessageID>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:MessageID>

      <wsa:RelatesTo>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:RelatesTo>

3 <wsa:MessageID>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:MessageID>

      <wsa:RelatesTo>41772060-76e5-11e1-ad77-85ac2d7785ac</wsa:RelatesTo>

解決方法是在裝置列表中增加UUID欄位,新增裝置時檢查列表中是否存在UUID的裝置,不存在就新增,存在就表示已經新增過了,丟棄此次應答包。具體實現時注意字串比較函式最好使用strstr(),不要使用strcmp(),原因是輸入引數可能分別是

urn:uuid:AA9DFBE7-4359-49D4-000E5324BB77

AA9DFBE7-4359-49D4-000E5324BB77

這樣的話strcmp就會出現錯誤。

3  GetCapabilities()函式返回401失敗,要求鑑權。

我測試的2種牌子表現不同。海康攝像頭直接呼叫該函式返回正確應答報文。升泰科技的攝像頭則返回401錯誤,必須新增鑑權資訊,然後再次呼叫GetCapabilities()才能正確返回應答報文。

新增鑑權過程研究了一段時間,終於弄明白了使用方法,請看部落格原文:

Linux裝置上的Onvif實現16:實現Onvif鑑權

http://gaohtao.blog.163.com/blog/static/58241823201383042530402/

4 GetProfiles()函式應答多個profile

使用GetProfiles()函式獲取媒體資訊,結果發現海康攝像頭返回2profile,升泰攝像頭返回3profile。這樣在解析資料時出現遺漏錯誤。經過測試,發現升泰攝像頭支援3個通道,這3profile都是實際可用的,必須予以支援。

解決方法:擴充套件裝置媒體資訊資料結構,最多支援3profile,對於超過3個的profile丟棄不處理。

<tt:VideoSourceConfigurationtoken="VS1token">

        <tt:Name>VS1</tt:Name>

       <tt:UseCount>3</tt:UseCount>

        <tt:SourceToken>VS1srctoken</tt:SourceToken>

        <tt:Bounds height="240"width="352" y="0" x="0"></tt:Bounds>

主通道:PROFILE-1

解析度  Width=1280, Height=720

幀率    FrameRateLimit=30

位元速率    BitrateLimit=5000        

編碼    Encoding=H264

子通道 PROFILE-2

解析度  Width=640, Height=480     設定正確的引數: 640x480

幀率    FrameRateLimit=30                       25

位元速率    BitrateLimit=5000                       1536

編碼    Encoding=MPEG4                          H264

子通道 PROFILE-3

解析度  Width=320, Height=240

幀率    FrameRateLimit=30

位元速率    BitrateLimit=5000

編碼    Encoding=H264

5 修改攝像頭的視訊配置引數,命令應答成功,實際無效。

ONVIF協議的一個突出優點是可以使用命令修改攝像頭的配置引數SetVideoEncoderConfiguration(),我就使用這個功能,把攝像頭的視訊流修改成我方裝置支援的型別,結果海康攝像頭工作正確,升泰攝像頭命令應答成功,但是實際上攝像頭引數根本沒有修改。

解決方法:與臺灣升泰公司的工程師聯絡,對方測試了這種情況,提供了攝像頭的升級韌體,更新後該問題解決。

6 RTSP鑑權的2種方式

RTSP協議中規定了2種鑑權方式,分別是基本認證(basic authentication)、摘要認證(digest authentication)。測試攝像頭髮現,有的攝像頭應答DESCRIBE命令如下:

DESCRIBErtsp://192.168.0.112:540/live/h264_ulaw/VGA RTSP/1.0

CSeq: 2

Accept: application/sdp

User-Agent: ABB Genway E-3000 RTSP 1.0

RTSP/1.0 401 Unauthorized

CSeq: 2

WWW-Authenticate: Basicrealm="Server"

WWW-Authenticate: Digestrealm="Server", nonce="50e5d8bff60b6f5b5a85e7a5b613e85e

19512c53defbdfb69d9dd2739877cc82"

有的如下:

RTSP/1.0 401 Unauthorized

CSeq: 5

WWW-Authenticate: Digestrealm="8ce748eafc54",nonce="db83d3233626b950b519a1f658af4a47", stale="FALSE"

WWW-Authenticate: Basicrealm="8ce748eafc54"

Date: Tue, Dec 31 2013 11:55:39 GMT

就是這兩種鑑權順序是反的。不知道是否第一個代表了預設鑑權方式。以前實現了基本認證,後來努力了很久實現了摘要認證,請看部落格原文:

Linux裝置上的Onvif實現17:實現RTSP摘要認證

有的攝像頭Web頁面上可以設定鑑權方式(開關、基本、摘要),對應的在自己的RTSP程式碼中也要使用響應的方式,例項如下:

typedef struct _WWW_Authenticate

{   

  intbAuthenticate;           //認證標記,0=none,1=basic, 2=digest

  intnc;                      //請求計數

 char realm[MAXPATH];         //認證的領域

 char nonce[MAXPATH];         //伺服器密碼隨機數

 char cnonce[MAXPATH];        //客戶端密碼隨機數

 char qop[MAXPATH];           //保護質量

 char username[MAXPATH];      //使用者名稱

 char password[MAXPATH];      //密碼

 char uri[MAXPATH];           //uri

 char method[MAXPATH];       //method

 char response[MAXPATH];      //摘要

 char opaque[MAXPATH];        //?? ,用於客戶端對服務端認證

 char Authenticate[2*MAXPATH];  //儲存完整的認證資訊

}WWW_Authenticate;

if(ret == 401)

{

     ret=simple_parse_www_authenticate(resp.www_authenticate1,resp.www_authenticate2);

     if(count<=0)

     {

        if(ret==0) //WWW-Authenticate: Basic

        {

            rtsp_client->wwwAuthenticate.bAuthenticate = 1;                           

        }               

        else if(ret==1) //WWW-Authenticate: Digest

        {                   

            rtsp_client->wwwAuthenticate.bAuthenticate = 2;

            ParseDigestAuthenticateInfo(rtsp_client,resp.www_authenticate1, resp.www_authenticate2);

         }

        clear_response(&resp);

        count++;

        goto AUTHENTICATE;

    }

    else

    {

        RTSP_ALERT("--ERROR: not support www_authenticate! \n");

    }

}

if(rtsp_client->wwwAuthenticate.bAuthenticate)

    {

       if(rtsp_client->wwwAuthenticate.bAuthenticate==2)

       {

           memset(rtsp_client->wwwAuthenticate.method, 0, MAXPATH);

           strcpy(rtsp_client->wwwAuthenticate.method, "DESCRIBE");

           memset(rtsp_client->wwwAuthenticate.uri, 0, MAXPATH);

           strcpy(rtsp_client->wwwAuthenticate.uri, rtsp_client->url);

           CreateDigestAuthenticate(rtsp_client,(u_char*)username,strlen(username));

       }

       else if(rtsp_client->wwwAuthenticate.bAuthenticate==1)

       {

           CreateBasicAuthenticate(rtsp_client,(u_char*)username,strlen(username));

       }

       cmd.authorization = malloc(2*MAXPATH);

       memset(cmd.authorization, 0, 2*MAXPATH);

       strncpy(cmd.authorization, rtsp_client->wwwAuthenticate.Authenticate,2*MAXPATH);

}

7 DESCRIBE命令應答解析

DESCRIBErtsp://192.168.0.112:540/live/h264_ulaw/VGA RTSP/1.0

CSeq: 3

Accept: application/sdp

Authorization: Basic YWRtaW46YWRtaW4=

User-Agent: ABB Genway E-3000 RTSP 1.0

RTSP/1.0 200 OK

CSeq: 3

Content-Base:rtsp://192.168.0.112:540/live/h264_ulaw/VGA/

Content-Type: application/sdp

Cache-Control: must-revalidate

x-Accept-Dynamic-Rate: 1

x-Accept-Dynamic-Rasave exit:isCheckpointed 1

te: 1 [not processing]

Content-Length: 305

response body length=305, current buffersize=305, offset=198

v=0

o=- 1 1 IN IP4 127.0.0.1

s=RTSP server

c=IN IP4 0.0.0.0

t=0 0

a=control:*

a=range:npt=now-

m=video 0 RTP/AVP 97

a=rtpmap:97 H264/90000

a=fmtp:97packetization-mode=1;profile-level-id=420028;sprop-parameter-sets=Z0IAKOkBQHsg,aM4xUg==;

a=control:track1

m=audio 0 RTP/AVP 0

a=control:track3

v-media = video

問題:其中a=control:track1,升泰攝像頭資料與海康攝像頭不一致,引起通道引數錯誤。

海康的應答結果有2種:

  a=control:trackID=1   相對路徑

  a=control:rtsp://...    絕對路徑

解決方法:由於a=control:欄位內容不規範,為了通用考慮,不能檢查control是否含有track/trackID,直接判斷是否含有”rtsp://…”

8 RTSP 傳送的H264資料流無法解析顯示。

除錯升泰攝像頭遇到接收到的H264資料流無法解碼顯示,對H264的資料包進行了深入分析:

接收到的第一個資料包:

--len=43   80 e1 49 37 01 5f f7 5c 97 48 1d 18 78 0016 67 42 00 1e e9 01 40 7b 5c 48 00 6d dd 00 0c df e6 00 d8 81 09 40 00 04 68ce 31 52 

解析如下:

80 cc(b4~b7)=0指示後面的CSRC個數=0,就是無CSRC

e1Marker(b0)=1,  payload type(b1~b7)=0x61

49 37SequenceNumber

01 5f f7 5c: Timestamp

97 48 1d 18: SSRC―同步源標識

78表示STAP-A單一時間組合包,其後實際上包括spspps兩幀

00 16 67 42 00 1e e901 40 7b 5c 48 00 6d dd 00 0c df e6 00 d8 81 09 40  sps幀長度=0x0016

00 04 68 ce 31 52    pps幀長度=0x0004

這裡新遇到了STAP-A單一時間組合包,就是spspps兩幀資料組合在一個數據包,以前沒有實現過這種包的解析,所以無法顯示。而海康的攝像頭髮出的SPSPPS分別是單獨的兩個資料包,就能正常解析顯示。

解決方法:增加解析STAP-A

/*----------------- 處理一包是多幀------------------ */

if(24<=NALU_parload &&NALU_parload<=27)

{

switch(NALU_parload)

{

case 24:  //STAP-A單一時間的組合包

{

/*帶有rtp包頭的資料內容示例 :    只包含sps/pps兩幀

80 e1 49 37 01 5f f7 5c 9748 1d 18    78 00 16 67 42 00 1e e9 01 407b 5c 48 00 6d dd 00 0c df e6 

*/

int i;               

for(i=13;i<Packlength;)

{

int len;   //內部一幀長度

len =(RtpPackage[i]<<8)+RtpPackage[i+1];

char * frameBuf= (char*)malloc(len);

memset(frameBuf,0, len);

  memcpy(frameBuf,RtpPackage+i+2,len);

OSA_DBG_MSG("ReciveRTPPackage()  STAP-A: framelen=%d  \n",len);

DoH264SinglePackage(frameBuf,len);

i= i+2+len;

free(frameBuf);

}              

}

break;

case 25:  //STAP-B單一時間的組合包,沒有實現解析

case 26:  //MTAP16多個時間的組合包

case 27:  //MTAP24多個時間的組合包

default:

SliceReady=-1;                    

break;           

}

goto NALU_end;

}

9 分機裝置開啟攝像頭,經常收不到攝像頭的視訊流,這時所有的RTSP命令都返回454錯誤。

升泰攝像頭遇到了這種情況,好好的在自己的裝置上監視攝像頭成功,停止,再一次監視就無法收到資料流。這時RTSP命令都返回錯誤。

5 ==================================

PLAYrtsp://192.168.0.112:540/live/h264_ulaw/VGA RTSP/1.0

CSeq: 5

Authorization: Basic YWRtaW46YWRtaW4=

Session: C3ED883FFE905F1816A2D1EC98DC5B

User-Agent: ABB Genway E-3000 RTSP 1.0

RTSP/1.0 200 OK

CSeq: 5

Session: C3ED883FFE905F1816A2D1EC98DC5B

6 ==================================

TEARDOWNrtsp://192.168.0.112:540/live/h264_ulaw/VGA RTSP/1.0

CSeq: 6

Authorization: Basic YWRtaW46YWRtaW4=

Session: 2D50C2A8C72EC5AB5605D6906FCD26

User-Agent: ABB Genway E-3000 RTSP 1.0

RTSP/1.0 454 Session Not Found

CSeq: 6

complete recv response, buffer state:offset=43 len=0

rtsp respond code(454) != 200

遇到的問題:

停止命令失敗,提示會話不存在。

2014-3-11 發現真正原因:

接收流程:分機裝置向攝像頭髮送PLAY命令,收到成功應答(200 OK)才開始建立rtprtcp socket連線,接收H264資料流。

分析故障時的網路抓包序列:

        

Package 26:分機向攝像頭髮送PLAY命令

Package 28:攝像頭應答200 OK

Package 30: 攝像頭髮出第一個資料包,實際內容就是SPSPPS的組合包。

Package 32:攝像頭髮出第二個資料包,內容是I幀。

Package 33:分機應答Portunreachable,之後攝像頭就停止了傳送資料。

原因分析:

通過網路上對“Portunreachable”的解釋,發現如果攝像頭向分機埠傳送UDP資料包時,分機端還未能建立RTP/RTCP,就可能出現分機的RTP埠無法接收資料,分機linux系統自動應答ICMP資料包Port unreachable, 該攝像頭連續收到2ICMP包就會中斷連線。由於之前SETUP建立的連線被中斷,之後分機發送TEARDOWN提示“Session Not Found”。

奇怪的是海康攝像頭就不會停止傳送資料,也不會中斷連線,這樣當分機端RTP連線建立成功後就能收到資料包了。

解決方法:

 1 修改流程:分機收到攝像頭SETUP成功應答後就要建立UDP接收執行緒,然後才能傳送PLAY請求。

 2 為了確保UDP接收執行緒中的socket初始化完成後才能傳送PlayReq,在IPC_CreateEngineThread執行緒中使用訊號量wait,在RTP_ReciveThread執行緒中發出post

這樣處理之後分機監視正常,反覆操作100次全部成功顯示。