1. 程式人生 > >onvif搜尋裝置

onvif搜尋裝置



概要:

目前ONVIF協議家族裝置已佔據數字監控行業半壁江山以上,親,作為開發者的你還在猶豫是否瞭解下嗎?本文介紹了ONVIF客戶端從裝置搜尋,鑑權,能力獲取,媒體資訊獲取,URI地址獲取的整套流程。文章只講述了比較重要或其他博文沒有講述的開發點,詳細可以參考文末參考文章。最後,能獲得rtsp地址之後,然後去做其他功能比如錄影,ptz這些就非常得心應手了。本文出自CSDN-固本培元 ,轉載註明出自:[email protected]

前言及鳴謝:

感謝guog先生,快活林高先生,onvif全國交流群的的酷夏先生在開發過程中給予的巨大支援,沒有你們的幫助開發過程將異常艱難啊。謝謝了!

ONVIF介紹:

ONVIF致力於通過全球性的開放介面標準來推進 網路視訊 在安防市場的應用,這一介面標準將確保不同廠商生產的網路視訊產品具有互通性。2008年11月,論壇正式釋出了ONVIF第一版規範——ONVIF核心規範1.0。隨著 視訊監控 的網路化應用,產業鏈的分工將越來越細。有些廠商專門做攝像頭,有些廠商專門做DVS,有些廠商則可能專門做平臺等,然後通過 整合商 進行整合,提供給最終客戶。這種產業合作模式,已經迫切的需要行業提供越來越標準化的介面平臺。

流程總覽:

本文開發環境:Centos6.4  Gsoap:2.8.16  soap:1.2 onvif:2.4 。 注: 本文提供的參考程式碼其實網上都可以找到,這裡做一個整理,供大家交流學習,共同提高。

搜尋: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及開發框架生成:

1.下載Gsoap:地址: http://sourceforge.net/projects/gsoap2/files/gSOAP/ 2.安裝:  ./configure && make && make install 期間可能會有一些報錯,自己解決哦。 3.離線或者線上生成onvif.h。如果不需要最新的版本推薦離線方式。筆者使用的是這種方式。 離線檔案下載地址:感謝guog先生的共享:

命令:

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/5875143 線上命令:
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/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 的客戶端和伺服器的Onvif開發框架及已經搭建完成。

裝置搜尋原理及程式設計技巧:

搜尋發現的基本原理是:裝置上伺服器監聽239.255.255.250的3702埠。所以,如果要實現跨網段搜尋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;
		}
	}
注:搜尋到的裝置可以加入到自己的裝置管理中,這裡就不做過多的說明了。

裝置鑑權:

鑑權的實現可以很簡單也可以很難,這裡筆者使用的是gsoap提供的方法:直接呼叫即可:
soap_wsse_add_UsernameTokenDigest(soap,"user", ONVIF_USER, ONVIF_PASSWORD);
原理也很容易明白其實,就是講http的soap訊息加入對應header中xml的元素而已,然後敏感訊息digest MD5加密編碼。 所以編譯過程中需要使用 lcrypto 也就很正常了。

獲取能力:

soap 的http訊息通訊,參考程式碼:
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:

soap 的http訊息通訊,參考程式碼:
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 [email protected]
	{
		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:

soap 的http訊息通訊,參考程式碼:
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

另外存在的客戶端搜尋不到裝置情況:

1.是否有vpn,存在的話,本機IP會產生變化導致不能搜到?抓包可以看到,3702埠包的資料來源地址改變。

2.uuid是否已經賦值。

3.有時,windows宿主機裝有虛擬機器,也可能造成onvif客戶端的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以及token關鍵字被賦值。其他的沒有,說明本點返回與伺服器的支援性有很大關係。及,開發過程中需要對應自己的需求,根據實際的需要和返回錯誤,讀取返回結構體資料。

資源:

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

參考文章:

onvif規範的實現:使用gSOAP建立SOAP呼叫例項

http://blog.csdn.net/ghostyu/article/details/8162280

Onvif開發之服務端成功對接Rtsp視訊流篇

http://blog.csdn.net/max_min_go/article/details/17964643

linux裝置上的Onvif 實現10:獲取支援通道的RTSP地址

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

Onvif開發之客戶端鑑權獲取引數篇

http://blog.csdn.net/max_min_go/article/details/17617057

ONVIF協議開發資源

http://www.csdn.net/tag/onvif%252520%2525E5%25258D%25258F%2525E8%2525AE%2525AE

onvif開發之裝置發現功能的實現

http://blog.csdn.net/love_xjhu/article/details/11821037

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

http://blog.csdn.net/u012084827/article/details/19031969

Onvif開發之Linux下gsoap的使用及移植

http://blog.csdn.net/love_xjhu/article/details/9772361

onvif開發總結

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

S​O​A​P​ ​錯​誤​代​碼​表

http://wenku.baidu.com/link?url=rujSmnpjBxjS3mGZrejoVVOShcPu_5Wu_9RKrQ6qWCB12xrZUvVoFkYRepLu0y6oTk6-bB5AnJ_7KxF6s8rXcb1BFko6DbBpXg0_7G0D7cu

linux裝置上的Onvif 實現8:編寫媒體資訊獲取程式

http://blog.csdn.net/u012084827/article/details/12201897