cgo+gSoap+onvif学习总结:5、获取profileToken、rtsp流地址、快照地址及cgo程序signal SIGSEGV: segmentation violation解决方法

26 篇文章 5 订阅

cgo+gSoap+onvif学习总结:5、获取profileToken、rtsp流地址、快照地址及cgo偶发signal SIGSEGV: segmentation violation分割违例解决方法


1. 前言

理论上我们搜索到设备并获取设备信息后,根据要筛选的设备信息条件筛选出我们需要的ipc后,我们还可以分别对其进行网络信息配置,这个之前go的方式我们已经使用过了,没有问题,gSoap方式应该问题也不大,所以这里我们就略过这块了,我们直接来实验获取设备能力之后,获取profileToken并获取rtsp流地址和快照地址,这部分获取到后就相当于可以从摄像头拉流了,AI处理很多时候都是只需要获取rtsp流,然后分析音视频流来做进一步的人、车、非等识别判断,所以这块虽然相对不是很难,但是使用上是非常广泛的。

根据之前的开发经验,这块从代码层面看还是分三步:1、gSoap生成c/c++代码框架;2、完成c/c++代码;3、完成cgo代码;从onvif协议角度看,流程稍微复杂一些:设备搜索、鉴权、获取设备信息、筛选设备、网络设置(网络设置一般不需要,但有时候根据需求需要自动修改出厂网络设置)、获取设备能力、选择媒体能力并获取ProfileToken、根据ProfileToken获取对应视频流的rtsp流地址、获取快照地址等,其它的一些ptz、预置点等都是针对获取到的设备能力来做的。

2. gSoap生成c/c++代码框架

网络接口规范地址:https://www.onvif.org/profiles/specifications/

cd ./soap/
//注意这里的typemap.dat是上节我们修改过的,不然还会出现duration.c编译报错
wsdl2h -c -t ./typemap.dat -o onvif.h http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl https://www.onvif.org/ver10/device/wsdl/devicemgmt.wsdl https://www.onvif.org/ver10/events/wsdl/event.wsdl https://www.onvif.org/ver10/media/wsdl/media.wsdl

vim onvif.h
添加 #import "wsse.h"

//需要依赖如下文件,从gSoap源码拷贝到我们的工程中(拷贝一次后续就可以一直用了)
dom.c、dom.h、wsseapi.c、wsseapi.h、smdevp.c、smdevp.h、mecevp.c、mecevp.h、threads.c、threads.h、wsaapi.c、wsaapi.h
//注意下面的文件需要custom文件夹,否则路径不对
struct_timeval.h、struct_timeval.c
//duration.c以及duration.h对于生成框架代码和编译c代码路径可能会有差异,根据实际情况可能需要两个位置都有

//只生成客户端源文件,我们只实现客户端,不添加-C会多生成服务端代码
soapcpp2 -x -L -C onvif.h

整个过程截图:

在这里插入图片描述

可以看到上一节我们跑通后,后续再生成框架代码就很流畅了。

3. 实现c代码并运行测试

我们还是根据生成的c框架代码写出获取profileToken、rtsp流地址、抓拍地址的c实例代码,并写简单的main函数做下测试,保证c实例代码编译运行正常。onvif流程如下:获取设备能力-》获取profileToken-》获取rtsp流地址和抓拍地址。

3.1 c代码

如下我们还是简单做下封装并写main函数:

//
// Created by admin on 2022/3/3.
//

#include <string.h>
#include "soap/soapStub.h"
#include "soap/wsdd.nsmap"
#include "soap/soapH.h"
#include "soap/wsseapi.h"
#include "client.h"

struct soap *new_soap(struct soap *soap) {
    //soap初始化,申请空间
    soap = soap_new();
    if (soap == NULL) {
        printf("func:%s,line:%d.malloc soap error.\n", __FUNCTION__, __LINE__);
        return NULL;
    }

    soap_set_namespaces(soap, namespaces);
    soap->recv_timeout = 5;
    printf("func:%s,line:%d.new soap success!\n", __FUNCTION__, __LINE__);
    return soap;
}

void del_soap(struct soap *soap) {
    //清除soap
    soap_end(soap);
    soap_free(soap);
}

