ONVIF协议网络摄像机客户端使用gsoap获取RTSP流地址GStreamer拉流播放

什么是ONVIF协议


        
ONVIF(开放式网络视频接口论坛)是一个全球性的开放式行业论坛,旨在促进开发和使用基于物理IP的安全产品接口的全球开放标准。

        ONVIF规范的目标是建立一个网络视频框架协议,使不同厂商生产的网络视频产品完全互通。

        1:设备管控和控制:通过Web Services提供接口,使用SOAP协议进行数据交互。

        2:音视频流‌:通过RTP/RTSP进行传输。

        3:‌统一接口‌:抽象了功能的接口,统一了对设备配置和操作的方式,使得控制端不再关心设备的型号,而是关注设备提供的Web Service‌23。

        学习并使用ONVIF协议我个人觉得要理清楚角色,就网络摄像机而言,对于应用场景可分为客户端和服务端。使用的网络摄像机就是服务端,也就是了解ONVIF出现最多的web services服务端,想要使用这个对接网络摄像机就是从这里面获取相关信息。    

        开发客户端通过ONVIF规范接口和网络摄像机进行通讯,其中常见的功能有:

        1:获取网络相机的基本信息

        2:修改网络的系统日期、时间

        3:修改网络摄像机的网络配置(IP、子网掩码等)

        4:获取、修改网络摄像头的各种参数(视频分辨率、码率、帧率、OSD(叠加信息)、云平台控制)

        在onvif协议中,有一些列profile的技术规格。引入这些规格是使得终端用户能够更容易区分各个profile所支持的特性,而无需确定各个版本间的兼容性。

        其中应用于视频流的主要是使用profile S 技术规格。

        ONVIF官方profile地址

在学习ONVIF协议时会出现很多名词:Web Service、WSDL、XML、SOAP。刚接触的初学者会一脸懵逼,这些都是什么玩意有什么用,以我对他们的了解而言,这些也就了解一下皮毛就行了,不用纯手搓从底层开始搓。因为有一个很强大的工具来简化这个过程那即是gsoap

gsoap官网:http://www.cs.fsu.edu/~engelen/soap.html

需要什么功能在使用这个工具的时候直接连接。

就像这样的链接:

http://www.onvif.org/onvif/ver20/util/operationIndex.html

http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl

http://www.onvif.org/onvif/ver10/deviceio.wsdl

http://www.onvif.org/onvif/ver10/event/wsdl/event.wsdl

http://www.onvif.org/onvif/ver20/analytics/wsdl/analytics.wsdl

http://www.onvif.org/onvif/ver10/analyticsdevice.wsdl

http://www.onvif.org/onvif/ver10/display.wsdl

http://www.onvif.org/onvif/ver20/imaging/wsdl/imaging.wsdl

http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl

http://www.onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl

http://www.onvif.org/onvif/ver10/Receiver.wsdl

http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl

http://www.onvif.org/onvif/ver10/Recording.wsdl

http://www.onvif.org/onvif/ver10/Replay.wsdl

http://www.onvif.org/onvif/ver10/Search.wsdl

需要什么链接什么

2:怎么使用

我是基于linux平台来进行相关的开发,那就需要自己去gsoap官网下载对应的工具库然后编译,很简单这个。

1:去官网下载相关的文件http://sourceforge.net/projects/gsoap2

2:解压压缩包然后进行配置

./configure --prefix=/usr/local/gSOAP

3:编译 make

4:安装:sudo make install

然后生成的编译好的库就在/usr/local/gSOAP这个路径下面

然后去文件夹bin里面找到

这两个工具程序,这俩是用来生成头文件框架的直接用就可以了

先把gsoap库里面的文件拷贝到自己的工程中

然后需要什么链接什么:

./wsdl2h -P -x -c -s -t ./typemap.dat -o  onvif.h https://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl https://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl https://www.onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl

