年底奉献-QT编写视频监管平台(开源)

忙忙碌碌又是一年,算算自己毕业四年半,一直在现在这家公司做研发外加总经理助理,研发起初用的VB.NET,而后全面转为C#,最后又全面转为QT,都是由于项目需要,算下来自己搞QT编程也已经四年了,2010年开始接触QT并编写一些公司需要的辅助工具,其实搞程序的,我感觉绝大部分都是出于本身兴趣爱好,然后持之以恒的钻研,不断成长和进步。

项目需求:某区下面有几百所学校,每个学校都有若干台NVR或者DVR,每台NVR和DVR都挂接着N个IPC(摄像机)(包括网络摄像机和模拟摄像机),现在需要对所有学校的监控进行查看以及回放和轮询,能够对指定学校进行视频监控,对所有学校的视重点部位视频进行查看轮询,可自定义轮询时间等。

开发过程:本着尽量追求简洁的要求,最终编写了如上图的主界面。没有采用QT自带的界面,而是重写了界面,自定义无边框拖动,自由换肤,全部采用QSS控制,从官网http://qt-project.org/doc/qt-4.8/stylesheet-examples.html彻底学习了下QSS的规则,整理了一套通用的换肤方案。

整个系统在开始架构的时候,本人都是写在草稿纸上的,包括布局,功能点,需要注意的处理等方面,现在要重新一一仔细写出来,还真不容易,这里就说个大概,然后将其中的部分功能处理用代码描述。

功能点罗列:

1:只限定一个实例处理。

视频监管平台是一个独占视频通道资源的系统,不能运行多个实例在同一台电脑上运行,所以在main函数中就限制了一个实例运行。

QSharedMemory mem("VM");
    if (!mem.create(1)) {
        myHelper::ShowMessageBoxError("程序已运行,软件将自动关闭!");
        return 1;

}

其中VM为自定义的名称,return 1表示退出程序返回1给操作系统。

如果重复运行会弹出如下提示:

2:F1键进入全屏模式,Esc键退出全屏模式。

几乎所有的视频监控系统,主界面都支持全屏显示及esc退出全屏,在QT中我是这样实现的,重写了主界面的keyPressEvent事件,拦截按键消息,判断对应按键,调用全屏及普通模式的方法。

void frmMain::keyPressEvent(QKeyEvent *event)
{
    //空格键进入全屏,esc键退出全屏
    switch(event->key()) {
    case Qt::Key_F1:
        screen_full();
        break;
    case Qt::Key_Escape:
        screen_normal();
        break;
    default:
        QDialog::keyPressEvent(event);
        break;
    }
}

void frmMain::screen_full()
{    
    this->setGeometry(qApp->desktop()->geometry());
    this->layout()->setContentsMargins(0, 0, 0, 0);
    ui->widget_main->layout()->setContentsMargins(0, 0, 0, 0);
    ui->widget_title->setVisible(false);
    ui->treeMain->setVisible(false);
}

void frmMain::screen_normal()
{    
    this->setGeometry(qApp->desktop()->availableGeometry());
    this->layout()->setContentsMargins(1, 1, 1, 1);
    ui->widget_main->layout()->setContentsMargins(5, 5, 5, 5);
    ui->widget_title->setVisible(true);
    ui->treeMain->setVisible(true);
}

3:支持QT4到QT5各个版本编译运行。

QT5与QT4的区别还是让很多搞QT开发的同学着实生气了一把,好端端的把一些方法去除掉了,而且有些头文件重新移到了其他地方,为了兼容QT4与QT5,在项目中就需要增加很多对版本的判断了。

例如头文件的包含:

#if (QT_VERSION > QT_VERSION_CHECK(5,0,0))
#include <QtWidgets>
#endif

例如设置UTF-8编码:

static void SetUTF8Code() {
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
        QTextCodec *codec = QTextCodec::codecForName("UTF-8");
        QTextCodec::setCodecForLocale(codec);
        QTextCodec::setCodecForCStrings(codec);
        QTextCodec::setCodecForTr(codec);
#endif

}

4:基本常用的数据库处理,添加删除修改操作,表格显示。

本人一直喜欢采用拼接sql字符串来执行SQL语句。觉得这样运行效率很高,而且这种方法通用任何编程语言。

