Onvif学习笔记3 - 获取相机视频流地址

流程

根据Onvif APG文档,视频流地址的获取分两个步骤:1、GetProfiles();2、GetStreamURI()。


GetProfiles

作为下一步的先决条件,GetProfiles的目的是获取Media Name和token。

首先需要定义两个结构体,用于信息的发送和接收:

#include "soapStub.h"
struct _trt__GetProfiles media_GetProfiles;
struct _trt__GetProfilesResponse media_GetProfilesResponse;
简单说一下这两个结构体的内容。struct _trt__GetProfiles里只有一个字节内容,无意义,只是用来发送获取信号;struct _trt__GetProfilesResponse里存储的是获取回来的信息。具体在“soapStub.h”这个文件中能找到。(结构体里套结构体,看起来太累)

功能实现函数:

一定要擦亮双眼啊。有几个下划线一定数数清楚。是一个下划线?还是两个?还是三个?整个协议源码里的下划线都要看清楚,尤其是没有代码补全的同学。当时初学,因为下划线浪费了两个小时。

soap_call___trt__GetProfiles(
    struct soap *soap,            //soap对象
    const char *soap_endpoint,    //能力集
    const char *soap_action,      //接口地址,一一对应,留空会自动赋值
    struct _trt__GetProfiles *trt__GetProfiles,  //发送内容
    struct _trt__GetProfilesResponse *trt__GetProfilesResponse  //接收内容
);
分块代码:
struct soap *soap = NULL;
char *soap_endpoint = (char *)malloc(256);
const char *soap_action = NULL;

struct SOAP_ENV__Header header;

struct _trt__GetProfiles media_GetProfiles;
struct _trt__GetProfilesResponse media_GetProfilesResponse;

/* 1 GetProfiles */
soap = ONVIF_Initsoap(&header, NULL, NULL, 5, 1);
memset(soap_endpoint, '\0', 256);
sprintf(soap_endpoint, "http://%s/onvif/Media", stOnvifCompleteInfo.UserInfo->IpAddr);