./wsdl2h -P -x -c -s -t ./typemap.dat -o  onvif.h https://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl https://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl https://www.onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl

根据上面的介绍,需要什么功能后面连接什么wsdl。

然后运行

soapcpp2 -C -c -x -I import -I custom xxx.h

然后就会生成这些文件了

那么基本框架就OK了

然后就是自己写程序了

我的程序

#include <gst/gst.h>
#include "soapH.h"

char* onvif() {
    struct soap *soap;
    struct _trt__GetProfilesResponse profilesResp;
    struct _trt__GetStreamUriResponse streamUriResp;
    char *rtspUri = NULL; // 用于存储RTSP URI的动态分配字符串

    // 初始化gSOAP运行时上下文
    soap = soap_new();
    if (soap == NULL) {
        fprintf(stderr, "soap_new failed\n");
        return NULL;
    }

    // 设置设备的端点URL和认证信息
    const char *device_url = "http://xxx.xxx.20.10:8000/onvif/device_service";
    soap->userid = "admin";
    soap->passwd = "admin";

    // 调用Web服务获取配置文件
    struct _trt__GetProfiles getProfiles;
    soap_call___trt__GetProfiles(soap, device_url, NULL, &getProfiles, &profilesResp);

    if (soap->error == SOAP_OK && profilesResp.Profiles) {
        // 假设我们使用第一个配置文件
        struct tt__Profile *profile = profilesResp.Profiles;

        // 获取每个配置文件的RTSP地址
        struct _trt__GetStreamUri getStreamUri;
        getStreamUri.ProfileToken = profile->token;

        // 分配并初始化Transport结构体
        struct tt__Transport *transport = (struct tt__Transport*)soap_malloc(soap, sizeof(struct tt__Transport));
        transport->Protocol = tt__TransportProtocol__RTSP; // 确保枚举值正确

        // 初始化StreamSetup结构体
        struct tt__StreamSetup streamSetup;
        streamSetup.Stream = tt__StreamType__RTP_Unicast; // 确保枚举值正确
        streamSetup.Transport = transport;

        // 赋值给getStreamUri.StreamSetup
        getStreamUri.StreamSetup = &streamSetup;

        soap_call___trt__GetStreamUri(soap, device_url, NULL, &getStreamUri, &streamUriResp);

        if (soap->error == SOAP_OK && streamUriResp.MediaUri) {
            // 动态分配字符串以存储RTSP URI
            rtspUri = malloc(strlen("rtsp://") + strlen(streamUriResp.MediaUri->Uri) + 1);
            if (rtspUri != NULL) {
                sprintf(rtspUri, "%s", streamUriResp.MediaUri->Uri);
            } else {
                fprintf(stderr, "Memory allocation failed for RTSP URI\n");
            }
        } else {
            soap_print_fault(soap, stderr);
        }

    } else {
        soap_print_fault(soap, stderr);
    }

    // 清理资源
    soap_destroy(soap);
    soap_end(soap);
    soap_free(soap);

    return rtspUri; // 返回动态分配的RTSP URI字符串
}

/* 回调函数 */
static void on_pad_added(GstElement *element, GstPad *pad, gpointer data);