void frmIPC::on_btnAdd_clicked()
{
    QString IPCID = ui->cboxIPCID->currentText();
    QString IPCName = ui->txtIPCName->text();
    QString NVRID = ui->cboxNVRID->currentText();
    QString NVRName = ui->cboxNVRName->currentText();
    QString IPCType = ui->cboxIPCType->currentText();
    QString IPCRtspAddrMain = ui->txtIPCRtspAddrMain->text();
    QString IPCRtspAddrSub = ui->txtIPCRtspAddrSub->text();
    QString IPCUserName = ui->txtIPCUserName->text();
    QString IPCUserPwd = ui->txtIPCUserPwd->text();
    QString IPCUse = ui->cboxIPCUse->currentText();

if (IPCName == "") {
        myHelper::ShowMessageBoxError("名称不能为空,请重新填写!");
        ui->txtIPCName->setFocus();
        return;
    }

if (NVRName == "") {
        myHelper::ShowMessageBoxError("NVR名称不能为空,请先添加好NVR!");
        return;
    }

if (IPCRtspAddrMain == "") {
        myHelper::ShowMessageBoxError("主码流地址不能为空,请重新填写!");
        ui->txtIPCRtspAddrMain->setFocus();
        return;
    }

if (IPCRtspAddrSub == "") {
        myHelper::ShowMessageBoxError("子码流地址不能为空,请重新填写!");
        ui->txtIPCRtspAddrSub->setFocus();
        return;
    }

//检测编号是否唯一
    if (IsExistIPCID(NVRID, IPCID)) {
        myHelper::ShowMessageBoxError("编号已经存在,请重新选择!");
        return;
    }

QSqlQuery query;
    QString sql = "insert into [IPCInfo](";
    sql += "[IPCID],[IPCName],[NVRID],[NVRName],";
    sql += "[IPCType],[IPCRtspAddrMain],[IPCRtspAddrSub],";
    sql += "[IPCUserName],[IPCUserPwd],[IPCUse])";
    sql += "values(‘";
    sql += IPCID + "‘,‘";
    sql += IPCName + "‘,‘";
    sql += NVRID + "‘,‘";
    sql += NVRName + "‘,‘";
    sql += IPCType + "‘,‘";
    sql += IPCRtspAddrMain + "‘,‘";
    sql += IPCRtspAddrSub + "‘,‘";
    sql += IPCUserName + "‘,‘";
    sql += IPCUserPwd + "‘,‘";
    sql += IPCUse + "‘)";
    query.exec(sql);

LoadIPCInfo();

ui->cboxIPCID->setCurrentIndex(ui->cboxIPCID->currentIndex() + 1);
    ui->txtIPCName->setText(QString("摄像机%1").arg(ui->cboxIPCID->currentText()));
}

void frmIPC::on_btnDelete_clicked()
{
    if (ui->tableMain->currentIndex().row() < 0) {
        myHelper::ShowMessageBoxError("请选择要删除的摄像机!");
        return;
    }

QString tempIPCID = queryModule->record(
                            ui->tableMain->currentIndex().row())
                        .value(0).toString();

if (myHelper::ShowMessageBoxQuesion("确定要删除摄像机吗?") == 1) {
        QSqlQuery query;
        QString sql = "delete from [IPCInfo] where [IPCID]=‘" + tempIPCID + "‘";
        query.exec(sql);
        myHelper::Sleep(100);

//同步删除轮询表中的摄像机信息
        sql = "delete from [PollInfo] where [IPCID]=‘" + tempIPCID + "‘";
        query.exec(sql);
        myHelper::Sleep(100);

LoadIPCInfo();
    }
}

