Qt編寫安防視訊監控系統40-onvif執行緒處理

語言: CN / TW / HK

一、前言

整個onvif模組大部分的功能都有了以後,除了在demo上點點按鈕可以執行獲取結果顯示外,最終還是要應用到視訊監控中,在按鈕上點點和系統中後臺自動執行是兩碼事,比如onvif校時和事件訂閱,不會說是傻到在監控系統介面上提供按鈕給使用者點選才去執行,最多做的應該是系統設定中提供兩個開關比如自動校時、事件訂閱,可以方便的開啟這幾個功能。開啟以後等監控系統啟動後自動去處理,比如挨個對攝像機進行校時處理以及訂閱事件,為了能夠做到新增攝像機後自動立即應用,特意改成了在開啟攝像機視訊畫面的時候,主動去例項化DeviceOnvif類(每個攝像機都對應一個例項)

最開始的做法是採用定時器去處理要指定的指令佇列,後面發現速度不好控制,畢竟網路請求受網速和網路環境的影響,有時候100毫秒就執行完成了,有時候又需要300毫秒不等,儘管網路請求的時候已經設定了超時時間(這個時間一般設定成2-3秒,保證請求有足夠的時候返回),這個時間有點大,如果按照這個網路請求超時時間來設定定時器,裝置數量很多的時候太慢了,監控系統一般幾十個裝置是有的,這蝸牛一樣的速度要處理到何年馬月,而且每個攝像機有多個指令需要處理比如自動校時、事件訂閱等。

那有沒有一種機制可以盡最快的速度排隊處理呢,答案是當然,這不就是執行緒擅長乾的事情嗎,使勁的幹,休息多久自由msleep控制即可,網路環境好的情況下,20個裝置的指令基本上在1s內完成的,這就能夠滿足使用者的需求,畢竟使用者開啟軟體後,大概率不想等待太長時間,就像能夠看到所有攝像機時間自動校準好了,搞個攝像機報警也能立即通過onvif協議上報,該處理的都儘快處理完了。

QNetworkAccessManager類如果一開始不是線上程中new出來的,會提示不能在其他執行緒執行,這就需要線上程的run函式中呼叫QMetaObject::invokeMethod來執行對應的處理,一個萬能的處理方法就是將需要執行的全部放在work函式中,搞個iswork標誌位,進入該開始的時候將標誌位iswork=true,處理結束後iswork=false,在run中先判斷標誌位是否為假,為假表示當前不在工作,則去呼叫work函式處理。這就規避了線上程中執行其他執行緒類物件函式的錯誤提示。

基本的處理思路

  • 查詢出所有的攝像機資訊。
  • 過濾攝像機資訊,找出所有具備onvif地址的,只有具備onvif地址的才是需要去處理的。
  • 從deviceonvif連結串列中找到當前onvif地址的裝置類物件,該方法同時肩帶new出例項在沒有找到對應例項的情況下。
  • 將對應的處理轉成命令指令佇列,帶有onvif地址標識,交給onvifthread執行緒類專門處理。
  • 所有的方法在該例項中都有對應方法進行處理,對該例項呼叫對應的方法比如校時、事件訂閱、抓圖等。
  • 處理完成後將對應的結果訊號發出去,對應三個引數分別表示onvif地址、指令、結果資料(QVariant型別)。

onvif主要的功能

  1. 搜尋裝置,獲取裝置的資訊比如廠家、型號等。
  2. 獲取裝置的多個配置檔案資訊profile。
  3. 獲取對應配置檔案的視訊流地址rtsp,以及解析度等引數。
  4. 雲臺控制,上下左右移動,焦距放大縮小,相對和絕對移動。
  5. 獲取預置位資訊,觸發預置位。
  6. 訂閱事件,接收裝置的各種訊息尤其是報警事件比如IO口的報警。
  7. 抓圖,獲取裝置當前的圖片。
  8. 獲取、建立、刪除使用者資訊。
  9. 獲取和裝置網路配置資訊比如IP地址等。
  10. 獲取和設定NTP時間同步。
  11. 獲取和設定裝置時間。
  12. 重啟裝置。

