一、前言说明
前段时间做了个需求,需要对多个监控设备同时云台控制,至于为什么有这个需求可以先不管,给钱实现就行,马不停蹄的开干。首选直接用onvif协议,用户只需要在局域网运行,如果需要在公网运行,则要采用gb28181协议,28181协议有个缺点就是上线要慢慢等,平台侧慢慢等设备主动连接,一般在60s内才能上线。而onvif协议刚好是反过来,通过组播搜索获取到所有设备,然后拿到rtsp视频流地址即可,简单易用,总之两种方式都可以实现云台控制,看你怎么方便怎么来,满足实际用户需求即可。
得益于之前就已经将onvif协议滚瓜烂熟了,云台控制就是发个post请求到对应设备地址即可,但是有个前提,对应的请求数据要带上用户认证信息,不然都是非法请求,会被禁止执行,次数多了还会被禁用30分钟惩罚等,之前已经定义了onvifdevice对象,每一个设备都对应一个这个对象,然后对多个云台操作,就是搞个循环每次都取出一个对象执行ptzControl即可,需要用QMetaObject::invokeMethod异步执行,而不是同步执行,不然会阻塞,做不到同时支持。其实我这个也不是完完全全的同时执行,毕竟现在都是非实时操作系统,都是操作系统调度的执行指令,只要不超过肉眼可见的时差就可以接受,就可以认为是同时执行,实际测试下来,十几个设备执行的误差时间在0.2s内,基本上都是可以接受的。
二、效果图
三、功能特点
- 广播搜索设备,支持IPC和NVR,依次返回。
- 可选择不同的网卡IP进行对应网段设备的搜索。
- 依次获取Onvif地址、Media地址、Profile文件、Rtsp地址。
- 可对指定的Profile获取视频流Rtsp地址,比如主码流地址、子码流地址。
- 可对每个设备设置Onvif用户信息,用于认证获取详细信息。
- 可实时预览摄像机图像。
- 支持云台控制,可上下左右调节云台,支持绝对移动、相对移动、连续移动三种方式,可对图像拉近拉远。
- 支持获取预置位集合、调用预置位、添加预置位、删除预置位等。
- 支持图片参数设置,包括亮度、对比度、饱和度、锐度等。
- 支持Qt4和Qt6任意Qt版本以及后续Qt版本。
- 支持任意编译器,亲测mingw、msvc、gcc、clang。
- 支持任意操作系统,亲测xp、win7、win10、android、linux、嵌入式linux、树莓派全志H3等。
- 支持任意Onvif摄像机和NVR,亲测海康、大华、宇视、天地伟业、华为、海思芯片内核等,可定制开发。
- 支持对指定IP地址及onvif地址进行单播搜索,比如跨网段情况下非常有用。
- 支持指定过滤条件过滤搜索设备,比如只搜索某个网段的设备或者针对某个地址的设备。
- 支持搜索间隔和搜索策略设置,保证所有设备搜索回来,在大量设备现场很有用(亲测上千个摄像机现场,搜索回来的设备数量比摄像机厂家自带搜索工具还要准确)。
- 可对设备进行重启、网络参数获取等。
- 支持各种事件订阅(入侵报警、越界报警、遮挡报警等)、Onvif抓图等操作。
- 支持NTP校时和时间同步设置。
- 支持OSD相关操作,可以增删改查OSD信息。
- 内置了线程实时执行Onvif指令队列,排队最大速度的执行对应的指令,执行结果信号发出。
- 采用的最底层的TCP+UDP通信机制,原创最底层协议解析,纯QtWidget编写。
- 超级小巧轻量,总共约3000行代码,不依赖任何第三方的库和组件,跨平台。
- 封装好了通用的数据发送和接收解析的函数,可以非常方便的自行拓展其他Onvif处理。
- 工具上提供了收发数据文本框,显示收发的数据,方便查看和分析。
- 支持所有Onvif设备,代码工整,接口友好,直接引入pri即可使用。
四、相关地址
- 国内站点:https://gitee.com/feiyangqingyun
- 国际站点:https://github.com/feiyangqingyun
- 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
- 文件地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 名称:bin_video_onvif
五、相关代码
#include "onvifptzmanage.h"
#include "qthelper.h"QTableWidget *OnvifPtzManage::tableWidget = NULL;
QList<OnvifDevice *> OnvifPtzManage::devices = QList<OnvifDevice *>();SINGLETON_IMPL(OnvifPtzManage)
OnvifPtzManage::OnvifPtzManage(QObject *parent) : QObject(parent)
{//实例化onvif搜索类并绑定信号槽onvifSearch = new OnvifSearch(this);connect(onvifSearch, SIGNAL(receiveDevice(OnvifDeviceInfo)), this, SLOT(receiveDevice(OnvifDeviceInfo)));
}bool OnvifPtzManage::search(const QString &localIP, const QString &deviceIP)
{//开启了清空则先清空if (AppConfig::SearchClear) {qDeleteAll(devices);devices.clear();if (tableWidget) {tableWidget->clearContents();}}return onvifSearch->search(localIP, deviceIP);
}void OnvifPtzManage::ptzControl(int position)
{//根据按下的不同部位发送云台控制命令//1. x、y、z 范围都在0-1之间。//2. x为负数,表示左转,x为正数,表示右转。//3. y为负数,表示下转,y为正数,表示上转。//4. z为正数,表示拉近,z为负数,表示拉远。//5. 通过x和y的组合,来实现云台的控制。//6. 通过z的组合,来实现焦距控制。//7. 0-8依次表示底部/左下角/左侧/左上角/顶部/右上角/右侧/右下角/中间//每次移动的步长qreal step = 0.5;//控制类型quint8 type = 3;//移动距离qreal x = 0.0;qreal y = 0.0;qreal z = 0.0;if (position == 0) {y = -step;} else if (position == 1) {x = -step;y = -step;} else if (position == 2) {x = -step;} else if (position == 3) {x = -step;y = step;} else if (position == 4) {y = step;} else if (position == 5) {x = step;y = step;} else if (position == 6) {x = step;} else if (position == 7) {x = step;y = -step;} else if (position == 8) {type = 0;} else if (position == 9) {//变倍+z = step;} else if (position == 10) {//变倍-z = -step;} else if (position == 11) {//变焦+} else if (position == 12) {//变焦-} else if (position == 13) {//光圈+} else if (position == 14) {//光圈-} else {type = 0;}foreach (OnvifDevice *device, devices) {QString token = device->getProfileToken();if (!token.isEmpty() && !device->getPtzUrl().isEmpty()) {//device->ptzControl(type, token, x, y, z);QMetaObject::invokeMethod(device, "ptzControl", Qt::QueuedConnection, Q_ARG(quint8, type), Q_ARG(QString, token), Q_ARG(qreal, x), Q_ARG(qreal, y), Q_ARG(qreal, z));}}
}void OnvifPtzManage::receiveDevice(const OnvifDeviceInfo &deviceInfo)
{//如果已经存在则不添加foreach (OnvifDevice *device, devices) {if (device->getOnvifAddr() == deviceInfo.onvifAddr) {return;}}//qDebug() << deviceInfo;QString addr = deviceInfo.onvifAddr;QString ip = deviceInfo.deviceIp;if (tableWidget) {QTableWidgetItem *itemName = new QTableWidgetItem(deviceInfo.name);QTableWidgetItem *itemAddr = new QTableWidgetItem(deviceInfo.onvifAddr);//自动选中当前设备QCheckBox *itemCk = new QCheckBox(tableWidget);itemCk->setChecked(true);//取IP地址转成整型作为排序依据quint32 ipAddr = OnvifHelper::ipv4StringToInt(ip);QTableWidgetItem *itemIPAddr = new QTableWidgetItem;itemIPAddr->setData(Qt::DisplayRole, ipAddr);int row = devices.count();tableWidget->setCellWidget(row, 0, itemCk);tableWidget->setItem(row, 0, new QTableWidgetItem);tableWidget->setItem(row, 1, itemIPAddr);tableWidget->setItem(row, 2, new QTableWidgetItem);tableWidget->setItem(row, 3, new QTableWidgetItem);tableWidget->setItem(row, 4, itemName);tableWidget->setItem(row, 5, itemAddr);tableWidget->setItem(row, 6, new QTableWidgetItem);tableWidget->setItem(row, 7, new QTableWidgetItem);tableWidget->setItem(row, 8, new QTableWidgetItem);tableWidget->setItem(row, 9, new QTableWidgetItem);//重新排序tableWidget->sortByColumn(1, Qt::AscendingOrder);tableWidget->setCurrentCell(row, 0);}//实例化设备对象添加到设备队列OnvifDevice *device = new OnvifDevice(this);device->setOnvifAddr(addr);device->setDeviceInfo(deviceInfo);devices << device;
}