什么是ONVIF协议
ONVIF(开放式网络视频接口论坛)是一个全球性的开放式行业论坛,旨在促进开发和使用基于物理IP的安全产品接口的全球开放标准。
ONVIF规范的目标是建立一个网络视频框架协议,使不同厂商生产的网络视频产品完全互通。
1:设备管控和控制:通过Web Services提供接口,使用SOAP协议进行数据交互。
2:音视频流:通过RTP/RTSP进行传输。
3:统一接口:抽象了功能的接口,统一了对设备配置和操作的方式,使得控制端不再关心设备的型号,而是关注设备提供的Web Service23。
学习并使用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.StreamSetupgetStreamUri.StreamSetup = &streamSetup;soap_call___trt__GetStreamUri(soap, device_url, NULL, &getStreamUri, &streamUriResp);if (soap->error == SOAP_OK && streamUriResp.MediaUri) {// 动态分配字符串以存储RTSP URIrtspUri = 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.是自己的程序,写好编译通过即可。