onvif的處理流程

  1. 繫結組播IP(239.255.255.250)和埠(3702),傳送固定的xml格式的資料搜尋裝置。
  2. 接收到的xml格式的資料解析,得到裝置的Onvif地址。
  3. 對Onvif地址傳送對應的資料,收到資料取出對應的節點資料。
  4. 請求Onvif地址獲取Media地址和Ptz地址,Media地址用來獲取詳細的配置檔案,Ptz地址用來雲臺控制。
  5. ptz控制是對Ptz地址傳送對應的資料即可。
  6. 設定了使用者認證的需要組織使用者token資訊一塊傳送,每次都需要作鑑權處理。
  7. 接收到的資料不是標準的xml資料,沒法按照正常的節點解析來處理,只能用QXmlQuery來做。
  8. 每個廠家裝置返回的資料未必完全一致,基本上都不一致,需要進行模糊查詢節點值。
  9. 特意採用底層協議解析,因為soap太臃腫函式名稱太另類,特意做的輕量級的。
  10. 兩個必備工具,Onvif Device Manager 和 Onvif Device Test Tool。

二、功能特點

(一)軟體模組

  1. 視訊監控模組,各種停靠小窗體子模組,包括裝置列表、圖文警情、視窗資訊、雲臺控制、預置位、巡航設定、裝置控制、懸浮地圖、網頁瀏覽等。
  2. 視訊回放模組,包括本地回放、遠端回放、裝置播放、圖片回放、視訊上傳等。
  3. 電子地圖模組,包括圖片地圖、線上地圖、離線地圖、路徑規劃等。
  4. 日誌查詢模組,包括本地日誌、裝置日誌等。
  5. 系統設定模組,包括系統設定(基本設定、視訊引數、資料庫設定、地圖配置、串列埠配置等)、錄影機管理、攝像機管理、輪詢配置、使用者管理等。

(二)基礎功能

  1. 支援各種視訊流(rtsp、rtmp、http等)、視訊檔案(mp4、rmvb、avi等)、本地USB攝像機播放。
  2. 支援多畫面切換,包括1、4、6、8、9、13、16、25、36、64畫面切換。
  3. 支援全屏切換,多種切換方式包括滑鼠右鍵選單、工具欄按鈕、快捷鍵(alt+enter全屏,esc退出全屏)。
  4. 支援視訊輪詢,包括1、4、9、16畫面輪詢,可設定輪詢分組(輪詢預案)、輪詢間隔、碼流型別等。
  5. 支援onvif協議,包括裝置搜尋、雲臺控制、裝置控制(圖片引數、校對時間、系統重啟,抓拍圖片等)。
  6. 支援許可權管理,不同的使用者可以對應不同的模組許可權,比如刪除日誌、關閉系統等。
  7. 資料庫支援多種,包括sqlite、mysql、sqlserver、postgresql、oracle、人大金倉等。
  8. 本地USB攝像機支援設定解析度、幀率等引數。
  9. 所有停靠模組都自動生成對應的選單用來控制顯示和隱藏,在標題欄右鍵可以彈出。
  10. 支援顯示所有模組、隱藏所有模組、復位普通佈局、復位全屏佈局。
  11. 雙擊裝置彈出實時預覽視訊,支援圖片地圖、線上地圖、離線地圖等。
  12. 攝像機節點拖曳到對應窗體播放視訊,同時支援拖曳本地檔案直接播放。
  13. 刪除視訊支援滑鼠右鍵刪除、懸浮條關閉刪除、拖曳到視訊監控面板外刪除等多種方式。
  14. 圖片地圖上裝置按鈕可自由拖動,自動儲存位置資訊。百度地圖上可以滑鼠單擊獲取經緯度資訊,用來更新裝置位置。
  15. 視訊監控面板窗體中任意通道支援拖曳交換,瞬間響應。
  16. 封裝了百度地圖,檢視切換,運動軌跡,裝置點位,滑鼠按下獲取經緯度等。
  17. 雙擊節點、拖曳節點、拖曳窗體交換位置等操作,均自動更新儲存最後的播放地址,下次軟體開啟自動應用。
  18. 右下角音量條控制元件,失去焦點自動隱藏,音量條帶靜音圖示。
  19. 支援視訊截圖,可指定單個或者對所有通道截圖,底部小工具欄也有截圖按鈕。
  20. 支援超時自動隱藏滑鼠指標、自動全屏機制。
  21. 支援onvif雲臺控制,可上下左右移動雲臺攝像機,包括復位和焦距調整等。
  22. 支援任意onvif攝像機,包括但不限於海康、大華、宇視、天地偉業、華為等。
  23. 可儲存視訊,可選定時儲存或者單檔案儲存,可選儲存間隔時間。
  24. 可設定視訊流通訊方式tcp+udp,可設定視訊解碼是速度優先、質量優先、均衡等。
  25. 可設定軟體中文名稱、英文名稱、LOGO圖示等。
  26. 儲存的視訊檔案支援匯出到指定目錄,支援批量上傳到伺服器。

