2025.06.27-14.44 C语言开发:Onvif(二)

认证

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。

大概分成几步:

  1. soap客户端请求。
  2. 服务端返回错误码401,以及鉴权码。
  3. soap客户端根据鉴权吗,加入认证信息,再次请求。
  4. 服务端认证通过。

示例如下:

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 的摄像机点播的时候,可以分成这样几步:

  1. 取得摄像机的Capabilities
  2. 取得摄像机的Profiles
  3. 取得相应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);  
}
相关推荐
Code Warrior36 分钟前
【每日算法】专题五_位运算
开发语言·c++
沐知全栈开发3 小时前
HTML DOM 访问
开发语言
脑袋大大的4 小时前
JavaScript 性能优化实战:减少 DOM 操作引发的重排与重绘
开发语言·javascript·性能优化
二进制person5 小时前
Java SE--方法的使用
java·开发语言·算法
OneQ6665 小时前
C++讲解---创建日期类
开发语言·c++·算法
Coding小公仔7 小时前
C++ bitset 模板类
开发语言·c++
凌肖战8 小时前
力扣网C语言编程题:在数组中查找目标值位置之二分查找法
c语言·算法·leetcode
小赖同学啊8 小时前
物联网数据安全区块链服务
开发语言·python·区块链
shimly1234568 小时前
bash 脚本比较 100 个程序运行时间,精确到毫秒,脚本
开发语言·chrome·bash