Qt編寫視頻監控系統71-外網訪問攝像頭等設備(獲取各種信息及拉流)

一、前言

最近遇到個需求是通過外網接入攝像機或者NVR,通用的做法是將視頻流推流到服務器,然後拉取rtmp視頻流,這樣就多了服務器的要求,而且實現的功能有限比如不能直接用onvif協議對設備獲取信息和配置信息,還有個做法就是將設備直接通過路由器端口映射到外網,根據外網的IP地址和端口對應填入即可。先不考慮安全問題,因爲現在設備廠家幾乎對安全性做的比較好,登錄和onvif交互等,都需要用戶驗證,甚至還可以開啓https機制。

通過端口映射後外網確實可以正常訪問到設備,比如網頁打開登錄,但是通過onvif消息交互就未必正確了,需要調整代碼,比如獲取到的視頻流地址是rtsp://192.168.0.5:554/1,由於經過了端口映射,所以你需要將這個onvif交互中的IP地址和端口都要換成映射後的地址和端口,否則不能正常訪問使用,由於設備本身並不知道你做過端口映射,他只會應答自己本身的信息打包發出去,所以需要在onvif代碼中將對應地址替換,取onvif地址中的IP地址即可,當然也有可能是需要微調的,比如設備多個的時候經過一個路由器端口有限,一個554端口只能對應映射到一個設備的554端口,其他設備要改成其他端口,這就需要將獲取到的地址後的端口再手動修改即可。

通過onvif機制和外網攝像頭設備交互,和內網比較,就差一個不能實現,那就是廣播搜索設備,因爲在外網上,不存在發個廣播消息,設備可以接收到的,畢竟互聯網上的設備萬億計算。除了這個無法實現以外,其他都正常,包括設置圖片參數(明亮度、飽和度、對比度、尖銳度),雲臺控制、預置位相關操作都ok。由於無法廣播搜索設備,需要對外網的設備挨個單播搜索獲取詳細的設備信息,本系統封裝好的onvif組件支持單播搜索,填入IP地址和端口即可。

可以通過端口映射,外網訪問攝像頭或者NVR等,基本步驟如下:

  1. 進入到路由器配置界面,查看分配的外網IP地址,記下來。
  2. 進入到端口映射配置界面,不同路由器位置不一樣,可以找找端口映射之類的字眼,也可能叫虛擬服務器。
  3. 添加對應的端口映射信息,外部端口對應外部訪問該設備的端口,內部端口對應之前內網訪問該設備的端口。
  4. 在設備搜索那邊指定設備填入IP地址和端口,單擊單播搜索按鈕,會自動搜索到設備的onvif地址,然後填入onvif用戶信息,此時再去單擊獲取所有,如果有返回信息則表示正常。

二、效果圖

三、體驗地址

  1. 體驗地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取碼:01jf 文件名:bin_video_system。
  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/

四、相關代碼

void OnvifQuery::checkAddr(const QString &ipport, QString &addr, bool checkOnvif, bool replacePort)
{
    QStringList list = addr.split("/");
    if (addr.isEmpty() || list.count() <= 3) {
        return;
    }

    //將內網的地址換成外網的(新增設備映射到外網後返回的數據中內網地址轉換成外網的)
    if (replacePort) {
        list[2] = ipport;
    } else {
        //只替換IP地址
        list[2] = ipport.split(":").first();
    }

    //將地址補全字符串(測試發現天地偉業的攝像機返回的地址都是沒有帶onvif字樣的需要補上纔行)
    //http://192.168.0.160:80/Media 要轉成 http://192.168.0.160:80/onvif/Media
    if (checkOnvif && !addr.contains("/onvif/")) {
        list.insert(3, "onvif");
    }

    addr = list.join("/");
}

OnvifHttpAddr OnvifQuery::getServices(const QString &ipport)
{
    //<tds:Namespace>http://www.onvif.org/ver10/media/wsdl</tds:Namespace>
    //<tds:XAddr>http://192.168.0.160:80/Media</tds:XAddr>
    //<tds:Namespace>http://www.onvif.org/ver20/ptz/wsdl</tds:Namespace>
    //<tds:XAddr>http://192.168.0.160:80/PTZ</tds:XAddr>

    OnvifHttpAddr httpAddr;

    //取出來時剛好按照順序一一對應
    QDomNodeList nodeNamespace = doc.elementsByTagName(QString("%1:Namespace").arg(wsdlAddr.wsdlDevice));
    QDomNodeList nodeXAddr = doc.elementsByTagName(QString("%1:XAddr").arg(wsdlAddr.wsdlDevice));
    int size1 = nodeNamespace.size();
    int size2 = nodeXAddr.size();
    if (size1 == size2) {
        for (int i = 0; i < size1; ++i) {
            QString name = nodeNamespace.at(i).toElement().text();
            QString addr = nodeXAddr.at(i).toElement().text();
            this->checkAddr(ipport, addr, true, true);

            //可以自行增加其他的
            if (name == "http://www.onvif.org/ver10/device/wsdl") {
                httpAddr.addrDevice = addr;
            } else if (name == "http://www.onvif.org/ver10/media/wsdl") {
                httpAddr.addrMedia = addr;
            } else if (name == "http://www.onvif.org/ver20/media/wsdl") {
                httpAddr.addrMedia2 = addr;
            } else if (name == "http://www.onvif.org/ver20/ptz/wsdl") {
                httpAddr.addrPtz = addr;
            } else if (name == "http://www.onvif.org/ver20/imaging/wsdl") {
                httpAddr.addrImaging = addr;
            } else if (name == "http://www.onvif.org/ver10/events/wsdl") {
                httpAddr.addrEvents = addr;
            } else if (name == "http://www.onvif.org/ver20/analytics/wsdl") {
                httpAddr.addrAnalytics = addr;
            }
        }
    }

    return httpAddr;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章