认证
SOAP报文认证
Onvif支持在SOAP报文里进行认证,即在XML结构里,加入用户名密码。
在gsoap的实现里,使用wsse插件中的 soap_wsse_add_UsernameTokenDigest
即可。
这个函数的原型为:
SOAP_FMAC1 int SOAP_FMAC2 soap_wsse_add_UsernameTokenDigest(struct soap *soap, const char *id, const char *username, const char *password);
使用的时候,在需要认证的url执行方法之前,调用这个函数加入用户名密码即可实现SOAP报文的认证。
HTTP认证
因为Onvif的Server就是Web Server,所以还有的Onvif服务,光在SOAP报文里认证不够,需要HTTP层的认证。
HTTP层的认证方式包括:要么Basic,要么Digest。
在gsoap的插件里,相应的实现是http_da。
大概分成几步:
- soap客户端请求。
- 服务端返回错误码401,以及鉴权码。
- soap客户端根据鉴权吗,加入认证信息,再次请求。
- 服务端认证通过。
示例如下:
C
// 下面的func、req、response为实际请求的方法、请求结构体指针以及响应结构体指针
int
soap_auth (struct gsoap *soap, const char *uri, const char *username,
const char *password)
{
int res;
struct http_da_info hdinfo[1];
// 1. 第一次提交请求,如果不出错,表示不需要认证,直接返回
res = func (soap, uri, NULL, req, response);
if (res == 0)
{
return 0;
}
// 2. 如果返回状态码不是401,表示不是认证的问题,直接返回-1
if (soap->status != 401)
{
fprintf (stderr, "soap need auth: %d, %d, %s\n", soap->error, *soap_faultcode (soap), *soap_faultstring (soap));
return -1;
}
// 3. 设置认证信息,包括服务端反正的authrealm,以及用户名、密码
http_da_save (soap, hdinfo, soap->authrealm, username, password);
// 再次提交请求
res = func (soap, uri, NULL, req, response);
http_da_release (soap, hdinfo);
// 4. 如果返回0,表示认证成功
if (res == 0)
{
return 0;
}
// 认证失败
fprintf (stderr, "soap auth error: %d, %s, %s\n", soap->error, *soap_faultcode (soap), *soap_faultstring (soap));
return -1;
}
需要注意的是,有的摄像机的HTTP服务端,实现的认证过程会多一次。
即,第一次返回401以后,客户端加入realm、用户名、密码,再次提交,再返回401,这时候客户端再次使用返回的realm,以及自己的用户名、密码,进行提交,才会认证通过。
Onvif摄像机点播
Onvif 的摄像机点播的时候,可以分成这样几步:
- 取得摄像机的Capabilities
- 取得摄像机的Profiles
- 取得相应Profile的StreamUri
取得Capabilites
取得Capabilities的函数为soap_call___tds__GetCapabilities
,输入参数分别为struct _tds__GetCapabilities *
和struct _tds__GetCapabilitiesResponse *
。
struct _tds__GetCapabilitiesResponse
的结构里有一个Capabilities结构,这个结构的Media就表示媒体信息。
C
gchar *
soap_getcapabilities (struct gsoap* soap, char M_XAddr[], size_t M_XSize)
{
struct _tds__GetCapabilities req[1];
struct _tds__GetCapabilitiesResponse response[1];
struct soap *soap;
int res;
req->__sizeCategory = 2;
req->Category = (enum tt__CapabilityCategory *)soap_malloc (
soap, 2 * sizeof (enum tt__CapabilityCategory));
*req->Category = tt__CapabilityCategory__Media;
*(req->Category + 1) = tt__CapabilityCategory__Events;
response->Capabilities = (struct tt__Capabilities *)soap_malloc (
soap, sizeof (struct tt__Capabilities));
xaddr = NULL;
res = soap_call___tds__GetCapabilities(soap, uri, req, response);
if (res == 0)
{
if (response->Capabilities != NULL && response->Capabilities->Media)
{
snprintf (M_XAddr, M_XSize, "%s",
response->Capabilities->Media->XAddr);
return 0;
}
}
return -1;
}
取得Profiles
取得Capabilites里面的地址之后,就可以根据地址,取得Profile。
取得Profile的函数为soap_call___trt__GetProfiles
。
C
int
soap_getprofiles (struct gsoap *soap, const char *media_uri)
{
struct _trt__GetProfiles req[1];
struct _trt__GetProfilesResponse response[1];
struct tt__Profile *profile;
int res;
soap_call___trt__GetProfiles(media_uri, req, response);
if (res == 0)
{
if (response->Profiles != NULL)
{
for (i = 0; i < response->__sizeProfiles; ++i)
{
profile = response->Profiles + i;
if (profile->VideoSourceConfiguration == NULL
|| profile->VideoSourceConfiguration->SourceToken == NULL)
continue;
fprintf (stdout, "Profile token: %s\n", profile->token);
if (profile->VideoSourceConfiguration->Bounds)
{
}
if (profile->VideoEncoderConfiguration)
{
if (op->ve_Encoding == tt__VideoEncoding__H264)
{
}
else if (op->ve_Encoding == tt__VideoEncoding__H265)
{
}
if (profile->VideoEncoderConfiguration->Resolution)
{
}
// profile->VideoEncoderConfiguration->Quality;
// profile->VideoEncoderConfiguration->RateControl;
}
if (profile->AudioEncoderConfiguration)
{
// profile->AudioEncoderConfiguration->Name);
// profile->AudioEncoderConfiguration->Encoding;
// profile->AudioEncoderConfiguration->Bitrate;
// profile->AudioEncoderConfiguration->SampleRate;
}
}
}
}
return 0;
}
取得StreamUri
我们遍历了Profile之后,就可以根据我们的意愿,选择取得哪个Profile的媒体地址,使用的函数是soap_call___trt__GetStreamUri
。
C
const gchar *
soap_getstreamuri (struct gsoap *soap, const char *media_uri, const char *profile, gchar *out, size_t outs
ize)
{
struct _trt__GetStreamUri req[1];
struct _trt__GetStreamUriResponse response[1];
int res;
req->StreamSetup = (struct tt__StreamSetup *)soap_malloc (
soap, sizeof (struct tt__StreamSetup));
req->StreamSetup->Stream = 0;
req->StreamSetup->Transport = (struct tt__Transport *)soap_malloc (
soap, sizeof (struct tt__Transport));
req->StreamSetup->Transport->Protocol = 0;
req->StreamSetup->Transport->Tunnel = 0;
req->StreamSetup->__size = 0;
req->StreamSetup->__any = NULL;
soap_default_xsd__anyAttribute (soap, &req->StreamSetup->__anyAttribute);
req->ProfileToken = (char *)profile;
res = soap_call___trt__GetStreamUri(soap, media_uri, req, response);
if (res == 0)
{
if (response->MediaUri == NULL)
{
sh_error ("soap no media uri.\n");
return NULL;
}
snprintf (out, outsize, "%s", response->MediaUri->Uri);
return out;
}
return NULL;
}
加入Onvif组播
Onvif支持通过组播的方式,向网络里面通告自己的Onvif服务。
绑定UDP的3702端口,加入239.255.255.250地址的组播地址,即可以使用soap_wsdd_listen
来监听Onvif客户端的查询请求。
C
void
app_listen (struct app_struct *app)
{
struct soap *soap;
soap = soap_new1 (SOAP_IO_UDP | SOAP_IO_FLUSH);
soap->user = app;
soap_set_mode (soap, SOAP_C_UTFSTRING);
soap->bind_flags = SO_REUSEADDR;
soap_register_plugin (soap, soap_wsa);
if (!soap_valid_socket (soap_bind (soap, app->host, 3702, 10)))
{
fprintf (stderr, "bind %s:%d error: %s\n", app->host, 3702, *soap_faultstring (soap));
soap_print_fault (soap, stderr);
}
else
{
struct ip_mreq mcast;
mcast.imr_multiaddr.s_addr = inet_addr ("239.255.255.250");
mcast.imr_interface.s_addr = inet_addr (app->host);
if (setsockopt (soap->master, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mcast,
sizeof (mcast))
< 0)
{
fprintf (stderr,
"onvif set 3702 port group error: %d, %s, %s\n", soap->error, *soap_faultcode (soap),
*soap_faultstring (soap));
}
else
{
while (soap_wsdd_listen (soap, -100000) == SOAP_OK)
{
printf ("onvif is listening\n");
}
soap_done (soap);
}
}
SOAPCLEANUP (soap);
soap_free (soap);
}