• 104阅读
  • 0回复

推迟了十年终于搞定/纯Qt实现onvif设备模拟器/虚拟监控摄像头/批量模拟几千路/电脑桌面转onvif [复制链接]

上一主题 下一主题
离线liudianwu
 



## 一、前言说明
视频监控设备一般都会支持onvif协议,在做视频监控平台软件开发的时候,一般需要外接监控设备进行测试,这种条件不是每个人每个时候都能具备的,比如有些时候设备可能拿出去用了,有些时候在家里加班调试程序,有时候需要几千路接入进行压力测试,林林种种的情况非常多,所以急切需要一个onvif设备模拟器,可以将视频文件转换成onvif设备,这样就无须单独具备监控设备,要多少路就模拟多少路,甚至还可以给监控平台软件做压力测试,比如一些监控平台号称可以同时接入几千路,同时显示64路,用这个模拟器对接后,一切性能都显出原形。同时显示64路主码流查看对应的电脑CPU和GPU使用情况。

整个onvif模拟器需要分三大块,第一块是要实现onvif组播搜索,第二块是实现onvif请求应答交互,第三步是实现rtsp推流,三部缺一不可,如果只是需要onvif通信交互不需要视频,那就rtsp推流这块不用。要在Qt中实现组播,要从4.8开始就可以,udp直接就提供了组播功能,这里有个插曲,应该很多做组播开发的人会遇到,当电脑上多个网卡的时候,组播的时候joinMulticastGroup第二个参数,就是指定网卡,不指定的话,很可能本地搜索不到,不指定网卡可能随机绑定到某个或者多个上面,导致不稳定,时好时坏,我就在这块搞了很多天,查阅了众多资料才找到的。

网上很多人做onvif设备端,都是使用第三方开源库soap啥的,我也用过,能用但是使用复杂,尤其是函数命名使用很不爽,部分接口有bug,需要自己调整,基于全宇宙原创者五个字的卖点,还是打算从协议底层解析来做,毕竟Qt的网络通信还是用的比较熟悉,协议这块通过抓包也是都能拿到,那就底层直接tcp监听来做,收发数据通过xml数据填充对应的字段即可,拓展性强,可控性也好。

## 二、效果图
window.open('http://www.qtcn.org/bbs/attachment/Mon_2507/44_110085_2a5b1e2cedc311c.jpg?410');" style="max-width:700px;max-height:700px;" onload="if(is_ie6&&this.offsetWidth>700)this.width=700;" >



