## 一、前言
视频监控系统中的图文警情模块,是通过Onvif协议的事件订阅拿到的,通过事件订阅后,设备的各种报警事件比如入侵报警/遮挡报警/越界报警/开关量报警等,触发后都会主动往订阅者发送,而且一般都是会发送两次,一次报警开始,一次报警结束,每一种事件都对应有文字字符协议的约定,所以可以搞个字典表,哪种英文表示哪种报警字样,方便区分,因为可能有厂家有自己定义的协议,如果写死在程序中,后期需要增删改那就不方便了,所以搞个记事本
文件或者json文件专门存储这个字典表,用户可以打开进行修改。
监控系统有个日志记录表,之前存储的各种用户操作日志和收到的报警日志等,专门留有一个备注字段,这个字段现在用来存储报警
图片或者报警录像文件的路径,这样用户在查阅这些日志记录的时候,可以双击当前选中的记录,如果是图片则弹出报警图片预览,如果是报警录像文件则弹出视频播放,这样相当于在查阅日志的时候,也能一一对应当时的报警状态图片或者录像文件,录像文件的优先级高于报警图片,报警后会先触发抓图,如果是约定的需要触发报警录像的报警事件,才会录像。
演示视频 [
https://www.bilibili.com/video/BV1Gp4y1N7bm](https://www.bilibili.com/video/BV1Gp4y1N7bm)
## 二、效果图


## 三、体验地址
1. 国内站点:[
https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)
2. 国际站点:[
https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
3. 个人作品:[
https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
4. 体验地址:[
https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g](https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g) 提取码:01jf 文件名:bin_video_system。
## 四、相关代码
```cpp
void DeviceOnvif::receiveEvent(const
QString &url, const OnvifEventInfo &event)
{
//可能临时关闭了事件订阅
if (!AppConfig::OnvifEvent) {
return;
}
//事件内容存放在结构体
数据中
//qDebug() << TIMEMS << event;
QString name = event.dataName;
QVariant value = event.dataValue;
//有多种关键字可以自行过滤需要的
if (!eventName.contains(name)) {
//对应收到的未知的报警可以自行根据厂家规则约定加到 config/event.txt 事件字典表中
qDebug() << TIMEMS << "未知报警事件" << QtHelper::getIP(url) << name << value;
return;
} else {
qDebug() << TIMEMS << "收到报警事件" << QtHelper::getIP(url) << name << value;
}
//过滤不在本系统中的设备发过来的报警
int index = DbData::IpcInfo_OnvifAddr.indexOf(url);
if (index < 0) {
return;
}
//true/1 false/0 字符转成bool类型方便统一判断
bool alarm = value.toBool();
QString ipcName = DbData::IpcInfo_IpcName.at(index);
int i = eventName.indexOf(name);
QString info = (alarm ? eventAlarm.at(i) : eventNormal.at(i));
//添加到信息栏
QString msg = QString("%1%2").arg(ipcName).arg(info);
DeviceHelper::addMsg(msg, alarm ? 2 : 0);
//抓拍对应通道
图像 QString flag, fileName;
if (alarm) {
QString rtspMain = DbData::IpcInfo_RtspMain.at(index);
QString rtspSub = DbData::IpcInfo_RtspSub.at(index);
VideoWidget *videoWidget = VideoManage::Instance()->getVideoWidget(rtspMain, rtspSub);
if (videoWidget) {
flag = videoWidget->objectName();
fileName = QString("%1/image_alarm/%2/%3_%4.jpg").arg(QtHelper::appPath()).arg(QDATE).arg(flag).arg(STRDATETIMEMS);
videoWidget->snap(fileName);
//放入队列
线程处理(保证抓图完成)
mutex.lock();
listMsg << info;
listFile << fileName;
mutex.unlock();
}
}
//右下角弹出提示
if (AppConfig::TipInterval != 10000) {
QtHelper::showTipBox("提示", msg, AppConfig::FullScreen, true, AppConfig::TipInterval);
}
//暂定开关量报警弹出报警视频并录像
if (alarm && info == "开关量报警") {
QString url = DbData::getRtspAddr(index);
//弹出了报警视频则日志记录中优先存储报警录像文件的名称(直接用报警图片的路径以便统一)
fileName.replace(".jpg", ".mp4");
fileName.replace("image_alarm", "video_alarm");
DeviceHelper::showVideo(url, flag, AppConfig::AlarmSaveTime, fileName);
}
//播放报警声音并插入到日志记录
数据库(报警带上报警图片路径)
if (alarm) {
DeviceHelper::playAlarm("8.wav");
DbQuery::addUserLog(flag.right(2), "报警日志", msg, fileName);
} else {
DeviceHelper::stopSound();
DbQuery::addUserLog("报警日志", msg);
}
}
#include "onvifevent.h"
OnvifEvent::OnvifEvent(QObject *parent) : QObject(parent)
{
device = (OnvifDevice *)parent;
//事件定时器请求事件地址
timerEvent = new QTimer(this);
connect(timerEvent, SIGNAL(timeout()), this, SLOT(getEvent()));
timerEvent->setInterval(10 * 60 * 1000);
//消息定时器请求事件内容
timerMessage = new QTimer(this);
connect(timerMessage, SIGNAL(timeout()), this, SLOT(pullMessage()));
timerMessage->setInterval(1 * 60 * 1000);
}
void OnvifEvent::receiveEvent(const OnvifEventInfo &event)
{
if (!event.dataName.isEmpty()) {
emit receiveEvent(device->onvifAddr, event);
//正确
格式 2020-10-10T08:40:14Z|LogicalState>|1
//过滤格式 2020-10-10T08:23:11.000000Z|State|true>
if (event.time.length() != 20) {
return;
}
}
pullMessage();
}
QString OnvifEvent::getEvent(const QString &timeout)
{
//读取文件传入带用户认证的通用头部数据和
其他参数构建要发送的数据
QString file = OnvifHelper::getFile(":/onvifsend/CreatePullPointSubscription.xml");
file = file.arg(device->getHeadData()).arg(timeout);
//发送网络请求
QByteArray dataSend = file.toUtf8();
QNetworkReply *reply = device->request->post(device->eventUrl, dataSend, OnvifRequest::timeout + 500);
//拿到请求结果并处理数据
QByteArray dataReceive;
bool ok = device->checkData(reply, dataReceive, "订阅事件服务");
if (ok) {
//解析事件请求地址
OnvifQuery query;
if (query.setData(dataReceive)) {
eventAddr = query.getEventAddr(device->addrPort);
QTimer::singleShot(100, this, SLOT(pullMessage()));
}
}
//启动事件定时器
if (!timerEvent->isActive()) {
timerEvent->start();
}
//启动消息定时器
if (!timerMessage->isActive()) {
//timerMessage->start();
}
return eventAddr;
}
void OnvifEvent::pullMessage(const QString &timeout)
{
QMutexLocker locker(&mutex);
if (eventAddr.isEmpty()) {
return;
}
emit receiveInfo(QString("请求事件 -> %1").arg(eventAddr));
//读取文件传入带用户认证的通用头部数据和其他参数构建要发送的数据
QString uuid = OnvifHelper::getUuid();
QString file = OnvifHelper::getFile(":/onvifsend/PullMessages.xml");
file = file.arg(device->getUserToken()).arg(uuid).arg(eventAddr).arg(timeout);
//发送网络请求
QByteArray dataSend = file.toUtf8();
device->request->post2(eventAddr, dataSend);
}
```