年底奉献-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目录下即可。 

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

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM