概要:
前言及鳴謝:
感謝guog先生。快活林高先生,onvif全國交流群的的酷夏先生在開發過程中給予的巨大支持,沒有你們的幫助開發過程將異常艱難啊。謝謝了!
ONVIF介紹:
有些廠商專門做攝像頭。有些廠商專門做DVS。有些廠商則可能專門做平台等,然后通過集成商進行集成,提供給終於客戶。這樣的產業合作模式,已經迫切的須要行業提供越來越標准化的接口平台。
流程總覽:
搜索:Probe: 發現網絡攝像頭。獲取webserver地址
http://192.168.15.240/onvif/device_service
能力獲取:GetCapabilities:獲取設備能力文件。從中識別出媒體信息地址URI: http://192.168.15.240/onvif/Media
媒體信息獲取:GetProfiles: 獲取媒體信息文件。識別主通道、子通道的視頻編碼分辨率
RTSP地址獲取:GetStreamUri:獲取指定通道的流媒體地址 rtsp://192.168.15.240:554/Streaming/Channels/2?
transportmode=unicast
Gsoap及開發框架生成:
筆者使用的是這樣的方式。
命令:
wsdl2h -o onvif.h -c -s -t ./typemap.dat devicemgmt.wsdl media.wsdl event.wsdl display.wsdl deviceio.wsdl imaging.wsdl ptz.wsdl receiver.wsdl recording.wsdl search.wsdl remotediscovery.wsdl replay.wsdl analytics.wsdl analyticsdevice.wsdl actionengine.wsdl accesscontrol.wsdl doorcontrol.wsdl
離線文件在:
http://download.csdn.net/detail/u011597695/5875143wsdl2h -o onvif.h -c -s -t ./typemap.dat http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl http://www.onvif.org/onvif/ver10/media/wsdl/media.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/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/ver10/actionengine.wsdl http://www.onvif.org/ver10/pacs/accesscontrol.wsdl http://www.onvif.org/ver10/pacs/doorcontrol.wsdl
(記得拷貝gsoap的typemap文件至生成文件夾下,wsdl2h命令須要這個。
)
wsdl2h -o onvif.h -c -s -t ./typemap.dat devicemgmt.wsdl media.wsdl event.wsdl display.wsdl deviceio.wsdl imaging.wsdl ptz.wsdl receiver.wsdl recording.wsdl search.wsdl remotediscovery.wsdl replay.wsdl analytics.wsdl analyticsdevice.wsdl actionengine.wsdl accesscontrol.wsdl doorcontrol.wsdl
如今能夠開始生成了:例如以下:
假設直接生成相應C的庫文件會發生反復定義錯誤。能夠改動該文件。
wsa5.h(288): **ERROR**: remote method name clash: struct/class 'SOAP_ENV__Fault' already declared at line 274
打開文件gsoap_2.8.16/gsoap-2.8/gsoap/import/ wsa5.h
將277行int SOAP_ENV__Fault改動為int SOAP_ENV__Fault_alex
筆者沒有使用這樣的方法,是將這個結構體直接凝視的方式。最后的結果是,都能夠使用。同一時候上一步生成的onvif.h文件里沒有打開wsse.h。 導致最后生成代碼中SOAP_ENV__Header 結構體中缺少定義 wsse__Security數據段,無法進行鑒權命令。
即:加入對openssl的支持,在上一步生成的onvif.h中加入(可選)
- #import "wsse.h"
隨后使用命令生成:
soapcpp2 -c onvif.h -x -I/root/Tools/Gsoap/gsoap-2.8/gsoap/import -I/root/Tools/Gsoap/gsoap-2.8/gsoap/ -I/root/Tools/Gsoap/gsoap-2.8/gsoap/custom -I/root/Tools/Gsoap/gsoap-2.8/gsoap/extras -I/root/Tools/Gsoap/gsoap-2.8/gsoap/plugin

