有了前幾篇的基礎(chǔ),現(xiàn)在可以正式開(kāi)始o(jì)nvif的實(shí)現(xiàn)工作,其中一項(xiàng)非常重要的部分就是視頻流的對(duì)接,即能夠在符合onvif標(biāo)準(zhǔn)的監(jiān)控客戶(hù)端軟件里接收到設(shè)備端NVT發(fā)來(lái)的RTSP視頻流。這里,我所用的客戶(hù)端軟件是Onvif Device Manager v2.2。【來(lái)自http://blog.csdn.net/ghostyu】
ONVIF?Profile S Specification文檔描述了Device或者說(shuō)DVT和Client可以使用的一種Profile,Profile這個(gè)詞在計(jì)算機(jī)領(lǐng)域非常常見(jiàn),我們可以理解成一種方案、配置、框架等。
文檔里描述了如果實(shí)現(xiàn)VideoStream,device和client應(yīng)該具備的條件,當(dāng)然如果實(shí)現(xiàn)文檔的所有條件,就可以說(shuō)該設(shè)備符合Profile S
如果單純實(shí)現(xiàn)VideoStream,只需完成下列命令。
[plain] view plain
copy 1、GetProfiles??2、GetStreamUri???填充rtsp路徑,例如:rtsp://192.168.1.201/petrov.m4e??3、Media?Streaming?using?RTSP??這里使用開(kāi)源的live555,完成rtsp功能??4、GetVideoEncoderConfiguration??5、GetVideoEncoderConfigurationOptions??6、GetCapabilities??NVC為了獲取DVT所支持的功能的命令??
參考文檔:
[plain] view plain
copy 1、ONVIF?Profile?S?Specification??描述ProfileS是什么樣的一個(gè)東西,如何實(shí)現(xiàn)??2、Reference_of_ONVIF_Development_v1.01.02??Onvif?DVT設(shè)計(jì)參考,指明了一條道路,但沒(méi)有具體內(nèi)容??3、ONVIF-Media-Service-Spec-v220??Onvif?Media的說(shuō)明介紹??4、http://www.onvif.org/onvif/ver20/util/operationIndex.html??onvif幾乎全部命令的詳細(xì)說(shuō)明,非常重要。該文檔告訴我們結(jié)構(gòu)體成員的意義和如何填充。Onvif開(kāi)發(fā)其實(shí)就是各種結(jié)構(gòu)體的填充。??
一、產(chǎn)生onvif源碼框架
1、從wsdl生成C頭文件
[cpp] view plain
copy wsdl2h?-o?onvif.h?-c?-s?-t?.\typemap.dat?http:??
跟前一篇discovery唯一不同的是,這里多了很多wsdl文件,這次創(chuàng)建完整的onvif代碼框架
2、從頭文件生成源碼框架
[cpp] view plain
copy soapcpp2?-c?onvif.h?-x?-I?/root/onvif/gsoap-2.8/gsoap/import?-I?/root/onvif/gsoap-2.8/gsoap/??
產(chǎn)生的C文件比較龐大,最大的有十幾兆,大部分的內(nèi)容沒(méi)有復(fù)用導(dǎo)致。
二、創(chuàng)建soap運(yùn)行環(huán)境
[cpp] view plain
copy int?main(int?argc,?char?**argv)????{????????int?m,?s;????????struct?soap?add_soap;????????int?server_udp;????????server_udp?=?create_server_socket_udp();????????????pthread_t?thrHello;??????pthread_t?thrProbe;??????????????????pthread_create(&thrProbe,NULL,main_Probe,server_udp);????????soap_init(&add_soap);????????soap_set_namespaces(&add_soap,?namespaces);????????????if?(argc?<?0)?{????????????printf("usage:?%s?<server_port>?\n",?argv[0]);????????????exit(1);????????}?else?{????????????m?=?soap_bind(&add_soap,?NULL,?80,?100);????????????if?(m?<?0)?{????????????????soap_print_fault(&add_soap,?stderr);????????????????exit(-1);????????????}????????????fprintf(stderr,?"Socket?connection?successful:?master?socket?=?%d\n",?m);????????????for?(;;)?{????????????????s?=?soap_accept(&add_soap);????????????????if?(s?<?0)?{????????????????????soap_print_fault(&add_soap,?stderr);????????????????????exit(-1);????????????????}????????????????fprintf(stderr,?"Socket?connection?successful:?slave?socket?=?%d\n",?s);????????????????soap_serve(&add_soap);????????????????soap_end(&add_soap);????????????}????????}????????return?0;????}???
注意,這里綁定了80端口,onvif使用的是http請(qǐng)求,然后附帶xml,其實(shí)正常的是將onvif集成到web服務(wù)器中,普通的http請(qǐng)求有web服務(wù)器處理,onvif的http請(qǐng)求則有soap處理。我們這里的做法也可行,只不過(guò)onvif的訪問(wèn)web服務(wù)器的功能是無(wú)法使用的。
三、RTSP視頻對(duì)接
1、實(shí)現(xiàn)GetCapabilities命令
客戶(hù)端發(fā)送GetCapabilities命令來(lái)得到設(shè)備端的能力,然后依據(jù)GetCapabilities返回的結(jié)果再來(lái)進(jìn)行下一步操作
在__tds__GetCapabilities函數(shù)中我們只需要填充Media部分和一些必要的即可
[cpp] view plain
copy ??tds__GetCapabilitiesResponse->Capabilities->Media?=?(struct?tt__MediaCapabilities*)soap_malloc(soap,?sizeof(struct?tt__MediaCapabilities));??tds__GetCapabilitiesResponse->Capabilities->Media->XAddr?=?(char?*)?soap_malloc(soap,?sizeof(char)?*?LARGE_INFO_LENGTH);??strcpy(tds__GetCapabilitiesResponse->Capabilities->Media->XAddr,?_IPv4Address);??tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities?=?(struct?tt__RealTimeStreamingCapabilities*)soap_malloc(soap,?sizeof(struct?tt__RealTimeStreamingCapabilities));??tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTPMulticast?=?(int?*)soap_malloc(soap,?sizeof(int));???*tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTPMulticast?=?_false;?????tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTP_USCORETCP?=?(int?*)soap_malloc(soap,?sizeof(int));??*tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTP_USCORETCP?=?_true;?????tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTP_USCORERTSP_USCORETCP?=?(int?*)soap_malloc(soap,?sizeof(int));??*tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTP_USCORERTSP_USCORETCP?=?_true;??????tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->Extension?=?NULL;??tds__GetCapabilitiesResponse->Capabilities->Media->Extension?=?NULL;??tds__GetCapabilitiesResponse->Capabilities->Media->__size?=?0;??tds__GetCapabilitiesResponse->Capabilities->Media->__any?=?0;??
另外必要填充的還有
[cpp] view plain
copy ??tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->VideoSources?=?TRUE;??tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->VideoOutputs?=?FALSE;??tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->AudioSources?=?FALSE;??tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->AudioOutputs?=?FALSE;??tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->RelayOutputs?=?FALSE;??tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->__size?=?0;??tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->__any?=?NULL;????tds__GetCapabilitiesResponse->Capabilities->Extension->Display?=?NULL;??tds__GetCapabilitiesResponse->Capabilities->Extension->Recording?=?NULL;??tds__GetCapabilitiesResponse->Capabilities->Extension->Search?=?NULL;??tds__GetCapabilitiesResponse->Capabilities->Extension->Replay?=?NULL;??tds__GetCapabilitiesResponse->Capabilities->Extension->Receiver?=?NULL;??tds__GetCapabilitiesResponse->Capabilities->Extension->AnalyticsDevice?=?NULL;??tds__GetCapabilitiesResponse->Capabilities->Extension->Extensions?=?NULL;??tds__GetCapabilitiesResponse->Capabilities->Extension->__size?=?0;??tds__GetCapabilitiesResponse->Capabilities->Extension->__any?=?NULL;??
2、實(shí)現(xiàn)GetServices命令
[cpp] view plain
copy int??__tds__GetServices(struct?soap*?soap,?struct?_tds__GetServices?*tds__GetServices,?struct?_tds__GetServicesResponse?*tds__GetServicesResponse)??{??????DBG("__tds__GetServices\n");????????????char?_IPAddr[INFO_LENGTH];??????int?i?=?0;??????sprintf(_IPAddr,?"http://%03d.%03d.%03d.%03d/onvif/services",?192,?168,?1,?233);??????tds__GetServicesResponse->__sizeService?=?1;????????tds__GetServicesResponse->Service?=?(struct?tds__Service?*)soap_malloc(soap,?sizeof(struct?tds__Service));??????tds__GetServicesResponse->Service[0].XAddr?=?(char?*)soap_malloc(soap,?sizeof(char)?*?INFO_LENGTH);??????tds__GetServicesResponse->Service[0].Namespace?=?(char?*)soap_malloc(soap,?sizeof(char)?*?INFO_LENGTH);??????strcpy(tds__GetServicesResponse->Service[0].Namespace,?"http://www.onvif.org/ver10/events/wsdl");??????strcpy(tds__GetServicesResponse[0].Service->XAddr,?_IPAddr);??????tds__GetServicesResponse->Service[0].Capabilities?=?NULL;??????tds__GetServicesResponse->Service[0].Version?=?(struct?tt__OnvifVersion?*)soap_malloc(soap,?sizeof(struct?tt__OnvifVersion));??????tds__GetServicesResponse->Service[0].Version->Major?=?0;??????tds__GetServicesResponse->Service[0].Version->Minor?=?3;??????tds__GetServicesResponse->Service[0].__any?=?(char?**)soap_malloc(soap,?sizeof(char?*));??????tds__GetServicesResponse->Service[0].__any[0]?=?(char?*)soap_malloc(soap,?sizeof(char)?*?INFO_LENGTH);??????strcpy(tds__GetServicesResponse->Service[0].__any[0],"why1");??????tds__GetServicesResponse->Service[0].__any[1]?=?(char?*)soap_malloc(soap,sizeof(char)?*?INFO_LENGTH);??????strcpy(tds__GetServicesResponse->Service[0].__any[1],"why2");??????tds__GetServicesResponse->Service[0].__size?=?NULL;??????tds__GetServicesResponse->Service[0].__anyAttribute?=?NULL;??????return?SOAP_OK;??}??
3、實(shí)現(xiàn)GetVideoSources命令
[html] view plain
copy int??__tmd__GetVideoSources(struct?soap*?soap,?struct?_trt__GetVideoSources?*trt__GetVideoSources,?struct?_trt__GetVideoSourcesResponse?*trt__GetVideoSourcesResponse)??{??????DBG("__tmd__GetVideoSources\n");????????int?size1;??????size1?=?1;??????trt__GetVideoSourcesResponse->__sizeVideoSources?=?size1;??????trt__GetVideoSourcesResponse->VideoSources?=?(struct?tt__VideoSource?*)soap_malloc(soap,?sizeof(struct?tt__VideoSource)?*?size1);??????trt__GetVideoSourcesResponse->VideoSources[0].Framerate?=?30;??????trt__GetVideoSourcesResponse->VideoSources[0].Resolution?=?(struct?tt__VideoResolution?*)soap_malloc(soap,?sizeof(struct?tt__VideoResolution));??????trt__GetVideoSourcesResponse->VideoSources[0].Resolution->Height?=?720;??????trt__GetVideoSourcesResponse->VideoSources[0].Resolution->Width?=?1280;??????trt__GetVideoSourcesResponse->VideoSources[0].token?=?(char?*)soap_malloc(soap,?sizeof(char)*INFO_LENGTH);??????strcpy(trt__GetVideoSourcesResponse->VideoSources[0].token,"GhostyuSource_token");?//注意這里需要和GetProfile中的sourcetoken相同????????trt__GetVideoSourcesResponse->VideoSources[0].Imaging?=(struct?tt__ImagingSettings*)soap_malloc(soap,?sizeof(struct?tt__ImagingSettings));??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Brightness?=?(float*)soap_malloc(soap,sizeof(float));??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Brightness[0]?=?128;??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->ColorSaturation?=?(float*)soap_malloc(soap,sizeof(float));??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->ColorSaturation[0]?=?128;??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Contrast?=?(float*)soap_malloc(soap,sizeof(float));??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Contrast[0]?=?128;??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->IrCutFilter?=?(int?*)soap_malloc(soap,sizeof(int));??????*trt__GetVideoSourcesResponse->VideoSources[0].Imaging->IrCutFilter?=?0;???????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Sharpness?=?(float*)soap_malloc(soap,sizeof(float));??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Sharpness[0]?=?128;??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->BacklightCompensation?=?(struct?tt__BacklightCompensation*)soap_malloc(soap,?sizeof(struct?tt__BacklightCompensation));??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->BacklightCompensation->Mode?=?0;??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->BacklightCompensation->Level?=?20;??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Exposure?=?NULL;??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Focus?=?NULL;??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WideDynamicRange?=?(struct?tt__WideDynamicRange*)soap_malloc(soap,?sizeof(struct?tt__WideDynamicRange));??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WideDynamicRange->Mode?=?0;?????????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WideDynamicRange->Level?=?20;??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WhiteBalance?=?(struct?tt__WhiteBalance*)soap_malloc(soap,?sizeof(struct?tt__WhiteBalance));??????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WhiteBalance->Mode?=?0;???????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WhiteBalance->CrGain?=?0;???????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WhiteBalance->CbGain?=?0;???????trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Extension?=?NULL;??????trt__GetVideoSourcesResponse->VideoSources[0].Extension?=?NULL;????????return?SOAP_OK;??}??
__tmd__GetVideoSources最重要的是token的填充,必須要和下面profile中的sourcetoken相同,需要匹配到這個(gè)視頻源
4、實(shí)現(xiàn)GetProfiles命令
[cpp] view plain
copy size?=?1;??trt__GetProfilesResponse->Profiles?=(struct?tt__Profile?*)soap_malloc(soap,?sizeof(struct?tt__Profile)?*?size);??trt__GetProfilesResponse->__sizeProfiles?=?size;????i=0;??trt__GetProfilesResponse->Profiles[i].Name?=?(char?*)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);??strcpy(trt__GetProfilesResponse->Profiles[i].Name,"my_profile");??trt__GetProfilesResponse->Profiles[i].token=?(char?*)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);??strcpy(trt__GetProfilesResponse->Profiles[i].token,"token_profile");??trt__GetProfilesResponse->Profiles[i].fixed?=?_false;??trt__GetProfilesResponse->Profiles[i].__anyAttribute?=?NULL;??
除了上面的基本信息,還需要填充兩大項(xiàng):VideoSourceConfiguration和VideoEncoderConfiguration,一個(gè)用于描述視頻源的信息,另外一個(gè)描述視頻的編碼信息
先給VideoSourceConfiguration分配空間
[cpp] view plain
copy trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration?=?(struct?tt__VideoSourceConfiguration?*)soap_malloc(soap,sizeof(struct?tt__VideoSourceConfiguration?));??trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Name?=?(char?*)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);??trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->token?=?(char?*)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);??trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->SourceToken?=?(char?*)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);??trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Bounds?=?(struct?tt__IntRectangle?*)soap_malloc(soap,sizeof(struct?tt__IntRectangle));??
然后在填充它
[cpp] view plain
copy ??strcpy(trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Name,"VS_Name");??strcpy(trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->token,"VS_Token");??strcpy(trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->SourceToken,"GhostyuSource_token");???trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->UseCount?=?1;??trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Bounds->x?=?1;??trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Bounds->y?=?1;??trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Bounds->height?=?720;??trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Bounds->width?=?1280;??
如果是指針必須先用soap_malloc分配內(nèi)存,然后才能賦值
下面是VideoEncoderConfiguration
[cpp] view plain
copy trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration?=?(struct?tt__VideoEncoderConfiguration?*)soap_malloc(soap,sizeof(struct?tt__VideoEncoderConfiguration));??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Name?=?(char?*)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->token=?(char?*)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);??strcpy(trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Name,"VE_Name1");??strcpy(trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->token,"VE_token1");??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->UseCount?=?1;??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Quality?=?10;??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Encoding?=?1;??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Resolution?=?(struct?tt__VideoResolution?*)soap_malloc(soap,?sizeof(struct?tt__VideoResolution));??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Resolution->Height?=?720;??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Resolution->Width?=?1280;??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->RateControl?=?(struct?tt__VideoRateControl?*)soap_malloc(soap,?sizeof(struct?tt__VideoRateControl));??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->RateControl->FrameRateLimit?=?30;??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->RateControl->EncodingInterval?=?1;??trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->RateControl->BitrateLimit?=?500;??
5、GetVideoSourceConfiguration和GetVideoEncoderConfiguration
[cpp] view plain
copy int??__trt__GetVideoSourceConfiguration(struct?soap*?soap,?struct?_trt__GetVideoSourceConfiguration?*trt__GetVideoSourceConfiguration,?struct?_trt__GetVideoSourceConfigurationResponse?*trt__GetVideoSourceConfigurationResponse)??{??????DBG("__trt__GetVideoSourceConfiguration\n");????????????return?SOAP_OK;??}????int??__trt__GetVideoEncoderConfiguration(struct?soap*?soap,?struct?_trt__GetVideoEncoderConfiguration?*trt__GetVideoEncoderConfiguration,?struct?_trt__GetVideoEncoderConfigurationResponse?*trt__GetVideoEncoderConfigurationResponse)??{??????DBG("__trt__GetVideoEncoderConfiguration\n");??????return?SOAP_OK;??}??
6、GetVideoEncoderConfigurationOptions
[cpp] view plain
copy int??__trt__GetVideoEncoderConfigurationOptions(struct?soap*?soap,?struct?_trt__GetVideoEncoderConfigurationOptions?*trt__GetVideoEncoderConfigurationOptions,?struct?_trt__GetVideoEncoderConfigurationOptionsResponse?*trt__GetVideoEncoderConfigurationOptionsResponse)??{??????DBG("__trt__GetVideoEncoderConfigurationOptions\n");????????????return?SOAP_OK;??}??
以上5、6不分的代碼直接返回SOAP_OK即可,正常來(lái)說(shuō)是應(yīng)該填充的,這里不影響RTSP Video Stream,暫時(shí)就不去動(dòng)它
四、運(yùn)行l(wèi)ive555MediaServer服務(wù)器
live555官網(wǎng)有很多測(cè)試文件,我這里用的是MPEG4的測(cè)試文件路勁為rtsp://192.168.1.201/petrov.m4e
五、啟動(dòng)Onvif Device Manager測(cè)試
有一個(gè)問(wèn)題,OnvifDeviceManager的并不能自動(dòng)發(fā)現(xiàn)設(shè)備(OnvifTestTool可以),還好它提供了手動(dòng)添加功能
單擊add,添加如下內(nèi)容:http://192.168.1.233/onvif/device_service
注意,我在程序中固定了兩個(gè)IP:linux192.168.1.233,windows:192.168.1.201,這里需看情況修改
測(cè)試截圖:
1、Live video
2、Video streaming
3、Profiles
最后是運(yùn)行的live555 rtsp服務(wù)器
終端打印的DEBUG信息
源代碼下載地址:http://download.csdn.net/detail/ghostyu/4796093
【來(lái)自http://blog.csdn.net/ghostyu,ghostyu#163.com】
總結(jié)
以上是生活随笔為你收集整理的onvif规范的实现:成功实现ONVIF协议RTSP-Video-Stream与OnvifDeviceManager的视频对接的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。