(三)特色功能

  1. 主介面採用停靠窗體模式,各種元件以小模組的形式加入,可自定義任意模組加入。
  2. 停靠模組可拖動任意位置嵌入和懸浮,支援最大化全屏,支援多螢幕。
  3. 雙重佈局檔案儲存機制,正常模式、全屏模式都對應不同的佈局方案,自動切換和儲存,比如全屏模式可以突出幾個模組透明顯示在指定位置,更具科幻感現代化。
  4. 原創onvif協議機制,採用底層協議解析(udp廣播搜尋+http請求執行命令)更輕量易懂易學習拓展,不依賴任何第三方元件比如gsoap。
  5. 原創資料匯入匯出機制,跨平臺不依賴任何元件,瞬間匯出資料。
  6. 內建多個原創元件,宇宙超值超級牛逼,包括資料匯入匯出元件(匯出到xls、pdf、列印)、資料庫元件(資料庫管理執行緒、自動清理資料執行緒、萬能分頁、資料請求等)、地圖元件、視訊監控元件、檔案多執行緒收發元件、onvif通訊元件、通用瀏覽器核心元件等。
  7. 自定義資訊框+錯誤框+詢問框+右下角提示框(包含多種格式)等。
  8. 精美換膚,高達17套面板樣式隨意更換,所有樣式全部統一,包括選單等。
  9. 視訊控制元件懸浮條可以自行增加多個按鈕,監控介面底部小工具欄也可自行增加按鈕。
  10. 雙擊攝像機節點自動播放視訊,雙擊節點自動依次新增視訊,會自動跳到下一個,雙擊父節點自動新增該節點下的所有視訊。可選主碼流、子碼流。
  11. 錄影機管理、攝像機管理,可新增刪除修改匯入匯出列印資訊,立即應用新的裝置資訊生成樹狀列表,不需重啟。
  12. 可選多種核心自由切換,ffmpeg、vlc、mpv等,均可在pro中設定。推薦用ffmpeg,跨平臺最多,預設提供好了linux和mac平臺上編譯好的庫。
  13. 支援硬解碼,可設定硬解碼型別(qsv、dxva2、d3d11va等)。
  14. 預設採用opengl繪製視訊,超低的CPU資源佔用,支援yuyv和nv12兩種格式繪製,很牛逼。
  15. 高度可定製化,使用者可以很方便的在此基礎上衍生自己的功能,比如增加自定義模組,增加執行模式、機器人監控、無人機監控、挖掘機監控等。
  16. 支援xp、win7、win10、linux、mac、各種國產系統(UOS、中標麒麟、銀河麒麟等)、嵌入式linux等系統。
  17. 註釋完整,專案結構清晰,超級詳細完整的使用開發手冊,精確到每個程式碼檔案的功能說明,不斷持續迭代版本。

三、體驗地址

  1. 體驗地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取碼:01jf 檔名:bin_video_system.zip。
  2. 國內站點:https://gitee.com/feiyangqingyun
  3. 國際站點:https://github.com/feiyangqingyun
  4. 個人主頁:https://blog.csdn.net/feiyangqingyun
  5. 知乎主頁:https://www.zhihu.com/people/feiyangqingyun/
  6. 線上文件:https://feiyangqingyun.gitee.io/qwidgetdemo/video_system.html

四、效果圖

五、核心程式碼

#include "onvifthread.h"

