Qt編寫安防視訊監控系統38-onvif校時

語言: CN / TW / HK

一、前言

在視訊監控系統中,對攝像機進行時間設定也是很有必要的,這樣就和伺服器或者軟體這邊統一了時間,一般在攝像機的畫面上可以設定OSD標識當前時間,這樣儲存到視訊檔案中回放的時候,也能和本地的時間一致,一般的視訊監控系統預設都會開啟ONVIF校時,通過標準的公開的onvif協議來對前端攝像機裝置進行時間設定,當然也可以獲取時間。前端攝像機裝置和後端管理軟體或者伺服器時間統一是非常重要的一個因素,本人經歷過很多視訊監控系統相關的專案,很多時候的報修情況就是因為前端裝置時間和伺服器端不一致的情況,導致的各種奇奇怪怪的問題。

onvif校時有兩種方式,一種是通過觸發呼叫NTP伺服器讓攝像機自己去核對一次時間,另外一種方式是直接將當前時間轉成UTC時間,填入到UTCDateTime這個節點資料中,http形式傳送給對應的攝像機onvif地址即可,設定好以後會自動應用。如何判斷設定是否成功呢,除了人肉眼觀察視訊監控畫面中的時間是否調整過來以外,還可以接收請求資料返回的資料進行解析判斷,有個通用的規則,比如設定時間傳送的節點關鍵是是SetSystemDateAndTime,設定成功會應答SetSystemDateAndTimeResponse,就是在關鍵字後面加個Response,所以可以寫個通用的規則進行這個解析處理就行,比如獲取主機名GetHostname、獲取閘道器GetNetworkDefaultGateway、獲取DNS資訊GetDNS等,都是按照這個規則走。

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

四、效果圖

五、核心程式碼

QString OnvifOther::writeData(const QString &key, const QString &value, const QString &flag,
                              bool xmlns, bool value4, quint8 type)
{
    QString file = OnvifXml::getSendData(device->getUserToken(), key, true, xmlns);
    QByteArray dataSend = file.toUtf8();
    //最後引數表示超時時間 一般請求都是很快的 除非對方不線上則卡很久 需要設定下超時時間
    QNetworkReply *reply = device->request->post(device->onvifAddr, dataSend, 3000);

    QStringList results;
    QByteArray dataReceive;
    bool ok = device->checkData(reply, dataReceive, flag);
    if (ok) {
        OnvifQuery query;
        if (!query.setData(dataReceive)) {
            return results.join(OnvifResultSplit);
        }

        if (type == 0) {
            if (!value.isEmpty()) {
                //可能有多個關鍵字需要獲取
                QStringList list = value.split("|");
                foreach (QString str, list) {
                    QString result = value4 ? query.getValue4(str) : query.getValue3(str);
                    if (result != "-1") {
                        results << QString("%1:%2").arg(str).arg(result);
                    }
                }
            }
        } else if (type == 1) {
            OnvifVideoInfo videoInfo = query.getVideoInfo();
            results << QString("%1:%2").arg("token").arg(videoInfo.token);
            results << QString("%1:%2").arg("framerate").arg(videoInfo.framerate);
            results << QString("%1:%2").arg("width").arg(videoInfo.width);
            results << QString("%1:%2").arg("height").arg(videoInfo.height);
        }
    }

    return results.join(OnvifResultSplit);
}

QString OnvifOther::getDateTime()
{
    QString result = writeData("GetSystemDateAndTime", "tt:Year|tt:Month|tt:Day|tt:Hour|tt:Minute|tt:Second|tt:TZ", "獲取時間", true, true);
    QStringList list = result.split(OnvifResultSplit);
    if (list.count() != 7) {
        return result;
    }

    QString year = list.at(0).split(":").last();
    QString month = list.at(1).split(":").last();
    QString day = list.at(2).split(":").last();
    QString hour = list.at(3).split(":").last();
    QString min = list.at(4).split(":").last();
    QString sec = list.at(5).split(":").last();

    //計算時區並賦值
    QString timezone = list.at(6);
    timezone = timezone.mid(6, timezone.length() - 6);
    device->timezone = timezone;

    //將日期根據時區進行運算
    QString str = QString("%1-%2-%3 %4:%5:%6").arg(year).arg(month).arg(day).arg(hour).arg(min).arg(sec);
    QDateTime dt = QDateTime::fromString(str, "yyyy-M-d h:m:s");
    if (!device->timezone.contains("GMT-08")) {
        dt = dt.addSecs(8 * 60 * 60);
    }

    //不足兩位補零
    list = dt.toString("yyyy-M-d-h-m-s").split("-");
    result = QString("%1-%2-%3 %4:%5:%6 %7").arg(list.at(0)).arg(list.at(1), 2, '0').arg(list.at(2), 2, '0')
             .arg(list.at(3), 2, '0').arg(list.at(4), 2, '0').arg(list.at(5), 2, '0').arg(timezone);
    return result;
}

bool OnvifOther::setDateTime(const QDateTime &datetime, bool ntp)
{
    QStringList temp = datetime.toString("yyyy-M-d-h-m-s").split("-");
    QString wsdl = "http://www.onvif.org/ver10/device/wsdl";
    QString schema = "http://www.onvif.org/ver10/schema";

    QStringList list;
    list << QString("    <SetSystemDateAndTime xmlns=\"%1\">").arg(wsdl);
    list << QString("      <DateTimeType>%1</DateTimeType>").arg(ntp ? "NTP" : "Manual");
    list << QString("      <DaylightSavings>%1</DaylightSavings>").arg("false");
    list << QString("      <TimeZone>");
    list << QString("        <TZ xmlns=\"%1\">%2</TZ>").arg(schema).arg(ntp ? device->timezone : "CST-8");
    list << QString("      </TimeZone>");

    if (!ntp) {
        list << QString("      <UTCDateTime>");
        list << QString("        <Date xmlns=\"%1\">").arg(schema);
        list << QString("          <Year>%1</Year>").arg(temp.at(0));
        list << QString("          <Month>%1</Month>").arg(temp.at(1));
        list << QString("          <Day>%1</Day>").arg(temp.at(2));
        list << QString("        </Date>");
        list << QString("        <Time xmlns=\"%1\">").arg(schema);
        list << QString("          <Hour>%1</Hour>").arg(temp.at(3));
        list << QString("          <Minute>%1</Minute>").arg(temp.at(4));
        list << QString("          <Second>%1</Second>").arg(temp.at(5));
        list << QString("        </Time>");
        list << QString("      </UTCDateTime>");
    }

    list << QString("    </SetSystemDateAndTime>");

    QString result = writeData(list.join("\r\n"), "SetSystemDateAndTimeResponse", "設定時間", false);
    return result.contains("SetSystemDateAndTimeResponse");
}