海康大华等厂家自己的客户端软件,基本上都是支持自家的设备,不支持其他家的摄像机和硬盘录像机,并不是因为技术上做不到,这些大厂要实现支持兼容其他的家的(他们家的服务端或者收费的都是支持其他家的),那都是分分钟的事情,无非就是走通用的标准onvif+rtsp+gb28181,为何目前客户端不兼容其他家的,可能也是由于商业角度考虑,这样可以最大化的绑定自家的硬件一起,毕竟客户端都是免费使用的,仅仅用他们免费的客户端而不用他们的硬件,那就没有太大意义。
用Qt编写这个视频监控系统,最初的目标就是要实现支持海康/大华/宇视/华为/天地伟业等各个厂家的设备,也一直朝着这个目标前进,好在有onvif+gb28181这种国际标准和国家标准,只要对方的设备支持这两种标准则都可以顺利接入,一般onvif用来搜索和获取设备信息,拿到rtsp地址可以用ffmpeg解码播放,而gb28181主要用来回放视频居多,难易程度上gb28181由于通信复杂更难,onvif相对来说更简单,onvif底层就是用的udp+http,先用udp发组播消息搜索设备,然后用http发送请求拿详细的数据。
目前市面上绝大部分厂家的设备都是支持onvif和gb28181的,所以这也给专门的软件厂商一个巨大机会,可以将各个硬件厂商的设备集中起来统一管理,除了局域网的设备可以集中管理,广域网的也可以通过各种中转统一管理,甚至推流通过公网管理,只要有权限,想看哪个摄像头哪个设备就调出来查看即可。
公众号:Qt实战,各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发。
公众号:Qt入门和进阶,专门介绍Qt/C++相关知识点学习,帮助Qt开发者更好的深入学习Qt。多位Qt元婴期大神,一步步带你从入门到进阶,走上财务自由之路。
#include "deviceonvif.h"
#include "quihelper.h"
#include "dbquery.h"
#include "playwav.h"
#include "devicehelper.h"
#include "onvifthread.h"
#include "videowidgetx.h"
bool DeviceOnvif::checkUrl(const QString &url, OnvifDeviceData &deviceData)
{
//不是rtsp开头也不是摄像机,为空也包含在这个判断中
if (!url.startsWith("rtsp")) {
return false;
}
//可能是主码流也可能是子码流
int index1 = DbData::IpcInfo_RtspMain.indexOf(url);
int index2 = DbData::IpcInfo_RtspSub.indexOf(url);
int index = -1;
if (index1 >= 0) {
index = index1;
} else if (index2 >= 0) {
index = index2;
}
//没有码流地址不用继续
if (index < 0) {
return false;
}
//只有onvif地址存在才是onvif设备
QString onvifAddr = DbData::IpcInfo_OnvifAddr.at(index);
if (onvifAddr.isEmpty()) {
return false;
}
//onvif地址中的IP和rtsp地址中的IP必须一致
//为什么会出现这个现象因为用户很可能直接在原来的正确的带有onvif地址的信息中修改了rtsp地址
if (OnvifHelper::getIP(onvifAddr) != OnvifHelper::getIP(url)) {
return false;
}
//对应结构体数据赋值
deviceData.userName = DbData::IpcInfo_UserName.at(index);
deviceData.userPwd = DbData::IpcInfo_UserPwd.at(index);
deviceData.onvifAddr = DbData::IpcInfo_OnvifAddr.at(index);
deviceData.profileToken = DbData::IpcInfo_ProfileToken.at(index);
deviceData.videoSource = DbData::IpcInfo_VideoSource.at(index);
return true;
}
bool DeviceOnvif::ptzControl(quint8 type, const QString &url, double x, double y, double z)
{
bool result = false;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
qDebug() << TIMEMS << "执行云台控制" << OnvifHelper::getIP(url);
result = device->ptzControl(type, deviceData.profileToken, x, y, z);
}
return result;
}
bool DeviceOnvif::ptzPreset(quint8 type, const QString &url, const QString &presetToken, const QString &presetName)
{
bool result = false;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
qDebug() << TIMEMS << "预置位处理" << OnvifHelper::getIP(url);
result = device->ptzPreset(type, deviceData.profileToken, presetToken, presetName);
}
return result;
}
QList DeviceOnvif::getPresets(const QString &url)
{
QList presets;
OnvifDeviceData deviceData;
if (checkUrl(url, deviceData)) {
OnvifDevice *device = OnvifThread::getOnvifDevice(deviceData);
qDebug() << TIMEMS << "获取预置位" << OnvifHelper::getIP(url);
presets = device->getPresets(deviceData.profileToken);
}
return presets;
}
SINGLETON_IMPL(DeviceOnvif)
DeviceOnvif::DeviceOnvif(QObject *parent) : QObject(parent)
{
//显示截图的标签
labImage.setWindowFlags(Qt::WindowStaysOnTopHint);
labImage.setFixedSize(QSize(800, 600));
labImage.setWindowTitle("抓拍图片预览");
QUIHelper::setFormInCenter(&labImage);
//关联信号
connect(OnvifThread::Instance(), SIGNAL(receiveImage(QString, QImage)),
this, SLOT(receiveImage(QString, QImage)));
connect(OnvifThread::Instance(), SIGNAL(receiveEvent(QString, OnvifEventInfo)),
this, SLOT(receiveEvent(QString, OnvifEventInfo)));
connect(OnvifThread::Instance(), SIGNAL(receiveResult(QString, QString, QVariant)),
this, SIGNAL(receiveResult(QString, QString, QVariant)));
//启动onvif线程
OnvifThread::Instance()->start();
//启动定时器判断摄像机上下线
QTimer *timerOffline = new QTimer(this);
connect(timerOffline, SIGNAL(timeout()), this, SLOT(checkOffline()));
timerOffline->start(3000);
}
void DeviceOnvif::checkOffline()
{
for (int i = 0; i < DbData::IpcInfo_Count; ++i) {
QString url = DbData::getRtspAddr(i);
QString ip = OnvifHelper::getIP(url);
int port = OnvifHelper::getPort(url);
//rtsp除外的认为永远存在,可以根据需要自行约定规则
bool online = true;
if (url.startsWith("rtsp")) {
online = QUIHelper::ipLive(ip, port);
}
//过滤下只有当状态变化了才需要
if (online) {
if (!DbData::IpcInfo_IpcOnline.at(i)) {
DeviceHelper::setVideoIcon2(ip, true);
}
} else {
if (DbData::IpcInfo_IpcOnline.at(i)) {
DeviceHelper::setVideoIcon2(ip, false);
}
}
DbData::IpcInfo_IpcOnline[i] = online;
}
}
void DeviceOnvif::receivePlayStart(int time)
{
//轮询阶段不处理
if (AppConfig::Polling) {
//return;
}
//拿到触发信号的控件
VideoWidget *widget = (VideoWidget *)sender();
//先校验当前视频对应的信息是否符合
OnvifDeviceData deviceData;
if (checkUrl(widget->getVideoPara().videoUrl, deviceData)) {
//交给线程执行指令
OnvifThread::Instance()->append(deviceData, "getServices");
if (AppConfig::OnvifNtp) {
OnvifThread::Instance()->append(deviceData, "setDateTime");
}
if (AppConfig::OnvifEvent) {
OnvifThread::Instance()->append(deviceData, "getEvent");
}
}
}
void DeviceOnvif::receivePlayFinsh()
{
//先校验当前视频对应的信息是否符合
VideoWidget *widget = (VideoWidget *)sender();
OnvifDeviceData deviceData;
if (checkUrl(widget->getVideoPara().videoUrl, deviceData)) {
//交给线程执行指令
OnvifThread::Instance()->append(deviceData, "remove");
}
}
void DeviceOnvif::receiveImage(const QString &url, const QImage &image)
{
QImage img = image;
if (!img.isNull()) {
//等比例缩放一下
img = img.scaled(labImage.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
labImage.setPixmap(QPixmap::fromImage(img));
labImage.show();
}
}
void DeviceOnvif::receiveEvent(const QString &url, const OnvifEventInfo &event)
{
//可能临时关闭了事件订阅
if (!AppConfig::OnvifEvent) {
return;
}
//事件内容存放在结构体数据中
//qDebug() << TIMEMS << event;
QString name = event.dataName;
QVariant value = event.dataValue;
//可能有多种关键字可以自行增加 LogicalState State IsMotion
if (!name.contains("LogicalState") && !name.contains("IsMotion")) {
return;
}
qDebug() << TIMEMS << "收到报警事件" << QUIHelper::getIP(url) << name << value;
//过滤不在本系统中的设备发过来的报警
int index = DbData::IpcInfo_OnvifAddr.indexOf(url);
if (index < 0) {
return;
}
//true false 1 0 字符串转成bool
bool alarm = value.toBool();
QString ipcName = DbData::IpcInfo_IpcName.at(index);
QString info, msg;
if (name.contains("IsMotion")) {
info = (alarm ? "移动侦测" : "移动结束");
msg = QString("%1%2").arg(ipcName).arg(info);
DeviceHelper::addMsg(msg, alarm ? 2 : 0);
} else {
info = (alarm ? "触发报警" : "报警恢复");
msg = QString("%1%2").arg(ipcName).arg(info);
DeviceHelper::addMsg(msg, alarm ? 2 : 0);
}
//播放报警声音
if (alarm) {
DeviceHelper::playAlarm("8.wav");
} else {
DeviceHelper::stopSound();
}
//插入到日志记录数据库
DbQuery::addUserLog("报警日志", msg);
//右下角弹出提示
if (AppConfig::TipInterval != 10000) {
QUIHelper::showTipBox("提示", msg, AppConfig::FullScreen, true, AppConfig::TipInterval);
}
}