到此為止,基於 C 的client和server的Onvif開發框架及已經搭建完畢。
設備搜索原理及編程技巧:
僅僅要組播數據包能收到,設備就能被搜到。
原理是這樣。
參考代碼:
struct soap* NewSoap(struct SOAP_ENV__Header *header,struct soap* soap, wsdd__ProbeType *req_, wsdd__ScopesType *sScope_) { soap = soap_new(); if(NULL == soap ) { printf("sopa new error\r\n"); return NULL; } soap->recv_timeout = 5; soap_set_namespaces(soap, namespaces); soap_default_SOAP_ENV__Header(soap, header); uuid_t uuid; char guid_string[100]; uuid_generate(uuid); uuid_unparse(uuid, guid_string); header->wsa__MessageID = guid_string; header->wsa__To = "urn:schemas-xmlsoap-org:ws:2005:04:discovery"; header->wsa__Action = "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe"; soap->header = header; soap_default_wsdd__ScopesType(soap, sScope_); sScope_->__item = ""; soap_default_wsdd__ProbeType(soap, req_); req_->Scopes = sScope_; req_->Types = ""; //"dn:NetworkVideoTransmitter"; return soap ; }
int i = 0; result = soap_send___wsdd__Probe(soap, MULTICAST_ADDRESS, NULL, &req); while(result == SOAP_OK) { result = soap_recv___wsdd__ProbeMatches(soap, &resp); if(result == SOAP_OK) { if(soap->error) { printf("soap error 1: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap)); result = soap->error; } else { printf("Onvif Device detected *********************************************\r\n"); for(i = 0; i < resp.wsdd__ProbeMatches->__sizeProbeMatch; i++) { printf("__sizeProbeMatch : %d\r\n", resp.wsdd__ProbeMatches->__sizeProbeMatch); printf("wsa__EndpointReference : %p\r\n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference); printf("Target EP Address : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address); printf("Target Type : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->Types); printf("Target Service Address : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs); printf("Target Metadata Version : %d\r\n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion); if(resp.wsdd__ProbeMatches->ProbeMatch->Scopes) { printf("Target Scopes Address : %s\r\n", resp.wsdd__ProbeMatches->ProbeMatch->Scopes->__item); } } break; } } else if (soap->error) { printf("[%d] soap error 2: %d, %s, %s\n", __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap)); result = soap->error; } }
設備鑒權:
soap_wsse_add_UsernameTokenDigest(soap,"user", ONVIF_USER, ONVIF_PASSWORD);
原理也非常easy明確事實上,就是講http的soap消息增加相應header中xml的元素而已。然后敏感消息digest MD5加密編碼。
獲取能力:
void UserGetCapabilities(struct soap *soap ,struct __wsdd__ProbeMatches *resp, struct _tds__GetCapabilities *capa_req,struct _tds__GetCapabilitiesResponse *capa_resp) { capa_req->Category = (enum tt__CapabilityCategory *)soap_malloc(soap, sizeof(int)); capa_req->__sizeCategory = 1; *(capa_req->Category) = (enum tt__CapabilityCategory)(tt__CapabilityCategory__Media); capa_resp->Capabilities = (struct tt__Capabilities*)soap_malloc(soap,sizeof(struct tt__Capabilities)) ; soap_wsse_add_UsernameTokenDigest(soap,"user", ONVIF_USER, ONVIF_PASSWORD); printf("\n--------------------Now Gettting Capabilities NOW --------------------\n\n"); int result = soap_call___tds__GetCapabilities(soap, resp->wsdd__ProbeMatches->ProbeMatch->XAddrs, NULL, capa_req, capa_resp); if (soap->error) { printf("[%s][%d]--->>> soap error: %d, %s, %s\n", __func__, __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap)); int retval = soap->error; exit(-1) ; } else { printf(" \n--------------------GetCapabilities OK! result=%d--------------\n \n",result); if(capa_resp->Capabilities==NULL) { printf(" GetCapabilities failed! result=%d \n",result); } else { printf(" Media->XAddr=%s \n", capa_resp->Capabilities->Media->XAddr); } } }
獲取媒體信息Profile:
void UserGetProfiles(struct soap *soap,struct _trt__GetProfiles *trt__GetProfiles, struct _trt__GetProfilesResponse *trt__GetProfilesResponse ,struct _tds__GetCapabilitiesResponse *capa_resp) { int result=0 ; printf("\n-------------------Getting Onvif Devices Profiles--------------\n\n"); soap_wsse_add_UsernameTokenDigest(soap,"user", ONVIF_USER, ONVIF_PASSWORD); result = soap_call___trt__GetProfiles(soap, capa_resp->Capabilities->Media->XAddr, NULL, trt__GetProfiles, trt__GetProfilesResponse); if (result==-1) //NOTE: it may be regular if result isn't SOAP_OK.Because some attributes aren't supported by server. //any question email leoluopy@gmail.com { printf("soap error: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap)); result = soap->error; exit(-1); } else{ printf("\n-------------------Profiles Get OK--------------\n\n"); if(trt__GetProfilesResponse->Profiles!=NULL) { if(trt__GetProfilesResponse->Profiles->Name!=NULL){ printf("Profiles Name:%s \n",trt__GetProfilesResponse->Profiles->Name); } if(trt__GetProfilesResponse->Profiles->token!=NULL){ printf("Profiles Taken:%s\n",trt__GetProfilesResponse->Profiles->token); } } else{ printf("Profiles Get inner Error\n"); } } printf("Profiles Get Procedure over\n"); }
獲取RTSP的URI:
void UserGetUri(struct soap *soap,struct _trt__GetStreamUri *trt__GetStreamUri,struct _trt__GetStreamUriResponse *trt__GetStreamUriResponse, struct _trt__GetProfilesResponse *trt__GetProfilesResponse,struct _tds__GetCapabilitiesResponse *capa_resp) { int result=0 ; trt__GetStreamUri->StreamSetup = (struct tt__StreamSetup*)soap_malloc(soap,sizeof(struct tt__StreamSetup));//初始化,分配空間 trt__GetStreamUri->StreamSetup->Stream = 0;//stream type trt__GetStreamUri->StreamSetup->Transport = (struct tt__Transport *)soap_malloc(soap, sizeof(struct tt__Transport));//初始化,分配空間 trt__GetStreamUri->StreamSetup->Transport->Protocol = 0; trt__GetStreamUri->StreamSetup->Transport->Tunnel = 0; trt__GetStreamUri->StreamSetup->__size = 1; trt__GetStreamUri->StreamSetup->__any = NULL; trt__GetStreamUri->StreamSetup->__anyAttribute =NULL; trt__GetStreamUri->ProfileToken = trt__GetProfilesResponse->Profiles->token ; printf("\n\n---------------Getting Uri----------------\n\n"); soap_wsse_add_UsernameTokenDigest(soap,"user", ONVIF_USER, ONVIF_PASSWORD); soap_call___trt__GetStreamUri(soap, capa_resp->Capabilities->Media->XAddr, NULL, trt__GetStreamUri, trt__GetStreamUriResponse); if (soap->error) { printf("soap error: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap)); result = soap->error; } else{ printf("!!!!NOTE: RTSP Addr Get Done is :%s \n",trt__GetStreamUriResponse->MediaUri->Uri); } }

開發注意事項:(必讀)
soap通信的命名空間假設錯誤則不能檢索到設備:編譯好的wsdd.nsmap文件須要改動命名空間,例如以下:
假設要正常開發,被檢索到,或者發現其它設備須要nsmap改動例如以下:1.1換1.2
下面命名空間表示SOAP1.1版本號: {"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/*/soap-envelope", NULL}, {"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", "http://www.w3.org/*/soap-encoding", NULL}, //1.1 下面命名空間表示SOAP1.2版本號: {"SOAP-ENV", "http://www.w3.org/2003/05/soap-envelope", "http://schemas.xmlsoap.org/soap/envelope/", NULL}, {"SOAP-ENC", "http://www.w3.org/2003/05/soap-encoding", "http://schemas.xmlsoap.org/soap/encoding/", NULL}, //1.2
另外存在的client搜索不到設備情況:
1.是否有vpn,存在的話。本機IP會產生變化導致不能搜到?抓包能夠看到,3702port包的數據源地址改變。
2.uuid是否已經賦值。
3.有時,windows宿主機裝有虛擬機,也可能造成onvifclient的ip獲取錯誤。
故搜索不到。
這些問題。在交換機或者路由支持本地局域網跨網段數據UDP交互時,均不會產生。
調試技巧:
fsend/ frecv 打印出發送和接收到的報文。使用xml編輯器分析。
當然也能夠直接用瀏覽器看。
1、打開onvif調試開關,以便讓onvif打印一些可用的調試信息。
在Makefile中加入調試宏定義如: CC = gcc -DDEBUG
2、打開調試宏后。默認在程序執行的文件夾產生三個文件:
RECV.log
SENT.log
TEST.log
RECV.log是onvif接收到的SOAP數據,沒接收一條。都會在RECV.log中記錄
SENT.log是onvif發送出去的SOAP數據。沒發送一套,也會在SENT.log中生成記錄
最后是TEST.log,假設說RECV和SENT能夠用wireshark工具抓包取代。那么TEST.log是誰也替代不了的,TEST.log記錄了onvif的實時的工作狀態。
尤其當出現segmentation fault錯誤。TEST.log就成了唯一一個可以定位到詳細內存出錯的地方了。
SOAP_TYPE返回soap->error=4的錯誤說明
關於數據正確(抓包可收到數據)。但soap返回錯誤,為4 及 SOAP_TYPE 的問題:
GetCapabilities的過程錯誤時。
多次調試后得出結論,是tt__CapabilityCategory 的設置問題,有的設備不具備所有功能,而請求所有或請求沒有的功能就可能造成這樣的問題,推薦寫5(tt__CapabilityCategory__Media) 這是大多數設置有的能力。並且最經常使用。
GetProfile時錯誤:
事實上數據在抓包過程中也能全然抓到,多次調試后。發現結構體須要的Name以及tokenkeyword被賦值。
其它的沒有,說明本點返回與server的支持性有非常大關系。及。開發過程中須要相應自己的需求,依據實際的須要和返回錯誤,讀取返回結構體數據。
資源:
ONVIFDEVICEMANAGER下載地址:
http://pan.baidu.com/share/link?
shareid=1967805400&uk=70662920&fid=3981296515
ONVIFTESTTOOL下載地址:
http://www.cr173.com/soft/66448.html
官網開發人員向導資料下載地址:
http://www.onvif.org/Resources/WhitePapers.aspx
參考文章:
http://blog.csdn.net/ghostyu/article/details/8162280
http://blog.csdn.net/max_min_go/article/details/17964643
linux設備上的Onvif 實現10:獲取支持通道的RTSP地址
http://gaohtao.blog.163.com/blog/static/58241823201381113214599/
http://blog.csdn.net/max_min_go/article/details/17617057
ONVIF協議開發資源
http://www.csdn.net/tag/onvif%252520%2525E5%25258D%25258F%2525E8%2525AE%2525AE
http://blog.csdn.net/love_xjhu/article/details/11821037
http://blog.csdn.net/u012084827/article/details/19031969
http://blog.csdn.net/love_xjhu/article/details/9772361
http://blog.csdn.net/zsl461975543/article/details/8971143
代碼框架生成之Onvif開發
http://www.yc-edu.org/C__peixun/6655.html
linux設備上的Onvif 實現4:成功編譯gsoap 2.8.15
http://blog.csdn.net/u012084827/article/details/12202133
onvif規范的實現:onvif開發經常使用調試方法 和常見的segmentation fault錯誤
http://blog.csdn.net/ghostyu/article/details/8432760
linux設備上的Onvif 實現6:獲取攝像頭的流媒體地址完整流程
http://blog.csdn.net/u012084827/article/details/12201997
SOAP 錯誤代碼表
http://blog.csdn.net/u012084827/article/details/12201897