• 105阅读
  • 0回复

基于Qt实现onvif设备端代码/onvif设备模拟器/记一个linux上组播失败的问题解决 [复制链接]

上一主题 下一主题
离线liudianwu
 

只看楼主 正序阅读 楼主  发表于: 08-31


## 一、前言说明
之前已经实现了完整的onvif设备模拟器代码,主要是在windows上测试的,按照之前的经验,代码是已经做了其他系统的兼容,估计linux系统运行也是没有问题的,有时候不亲测测试还真不知道什么情况,比如这次就翻车了,用户说在linux上只能单播不能广播,组播权限也都是开启的,而且抓包也能抓到对应组播的包,说明环境没有问题,这是最大的前提,如果抓包也抓不到就需要检查环境问题。那问题出在哪里呢,组播用的是qudpsocket是监听的,而且代码在windows上也完全正常,估计是某个细节导致的,好在现在AI非常发达,问了问豆包,给出一段代码测试,和我自己写的代码,几乎一样,但是他的代码运行,居然可以收到组播包,原来有个区别,udpSocket->bind(QHostAddress::AnyIPv4, 6000),而我用的udpSocket->bind(QHostAddress("192.168.0.110"), 6000),因为只有一个网卡,所以不涉及到多网卡指定的问题,AnyIPv4最多就是还多了个127.0.0.1的内部虚拟的网卡,根本不会经过实体网卡,但是恰恰是这个小细节,导致了组播失效。

原来windows和linux的组播机制有点不一样,linux上组播数据的传递依赖于组播组地址(如239.255.43.21),而非接收端的本地 IP 地址。当你绑定到具体 IP(如192.168.0.110)时,操作系统会对接收的数据包进行严格过滤,只有目标 IP 是192.168.0.110的单播数据包会被接收,目标 IP 是组播地址(如239.255.43.21)的数据包会被过滤掉,因为它不匹配绑定的具体 IP。而绑定QHostAddress::AnyIPv4(等价于0.0.0.0)时,操作系统会接收所有 IP 地址(包括单播、组播)的数据包,此时组播数据才能被QUdpSocket捕获。但是windows上默认不会去匹配具体绑定的IP,只要加入了组播就可以。


## 二、效果图




## 三、相关地址
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. 支持gb28181设备模拟,具备设备注册、设备注销、设备心跳、设备信息、设备配置、设备状态应答等。
19. 支持模拟报警和位置上报等,方便平台侧显示对应设备的实时位置。
20. 支持一键添加批量模拟28181设备,实时显示已注册和已注销状态。
21. 支持将本地桌面、本地摄像头、任意视频文件、视频流文件、手机摄像头等转换成28181设备,添加到NVR或者国标软件平台。
22. sip协议同时支持udp和tcp两种通信方式,视频点播同时支持udp/tcp主动/tcp被动三种方式,涵盖所有可能的场景需求。
23. 无论是onvif设备模拟组件还是28181设备模拟组件,全部原创底层协议解析,纯Qt实现,跨任意平台。
24. 代码结构框架非常清晰,注释详细,代码精简不繁琐,非常易于学习和移植,可以很容易拓展其他接口需求。
25. 支持Qt4/Qt5/Qt6以及后续所有版本、所有编译器、所有开发环境。
26. 支持windows、linux、mac、国产OS、嵌入式linux、RK3588、树莓派、香橙派等系统。

## 五、相关代码
```cpp
#include "onvifdevicesearch.h"
#include "onvifdevicehelper.h"
#include "onvifdevicelisten.h"
#include "onvifdevicepush.h"

OnvifDeviceSearch::OnvifDeviceSearch(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
}

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

int OnvifDeviceSearch::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 OnvifDeviceSearch::setPara(const QStringList &hards, const QStringList &addrs)
{
    this->hards = hards;
    this->addrs = addrs;
}

bool OnvifDeviceSearch::start(const QString &ip)
{
    isOk = udpSocket->bind(QHostAddress("0.0.0.0"), 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
    } else {
        QMessageBox::critical(NULL, "错误", QString("绑定搜索失败, 原因: %1").arg(udpSocket->errorString()));
    }

    return isOk;
}

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

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

void OnvifDeviceSearch::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;
        }

        //过滤关键字/排除非搜索数据/因为是组播/所以还会收到自身以及其他设备的搜索应答数据
        bool isSearch = data.contains("</Probe>");
        emit receiveInfo(QString("%1 -> %2").arg(isSearch ? "搜索设备" : "设备应答").arg(host.toString()));
        emit receiveData(data);
        if (!isSearch) {
            continue;
        }

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

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

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

    //逐个取出地址发送数据
    int count = addrs.count();
    for (int i = 0; i < count; ++i) {
        QString hard = hards.at(i);
        QString addr = addrs.at(i);
        QString url = addr + "/device_service";
        QString uuid1 = OnvifDeviceHelper::getUuid();

        QByteArray buffer = QString(data).arg(uuid1, uuid, hard, url).toUtf8();
        udpSocket->writeDatagram(buffer, host, port);
        udpSocket->flush();
        emit receiveInfo(QString("搜索应答 -> %1").arg(addr));
        emit sendData(buffer);
    }
}

```
欢迎关注微信公众号:Qt实战/Qt入门和进阶(各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发) QQ:517216493  WX:feiyangqingyun  QQ群:751439350
快速回复
限100 字节
 
上一个 下一个