do
{
    soap_call___trt__GetProfiles(soap, soap_endpoint, soap_action, &media_GetProfiles, &media_GetProfilesResponse);
    if (soap->error)
    {
        printf("[%s][%d]--->>> soap error: %d, %s, %s\n", __func__, __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
        retval = soap->error;
        break;
    }
    else
    {
        printf("==== [ Media Profiles Response ] ====\n"
            "> Name  :  %s\n"
            "> token :  %s\n\n", \
            media_GetProfilesResponse.Profiles->Name, \
            media_GetProfilesResponse.Profiles->token );
    }
}while(0);

GetStreamURI

该函数需要用到的两个结构体:

struct _trt__GetStreamUri media_GetStreamUri;
struct _trt__GetStreamUriResponse media_GetStreamUriResponse;
因为上面获取的media token需要用于这里的发送,所以这两个结构体都是有内容的。这里需要注意的一点:因为结构体定义的是指针,指针指向的结构体同样定义的是指针,所以 要一层一层的分配内存空间。但是如果有的数据是不需要的,可以不分配空间。

media_GetStreamUri.StreamSetup = (struct tt__StreamSetup *)soap_malloc(soap, sizeof(struct tt__StreamSetup));
media_GetStreamUri.StreamSetup->Transport = (struct tt__Transport *)soap_malloc(soap, sizeof(struct tt__Transport));
// 以下的赋值可根据源码内的说明自行修改
media_GetStreamUri.StreamSetup->Stream = 0;
media_GetStreamUri.StreamSetup->Transport->Protocol = 0;
media_GetStreamUri.StreamSetup->Transport->Tunnel = 0;
media_GetStreamUri.StreamSetup->__size = 1;
media_GetStreamUri.StreamSetup->__any = NULL;
media_GetStreamUri.StreamSetup->__anyAttribute = NULL;
media_GetStreamUri.ProfileToken = media_GetProfilesResponse.Profiles->token;
功能实现函数:

soap_call___trt__GetStreamUri(
    struct soap *soap,
    const char *soap_endpoint,
    const char *soap_action,
    struct _trt__GetStreamUri *trt__GetStreamUri,
    struct _trt__GetStreamUriResponse *trt__GetStreamUriResponse
);
分块代码:
struct _trt__GetStreamUri media_GetStreamUri;
struct _trt__GetStreamUriResponse media_GetStreamUriResponse;

/* 2 GetStreamUri */
soap = ONVIF_Initsoap(&header, NULL, NULL, 5, 1);
memset(soap_endpoint, '\0', 256);
sprintf(soap_endpoint, "http://%s/onvif/Media", stOnvifCompleteInfo.UserInfo->IpAddr);

media_GetStreamUri.StreamSetup = (struct tt__StreamSetup *)soap_malloc(soap, sizeof(struct tt__StreamSetup));
media_GetStreamUri.StreamSetup->Transport = (struct tt__Transport *)soap_malloc(soap, sizeof(struct tt__Transport));

media_GetStreamUri.StreamSetup->Stream = 0;
media_GetStreamUri.StreamSetup->Transport->Protocol = 0;
media_GetStreamUri.StreamSetup->Transport->Tunnel = 0;
media_GetStreamUri.StreamSetup->__size = 1;
media_GetStreamUri.StreamSetup->__any = NULL;
media_GetStreamUri.StreamSetup->__anyAttribute = NULL;
media_GetStreamUri.ProfileToken = media_GetProfilesResponse.Profiles->token;

do
{
    soap_call___trt__GetStreamUri(soap, soap_endpoint, soap_action, &media_GetStreamUri, &media_GetStreamUriResponse);
    if (soap->error)
    {
        printf("[%s][%d]--->>> soap error: %d, %s, %s\n", __func__, __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
        retval = soap->error;
        break;
    }
    else
    {
        printf("==== [ Media Stream Uri Response ] ====\n"
               "> MediaUri :\n\t%s\n", \
               media_GetStreamUriResponse.MediaUri->Uri);
    }
}while(0);

完整示例

应该是可以运行的吧……家里没Onvif设备,不测了。

#include "wsdd.h"
#include <stdio.h>
#include <ctype.h>
#include <openssl/sha.h>

#include "cameraonvif.h"

/* 设备信息 */
typedef struct USER_INFO
{
    char username[16];
    char password[32];
    char IpAddr[16];
} UserInfo_S;

/* 设备能力地址 */
typedef struct CAPABILITIES_ADDR
{
    char *Device;
    char *Imaging;
    char *Media;
    char *PTZ;
} CapabilitiesAddr_S;

/* 设备参数标识 */
typedef struct PROFILE_TOKEN
{
    char *Media;
    char *PTZConfiguration;
} ProfileToken_S;

/* 设备Uri信息 */
typedef struct URI
{
    char *StreamUri;
    char *SnapshotUri;
} Uri_S;

/** 初始化信息集合 **/
typedef struct ONVIF_COMPLETE_INFO
{
    UserInfo_S *UserInfo;
    CapabilitiesAddr_S *CapabilitiesAddr;
    ProfileToken_S *ProfileToken;
    Uri_S *Uri;
} OnvifCompleteInfo_S;
OnvifCompleteInfo_S stOnvifCompleteInfo;

static const char base64digits[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

#define BAD     -1
static const signed char base64val[] = {
    BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD,
    BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD,
    BAD,BAD,BAD,BAD, BAD,BAD,BAD,BAD, BAD,BAD,BAD, 62, BAD,BAD,BAD, 63,
    52, 53, 54, 55,  56, 57, 58, 59,  60, 61,BAD,BAD, BAD,BAD,BAD,BAD,
    BAD,  0,  1,  2,   3,  4,  5,  6,   7,  8,  9, 10,  11, 12, 13, 14,
    15, 16, 17, 18,  19, 20, 21, 22,  23, 24, 25,BAD, BAD,BAD,BAD,BAD,
    BAD, 26, 27, 28,  29, 30, 31, 32,  33, 34, 35, 36,  37, 38, 39, 40,
    41, 42, 43, 44,  45, 46, 47, 48,  49, 50, 51,BAD, BAD,BAD,BAD,BAD
};
#define DECODE64(c)  (isascii(c) ? base64val[c] : BAD)

/**
 * @brief base64加密函数
 * @param out
 * @param in
 * @param inlen
 */
void base64_bits_to_64(unsigned char *out, const unsigned char *in, int inlen)
{
    for (; inlen >= 3; inlen -= 3)
    {
        *out++ = base64digits[in[0] >> 2];
        *out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)];
        *out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
        *out++ = base64digits[in[2] & 0x3f];
        in += 3;
    }

    if (inlen > 0)
    {
        unsigned char fragment;

        *out++ = base64digits[in[0] >> 2];
        fragment = (in[0] << 4) & 0x30;

        if (inlen > 1)
            fragment |= in[1] >> 4;

        *out++ = base64digits[fragment];
        *out++ = (inlen < 2) ? '=' : base64digits[(in[1] << 2) & 0x3c];
        *out++ = '=';
    }

    *out = '\0';
}

/**
 * @brief base64解密函数
 * @param out
 * @param in
 * @return
 */
int base64_64_to_bits(char *out, const char *in)
{
    int len = 0;
    register unsigned char digit1, digit2, digit3, digit4;

    if (in[0] == '+' && in[1] == ' ')
        in += 2;
    if (*in == '\r')
        return(0);

    do {
        digit1 = in[0];
        if (DECODE64(digit1) == BAD)
            return(-1);
        digit2 = in[1];
        if (DECODE64(digit2) == BAD)
            return(-1);
        digit3 = in[2];
        if (digit3 != '=' && DECODE64(digit3) == BAD)
            return(-1);
        digit4 = in[3];
        if (digit4 != '=' && DECODE64(digit4) == BAD)
            return(-1);
        in += 4;
        *out++ = (DECODE64(digit1) << 2) | (DECODE64(digit2) >> 4);
        ++len;
        if (digit3 != '=')
        {
            *out++ = ((DECODE64(digit2) << 4) & 0xf0) | (DECODE64(digit3) >> 2);
            ++len;
            if (digit4 != '=')
            {
                *out++ = ((DECODE64(digit3) << 6) & 0xc0) | DECODE64(digit4);
                ++len;
            }
        }
    } while (*in && *in != '\r' && digit4 != '=');

    return (len);
}

/**
 * @brief ONVIF鉴权生成
 * @param pwddigest_out
 * @param pwd
 * @param nonc
 * @param time
 */
static void ONVIF_GenrateDigest(unsigned char *pwddigest_out, unsigned char *pwd, char *nonc, char *time)
{
    const unsigned char *tdist;
    unsigned char dist[1024] = {0};
    char tmp[1024] = {0};
    unsigned char bout[1024] = {0};
    strcpy(tmp,nonc);
    base64_64_to_bits((char*)bout, tmp);
    sprintf(tmp,"%s%s%s",bout,time,pwd);
    SHA1((const unsigned char*)tmp,strlen((const char*)tmp),dist);
    tdist = dist;
    memset(bout,0x0,1024);
    base64_bits_to_64(bout,tdist,(int)strlen((const char*)tdist));
    strcpy((char *)pwddigest_out,(const char*)bout);
}

/**
 * @brief ONVIF初始化一个soap对象
 * @param header            SOAP_ENV__HEADER
 * @param was_To            *wsa_To
 * @param was_Action        *wsa_Action
 * @param timeout           超时设置
 * @return soap结构体地址
 */
static struct soap* ONVIF_Initsoap(struct SOAP_ENV__Header *header, const char *was_To, const char *was_Action, int timeout, int toAuthenticate)
{
    //printf("> %s\n> %s\n> %s\n", pUserInfo->username, pUserInfo->password, pUserInfo->IpAddr);
    struct soap *soap = NULL;
    unsigned char macaddr[6];
    char _HwId[1024];
    unsigned int Flagrand;
    soap = soap_new();
    if(soap == NULL)
    {
        printf("[%d]soap = NULL\n", __LINE__);
        return NULL;
    }
     soap_set_namespaces( soap, namespaces);
    //超过5秒钟没有数据就退出
    if (timeout > 0)
    {
        soap->recv_timeout = timeout;
        soap->send_timeout = timeout;
        soap->connect_timeout = timeout;
    }
    else
    {
        //如果外部接口没有设备默认超时时间的话,我这里给了一个默认值10s
        soap->recv_timeout    = 10;
        soap->send_timeout    = 10;
        soap->connect_timeout = 10;
    }
    soap_default_SOAP_ENV__Header(soap, header);

    //为了保证每次搜索的时候MessageID都是不相同的!因为简单,直接取了随机值
    srand((int)time(0));
    Flagrand = rand()%9000 + 1000; //保证四位整数
    macaddr[0] = 0x1; macaddr[1] = 0x2; macaddr[2] = 0x3; macaddr[3] = 0x4; macaddr[4] = 0x5; macaddr[5] = 0x6;
    sprintf(_HwId,"urn:uuid:%ud68a-1dd2-11b2-a105-%02X%02X%02X%02X%02X%02X",
            Flagrand, macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]);
    header->wsa__MessageID =(char *)malloc( 100);
    memset(header->wsa__MessageID, 0, 100);
    strncpy(header->wsa__MessageID, _HwId, strlen(_HwId));

    //这里开始作鉴权处理了,如果有用户信息的话,就会处理鉴权问题
    //如果设备端不需要鉴权的话,在外层调用此接口的时候把 toAuthenticate 置0
    if( toAuthenticate )
    {
        header->wsse__Security = (struct _wsse__Security *)malloc(sizeof(struct _wsse__Security));
        memset(header->wsse__Security, 0 , sizeof(struct _wsse__Security));

        header->wsse__Security->UsernameToken = (struct _wsse__UsernameToken *)calloc(1,sizeof(struct _wsse__UsernameToken));
        header->wsse__Security->UsernameToken->Username = (char *)malloc(64);
        memset(header->wsse__Security->UsernameToken->Username, '\0', 64);

        header->wsse__Security->UsernameToken->Nonce = (char*)malloc(64);
        memset(header->wsse__Security->UsernameToken->Nonce, '\0', 64);
        strcpy(header->wsse__Security->UsernameToken->Nonce,"LKqI6G/AikKCQrN0zqZFlg=="); //注意这里

        header->wsse__Security->UsernameToken->wsu__Created = (char*)malloc(64);
        memset(header->wsse__Security->UsernameToken->wsu__Created, '\0', 64);
        strcpy(header->wsse__Security->UsernameToken->wsu__Created,"2010-09-16T07:50:45Z");

        strcpy(header->wsse__Security->UsernameToken->Username, stOnvifCompleteInfo.UserInfo->username /*pUserInfo->username*/);
        header->wsse__Security->UsernameToken->Password = (struct _wsse__Password *)malloc(sizeof(struct _wsse__Password));
        header->wsse__Security->UsernameToken->Password->Type = (char*)malloc(128);
        memset(header->wsse__Security->UsernameToken->Password->Type, '\0', 128);
        strcpy(header->wsse__Security->UsernameToken->Password->Type,\
                "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
        header->wsse__Security->UsernameToken->Password->__item = (char*)malloc(128);
        ONVIF_GenrateDigest((unsigned char*)header->wsse__Security->UsernameToken->Password->__item, \
            (unsigned char*)stOnvifCompleteInfo.UserInfo->password/*pUserInfo->password*/, header->wsse__Security->UsernameToken->Nonce, header->wsse__Security->UsernameToken->wsu__Created);

    }
    if (was_Action != NULL)
    {
        header->wsa__Action =(char *)malloc(1024);
        memset(header->wsa__Action, '\0', 1024);
        strncpy(header->wsa__Action, was_Action, 1024);//"http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe";
    }
    if (was_To != NULL)
    {
        header->wsa__To =(char *)malloc(1024);
        memset(header->wsa__To, '\0', 1024);
        strncpy(header->wsa__To,  was_To, 1024);//"urn:schemas-xmlsoap-org:ws:2005:04:discovery";
    }
    soap->header = header;
    return soap;
}

/**
 * @brief ONVIF获取视频流地址
 * @return 成功 0 / 失败 soap_error错误代码
 */
int ONVIF_GetRTSPStream()
{
    int retval = 0;
    struct soap *soap = NULL;
    char *soap_endpoint = (char *)malloc(256);
    const char *soap_action = NULL;

    struct SOAP_ENV__Header header;

    struct _trt__GetProfiles media_GetProfiles;
    struct _trt__GetProfilesResponse media_GetProfilesResponse;
    struct _trt__GetStreamUri media_GetStreamUri;
    struct _trt__GetStreamUriResponse media_GetStreamUriResponse;

    /* 1 GetProfiles */
    soap = ONVIF_Initsoap(&header, NULL, NULL, 5, 1);
    memset(soap_endpoint, '\0', 256);
    sprintf(soap_endpoint, "http://%s/onvif/Media", stOnvifCompleteInfo.UserInfo->IpAddr);

    do
    {
        soap_call___trt__GetProfiles(soap, soap_endpoint, soap_action, &media_GetProfiles, &media_GetProfilesResponse);
        if (soap->error)
        {
            printf("[%s][%d]--->>> soap error: %d, %s, %s\n", __func__, __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
            retval = soap->error;
            break;
        }
        else
        {
            printf("==== [ Media Profiles Response ] ====\n"
                   "> Name  :  %s\n"
                   "> token :  %s\n\n", \
                   media_GetProfilesResponse.Profiles->Name, \
                   media_GetProfilesResponse.Profiles->token );
        }
    }while(0);

    /* 2 GetStreamUri */
    soap = ONVIF_Initsoap(&header, NULL, NULL, 5, 1);
    memset(soap_endpoint, '\0', 256);
    sprintf(soap_endpoint, "http://%s/onvif/Media", stOnvifCompleteInfo.UserInfo->IpAddr);

    media_GetStreamUri.StreamSetup = (struct tt__StreamSetup *)soap_malloc(soap, sizeof(struct tt__StreamSetup));
    media_GetStreamUri.StreamSetup->Transport = (struct tt__Transport *)soap_malloc(soap, sizeof(struct tt__Transport));

    media_GetStreamUri.StreamSetup->Stream = 0;
    media_GetStreamUri.StreamSetup->Transport->Protocol = 0;
    media_GetStreamUri.StreamSetup->Transport->Tunnel = 0;
    media_GetStreamUri.StreamSetup->__size = 1;
    media_GetStreamUri.StreamSetup->__any = NULL;
    media_GetStreamUri.StreamSetup->__anyAttribute = NULL;
    media_GetStreamUri.ProfileToken = media_GetProfilesResponse.Profiles->token;

    do
    {
        soap_call___trt__GetStreamUri(soap, soap_endpoint, soap_action, &media_GetStreamUri, &media_GetStreamUriResponse);
        if (soap->error)
        {
            printf("[%s][%d]--->>> soap error: %d, %s, %s\n", __func__, __LINE__, soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
            retval = soap->error;
            break;
        }
        else
        {
            printf("==== [ Media Stream Uri Response ] ====\n"
                   "> MediaUri :\n\t%s\n", \
                   media_GetStreamUriResponse.MediaUri->Uri);
            printf("[\033[1;32m success\033[0m ] Get Stream Uri!\n");
#if 0   // 此处可使用ffplay播放视频流, 请将stUserInfo结构体内容替换成相应的信息
            char *rtspStreamUri = (char*)malloc(sizeof(char));
            strncpy(rtspStreamUri, media_GetStreamUriResponse.MediaUri->Uri, 7);
            strcat(rtspStreamUri, stUserInfo->username);
            strcat(rtspStreamUri, ":");
            strcat(rtspStreamUri, stUserInfo->password);
            strcat(rtspStreamUri, "@");
            strcat(rtspStreamUri, media_GetStreamUriResponse.MediaUri->Uri + 7);
            printf("> Completing Uri :\n\t%s\n\n", rtspStreamUri);

            char buf[1024];
            sprintf(buf, "ffplay %s", rtspStreamUri);
            system(buf);
#endif
        }
    }while(0);

    return retval;
}

/**
 * @brief 主函数
 * @return
 */
int main()
{
    // 这个结构体是我为了方便使用而定义的.
    // 一些无关的内容就不赋值了.
    /* 结构体初始化 */
    stOnvifCompleteInfo.UserInfo = (UserInfo_S *)malloc(sizeof(UserInfo_S));
    stOnvifCompleteInfo.CapabilitiesAddr = (CapabilitiesAddr_S *)malloc(sizeof(CapabilitiesAddr_S));
    stOnvifCompleteInfo.CapabilitiesAddr->Device = (char *)malloc(sizeof(char));
    stOnvifCompleteInfo.CapabilitiesAddr->Imaging = (char *)malloc(sizeof(char));
    stOnvifCompleteInfo.CapabilitiesAddr->Media = (char *)malloc(sizeof(char));
    stOnvifCompleteInfo.CapabilitiesAddr->PTZ = (char *)malloc(sizeof(char));
    stOnvifCompleteInfo.ProfileToken = (ProfileToken_S *)malloc(sizeof(ProfileToken_S));
    stOnvifCompleteInfo.ProfileToken->Media = (char *)malloc(sizeof(char));
    stOnvifCompleteInfo.ProfileToken->PTZConfiguration = (char *)malloc(sizeof(char));
    stOnvifCompleteInfo.Uri = (Uri_S *)malloc(sizeof(Uri_S));
    stOnvifCompleteInfo.Uri->StreamUri = (char *)malloc(sizeof(char));
    stOnvifCompleteInfo.Uri->SnapshotUri = (char *)malloc(sizeof(char));

    stOnvifCompleteInfo.UserInfo->username = "admin";
    stOnvifCompleteInfo.UserInfo->password = "admin";
    stOnvifCompleteInfo.UserInfo->IpAddr = "192.168.1.123";
    stOnvifCompleteInfo.CapabilitiesAddr->Device = "";
    stOnvifCompleteInfo.CapabilitiesAddr->Imaging = "";
    stOnvifCompleteInfo.CapabilitiesAddr->Media = "";
    stOnvifCompleteInfo.CapabilitiesAddr->PTZ = "";
    stOnvifCompleteInfo.ProfileToken->Media = "";
    stOnvifCompleteInfo.ProfileToken->PTZConfiguration = "";
    stOnvifCompleteInfo.Uri->StreamUri = "";
    stOnvifCompleteInfo.Uri->SnapshotUri = "";

    if (ONVIF_GetRTSPStream() != 0)
    {
        printf("[%s][%d]--->>> get RTSP stream failed!\n", __func__, __LINE__);
        return -1;
    }
    return 0;
}


  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值