void frmIPC::on_btnUpdate_clicked()
{
    if (ui->tableMain->currentIndex().row() < 0) {
        myHelper::ShowMessageBoxError("请选择要修改的摄像机!");
        return;
    }

QString tempIPCID = queryModule->record(
                            ui->tableMain->currentIndex().row())
                        .value(0).toString();

QString IPCID = ui->cboxIPCID->currentText();
    QString IPCName = ui->txtIPCName->text();
    QString NVRID = ui->cboxNVRID->currentText();
    QString NVRName = ui->cboxNVRName->currentText();
    QString IPCType = ui->cboxIPCType->currentText();
    QString IPCRtspAddrMain = ui->txtIPCRtspAddrMain->text();
    QString IPCRtspAddrSub = ui->txtIPCRtspAddrSub->text();
    QString IPCUserName = ui->txtIPCUserName->text();
    QString IPCUserPwd = ui->txtIPCUserPwd->text();
    QString IPCUse = ui->cboxIPCUse->currentText();

if (IPCID != tempIPCID) {
        //检测编号是否和已经存在的除自己之外的编号相同
        if (IsExistIPCID(NVRID, IPCID)) {
            myHelper::ShowMessageBoxError("编号已经存在,请重新选择!");
            return;
        }
    }

QSqlQuery query;
    QString sql = "update [IPCInfo] set";
    sql += " [IPCID]=‘" + IPCID;
    sql += "‘,[IPCName]=‘" + IPCName;
    sql += "‘,[NVRID]=‘" + NVRID;
    sql += "‘,[NVRName]=‘" + NVRName;
    sql += "‘,[IPCType]=‘" + IPCType;
    sql += "‘,[IPCRtspAddrMain]=‘" + IPCRtspAddrMain;
    sql += "‘,[IPCRtspAddrSub]=‘" + IPCRtspAddrSub;
    sql += "‘,[IPCUserName]=‘" + IPCUserName;
    sql += "‘,[IPCUserPwd]=‘" + IPCUserPwd;
    sql += "‘,[IPCUse]=‘" + IPCUse;
    sql += "‘ where [IPCID]=‘" + tempIPCID + "‘";
    query.exec(sql);
    myHelper::Sleep(100);

//同步修改轮询表的信息
    sql = "update [PollInfo] set";
    sql += " [IPCID]=‘" + IPCID;
    sql += "‘,[IPCName]=‘" + IPCName;
    sql += "‘,[NVRID]=‘" + NVRID;
    sql += "‘,[NVRName]=‘" + NVRName;
    sql += "‘,[IPCRtspAddrMain]=‘" + IPCRtspAddrMain;
    sql += "‘,[IPCRtspAddrSub]=‘" + IPCRtspAddrSub;
    sql += "‘ where [IPCID]=‘" + tempIPCID + "‘";
    query.exec(sql);
    myHelper::Sleep(100);

LoadIPCInfo();

}

5:QTreeView及QTableView数据加载和双击处理。

void frmPollConfig::LoadNVRIPC()
{
    ui->treeMain->clear();

QSqlQuery queryNVR;
    QString sqlNVR = "select [NVRID],[NVRName],[NVRIP] from [NVRInfo] where [NVRUse]=‘启用‘";
    queryNVR.exec(sqlNVR);

while (queryNVR.next()) {
        QString tempNVRID = queryNVR.value(0).toString();
        QString tempNVRName = queryNVR.value(1).toString();
        QString tempNVRIP = queryNVR.value(2).toString();

QTreeWidgetItem *itemNVR = new QTreeWidgetItem
        (ui->treeMain, QStringList(tempNVRName + "[" + tempNVRIP + "]"));
        itemNVR->setIcon(0, QIcon(":/image/nvr.png"));

//查询没有添加在轮询表中的摄像机信息
        QSqlQuery queryIPC;
        QString sqlIPC = "select [IPCID],[IPCName],[IPCRtspAddrMain] from [IPCInfo]";
        sqlIPC += " where [NVRID]=‘" + tempNVRID;
        sqlIPC += "‘ and [IPCUse]=‘启用‘";
        sqlIPC += " order by [IPCID] asc";
        queryIPC.exec(sqlIPC);

while (queryIPC.next()) {
            QString tempIPCID = queryIPC.value(0).toString();

//如果该摄像机已经存在轮询表,则跳过
            if (IsExistIPCID(tempIPCID)) {
                continue;
            }

QString tempIPCName = queryIPC.value(1).toString();
            QString rtspAddr = queryIPC.value(2).toString();

QStringList temp = rtspAddr.split("/");
            QString ip = temp[2].split(":")[0];

temp = QStringList(QString(tempIPCName + "[" + ip + "](" + tempIPCID + ")"));
            QTreeWidgetItem *itemIPC = new QTreeWidgetItem(itemNVR, temp);
            itemIPC->setIcon(0, QIcon(":/image/ipc_normal.png"));
            itemNVR->addChild(itemIPC);
        }
    }
    ui->treeMain->expandAll();
}

6:16通道画面展示区域处理,自由切换1画面4画面9画面16画面。

void frmMain::show_video_4()
{
    removelayout();
    video_max = false;
    int index = 0;

QAction *action = (QAction *)sender();
    QString name = action->text();
    if (name == "通道1-通道4") {
        index = 0;
        myApp::VideoType = "1_4";
    } else if (name == "通道5-通道8") {
        index = 4;
        myApp::VideoType = "5_8";
    } else if (name == "通道9-通道12") {
        index = 8;
        myApp::VideoType = "9_12";
    } else if (name == "通道13-通道16") {
        index = 12;
        myApp::VideoType = "13_16";
    }

change_video_4(index);
    myApp::WriteConfig();
}

void frmMain::change_video_4(int index)
{
    for (int i = (index + 0); i < (index + 2); i++) {
        VideoLay[0]->addWidget(VideoLab[i]);
        VideoLab[i]->setVisible(true);
    }

for (int i = (index + 2); i < (index + 4); i++) {
        VideoLay[1]->addWidget(VideoLab[i]);
        VideoLab[i]->setVisible(true);
    }
}

7:精美开关按钮。

现在流行APP,各种APP上面都带有很精美的开关,参考了360安全卫士以及金山毒霸的开关按钮,用QT也实现了一个,原理很简单,就是贴图。

#include "switchbutton.h"

/* 说明:自定义开关按钮控件实现文件
 * 功能:用来控制配置文件的开关设置
 * 作者:刘典武  QQ:517216493
 * 时间:2013-12-19  检查:2014-1-10
 */
SwitchButton::SwitchButton(QWidget *parent): QPushButton(parent)
{
    setCursor(QCursor(Qt::PointingHandCursor));
    isCheck = false;
    styleOn = "background-image: url(:/image/btncheckon.png); border: 0px;";
    styleOff = "background-image: url(:/image/btncheckoff.png); border: 0px;";

setFocusPolicy(Qt::NoFocus);
    setFixedSize(87, 28); //不允许变化大小
    setStyleSheet(styleOff);//设置当前样式
    connect(this, SIGNAL(clicked()), this, SLOT(ChangeOnOff()));
}

void SwitchButton::ChangeOnOff()
{
    if (isCheck) {
        setStyleSheet(styleOff);
        isCheck = false;
    } else {
        setStyleSheet(styleOn);
        isCheck = true;
    }
}

//设置当前选中状态
void SwitchButton::SetCheck(bool isCheck)
{
    if (this->isCheck != isCheck) {
        this->isCheck = !isCheck;
        ChangeOnOff();
    }
}

8:重写过的消息框,错误框,询问框及输入框。

本人不喜欢系统的MessageBox,用QDialog重新布局自定义了一个。只需一句话调用即可。

在win7下运行截图如下:

在XP下运行截图如下:

在ubuntu上运行截图:

可执行文件下载:http://pan.baidu.com/s/1hqxhtbA

源码下载:http://pan.baidu.com/s/1mgFWeDU

编译运行后如果提示缺少数据库。将源码下的file文件夹下的配置文件config.txt及VM.db数据库文件复制到bin目录下即可。

说明:公开的源码去除了视频处理部分及样式部分,其余功能全部保留,并可完整编译运行。欢迎提出建议共同学习进步!

时间: 2024-10-25 09:25:59

年底奉献-QT编写视频监管平台(开源)的相关文章

QT开发之旅五视频监管平台

忙忙碌碌又是一年,算算自己毕业四年半,一直在现在这家公司做研发外加总经理助理,研发起初用的VB.NET,而后全面转为C#,最后又全面转为QT,都是由于项目需要,算下来自己搞QT编程也已经四年了,2010年开始接触QT并编写一些公司需要的辅助工具,其实在公司绝大部分时间在干杂七杂八的事情,例如采购.人事.招投标.现场技术支持等,一年下来大概只有不到三分之一的时间在钻研程序,其中这三分之一的时间晚上占了一大半,其实搞程序的,我感觉绝大部分都是出于本身兴趣爱好,然后持之以恒的钻研,不断成长和进步. 用

Qt编写项目作品大全(自定义控件+输入法+大屏电子看板+视频监控+楼宇对讲+气体安全等)

一.自定义控件大全 (一).控件介绍 超过150个精美控件,涵盖了各种仪表盘.进度条.进度球.指南针.曲线图.标尺.温度计.导航条.导航栏,flatui.高亮按钮.滑动选择器.农历等.远超qwt集成的控件数量. 每个类都可以独立成一个单独的控件,零耦合,每个控件一个头文件和一个实现文件,不依赖其他文件,方便单个控件以源码形式集成到项目中,较少代码量.qwt的控件类环环相扣,高度耦合,想要使用其中一个控件,必须包含所有的代码. 全部纯Qt编写,QWidget+QPainter绘制,支持Qt4.6到

Qt编写图片及视频TCP/UDP网络传输