int discovery(struct soap *soap) {
    //发送消息描述
    struct wsdd__ProbeType req;
    struct __wsdd__ProbeMatches resp;
    //描述查找那类的Web消息
    struct wsdd__ScopesType sScope;
    //soap消息头消息
    struct SOAP_ENV__Header header;
    //获得的设备信息个数
    int count = 0;
    //返回值
    int res;
    //存放uuid 格式(8-4-4-4-12)
    char uuid_string[64];

    printf("func:%s,line:%d.discovery dev!\n", __FUNCTION__, __LINE__);
    if (soap == NULL) {
        printf("func:%s,line:%d.soap is nil.\n", __FUNCTION__, __LINE__);
        return -1;
    }

    sprintf(uuid_string, "464A4854-4656-5242-4530-110000000000");
    printf("func:%s,line:%d.uuid = %s\n", __FUNCTION__, __LINE__, uuid_string);

    //将header设置为soap消息,头属性,暂且认为是soap和header绑定
    soap_default_SOAP_ENV__Header(soap, &header);
    header.wsa5__MessageID = uuid_string;
    header.wsa5__To = "urn:schemas-xmlsoap-org:ws:2005:04:discovery";
    header.wsa5__Action = "http://schemas.xmllocal_soap.org/ws/2005/04/discovery/Probe";
    //设置soap头消息的ID
    soap->header = &header;

    /* 设置所需寻找设备的类型和范围,二者至少设置一个
        否则可能收到非ONVIF设备,出现异常
     */
    //设置soap消息的请求服务属性
    soap_default_wsdd__ScopesType(soap, &sScope);
    sScope.__item = "onvif://www.onvif.org";
    soap_default_wsdd__ProbeType(soap, &req);
    req.Scopes = &sScope;

    /* 设置所需设备的类型,ns1为命名空间前缀,在wsdd.nsmap 文件中
       {"tdn","http://www.onvif.org/ver10/network/wsdl"}的tdn,如果不是tdn,而是其它,
       例如ns1这里也要随之改为ns1
    */
    req.Types = "ns1:NetworkVideoTransmitter";

    //调用gSoap接口 向 239.255.255.250:3702 发送udp消息
    res = soap_send___wsdd__Probe(soap, "soap.udp://239.255.255.250:3702/", NULL, &req);

    if (res == -1) {
        printf("func:%s,line:%d.soap error: %d, %s, %s \n", __FUNCTION__, __LINE__, soap->error, *soap_faultcode(soap),
               *soap_faultstring(soap));
        res = soap->error;
    } else {
        do {
            printf("func:%s,line:%d.begin receive probe match, find dev count:%d.... \n", __FUNCTION__, __LINE__,
                   count);

            //接收 ProbeMatches,成功返回0,错误返回-1
            res = soap_recv___wsdd__ProbeMatches(soap, &resp);
            printf("func:%s,line:%d.result=%d \n", __FUNCTION__, __LINE__, res);
            if (res == -1) {
                break;
            } else {
                //读取服务器回应的Probematch消息
                printf("soap_recv___wsdd__Probe: __sizeProbeMatch = %d \n", resp.wsdd__ProbeMatches->__sizeProbeMatch);
                printf("Target EP Address : %s \n",
                       resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);
                printf("Target Type : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Types);
                printf("Target Service Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);
                printf("Target Metadata Version: %d \n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);
                printf("Target Scope Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Scopes->__item);
                count++;
            }
        } while (1);
    }

    return res;
}

int set_auth_info(struct soap *soap, const char *username, const char *password) {
    if (NULL == username) {
        printf("func:%s,line:%d.username is null.\n", __FUNCTION__, __LINE__);
        return -1;
    }
    if (NULL == password) {
        printf("func:%s,line:%d.password is nil.\n", __FUNCTION__, __LINE__);
        return -2;
    }

    int result = soap_wsse_add_UsernameTokenDigest(soap, NULL, username, password);

    return result;
}

int get_device_info(struct soap *soap, const char *username, const char *password, char *xAddr) {
    if (NULL == xAddr) {
        printf("func:%s,line:%d.dev addr is nil.\n", __FUNCTION__, __LINE__);
        return -1;
    }
    if (soap == NULL) {
        printf("func:%s,line:%d.malloc soap error.\n", __FUNCTION__, __LINE__);
        return -2;
    }

    struct _tds__GetDeviceInformation deviceInformation;
    struct _tds__GetDeviceInformationResponse deviceInformationResponse;

    set_auth_info(soap, username, password);

    int res = soap_call___tds__GetDeviceInformation(soap, xAddr, NULL, &deviceInformation, &deviceInformationResponse);

    if (NULL != soap) {
        printf("Manufacturer:%s\n", deviceInformationResponse.Manufacturer);
        printf("Model:%s\n", deviceInformationResponse.Model);
        printf("FirmwareVersion:%s\n", deviceInformationResponse.FirmwareVersion);
        printf("SerialNumber:%s\n", deviceInformationResponse.SerialNumber);
        printf("HardwareId:%s\n", deviceInformationResponse.HardwareId);
    }
    return res;
}

int get_capabilities(struct soap *soap, const char *username, const char *password, char *xAddr, char *mediaAddr) {
    struct _tds__GetCapabilities capabilities;
    struct _tds__GetCapabilitiesResponse capabilitiesResponse;

    set_auth_info(soap, username, password);
    int res = soap_call___tds__GetCapabilities(soap, xAddr, NULL, &capabilities, &capabilitiesResponse);
    if (soap->error) {
        printf("func:%s,line:%d.soap error: %d, %s, %s\n", __FUNCTION__, __LINE__, soap->error, *soap_faultcode(soap),
               *soap_faultstring(soap));
        return soap->error;
    }
    if (capabilitiesResponse.Capabilities == NULL) {
        printf("func:%s,line:%d.GetCapabilities  failed!  result=%d \n", __FUNCTION__, __LINE__, res);
    } else {
        printf("func:%s,line:%d.Media->XAddr=%s \n", __FUNCTION__, __LINE__,
               capabilitiesResponse.Capabilities->Media->XAddr);
        strcpy(mediaAddr, capabilitiesResponse.Capabilities->Media->XAddr);
    }
    return res;
}

int get_profiles(struct soap *soap, const char *username, const char *password, char *profileToken, char *xAddr) {
    struct _trt__GetProfiles profiles;
    struct _trt__GetProfilesResponse profilesResponse;
    set_auth_info(soap, username, password);
    int res = soap_call___trt__GetProfiles(soap, xAddr, NULL, &profiles, &profilesResponse);
    if (res == -1)
        //NOTE: it may be regular if result isn't SOAP_OK.Because some attributes aren't supported by server.
        //any question email leoluopy@gmail.com
    {
        printf("func:%s,line:%d.soap error: %d, %s, %s\n", __FUNCTION__, __LINE__, soap->error, *soap_faultcode(soap),
               *soap_faultstring(soap));
        return soap->error;
    }

    if (profilesResponse.Profiles != NULL) {
        if (profilesResponse.Profiles->Name != NULL) {
            printf("func:%s,line:%d.Profiles Name:%s\n", __FUNCTION__, __LINE__, profilesResponse.Profiles->Name);
        }
        if (profilesResponse.Profiles->token != NULL) {
            printf("func:%s,line:%d.Profiles Taken:%s\n", __FUNCTION__, __LINE__, profilesResponse.Profiles->token);
            strcpy(profileToken, profilesResponse.Profiles->token);
        }
    } else {
        printf("func:%s,line:%d.Profiles Get inner Error\n", __FUNCTION__, __LINE__);
    }
    return res;
}

int get_rtsp_uri(struct soap *soap, const char *username, const char *password, char *profileToken, char *xAddr) {
    struct _trt__GetStreamUri streamUri;
    struct _trt__GetStreamUriResponse streamUriResponse;
    streamUri.StreamSetup = (struct tt__StreamSetup *) soap_malloc(soap, sizeof(struct tt__StreamSetup));
    streamUri.StreamSetup->Stream = 0;
    streamUri.StreamSetup->Transport = (struct tt__Transport *) soap_malloc(soap, sizeof(struct tt__Transport));
    streamUri.StreamSetup->Transport->Protocol = 0;
    streamUri.StreamSetup->Transport->Tunnel = 0;
    streamUri.StreamSetup->__size = 1;
    streamUri.StreamSetup->__any = NULL;
    streamUri.StreamSetup->__anyAttribute = NULL;
    strcpy(streamUri.ProfileToken, profileToken);
    set_auth_info(soap, username, password);
    int res = soap_call___trt__GetStreamUri(soap, xAddr, NULL, &streamUri, &streamUriResponse);
    if (soap->error) {
        printf("func:%s,line:%d.soap error: %d, %s, %s\n", __FUNCTION__, __LINE__, soap->error, *soap_faultcode(soap),
               *soap_faultstring(soap));
        return soap->error;
    }
    printf("func:%s,line:%d.RTSP uri is :%s \n", __FUNCTION__, __LINE__, streamUriResponse.MediaUri->Uri);
    return res;
}

int get_snapshot(struct soap *soap, const char *username, const char *password, char *profileToken, char *xAddr) {
    struct _trt__GetSnapshotUri snapshotUri;
    struct _trt__GetSnapshotUriResponse snapshotUriResponse;

    set_auth_info(soap, username, password);
    snapshotUri.ProfileToken = profileToken;
    int res = soap_call___trt__GetSnapshotUri(soap, xAddr, NULL, &snapshotUri, &snapshotUriResponse);
    if (soap->error) {
        printf("func:%s,line:%d.soap error: %d, %s, %s\n", __FUNCTION__, __LINE__, soap->error, *soap_faultcode(soap),
               *soap_faultstring(soap));
        return soap->error;
    }
    printf("func:%s,line:%d.Snapshot uri is :%s \n", __FUNCTION__, __LINE__,
           snapshotUriResponse.MediaUri->Uri);
    return res;
}

int main() {
    struct soap *soap = NULL;
    soap = new_soap(soap);
    const char username[] = "admin";
    const char password[] = "admin";
    char serviceAddr[] = "http://40.40.40.101:80/onvif/device_service";

    discovery(soap);

    get_device_info(soap, username, password, serviceAddr);

    char mediaAddr[200] = {'\0'};
    get_capabilities(soap, username, password, serviceAddr, mediaAddr);

    char profileToken[200] = {'\0'};
    get_profiles(soap, username, password, profileToken, mediaAddr);

    get_rtsp_uri(soap, username, password, profileToken, mediaAddr);

    get_snapshot(soap, username, password, profileToken, mediaAddr);

    del_soap(soap);
}

3.2 camke

cmake_minimum_required(VERSION 3.0)
project(onvif-cgo)

set(CMAKE_C_FLAGS "-DWITH_DOM -DWITH_OPENSSL -DWITH_NONAMESPACES")
aux_source_directory(./soap/ SRC_LIST)
include_directories(./ /usr/include/ ./soap/custom)
link_directories(~/ /usr/local/ /usr/lib/)
add_executable(gsoap-onvif ${SRC_LIST} client.c client.h ./soap/custom)
#add_executable(gsoap-onvif ${SRC_LIST} ./soap/custom device.c)
#add_executable(discovery_onvif ${SRC_LIST} main.c ./soap/custom)
target_link_libraries(gsoap-onvif -lpthread -ldl -lssl -lcrypto)
#ADD_LIBRARY(c_onvif SHARED ${SRC_LIST} client.c)
#ADD_LIBRARY(c_onvif_static STATIC ${SRC_LIST} client.c client.h ./soap/custom)

3.3 运行结果

结果如下:

在这里插入图片描述

rtsp地址测试结果:

在这里插入图片描述

抓拍地址测试结果:

在这里插入图片描述

4. 实现cgo代码并运行测试

cgo代码由于需要收发字符串内容,所以需要先了解以下cgo中c和go如何进行字符数组的交互,这块找一下示例之后照猫画虎即可。

4.1 修改后的c代码

client.c

//
// Created by admin on 2022/3/3.
//

#include <string.h>
#include "soap/soapStub.h"
#include "soap/wsdd.nsmap"
#include "soap/soapH.h"
#include "soap/wsseapi.h"
#include "client.h"

struct soap *new_soap(struct soap *soap) {
    //soap初始化,申请空间
    soap = soap_new();
    if (soap == NULL) {
        printf("func:%s,line:%d.malloc soap error.\n", __FUNCTION__, __LINE__);
        return NULL;
    }

    soap_set_namespaces(soap, namespaces);
    soap->recv_timeout = 5;
    printf("func:%s,line:%d.new soap success!\n", __FUNCTION__, __LINE__);
    return soap;
}

void del_soap(struct soap *soap) {
    //清除soap
    soap_end(soap);
    soap_free(soap);
}

int discovery(struct soap *soap) {
    //发送消息描述
    struct wsdd__ProbeType req;
    struct __wsdd__ProbeMatches resp;
    //描述查找那类的Web消息
    struct wsdd__ScopesType sScope;
    //soap消息头消息
    struct SOAP_ENV__Header header;
    //获得的设备信息个数
    int count = 0;
    //返回值
    int res;
    //存放uuid 格式(8-4-4-4-12)
    char uuid_string[64];

    printf("func:%s,line:%d.discovery dev!\n", __FUNCTION__, __LINE__);
    if (soap == NULL) {
        printf("func:%s,line:%d.soap is nil.\n", __FUNCTION__, __LINE__);
        return -1;
    }

    sprintf(uuid_string, "464A4854-4656-5242-4530-110000000000");
    printf("func:%s,line:%d.uuid = %s\n", __FUNCTION__, __LINE__, uuid_string);

    //将header设置为soap消息,头属性,暂且认为是soap和header绑定
    soap_default_SOAP_ENV__Header(soap, &header);
    header.wsa5__MessageID = uuid_string;
    header.wsa5__To = "urn:schemas-xmlsoap-org:ws:2005:04:discovery";
    header.wsa5__Action = "http://schemas.xmllocal_soap.org/ws/2005/04/discovery/Probe";
    //设置soap头消息的ID
    soap->header = &header;

    /* 设置所需寻找设备的类型和范围,二者至少设置一个
        否则可能收到非ONVIF设备,出现异常
     */
    //设置soap消息的请求服务属性
    soap_default_wsdd__ScopesType(soap, &sScope);
    sScope.__item = "onvif://www.onvif.org";
    soap_default_wsdd__ProbeType(soap, &req);
    req.Scopes = &sScope;

    /* 设置所需设备的类型,ns1为命名空间前缀,在wsdd.nsmap 文件中
       {"tdn","http://www.onvif.org/ver10/network/wsdl"}的tdn,如果不是tdn,而是其它,
       例如ns1这里也要随之改为ns1
    */
    req.Types = "ns1:NetworkVideoTransmitter";

    //调用gSoap接口 向 239.255.255.250:3702 发送udp消息
    res = soap_send___wsdd__Probe(soap, "soap.udp://239.255.255.250:3702/", NULL, &req);

    if (res == -1) {
        printf("func:%s,line:%d.soap error: %d, %s, %s \n", __FUNCTION__, __LINE__, soap->error, *soap_faultcode(soap),
               *soap_faultstring(soap));
        res = soap->error;
    } else {
        do {
            printf("func:%s,line:%d.begin receive probe match, find dev count:%d.... \n", __FUNCTION__, __LINE__,
                   count);

            //接收 ProbeMatches,成功返回0,错误返回-1
            res = soap_recv___wsdd__ProbeMatches(soap, &resp);
            printf("func:%s,line:%d.result=%d \n", __FUNCTION__, __LINE__, res);
            if (res == -1) {
                break;
            } else {
                //读取服务器回应的Probematch消息
                printf("soap_recv___wsdd__Probe: __sizeProbeMatch = %d \n", resp.wsdd__ProbeMatches->__sizeProbeMatch);
                printf("Target EP Address : %s \n",
                       resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);
                printf("Target Type : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Types);
                printf("Target Service Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);
                printf("Target Metadata Version: %d \n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);
                printf("Target Scope Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Scopes->__item);
                count++;
            }
        } while (1);
    }

    return res;
}

int set_auth_info(struct soap *soap, const char *username, const char *password) {
    if (NULL == username) {
        printf("func:%s,line:%d.username is null.\n", __FUNCTION__, __LINE__);
        return -1;
    }
    if (NULL == password) {
        printf("func:%s,line:%d.password is nil.\n", __FUNCTION__, __LINE__);
        return -2;
    }

    int result = soap_wsse_add_UsernameTokenDigest(soap, NULL, username, password);

    return result;
}

int get_device_info(struct soap *soap, const char *username, const char *password, char *xAddr) {
    if (NULL == xAddr) {
        printf("func:%s,line:%d.dev addr is nil.\n", __FUNCTION__, __LINE__);
        return -1;
    }
    if (soap == NULL) {
        printf("func:%s,line:%d.malloc soap error.\n", __FUNCTION__, __LINE__);
        return -2;
    }

    struct _tds__GetDeviceInformation deviceInformation;
    struct _tds__GetDeviceInformationResponse deviceInformationResponse;

    set_auth_info(soap, username, password);

    int res = soap_call___tds__GetDeviceInformation(soap, xAddr, NULL, &deviceInformation, &deviceInformationResponse);

    if (NULL != soap) {
        printf("Manufacturer:%s\n", deviceInformationResponse.Manufacturer);
        printf("Model:%s\n", deviceInformationResponse.Model);
        printf("FirmwareVersion:%s\n", deviceInformationResponse.FirmwareVersion);
        printf("SerialNumber:%s\n", deviceInformationResponse.SerialNumber);
        printf("HardwareId:%s\n", deviceInformationResponse.HardwareId);
    }
    return res;
}

int get_capabilities(struct soap *soap, const char *username, const char *password, char *xAddr, char *mediaAddr) {
    struct _tds__GetCapabilities capabilities;
    struct _tds__GetCapabilitiesResponse capabilitiesResponse;

    set_auth_info(soap, username, password);
    int res = soap_call___tds__GetCapabilities(soap, xAddr, NULL, &capabilities, &capabilitiesResponse);
    if (soap->error) {
        printf("func:%s,line:%d.soap error: %d, %s, %s\n", __FUNCTION__, __LINE__, soap->error, *soap_faultcode(soap),
               *soap_faultstring(soap));
        return soap->error;
    }
    if (capabilitiesResponse.Capabilities == NULL) {
        printf("func:%s,line:%d.GetCapabilities  failed!  result=%d \n", __FUNCTION__, __LINE__, res);
    } else {
        printf("func:%s,line:%d.Media->XAddr=%s \n", __FUNCTION__, __LINE__,
               capabilitiesResponse.Capabilities->Media->XAddr);
        strcpy(mediaAddr, capabilitiesResponse.Capabilities->Media->XAddr);
    }
    return res;
}

int get_profiles(struct soap *soap, const char *username, const char *password, char *profileToken, char *xAddr) {
    struct _trt__GetProfiles profiles;
    struct _trt__GetProfilesResponse profilesResponse;
    set_auth_info(soap, username, password);
    int res = soap_call___trt__GetProfiles(soap, xAddr, NULL, &profiles, &profilesResponse);
    if (res == -1)
        //NOTE: it may be regular if result isn't SOAP_OK.Because some attributes aren't supported by server.
        //any question email leoluopy@gmail.com
    {
        printf("func:%s,line:%d.soap error: %d, %s, %s\n", __FUNCTION__, __LINE__, soap->error, *soap_faultcode(soap),
               *soap_faultstring(soap));
        return soap->error;
    }

    if (profilesResponse.Profiles != NULL) {
        if (profilesResponse.Profiles->Name != NULL) {
            printf("func:%s,line:%d.Profiles Name:%s\n", __FUNCTION__, __LINE__, profilesResponse.Profiles->Name);
        }
        if (profilesResponse.Profiles->token != NULL) {
            printf("func:%s,line:%d.Profiles Taken:%s\n", __FUNCTION__, __LINE__, profilesResponse.Profiles->token);
            strcpy(profileToken, profilesResponse.Profiles->token);
        }
    } else {
        printf("func:%s,line:%d.Profiles Get inner Error\n", __FUNCTION__, __LINE__);
    }
    return res;
}

int get_rtsp_uri(struct soap *soap, const char *username, const char *password, char *profileToken, char *xAddr) {
    struct _trt__GetStreamUri streamUri;
    struct _trt__GetStreamUriResponse streamUriResponse;
    streamUri.StreamSetup = (struct tt__StreamSetup *) soap_malloc(soap, sizeof(struct tt__StreamSetup));
    streamUri.StreamSetup->Stream = 0;
    streamUri.StreamSetup->Transport = (struct tt__Transport *) soap_malloc(soap, sizeof(struct tt__Transport));
    streamUri.StreamSetup->Transport->Protocol = 0;
    streamUri.StreamSetup->Transport->Tunnel = 0;
    streamUri.StreamSetup->__size = 1;
    streamUri.StreamSetup->__any = NULL;
    streamUri.StreamSetup->__anyAttribute = NULL;
    streamUri.ProfileToken = profileToken;
    set_auth_info(soap, username, password);
    int res = soap_call___trt__GetStreamUri(soap, xAddr, NULL, &streamUri, &streamUriResponse);
    if (soap->error) {
        printf("func:%s,line:%d.soap error: %d, %s, %s\n", __FUNCTION__, __LINE__, soap->error, *soap_faultcode(soap),
               *soap_faultstring(soap));
        return soap->error;
    }
    printf("func:%s,line:%d.RTSP uri is :%s \n", __FUNCTION__, __LINE__, streamUriResponse.MediaUri->Uri);
    return res;
}

int get_snapshot(struct soap *soap, const char *username, const char *password, char *profileToken, char *xAddr) {
    struct _trt__GetSnapshotUri snapshotUri;
    struct _trt__GetSnapshotUriResponse snapshotUriResponse;

    set_auth_info(soap, username, password);
    snapshotUri.ProfileToken = profileToken;
    int res = soap_call___trt__GetSnapshotUri(soap, xAddr, NULL, &snapshotUri, &snapshotUriResponse);
    if (soap->error) {
        printf("func:%s,line:%d.soap error: %d, %s, %s\n", __FUNCTION__, __LINE__, soap->error, *soap_faultcode(soap),
               *soap_faultstring(soap));
        return soap->error;
    }
    printf("func:%s,line:%d.Snapshot uri is :%s \n", __FUNCTION__, __LINE__,
           snapshotUriResponse.MediaUri->Uri);
    return res;
}

/*
int main() {
    struct soap *soap = NULL;
    soap = new_soap(soap);
    const char username[] = "admin";
    const char password[] = "admin";
    char serviceAddr[] = "http://40.40.40.101:80/onvif/device_service";

    discovery(soap);

    get_device_info(soap, username, password, serviceAddr);

    char mediaAddr[200] = {'\0'};
    get_capabilities(soap, username, password, serviceAddr, mediaAddr);

    char profileToken[200] = {'\0'};
    get_profiles(soap, username, password, profileToken, mediaAddr);

    get_rtsp_uri(soap, username, password, profileToken, mediaAddr);

    get_snapshot(soap, username, password, profileToken, mediaAddr);

    del_soap(soap);
}
*/

client.h

//
// Created by admin on 2022/3/3.
//

#ifndef ONVIF_CGO_CLIENT_H
#define ONVIF_CGO_CLIENT_H


typedef struct soap *P_Soap;

extern struct soap *new_soap(struct soap *soap);

extern void del_soap(struct soap *soap);

extern int discovery(struct soap *soap);

extern int get_device_info(struct soap *soap, const char *username, const char *password, char *xAddr);

extern int get_capabilities(struct soap *soap, const char *username, const char *password, char *xAddr, char *mediaAddr);

extern int
get_profiles(struct soap *soap, const char *username, const char *password, char *profileToken, char *xAddr);

extern int
get_rtsp_uri(struct soap *soap, const char *username, const char *password, char *profileToken, char *xAddr);

extern int
get_snapshot(struct soap *soap, const char *username, const char *password, char *profileToken, char *xAddr);

#endif //ONVIF_CGO_CLIENT_H

4.2 修改后的cmake

cmake_minimum_required(VERSION 3.0)
project(onvif-cgo)

set(CMAKE_C_FLAGS "-DWITH_DOM -DWITH_OPENSSL -DWITH_NONAMESPACES")
aux_source_directory(./soap/ SRC_LIST)
include_directories(./ /usr/include/ ./soap/custom)
link_directories(~/ /usr/local/ /usr/lib/)
#add_executable(gsoap-onvif ${SRC_LIST} client.c client.h ./soap/custom)
#add_executable(gsoap-onvif ${SRC_LIST} ./soap/custom device.c)
#add_executable(discovery_onvif ${SRC_LIST} main.c ./soap/custom)
#target_link_libraries(gsoap-onvif -lpthread -ldl -lssl -lcrypto)
#ADD_LIBRARY(c_onvif SHARED ${SRC_LIST} client.c)
ADD_LIBRARY(c_onvif_static STATIC ${SRC_LIST} client.c client.h ./soap/custom)

4.3 cgo代码

package main

/*
#cgo CFLAGS: -I ./ -I /usr/local/ -I ~/work/gsoap-2.8/gsoap/
#cgo LDFLAGS: -L ./build -lc_onvif_static -lpthread -ldl -lssl -lcrypto
#include "client.h"
#include "malloc.h"
*/
import "C"

import (
    "fmt"
    "unsafe"
)

func main() {
    var soap C.P_Soap
    soap = C.new_soap(soap)
    username := C.CString("admin")
    password := C.CString("admin")
    serviceAddr := C.CString("http://40.40.40.101:80/onvif/device_service")

    C.discovery(soap)

    C.get_device_info(soap, username, password, serviceAddr)

    mediaAddr := [200]C.char{}
    C.get_capabilities(soap, username, password, serviceAddr, &mediaAddr[0])
    fmt.Println(C.GoString(&mediaAddr[0]))

    profileToken := [200]C.char{}
    C.get_profiles(soap, username, password, &profileToken[0], &mediaAddr[0])
    fmt.Println(C.GoString(&profileToken[0]))

    C.get_rtsp_uri(soap, username, password, &profileToken[0], &mediaAddr[0])

    C.get_snapshot(soap, username, password, &profileToken[0], &mediaAddr[0])

    C.del_soap(soap)

    C.free(unsafe.Pointer(username))
    C.free(unsafe.Pointer(password))
    C.free(unsafe.Pointer(serviceAddr))
}
GOOS=linux GOARCH=amd64 CGO_ENABLE=1 go build -o onvif_cgo main.go

4.4 运行结果

在这里插入图片描述

5. cgo偶发的fatal error: unexpected signal during runtime execution问题

在运行期间我们发现:编译出的cgo可执行程序运行时会随机性出现错误异常退出,错误信息类似:"fatal error: unexpected signal during runtime execution

signal SIGSEGV: segmentation violation xxx"

zy@LS2-R910CQQT:/mnt/d/code/onvif_cgo$ ./onvif_cgo
func:new_soap,line:23.new soap success!
func:discovery,line:48.discovery dev!
func:discovery,line:55.uuid = 464A4854-4656-5242-4530-110000000000
func:discovery,line:89.begin receive probe match, find dev count:0....
func:discovery,line:94.result=-1
Manufacturer:UNIVIEW
Model:IPC-S632-IR@P-X33-VG
FirmwareVersion:CIPC-B2302.3.65.L02.211207
SerialNumber:210235C52Q321A000109
HardwareId:IPC-S632-IR@P-X33-VG
fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x2 addr=0xe78000 pc=0x61a051]

runtime stack:
runtime.throw(0x9a7e10, 0x2a)
        /usr/local/go/src/runtime/panic.go:1117 +0x72
runtime.sigpanic()
        /usr/local/go/src/runtime/signal_unix.go:718 +0x2e5

goroutine 1 [syscall]:
runtime.cgocall(0x5b4430, 0xc00004dee8, 0xc000000000)
        /usr/local/go/src/runtime/cgocall.go:154 +0x5b fp=0xc00004deb8 sp=0xc00004de80 pc=0x552f5b
main._Cfunc_get_capabilities(0x7f06a87d0010, 0xe58680, 0xe586a0, 0xe57f70, 0x0)
        _cgo_gotypes.go:96 +0x48 fp=0xc00004dee8 sp=0xc00004deb8 pc=0x5b3ce8
main.main.func4(0xc00004df50, 0xe58680, 0xe586a0, 0xe57f70, 0xc000000000)
        /mnt/d/code/onvif_cgo/main.go:31 +0x77 fp=0xc00004df28 sp=0xc00004dee8 pc=0x5b4317
main.main()
        /mnt/d/code/onvif_cgo/main.go:31 +0x145 fp=0xc00004df88 sp=0xc00004df28 pc=0x5b40a5
runtime.main()
        /usr/local/go/src/runtime/proc.go:225 +0x256 fp=0xc00004dfe0 sp=0xc00004df88 pc=0x581736
runtime.goexit()
        /usr/local/go/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc00004dfe8 sp=0xc00004dfe0 pc=0x5ad221

如下图所示(运行几次可能就会出现,也可能第一次出现,后续又好了,弄了很久):

在这里插入图片描述

在搜索一些资料后发现目前似乎go的高版本解决了该问题,如果使用cgo就很可能出现类似问题,之前的go的dns库等很多借助cgo的库都出现过类似问题,似乎是glibc相关的内存栈分割和go本身存在差异导致的。

原文:

https://marcan.st/2017/12/debugging-an-evil-go-runtime-bug/

csdn转载:

https://blog.csdn.net/dev_csdn/article/details/78813904

github上的修改点(可以看出tag版本为1.18rc1):

https://github.com/golang/go/commit/a158382b1c9c0b95a7d41865a405736be6bc585f

然后我在wsl上安装1.18rc1做了下测试(截至到我处理该问题时稳定版本只到1.17.x,1.18.x目前还是不稳定版本):

go下载地址:https://golang.google.cn/dl/

# download go
sudo wget https://golang.google.cn/dl/go1.18rc1.linux-amd64.tar.gz

# tar
sudo tar -zxvf go1.18rc1.linux-amd64.tar.gz -C /usr/local/

#set env
vim ~/.bashrc

# add this to .bashrc
export PATH=$PATH:/usr/local/go/bin
export GOPATH=~/work/goStudy
export PATH=$PATH:$GOPATH/bin
# Enable the go modules feature
export GO111MODULE=on
# Set the GOPROXY environment variable
export GOPROXY=https://goproxy.io

source ~/.bashrc

# go version and env
go version
go env

然后我用1.18版本重新编译测试,运行很多次都是正常的,没有再出现上述分割违例错误。

6. 整体项目结构

zy@LS2-R910CQQT:/mnt/d/code/onvif_cgo$ tree -a -I ".idea|build|.git"
.
├── CMakeLists.txt
├── client.c
├── client.h
├── onvif_cgo
└── soap
    ├── DeviceBinding.nsmap
    ├── MediaBinding.nsmap
    ├── PullPointSubscriptionBinding.nsmap
    ├── RemoteDiscoveryBinding.nsmap
    ├── custom
    │   ├── README.txt
    │   ├── chrono_duration.cpp
    │   ├── chrono_duration.h
    │   ├── chrono_time_point.cpp
    │   ├── chrono_time_point.h
    │   ├── duration.c
    │   ├── duration.h
    │   ├── float128.c
    │   ├── float128.h
    │   ├── int128.c
    │   ├── int128.h
    │   ├── long_double.c
    │   ├── long_double.h
    │   ├── long_time.c
    │   ├── long_time.h
    │   ├── qbytearray_base64.cpp
    │   ├── qbytearray_base64.h
    │   ├── qbytearray_hex.cpp
    │   ├── qbytearray_hex.h
    │   ├── qdate.cpp
    │   ├── qdate.h
    │   ├── qdatetime.cpp
    │   ├── qdatetime.h
    │   ├── qstring.cpp
    │   ├── qstring.h
    │   ├── qtime.cpp
    │   ├── qtime.h
    │   ├── struct_timeval.c
    │   ├── struct_timeval.h
    │   ├── struct_tm.c
    │   ├── struct_tm.h
    │   ├── struct_tm_date.c
    │   └── struct_tm_date.h
    ├── dom.c
    ├── dom.h
    ├── duration.c
    ├── duration.h
    ├── mecevp.c
    ├── mecevp.h
    ├── onvif.h
    ├── smdevp.c
    ├── smdevp.h
    ├── soapC.c
    ├── soapClient.c
    ├── soapH.h
    ├── soapStub.h
    ├── stdsoap2.h
    ├── stdsoap2_ssl.c
    ├── struct_timeval.c
    ├── struct_timeval.h
    ├── threads.c
    ├── threads.h
    ├── typemap.dat
    ├── wsaapi.c
    ├── wsaapi.h
    ├── wsdd.nsmap
    ├── wsseapi.c
    └── wsseapi.h

2 directories, 69 files

7. 最后

获取到rtsp流数据后,我们可以结合ffmpeg做一些对rtsp流的编解码,之后可以针对根据获取到的音频、视频流等做AI处理。在一些嵌入式环境下还可结合live555做音视频直播。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昵称系统有问题

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值