年末奉獻-QT編寫視頻監管平臺(開源)

忙忙碌碌又是一年,算算本身畢業四年半,一直在如今這家公司作研發外加總經理助理,研發起初用的VB.NET,然後全面轉爲C#,最後又全面轉爲QT,都是因爲項目須要,算下來本身搞QT編程也已經四年了,2010年開始接觸QT並編寫一些公司須要的輔助工具,其實搞程序的,我感受絕大部分都是出於自己興趣愛好,而後鍥而不捨的鑽研,不斷成長和進步。html

項目需求:某區下面有幾百所學校,每一個學校都有若干臺NVR或者DVR,每臺NVRDVR都掛接着NIPC(攝像機)(包括網絡攝像機和模擬攝像機),如今須要對全部學校的監控進行查看以及回放和輪詢,可以對指定學校進行視頻監控,對全部學校的視重點部位視頻進行查看輪詢,可自定義輪詢時間等。sql

開發過程:本着儘可能追求簡潔的要求,最終編寫了如上圖的主界面。沒有采用QT自帶的界面,而是重寫了界面,自定義無邊框拖動,自由換膚,所有采用QSS控制,從官網http://qt-project.org/doc/qt-4.8/stylesheet-examples.html完全學習了下QSS的規則,整理了一套通用的換膚方案。數據庫

PS:這是6年前寫的項目,新版在這,不開源 http://www.javashuo.com/article/p-cwjvmulv-kn.html編程

整個系統在開始架構的時候,本人都是寫在草稿紙上的,包括佈局,功能點,須要注意的處理等方面,如今要從新一一仔細寫出來,還真不容易,這裏就說個大概,而後將其中的部分功能處理用代碼描述。ubuntu

功能點羅列:安全

1:只限定一個實例處理。網絡

視頻監管平臺是一個獨佔視頻通道資源的系統,不能運行多個實例在同一臺電腦上運行,因此在main函數中就限制了一個實例運行。架構

QSharedMemory mem( " VM ");
     if (!mem.create( 1)) {
        myHelper::ShowMessageBoxError( " 程序已運行,軟件將自動關閉! ");
         return  1;

    }編程語言

其中VM爲自定義的名稱,return 1表示退出程序返回1給操做系統。ide

若是重複運行會彈出以下提示:

 

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目錄下便可。 

說明:公開的源碼去除了視頻處理部分及樣式部分,其他功能所有保留,並可完整編譯運行。歡迎提出建議共同窗習進步!

相關文章
相關標籤/搜索