zoukankan      html  css  js  c++  java
  • 年底奉献-QT编写视频监管平台(开源)

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

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

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

    PS:这是6年前写的项目,新版在这,不开源 https://www.cnblogs.com/feiyangqingyun/p/10913375.html

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

    功能点罗列:

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

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

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

        }

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

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

     

    2F1键进入全屏模式,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(0000);
        ui->widget_main->layout()->setContentsMargins(0000);
        ui->widget_title->setVisible(false);
        ui->treeMain->setVisible(false);
    }

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

     3:支持QT4QT5各个版本编译运行。

    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();

    } 

    5QTreeViewQTableView数据加载和双击处理。

    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();
    }

     

    616通道画面展示区域处理,自由切换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(8728); //不允许变化大小
        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目录下即可。 

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

  • 相关阅读:
    CBP是什么?
    编码器变换及量化的流程?
    CABAC与CAVLC有什么区别?
    如何在JM8.6编码端提取QDCT?
    宏块都有哪些类型?
    H264帧间预测流程?
    H264子宏块的划分有哪些?
    H264提供了哪些帧内预测?
    加强预测编码?
    centos7 下通过nginx+uwsgi部署django应用
  • 原文地址:https://www.cnblogs.com/feiyangqingyun/p/4192484.html
Copyright © 2011-2022 走看看