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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章