ONVIF系列:
ONVIF系列一:ONVIF介绍
ONVIF系列二:Ubuntu安装gSOAP、生成ONVIF代码框架
ONVIF系列三:ONVIF客户端实现
在系列二中完成了在Ubuntu上安装gSOAP并生成ONVIF代码框架,接下来我们利用生成的框架实现ONVIF客户端。
一、设备发现(WS-Discovery)
一般C/S模型中,客户端事先知道Web Service的地址,通过这个地址(HTTP URL)向Web Service发情请求,但是在ONVIF中,客户端不知道IPC摄像头的ONVIF服务地址,要怎么向服务端发起请求呢,这就需要WS-Discovery设备发现协议。
WS-Discovery的全称为Web Services Dynamic Discovery,这个技术规范定义了一套多播发现协议来定位服务,它工作在TCP和UDP的DISCOVERY_PORT为3702,其使用IP组播地址为239.255.255.250(IPV4)或FF02::C(IPV6)。客户端预先不知道目标服务地址的情况下,可以动态地探测到可用的目标服务,以便进行服务调用,这个过程就是「设备发现」的过程。关于设备发现可参考这篇文章:ONVIF协议--ONVIF WS-Discovery (设备发现协议)-CSDN博客。
支持ONVIF协议的设备启动之后会加入多播组239.255.255.250端口3702,ONVIF客户端通过UDP向239.255.255.250:3702多播地址发送探测消息(Probe),ONVIF设备收到消息会返回响应(Probe Match),响应消息中包含设备的ONVIF服务地址,注意:Probe是多播过程,ONVIF客户端发,多个ONVIF设备收;Probe Match是UDP单播,ONVIF设备向ONVIF客户端发送。
Probe请求SOAP消息示例:
<Envelope xmlns:dn="http://www.onvif.org/ver10/network/wsdl" xmlns="http://www.w3.org/2003/05/soap-envelope">
<Header>
<wsa:MessageID xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
uuid:25862dc4-4866-42a9-87a7-edcf2918ab10
</wsa:MessageID>
<wsa:To xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
urn:schemas-xmlsoap-org:ws:2005:04:discovery
</wsa:To>
<wsa:Action xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe
</wsa:Action>
</Header>
<Body>
<Probe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/ws/2005/04/discovery">
<Types>dn:NetworkVideoTransmitter</Types>
<Scopes />
</Probe>
</Body>
</Envelope>
Probe Match响应SOAP消息示例:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xs="http://www.w3.org/2000/10/XMLSchema"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsa5="http://www.w3.org/2005/08/addressing"
xmlns:xop="http://www.w3.org/2004/08/xop/include"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:tt="http://www.onvif.org/ver10/schema"
xmlns:ns1="http://www.w3.org/2005/05/xmlmime"
xmlns:wstop="http://docs.oasis-open.org/wsn/t-1"
xmlns:ns7="http://docs.oasis-open.org/wsrf/r-2"
xmlns:ns2="http://docs.oasis-open.org/wsrf/bf-2"
xmlns:dndl="http://www.onvif.org/ver10/network/wsdl/DiscoveryLookupBinding"
xmlns:dnrd="http://www.onvif.org/ver10/network/wsdl/RemoteDiscoveryBinding"
xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"
xmlns:dn="http://www.onvif.org/ver10/network/wsdl"
xmlns:ns10="http://www.onvif.org/ver10/replay/wsdl"
xmlns:ns11="http://www.onvif.org/ver10/search/wsdl"
xmlns:ns13="http://www.onvif.org/ver20/analytics/wsdl/RuleEngineBinding"
xmlns:ns14="http://www.onvif.org/ver20/analytics/wsdl/AnalyticsEngineBinding"
xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl"
xmlns:ns15="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding"
xmlns:ns16="http://www.onvif.org/ver10/events/wsdl/EventBinding"
xmlns:tev="http://www.onvif.org/ver10/events/wsdl"
xmlns:ns17="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding"
xmlns:ns18="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding"
xmlns:ns19="http://www.onvif.org/ver10/events/wsdl/NotificationConsumerBinding"
xmlns:ns20="http://www.onvif.org/ver10/events/wsdl/PullPointBinding"
xmlns:ns21="http://www.onvif.org/ver10/events/wsdl/CreatePullPointBinding"
xmlns:ns22="http://www.onvif.org/ver10/events/wsdl/PausableSubscriptionManagerBinding"
xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2"
xmlns:ns3="http://www.onvif.org/ver10/analyticsdevice/wsdl"
xmlns:ns4="http://www.onvif.org/ver10/deviceIO/wsdl"
xmlns:ns5="http://www.onvif.org/ver10/display/wsdl"
xmlns:ns8="http://www.onvif.org/ver10/receiver/wsdl"
xmlns:ns9="http://www.onvif.org/ver10/recording/wsdl"
xmlns:tds="http://www.onvif.org/ver10/device/wsdl"
xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl"
xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl"
xmlns:trt="http://www.onvif.org/ver10/media/wsdl"
xmlns:trt2="http://www.onvif.org/ver20/media/wsdl"
xmlns:ter="http://www.onvif.org/ver10/error"
xmlns:tns1="http://www.onvif.org/ver10/topics"
xmlns:tnsn="http://www.eventextension.com/2011/event/topics">
<SOAP-ENV:Header>
<wsa:MessageID>urn:uuid:327b23c6-5566-7788-99ac-001217d7bf0f</wsa:MessageID>
<wsa:RelatesTo>uuid:25862dc4-4866-42a9-87a7-edcf2918ab10</wsa:RelatesTo>
<wsa:To SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>
<wsa:Action SOAP-ENV:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsa:Action>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<d:ProbeMatches>
<d:ProbeMatch>
<wsa:EndpointReference>
<wsa:Address>urn:uuid:b12dca78-5566-7788-99aa-001217d7bf0f</wsa:Address>
</wsa:EndpointReference>
<d:Types>dn:NetworkVideoTransmitter</d:Types>
<d:Scopes>
onvif://www.onvif.org/type/video_encoder
onvif://www.onvif.org/type/audio_encoder
onvif://www.onvif.org/hardware/IPC-model
onvif://www.onvif.org/location/country/china
onvif://www.onvif.org/name/NVT
onvif://www.onvif.org/Profile/Streaming
</d:Scopes>
<d:XAddrs>http://192.168.100.123:8099/onvif/device_service</d:XAddrs>
<d:MetadataVersion>10</d:MetadataVersion>
</d:ProbeMatch>
</d:ProbeMatches>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
ONVIF设备服务地址就是http://192.168.100.123:8099/onvif/device_service,每个消息都会有一个MessageID,且每个消息都不一样;RelatesTo表示当前消息是对哪一个MessageID的消息的响应。拿到ONVIF设备服务地址我们就可以向设备发起ONVIF请求了,上面的XML并不需要我们自己封装,gSOAP生成的代码框架会帮我们完成,我们只需要调用API即可。
二:ONVIF客户端开发
基于系列二,我们在/work/onvif下创建一个client目录用来保存onvif客户端代码
cd /work/onvif
mkdir client
cd client
把gSOAP生成的客户端框架拷贝到client下,之前在安装的时候加了鉴权,在wsse.h中指出了鉴权依赖的文件,这里也需要拷贝过来,除此之外,还要拷贝其他一些文件过来。
# /work/onvif/onvif-code为onvif框架生成路径
cp /work/onvif/onvif-code/soap/soapC.c .
cp /work/onvif/onvif-code/soap/soapH.h .
cp /work/onvif/onvif-code/soap/soapStub.h .
cp /work/onvif/onvif-code/soap/soapClient.c .
cp /work/onvif/onvif-code/soap/wsdd.nsmap .
cp /work/onvif/onvif-code/gsoap/plugin/wsseapi.h .
cp /work/onvif/onvif-code/gsoap/plugin/wsseapi.c .
cp /work/onvif/onvif-code/gsoap/plugin/mecevp.h .
cp /work/onvif/onvif-code/gsoap/plugin/mecevp.c .
cp /work/onvif/onvif-code/gsoap/plugin/smdevp.h .
cp /work/onvif/onvif-code/gsoap/plugin/smdevp.c .
cp /work/onvif/onvif-code/gsoap/custom/struct_timeval.h .
cp /work/onvif/onvif-code/gsoap/custom/struct_timeval.c .
cp /work/onvif/onvif-code/gsoap/plugin/wsaapi.h .
cp /work/onvif/onvif-code/gsoap/plugin/wsaapi.c .
cp /work/onvif/onvif-code/gsoap/plugin/threads.h .
cp /work/onvif/onvif-code/gsoap/plugin/threads.c .
cp /work/onvif/onvif-code/gsoap/import/dom.h .
cp /work/onvif/onvif-code/gsoap/dom.c .
# /work/onvif/gsoap-2.8为gsoap源码路径
cp /work/onvif/gsoap-2.8/gsoap/stdsoap2.c .
cp /work/onvif/gsoap-2.8/gsoap/stdsoap2.h .
在client中添加main.c和CMakeLists.txt
CMakeLists.txt内容如下:
cmake_minimum_required(VERSION 3.5)
project(ONVIF-DEMO)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DWITH_NONAMESPACES -DWITH_DOM -DWITH_OPENSSL")
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(/usr/include)
add_definitions("-Wall -g ")
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
target_link_libraries(${PROJECT_NAME} -lcrypto -lssl)
在main.c中实现ONVIF客户端。
下面介绍gSOAP生成的代码框架中的API,这里主要讲解客户端的API。
使用gsoap框架必须要初始化一个上下文struct soap,gSOAP API都需要传递一个struct soap参数并且是第一个参数,每个线程都需要创建一个struct soap,不支持多线程。
创建销毁一个struct soap
struct soap *soap = soap_new();
soap_free(soap);
创建完struct soap,第一步要调用soap_set_namespaces设置xml中的命名空间:
soap_set_namespaces(soap, namespaces); //设置命名空间,就是xml文件的头
namespaces定义在wsdd.nsmap文件中,要在main.c中包含该文件:#include "wsdd.nsmap"
struct soap也可以在栈上创建:
struct soap soap1;
soap_init(soap1);
在开发中我们可以使用malloc分配内存,还可以调用soap_malloc分配内存。
void* soap_malloc(struct soap *soap, size_t n)
此时分配的内存被struct soap管理,n为分配的内存大小,返回值是分配的内存首地址,这个地址我们直接用即可,不用使用free释放,在任务结束的时候调用soap_end即可释放这块内存。函数原型:
void soap_end(struct soap *soap)
上下文资源释放,一般涉及到4个API
soap_destroy(struct soap *soap); // 删除反序列化的类实例(仅限C++)
soap_end(struct soap *soap); // 清理反序列化的数据(类实例除外)和临时数据
soap_done(struct soap *soap); // 重置、关闭通信并删除回调
soap_free(struct soap *soap); // 重置和取消分配使用soap_new或soap_copy创建的上下文
客户端API:
如上图所示,是客户端侧的API,soap_send_SOAP_ENV__Fault是用来发送错误消息,soap_recv_SOAP_ENV__Fault是用来接收错误消息的,回顾一下SOAP组成部分: Envelope、Header、Body、Fault。这里对Fault不做过多介绍,可自行了解。
gSOAP API命名规则soap_call/send/recv__命名空间__服务名(RPC函数名),call是同步操作,send和recv是异步操作。例如:soap_send___wsdd__Probe表示发送一个探测消息发现设备,soap_recv___wsdd__Probe表示接收探测消息,soap_recv___wsdd__ProbeMatches表示接收Probe Match消息。
关于ONVIF各个服务的名称、参数、返回结果在WSDL中查看,地址:https://www.onvif.org/profiles/specifications/。
ONVIF客户端开发按流程:
1、设备发现获取ONVIF设备服务地址
2、获取设备能力GetCapabilities
服务返回设备对各个功能的支持情况,及各个功能的服务地址。我们比较关心的是Media,GetCapabilitiesResponse返回的Media信息如下图所示:
这里会返回Media服务的地址,这个地址后面会用来获取摄像头的RTSP地址。media.wsdl描述了Media服务。
3、获取媒体配置信息,记录token。通过Media服务地址,调用Media服务里面的GetProfiles服务,WSDL对GetProfiles的描述如下图所示:
Profiles.token需要保存下来,后面访问Media都要用到。
4、通过Media服务地址,调用Media服务中的GetStreamUri服务,获取RTSP地址,需要步骤三中的token,WSDL对GetStreamUri的描述如下图所示:
Output中的MediaUrl.Url就是RTSP地址,至此ONVIF客户端基本功能就开发完了,拿到RTSP地址就可以拉取视频流了,就可以为所欲为了。
main.c代码如下:
#include "soapH.h"
#include "soapStub.h"
#include "wsaapi.h"
#include "wsdd.nsmap"
#include "wsseapi.h"
#include <assert.h>
#define SOAP_ASSERT assert
#define SOAP_DBGLOG printf
#define SOAP_DBGERR printf
#define SOAP_TO "urn:schemas-xmlsoap-org:ws:2005:04:discovery"
#define SOAP_ACTION "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe"
#define SOAP_MCAST_ADDR "soap.udp://239.255.255.250:3702" // onvif规定的组播地址
#define SOAP_ITEM "" // 寻找的设备范围
#define SOAP_TYPES "dn:NetworkVideoTransmitter" // 寻找的设备类型
#define SOAP_SOCK_TIMEOUT (10) // socket超时时间(单秒秒)
#define nullptr NULL
#define bool int
#define true 1
#define false 0
void soap_perror(struct soap *soap, const char *str)
{
if (nullptr == str) {
SOAP_DBGERR("[soap] error: %d, %s, %s\n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
} else {
SOAP_DBGERR("[soap] %s error: %d, %s, %s\n", str, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
}
}
void *ONVIF_soap_malloc(struct soap *soap, unsigned int n)
{
void *p = nullptr;
if (n > 0) {
p = soap_malloc(soap, n);
SOAP_ASSERT(nullptr != p);
memset(p, 0x00, n);
}
return p;
}
struct soap *ONVIF_soap_new(int timeout)
{
struct soap *soap = nullptr; // soap环境变量
SOAP_ASSERT(nullptr != (soap = soap_new()));
soap_set_namespaces(soap, namespaces); // 设置soap的namespaces
soap->recv_timeout = timeout; // 设置超时(超过指定时间没有数据就退出)
soap->send_timeout = timeout;
soap->connect_timeout = timeout;
#if defined(__linux__) || defined(__linux) // 参考https://www.genivia.com/dev.html#client-c的修改:
soap->socket_flags = MSG_NOSIGNAL; // To prevent connection reset errors
#endif
soap_set_mode(soap, SOAP_C_UTFSTRING); // 设置为UTF-8编码,否则叠加中文OSD会乱码
return soap;
}
void ONVIF_soap_delete(struct soap *soap)
{
soap_destroy(soap); // remove deserialized class instances (C++ only)
soap_end(soap); // Clean up deserialized data (except class instances) and temporary data
soap_done(soap); // Reset, close communications, and remove callbacks
soap_free(soap); // Reset and deallocate the context created with soap_new or soap_copy
}
/************************************************************************
**函数:ONVIF_init_header
**功能:初始化soap描述消息头
**参数:
[in] soap - soap环境变量
**返回:无
**备注:
1). 在本函数内部通过ONVIF_soap_malloc分配的内存,将在ONVIF_soap_delete中被释放
************************************************************************/
void ONVIF_init_header(struct soap *soap)
{
struct SOAP_ENV__Header *header = nullptr;
SOAP_ASSERT(nullptr != soap);
header = (struct SOAP_ENV__Header *)ONVIF_soap_malloc(soap, sizeof(struct SOAP_ENV__Header));
soap_default_SOAP_ENV__Header(soap, header);
header->wsa__MessageID = (char *)soap_wsa_rand_uuid(soap);
header->wsa__To = (char *)ONVIF_soap_malloc(soap, strlen(SOAP_TO) + 1);
header->wsa__Action = (char *)ONVIF_soap_malloc(soap, strlen(SOAP_ACTION) + 1);
strcpy(header->wsa__To, SOAP_TO);
strcpy(header->wsa__Action, SOAP_ACTION);
soap->header = header;
}
/************************************************************************
**函数:ONVIF_init_ProbeType
**功能:初始化探测设备的范围和类型
**参数:
[in] soap - soap环境变量
[out] probe - 填充要探测的设备范围和类型
**返回:
0表明探测到,非0表明未探测到
**备注:
1). 在本函数内部通过ONVIF_soap_malloc分配的内存,将在ONVIF_soap_delete中被释放
************************************************************************/
void ONVIF_init_ProbeType(struct soap *soap, struct wsdd__ProbeType *probe)
{
struct wsdd__ScopesType *scope = nullptr; // 用于描述查找哪类的Web服务
SOAP_ASSERT(nullptr != soap);
SOAP_ASSERT(nullptr != probe);
scope = (struct wsdd__ScopesType *)ONVIF_soap_malloc(soap, sizeof(struct wsdd__ScopesType));
soap_default_wsdd__ScopesType(soap, scope); // 设置寻找设备的范围
scope->__item = (char *)ONVIF_soap_malloc(soap, strlen(SOAP_ITEM) + 1);
strcpy(scope->__item, SOAP_ITEM);
memset(probe, 0x00, sizeof(struct wsdd__ProbeType));
soap_default_wsdd__ProbeType(soap, probe);
probe->Scopes = scope;
probe->Types = (char *)ONVIF_soap_malloc(soap, strlen(SOAP_TYPES) + 1); // 设置寻找设备的类型
strcpy(probe->Types, SOAP_TYPES);
}
void ONVIF_DetectDevice(void (*cb)(char *DeviceXAddr))
{
int i;
int result = 0;
unsigned int count = 0; // 搜索到的设备个数
struct soap *soap = nullptr; // soap环境变量
struct wsdd__ProbeType req; // 用于发送Probe消息
struct __wsdd__ProbeMatches rep; // 用于接收Probe应答
struct wsdd__ProbeMatchType *probeMatch;
SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
ONVIF_init_header(soap); // 设置消息头描述
ONVIF_init_ProbeType(soap, &req); // 设置寻找的设备的范围和类型
result = soap_send___wsdd__Probe(soap, SOAP_MCAST_ADDR, nullptr, &req); // 向组播地址广播Probe消息
while (SOAP_OK == result) // 开始循环接收设备发送过来的消息
{
memset(&rep, 0x00, sizeof(rep));
result = soap_recv___wsdd__ProbeMatches(soap, &rep);
if (SOAP_OK == result) {
if (soap->error) {
soap_perror(soap, "ProbeMatches");
} else { // 成功接收到设备的应答消息
// printf("__sizeProbeMatch:%d\n",rep.wsdd__ProbeMatches->__sizeProbeMatch);
if (nullptr != rep.wsdd__ProbeMatches) {
count += rep.wsdd__ProbeMatches->__sizeProbeMatch;
for (i = 0; i < rep.wsdd__ProbeMatches->__sizeProbeMatch; i++) {
probeMatch = rep.wsdd__ProbeMatches->ProbeMatch + i;
if (nullptr != cb) {
char *url = "http://192.168.0.116/onvif/device_service";
if (memcpy(probeMatch->XAddrs, url, strlen(url)) == 0) {
cb(probeMatch->XAddrs); // 使用设备服务地址执行函数回调
}
}
}
}
}
} else if (soap->error) {
break;
}
}
SOAP_DBGLOG("\ndetect end! It has detected %d devices!\n", count);
if (nullptr != soap) {
ONVIF_soap_delete(soap);
}
}
#define SOAP_CHECK_ERROR(result, soap, str) \
do { \
if (SOAP_OK != (result) || SOAP_OK != (soap)->error) { \
soap_perror((soap), (str)); \
if (SOAP_OK == (result)) { \
(result) = (soap)->error; \
} \
goto EXIT; \
} \
} while (0)
/************************************************************************
**函数:ONVIF_SetAuthInfo
**功能:设置认证信息
**参数:
[in] soap - soap环境变量
[in] username - 用户名
[in] password - 密码
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
static int ONVIF_SetAuthInfo(struct soap *soap, const char *username, const char *password)
{
int result = 0;
SOAP_ASSERT(nullptr != username);
SOAP_ASSERT(nullptr != password);
result = soap_wsse_add_UsernameTokenDigest(soap, NULL, username, password);
SOAP_CHECK_ERROR(result, soap, "add_UsernameTokenDigest");
EXIT:
return result;
}
#define USERNAME "admin"
#define PASSWORD "hik12345"
/************************************************************************
**函数:ONVIF_GetDeviceInformation
**功能:获取设备基本信息
**参数:
[in] DeviceXAddr - 设备服务地址
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_GetDeviceInformation(const char *DeviceXAddr)
{
int result = 0;
struct soap *soap = nullptr;
struct _tds__GetDeviceInformation devinfo_req;
struct _tds__GetDeviceInformationResponse devinfo_resp;
SOAP_ASSERT(nullptr != DeviceXAddr);
SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
result = soap_call___tds__GetDeviceInformation(soap, DeviceXAddr, nullptr, &devinfo_req, &devinfo_resp);
SOAP_CHECK_ERROR(result, soap, "GetDeviceInformation");
// std::cout << " Manufacturer:\t" << devinfo_resp.Manufacturer << "\n";
// std::cout << " Model:\t" << devinfo_resp.Model << "\n";
// std::cout << " FirmwareVersion:\t" << devinfo_resp.FirmwareVersion << "\n";
// std::cout << " SerialNumber:\t" << devinfo_resp.SerialNumber << "\n";
// std::cout << " HardwareId:\t" << devinfo_resp.HardwareId << "\n";
EXIT:
if (nullptr != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
/************************************************************************
**函数:ONVIF_GetSnapshotUri
**功能:获取设备图像抓拍地址(HTTP)
**参数:
[in] MediaXAddr - 媒体服务地址
[in] ProfileToken - the media profile token
[out] uri - 返回的地址
[in] sizeuri - 地址缓存大小
**返回:
0表明成功,非0表明失败
**备注:
1). 并非所有的ProfileToken都支持图像抓拍地址。举例:XXX品牌的IPC有如下三个配置profile0/profile1/TestMediaProfile,其中TestMediaProfile返回的图像抓拍地址就是空指针。
************************************************************************/
int ONVIF_GetSnapshotUri(const char *MediaXAddr, const char *ProfileToken, char **snapUri)
{
int result = 0;
struct soap *soap = nullptr;
struct _trt__GetSnapshotUri req;
struct _trt__GetSnapshotUriResponse rep;
SOAP_ASSERT(MediaXAddr != NULL && ProfileToken != NULL);
SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
req.ProfileToken = (char *)ProfileToken;
result = soap_call___trt__GetSnapshotUri(soap, MediaXAddr, NULL, &req, &rep);
SOAP_CHECK_ERROR(result, soap, "GetSnapshotUri");
if (nullptr != rep.MediaUri && nullptr != rep.MediaUri->Uri) {
*snapUri = rep.MediaUri->Uri;
}
EXIT:
if (NULL != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
bool ONVIF_GetProfiles(const char *mediaXAddr, char **profilesToken)
{
int result = 0;
struct soap *soap = nullptr;
struct _trt__GetProfiles devinfo_req;
struct _trt__GetProfilesResponse devinfo_resp;
SOAP_ASSERT(mediaXAddr != NULL);
SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
result = soap_call___trt__GetProfiles(soap, mediaXAddr, nullptr, &devinfo_req, &devinfo_resp);
SOAP_CHECK_ERROR(result, soap, "ONVIF_GetProfiles");
SOAP_ASSERT(devinfo_resp.__sizeProfiles > 0);
*profilesToken = devinfo_resp.Profiles->token;
EXIT:
if (nullptr != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
/************************************************************************
**函数:ONVIF_GetCapabilities
**功能:获取设备能力信息
**参数:
[in] DeviceXAddr - 设备服务地址
[in]
**返回:
0表明成功,非0表明失败
**备注:
1). 其中最主要的参数之一是媒体服务地址
************************************************************************/
int ONVIF_GetCapabilities(const char *deviceXAddr, char **mediaXAddr)
{
int result = 0;
struct soap *soap = nullptr;
struct _tds__GetCapabilities devinfo_req;
struct _tds__GetCapabilitiesResponse devinfo_resp;
SOAP_ASSERT(deviceXAddr != NULL);
SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
result = soap_call___tds__GetCapabilities(soap, deviceXAddr, NULL, &devinfo_req, &devinfo_resp);
SOAP_CHECK_ERROR(result, soap, "GetCapabilities");
if (devinfo_resp.Capabilities->Media != nullptr) {
*mediaXAddr = devinfo_resp.Capabilities->Media->XAddr;
}
EXIT:
if (nullptr != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
/************************************************************************
**函数:make_uri_withauth
**功能:构造带有认证信息的URI地址
**参数:
[in] src_uri - 未带认证信息的URI地址
[in] username - 用户名
[in] password - 密码
[out] dest_uri - 返回的带认证信息的URI地址
[in] size_dest_uri - dest_uri缓存大小
**返回:
0成功,非0失败
**备注:
1). 例子:
无认证信息的uri:rtsp://100.100.100.140:554/av0_0
带认证信息的uri:rtsp://username:password@100.100.100.140:554/av0_0
************************************************************************/
static int make_uri_withauth(const char *src_uri, const char *username, char *password, char **dest_uri)
{
int result = 0;
SOAP_ASSERT(src_uri != NULL);
if (username == NULL && password == NULL) { // 生成新的uri地址
*dest_uri = src_uri;
} else {
/*
std::string::size_type position = src_uri.find("//");
if (std::string::npos == position) {
SOAP_DBGERR("can't found '//', src uri is: %s.\n", src_uri.c_str());
result = -1;
return result;
}
position += 2;
dest_uri->append(src_uri,0, position) ;
dest_uri->append(username + ":" + password + "@");
dest_uri->append(src_uri,position, std::string::npos) ;
*/
}
return result;
}
/************************************************************************
**函数:ONVIF_GetStreamUri
**功能:获取设备码流地址(RTSP)
**参数:
[in] MediaXAddr - 媒体服务地址
[in] ProfileToken - the media profile token
**返回:
0表明成功,非0表明失败
**备注:
************************************************************************/
int ONVIF_GetStreamUri(const char *MediaXAddr, const char *ProfileToken)
{
int result = 0;
struct soap *soap = nullptr;
struct tt__StreamSetup ttStreamSetup;
struct tt__Transport ttTransport;
struct _trt__GetStreamUri req;
struct _trt__GetStreamUriResponse rep;
SOAP_ASSERT(MediaXAddr != NULL);
SOAP_ASSERT(nullptr != (soap = ONVIF_soap_new(SOAP_SOCK_TIMEOUT)));
ttStreamSetup.Stream = tt__StreamType__RTP_Unicast;
ttStreamSetup.Transport = &ttTransport;
ttStreamSetup.Transport->Protocol = tt__TransportProtocol__RTSP;
ttStreamSetup.Transport->Tunnel = nullptr;
req.StreamSetup = &ttStreamSetup;
req.ProfileToken = (char *)(ProfileToken);
ONVIF_SetAuthInfo(soap, USERNAME, PASSWORD);
result = soap_call___trt__GetStreamUri(soap, MediaXAddr, nullptr, &req, &rep);
SOAP_CHECK_ERROR(result, soap, "GetServices");
if (nullptr != rep.MediaUri) {
if (nullptr != rep.MediaUri->Uri) {
// std::cout << rep.MediaUri->Uri << "\n";
printf("Url:%s\n", rep.MediaUri->Uri);
}
}
EXIT:
if (nullptr != soap) {
ONVIF_soap_delete(soap);
}
return result;
}
void cb_discovery(char *deviceXAddr)
{
char *mediaXAddr, profilesToken, snapUri, snapAuthUri;
ONVIF_GetCapabilities(deviceXAddr, &mediaXAddr);
ONVIF_GetProfiles(mediaXAddr, &profilesToken);
ONVIF_GetStreamUri(mediaXAddr, profilesToken);
/* ONVIF_GetSnapshotUri(mediaXAddr, profilesToken, &snapUri);
make_uri_withauth(snapUri, USERNAME, PASSWORD, &snapAuthUri);
char cmd[256];
sprintf(cmd, "wget -O %s '%s'", "out.jpeg", snapAuthUri.c_str()); // 使用wget下载图片
system(cmd);*/
}
int main(int argc, char **argv)
{
ONVIF_DetectDevice(cb_discovery);
return 0;
}