QScopedPointer<OnvifThread> OnvifThread::self;
OnvifThread *OnvifThread::Instance()
{
    if (self.isNull()) {
        static QMutex mutex;
        QMutexLocker locker(&mutex);
        if (self.isNull()) {
            self.reset(new OnvifThread);
        }
    }

    return self.data();
}

OnvifThread::OnvifThread(QObject *parent) : QThread(parent)
{
    stopped = false;
    working = false;
}

OnvifThread::~OnvifThread()
{
    this->stop();
    this->wait();
}

void OnvifThread::run()
{
    while (!stopped) {
        //先判斷是否在工作
        if (!working) {
            //非同步執行
            QMetaObject::invokeMethod(this, "work");
        }

        //可以自行調整休息的時間
        msleep(100);
    }

    stopped = false;
    working = false;
}

void OnvifThread::stop()
{
    stopped = true;
}

void OnvifThread::work()
{
    //設定正在工作標誌位
    working = true;

    if (devices.count() > 0) {
        mutex.lock();
        OnvifDevice *device = devices.takeFirst();
        QString cmd = cmds.takeFirst();
        QVariant data = datas.takeFirst();
        mutex.unlock();

        QList<QVariant> list = data.toList();
        QString url = device->getOnvifAddr();
        QString ip = OnvifHelper::getIP(url);
        if (cmd == "remove") {
            bool ok = OnvifHelper::onvifDevices.removeOne(device);
            device->deleteLater();
            qDebug() << TIMEMS << "執行移除物件" << ip << ok;
        } else if (cmd == "systemReboot") {
            QString result = device->systemReboot();
            qDebug() << TIMEMS << "遠端重啟裝置" << ip << result;
        } else if (cmd == "setDateTime") {
            //兩種方式都設定下 一種是直接設定日期時間字串 一種是觸發NTP同步
            bool ok = device->setDateTime(QDateTime::currentDateTime().toUTC());
            //bool ok = device->setDateTime(QDateTime::currentDateTime().toUTC(), true);
            qDebug() << TIMEMS << "設定裝置時間" << ip << ok;
        } else if (cmd == "getEvent") {
            QString result = device->getEvent();
            qDebug() << TIMEMS << "訂閱報警事件" << ip << result;
        } else if (cmd == "getProfile") {
            QString result = device->getProfile();
            qDebug() << TIMEMS << "獲取配置檔案" << ip << result;
        } else if (cmd == "snapImage") {
            QImage image = device->snapImage(device->getProfile());
            emit receiveImage(url, image);
            qDebug() << TIMEMS << "手動抓拍圖片" << ip << (!image.isNull());
        } else if (cmd == "getVideoSource") {
            QString result = device->getVideoSource();
            emit receiveResult(url, cmd, result);
            qDebug() << TIMEMS << "獲取視訊引數" << ip << result;
        } else if (cmd == "getImageSetting") {
            int brightness, colorSaturation, contrast;
            QString result = device->getImageSetting(brightness, colorSaturation, contrast);
            QVariant data = (QList<QVariant>() << brightness << colorSaturation << contrast);
            emit receiveResult(url, cmd, data);
            qDebug() << TIMEMS << "獲取圖片引數" << ip << result;
        } else if (cmd == "setImageSetting") {
            int brightness = list.at(0).toInt();
            int colorSaturation = list.at(1).toInt();
            int contrast = list.at(2).toInt();
            bool ok = device->setImageSetting(brightness, colorSaturation, contrast);
            qDebug() << TIMEMS << "設定圖片引數" << ip << ok;
        }
    }

    working = false;
}

void OnvifThread::append(const OnvifDeviceUser &deviceUser, const QString &cmd, const QVariant &data)
{
    mutex.lock();

    //先解綁以及重新繫結事件
    OnvifDevice *device = OnvifHelper::getOnvifDevice(deviceUser);
    disconnect(device, SIGNAL(receiveEvent(QString, OnvifEventInfo)), this, SIGNAL(receiveEvent(QString, OnvifEventInfo)));
    connect(device, SIGNAL(receiveEvent(QString, OnvifEventInfo)), this, SIGNAL(receiveEvent(QString, OnvifEventInfo)));

    //新增到佇列等待處理
    devices << device;
    cmds << cmd;
    datas << data;

    mutex.unlock();
}