一.前言 很多年前就做过类似的项目,无非就是将本地的图片上传到服务器,就这么简单,其实用http的post上传比较简单容易,无需自定义协议,直接设置好二进制数据即可,而采用TCP或者UDP通信的话,必须自定义协议,因为不知道什么时候数据接收完了是完整的图片数据,可能同时在发送很多图片数据,而且还不能区分收到的图片是哪个客户端发来的,TCP长连接的话,还需要有心跳来检测连接,所以必须自定义一套协议来支撑通信,这套协议采用的是上海监管平台的通信协议格式,拓展性比较强,其中头部信息包括了类型+当前完整

Qt编写安防视频监控系统18-云台控制

一.前言 云台控制是视频监控系统中必备的一个功能,对球机进行上下左右的移动,还有焦距的控制,其实核心就是控制XYZ三个坐标轴,为了开发这个模块,特意研究了各种云台控制的方法和开源库比如soap,有些厂家使用自家SDK控制云台,但是大部分都会选择onvif来控制,毕竟是国际标准的通用的,只要符合这个标准的都可以使用,onvif协议的解析通常用的开源库是soap,涵盖的内容比较全,包括获取各种设备信息和回控等,缺点就是比较臃肿,使用非常不容易,函数名实在是有点不顺手,很多新手都绕在其中不知所措最后放

MAC 平台 QT编写iphone程序,“error loading iPhoneSimulatorRemoteClientRun ended unexpectedly”

本日这么多年一直做C++开发,最近要做QT项目,被QT做界面的新特性所吸引.QSS QML的确是亮点. 还有一个就是跨平台这方面,自己玩了玩. 用的QT 的开发包是在官网上下载 qt-opensource-mac-x64-android-ios-5.2.1.dmg 在使用Qt creator写一个hellworld 最后弹出 error loading  iPhoneSimulatorRemoteClientRun ended unexpectedly. 解决办法这里面 https://bugr

.NET平台开源项目速览(13)机器学习组件Accord.NET框架功能介绍

Accord.NET Framework是在AForge.NET项目的基础上封装和进一步开发而来.因为AForge.NET更注重与一些底层和广度,而Accord.NET Framework更注重与机器学习算法以及提供计算机视频.音频.信号处理以及统计应用相关的解决方案.该项目使用C#语言编写,项目主页:http://accord-framework.net/ 说明:该文章只是一个基本介绍,主要内容是翻译的官方文档和介绍,部分英文表述个人能力有限,不太熟悉,所以直接照搬原文,有比较确切的知道中文名

Qt编写的项目作品7-视频监控系统

一.功能特点 支持16画面切换,全屏切换等,包括1+4+6+8+9+13+16画面切换. 支持alt+enter全屏,esc退出全屏. 自定义信息框+错误框+询问框+右下角提示框. 17套皮肤样式随意更换,所有样式全部统一,包括菜单等. 云台仪表盘鼠标移上去高亮,八个方位精准识别. 底部画面工具栏(画面分割切换+截图声音等设置)移上去高亮. 可在配置文件更改左上角logo+中文软件名称+英文软件名称. 封装了百度地图,视图切换,设备点位,鼠标按下获取经纬度等. 堆栈窗体,每个窗体都是个单独的qw

Qt编写可换肤的中文双拼汉字输入法

时间过得真快,不知不觉已到2015年,农历春节一眨眼就过去了,端正状态收拾心情整装待发出发. 曾经有段时间,我有一个很执着的梦想,我要导演出一部空前绝后的巨幕.不过现实无情地碾碎我的梦想,也同时将我推进了人生的低谷. 我开始思考,思想很多很多我以前总是回避的问题,在一次次思考中,我能感觉到我正在向一个陌生的地方走去,前途是光是暗也无法推测,我发现我在赌,赌注是一辈子的懊悔或是郁郁终生. 但当我看到阳光弥漫在我身边时,才发现以前的我是那么幼稚,我始终在找借口或不是借口的借口推卸掉与生俱来的责任,在

简单几步打造网络视频直播平台(转)

简单几步打造网络视频直播平台 分类:学以致用 2013-02-04 13:20 阅读(1132)评论(0) 不管是个人玩还是企业用,直播电台总是显得比较高端和神秘,今天我们来看看怎么用简单几步实现基于Flash流媒体服务器的网络视频直接直播平台. 1.服务端准备 常见的免费Flash流媒体服务器有国产的UMS和国际开源的Red5,UMS只有windows平台安装包,而用java开发的Red5更秉承了开源软件一贯兼容特色,可以多平台运行. UMS相对简单,全经典windows程序安装步骤,安装过程