int main(int argc, char *argv[]) {
    GstElement *pipeline, *source, *depay, *parse, *decoder, *convert, *sink;
    GstBus *bus;
    GstMessage *msg;
    GstStateChangeReturn ret;
    char *rtspUri = onvif();

    /* 初始化 GStreamer库 */
    gst_init(&argc, &argv);

    /* 创建各个元素 */
    source = gst_element_factory_make("rtspsrc", "source");// RTSP 源
    depay = gst_element_factory_make("rtph264depay", "depay");//H.264 RTP 解封装
    parse = gst_element_factory_make("h264parse", "parse");// H.264 解析器
    decoder = gst_element_factory_make("mppvideodec", "decoder");// MPP 视频解码器
    convert = gst_element_factory_make("videoconvert", "convert");// 视频格式转换
    sink = gst_element_factory_make("autovideosink", "sink");// 自动选择视频输出

    if (!source || !depay || !parse || !decoder || !convert || !sink) {
        g_printerr("Not all elements could be created.\n");
        return -1;
    }

    /* Create the empty pipeline */
    pipeline = gst_pipeline_new("test-pipeline");
    if (!pipeline) {
        g_printerr("无法创建管道\n");
        gst_object_unref(source);
        gst_object_unref(depay);
        gst_object_unref(parse);
        gst_object_unref(decoder);
        gst_object_unref(convert);
        gst_object_unref(sink);
        return -1;
    }

     /* 设置源的属性 */
    g_object_set(G_OBJECT(source), "location", rtspUri, NULL);

     /* 构建管道 */
    gst_bin_add_many(GST_BIN(pipeline), source, depay, parse, decoder, convert, sink, NULL);

     /* 手动连接元素 */
    if (gst_element_link(depay, parse) != TRUE ||
        gst_element_link(parse, decoder) != TRUE ||
        gst_element_link(decoder, convert) != TRUE ||
        gst_element_link(convert, sink) != TRUE) {
        g_printerr("Elements could not be linked.\n");
        gst_object_unref(pipeline);
        return -1;
    }

    /* 动态连接 rtspsrc 到 rtph264depay */
    g_signal_connect(source, "pad-added", G_CALLBACK(on_pad_added), depay);

    /* 开始播放 */
    ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
    if (ret == GST_STATE_CHANGE_FAILURE) {
        g_printerr("Unable to set the pipeline to the playing state.\n");
        gst_object_unref(pipeline);
        return -1;
    }

    /* 等待错误或 EOS(End-Of-Stream)消息 */
    bus = gst_element_get_bus(pipeline);
    msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

    /* 解析消息 */
    if (msg != NULL) {
        GError *err;
        gchar *debug_info;

        switch (GST_MESSAGE_TYPE(msg)) {
            case GST_MESSAGE_ERROR:
                gst_message_parse_error(msg, &err, &debug_info);
                g_printerr("Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message);
                g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none");
                g_clear_error(&err);
                g_free(debug_info);
                break;
            case GST_MESSAGE_EOS:
                g_print("达到流末尾.\n");
                break;
            default:
                g_printerr("收到意外的消息.\n");
                break;
        }
        gst_message_unref(msg);
    }

    /* Free resources */
    gst_object_unref(bus);
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);
    return 0;
}
/* 回调函数,用于动态链接 rtspsrc 到 rtph264depay */
static void on_pad_added(GstElement *element, GstPad *pad, gpointer data) {
    GstPad *sinkpad;
    GstElement *depay = (GstElement *)data;

    g_print("Received new pad '%s' from '%s':\n", GST_PAD_NAME(pad), GST_ELEMENT_NAME(element));

    /* If our depay is already linked, we have nothing to do here */
    sinkpad = gst_element_get_static_pad(depay, "sink");
    if (sinkpad && gst_pad_is_linked(sinkpad)) {
        g_print("We are already linked. Ignoring.\n");
        gst_object_unref(sinkpad);
        return;
    }

     /* 尝试链接 */
    if (sinkpad && gst_pad_link(pad, sinkpad) != GST_PAD_LINK_OK) {
        g_print("Type of the pad does not match type of sink pad.\n");
    } else {
        g_print("Link succeeded.\n");
    }

    /* 释放 sink pad */
    if (sinkpad) {
        gst_object_unref(sinkpad);
    }
}

我看好多文章都没说怎么编译的,直接用gcc就可以

就像这样,也可以把这些.c文件编译库连接,这样好一点。calcclient.是自己的程序,写好编译通过即可。

上一篇:数据并行、模型并行与张量并行:深度学习中的并行计算策略(中英双语)


下一篇:Day48 | 动态规划 :线性DP 编辑距离