## 三、相关地址
1. 国内站点:[https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)
2. 国际站点:[https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
3. 个人作品:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
4. 文件地址:[https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g](https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g) 提取码:01jf 文件名:bin_video_simulate。

## 四、功能特点
1. 标准onvif协议,支持设备搜索、获取参数、快照抓图等。
2. 支持264/265/aac等标准视音频协议传输。
3. 支持多路批量onvif设备模拟,每一路都独立的端口。
4. 支持本地摄像头采集转成onvif,可选择不同的设备、分辨率、帧率等参数。
5. 支持本地桌面采集转成onvif,可选择不同的屏幕、分辨率、帧率等参数。
6. 支持各种视频文件和视频流转成onvif,可重新设置编码转换以及分辨率转换。
7. 支持4K、8K等高清分辨率,不限制分辨率,非264/265会自动转码推流。
8. 每一路都可以设置统一或者独立的用户验证信息,为空则表示不验证。
9. 可以把任意内容接入到NVR以及视频监控系统,方便保存录像文件,以便回放可查。
10. 也可作为压力测试工具,比如模拟几千路onvif设备,让集成平台软件做接入压力测试。
11. 推出去的流不仅有rtsp格式,还支持rtmp、http、flv、ws-flv、webrtc等方式访问,可以直接网页查看。
12. 在管理工具上可以看到每一路的推流状况以及分辨率信息,非常直观。
13. 支持自动重连拉流,重连推流,保证7乘以24小时稳定运行。
14. 可设置开机自启动运行和后台运行,不显示在任务栏,作为后台服务运行。
15. 可批量添加文件、添加目录,自动将目录下的所有文件添加到模拟器。
16. 多功能添加地址面板,可以选择本地设备和监控设备,本地设备会自动识别摄像头设备和桌面设备,监控设备可以选择不同厂家,自动填充对应rtsp格式,填入用户信息即可,可以批量递增添加监控设备。
17. 可无缝上传到市面上所有的onvif协议设备,包括海康、大华、宇视、华为、天地伟业等,也支持ONVIF Device Manager国际onvif工具。
18. 支持Qt4/Qt5/Qt6以及后续所有版本、所有编译器、所有开发环境。
19. 支持windows、linux、mac、国产OS、嵌入式linux、RK3588、树莓派、香橙派等系统。

## 五、相关代码
```cpp
#include "onvifsearchserver.h"
#include "onvifserverhelper.h"
#include "onviflistenserver.h"
#include "onvifpushserver.h"

OnvifSearchServer::OnvifSearchServer(QObject *parent) : QObject(parent)
{
    isOk = false;
    udpSocket = new QUdpSocket(this);
    connect(udpSocket, SIGNAL(readyRead()), this, SLOT(readData()));
#if (QT_VERSION >= QT_VERSION_CHECK(4,8,5))
    udpSocket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, 1);
#endif
}

OnvifSearchServer::~OnvifSearchServer()
{
    this->stop();
}

int OnvifSearchServer::getInterfaces(const QString &ip)
{
    QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
    for (int i = 0; i < interfaces.count(); ++i) {
        QList<QNetworkAddressEntry> addrs = interfaces.at(i).addressEntries();
        foreach (QNetworkAddressEntry addr, addrs) {
            if (addr.ip().toString() == ip) {
                return i;
            }
        }
    }

    return -1;
}

void OnvifSearchServer::setAddrs(const QStringList &addrs)
{
    this->addrs = addrs;
}

bool OnvifSearchServer::start(const QString &ip)
{
    isOk = udpSocket->bind(QHostAddress(ip), OnvifPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
    if (isOk) {
#if (QT_VERSION >= QT_VERSION_CHECK(4,8,5))
        int index = getInterfaces(ip);
        QNetworkInterface interface;
        if (index >= 0) {
            interface = QNetworkInterface::allInterfaces().at(index);
        }

        //第二个参数用来绑定网卡/不绑定的话很可能组播消息收不到/因为没有绑定在需要的网卡
        isOk = udpSocket->joinMulticastGroup(QHostAddress(OnvifHost), interface);
        //udpSocket->setSocketOption(QUdpSocket::MulticastLoopbackOption, 0);
#endif
    }

    return isOk;
}

void OnvifSearchServer::stop()
{
    if (!isOk) {
        return;
    }

#if (QT_VERSION >= QT_VERSION_CHECK(4,8,5))
    udpSocket->leaveMulticastGroup(QHostAddress(OnvifHost));
#endif
    udpSocket->abort();
    isOk = false;
}

void OnvifSearchServer::readData()
{
    QByteArray data;
    QHostAddress host;
    quint16 port = 0;

    while (udpSocket->hasPendingDatagrams()) {
        data.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(data.data(), data.size(), &host, &port);
        if (data.size() == 0) {
            continue;
        }

        //过滤关键字/排除非搜索数据
        emit receiveInfo(QString("搜索设备 -> %1").arg(host.toString()));
        emit receiveData(data);
        if (!data.contains("</Probe>")) {
            continue;
        }

        //取出uuid/应答数据需要用这个
        QString uuid = OnvifServerHelper::getMessageID(data);
        if (!uuid.isEmpty()) {
            this->writeData(host, port, uuid);
        }
    }
}

void OnvifSearchServer::writeData(QHostAddress host, quint16 port, const QString &uuid)
{
    if (!isOk) {
        return;
    }

    //要发送的数据/待填充数据
    static QByteArray data = OnvifServerHelper::getFile(":/onvifresponse/SearchDevice.xml");
    if (data.isEmpty()) {
        return;
    }

    //逐个取出地址发送数据
    foreach (QString addr, addrs) {
        QString url = addr + "/device_service";
        QString uuid1 = OnvifServerHelper::getUuid();
        QString uuid2 = OnvifServerHelper::getUuid();
        QByteArray buffer = QString(data).arg(uuid1, uuid, uuid2, url).toUtf8();
        udpSocket->writeDatagram(buffer, host, port);
        emit receiveInfo(QString("搜索应答 -> %1").arg(addr));
        emit sendData(buffer);
    }
}
```
欢迎关注微信公众号:Qt实战/Qt入门和进阶(各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发) QQ:517216493  WX:feiyangqingyun  QQ群:751439350
快速回复
限100 字节
 
上一个 下一个