1. 程式人生 > 實用技巧 >Qt音視訊開發34-Onvif時間設定

Qt音視訊開發34-Onvif時間設定

一、前言

對裝置設定時間很有必要,這個是必備的功能,畢竟大部分的前端裝置比如攝像機本身不帶BIOS電池的,所以沒法儲存時間,要麼設定了NTP地址來同步時間,要麼其他裝置主動對他進行設定時間,如果時間不正確了,意味著本地畫面顯示的時間字串,本地儲存的視訊錄影檔案等,都可能是不正確的,所以一般的處理是NVR一旦連上攝像機裝置以後,立馬將攝像機的時間設定成NVR的時間,這樣就保持了一致。

用onvif進行時間設定主要由兩種,一種是通過設定NTP服務地址以後,主動呼叫NTP同步來進行,另外一種就是傳送日期時間的資料包給裝置,讓他自己解析處理,這裡要注意的是,資料包中的日期時間是UTC格式的,即倫敦時間,所以在使用的時候需要自己本地先轉換成UTC時間在傳送,Qt內建了轉換成UTC時間的方法 QDateTime::currentDateTime().toUTC()。

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. 廣播搜尋裝置,支援IPC和NVR,依次返回,可選擇不同的網絡卡IP。
  2. 依次獲取Onvif地址、Media地址、Profile檔案、Rtsp地址。
  3. 可對指定的Profile獲取視訊流Rtsp地址,比如主碼流子碼流地址。
  4. 可對每個裝置設定Onvif使用者資訊,用於認證獲取詳細資訊。
  5. 可實時預覽攝像機影象。
  6. 支援雲臺控制,可上下左右調節雲臺,支援絕對移動和相對移動,可放到和縮小影象遠近。
  7. 支援Qt4和Qt5任意Qt版本,親測Qt4.7.0到Qt5.14.2。
  8. 支援任意編譯器,親測mingw、msvc、gcc、clang。
  9. 支援任意作業系統,親測xp、win7、win10、android、linux、嵌入式linux、樹莓派全志H3等。
  10. 支援任意Onvif攝像機和NVR,親測海康、大華、宇視、華為、海思晶片核心等,可定製開發。
  11. 支援對指定IP地址及onvif地址進行單播搜尋,比如跨網段情況下非常有用。
  12. 支援指定過濾條件過濾搜尋裝置。
  13. 支援搜尋間隔設定,保證所有裝置搜尋回來,在大量裝置現場很有用。
  14. 可對圖片引數(亮度、色彩度、飽和度)進行設定。
  15. 支援NTP校時和時間同步設定。
  16. 純Qt編寫,超級小巧輕量,總共約2000行程式碼,不依賴任何第三方的庫和元件,跨平臺。
  17. 封裝好了通用的資料傳送和接收解析的函式,可以非常方便的自行拓展其他Onvif處理。
  18. 工具上提供了收發資料文字框,顯示收發的資料,方便檢視和分析。
  19. 支援所有Onvif裝置,程式碼工整,介面友好,直接引入pri即可使用。

三、效果圖

四、相關站點

  1. 國內站點:https://gitee.com/feiyangqingyun/QWidgetDemo
  2. 國際站點:https://github.com/feiyangqingyun/QWidgetDemo
  3. 個人主頁:https://blog.csdn.net/feiyangqingyun
  4. 知乎主頁:https://www.zhihu.com/people/feiyangqingyun/
  5. 體驗地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652

五、核心程式碼

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

QString OnvifOther::writeData(const QString &key, const QString &value, const QString &flag,
                              bool xmlns, bool value4, quint8 type)
{
    if (device->deviceUrl.isEmpty()) {
        return QString();
    }

    QString file = device->request->getSendData(key, true, xmlns);
    QByteArray dataSend = file.toUtf8();
    //最後引數表示超時時間 一般請求都是很快的 除非對方不線上則卡很久 需要設定下超時時間
    QNetworkReply *reply = device->request->post(device->deviceUrl, dataSend, 3000);
    emit sendData(dataSend, device->deviceUrl);

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

        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) {
            results = query.getVideoSource();
        }
    }

    return results.join(ResultSplit);
}