当前位置: 首页 > article >正文

Onvif协议NVR开发方案指南

Onvif协议NVR开发方案指南


一、架构设计

1. 双重角色功能
角色功能描述协议交互对象
服务端提供多通道视频流、事件订阅、PTZ控制,向上级平台暴露ONVIF接口上级监控平台、第三方客户端
客户端发现并接入IPC设备,拉取视频流、订阅事件,将数据整合到本地服务下级IPC设备
2. 数据流逻辑
1. 视频流/事件
2. 存储/分析
3. 协议转换
4. 级联视频流
5. 转发事件
6. 控制指令
7. 指令转发
IPC设备
NVR客户端模块
本地存储
NVR服务端模块
上级平台

二、核心模块实现


模块1:设备发现与动态通道管理
实现思路
  1. 服务端发现

    • NVR作为设备响应WS-Discovery Probe请求,返回服务地址和Profile信息。
    • 使用UDP多播(端口3702)实现,兼容ONVIF 2.0/2018版本。
  2. 客户端发现

    • NVR主动探测局域网IPC,通过GetCapabilities协商能力。
    • 动态注册IPC到通道表,支持通道增删改查。
关键代码
// 服务端:响应Probe请求
int onvif_probe_handler(...) {
    struct wsdd__ProbeMatchesType resp;
    resp.ProbeMatch->XAddrs = "http://nvr_ip:8080/onvif/device_service";
    resp.ProbeMatch->Scopes = "onvif://www.onvif.org/Profile/S";
    return soap_wsdd_ProbeMatches(soap, ...);
}

// 客户端:发现并注册IPC
void discover_ipc() {
    soap_wsdd_Probe(soap, SOAP_WSDD_ADHOC, "dn:NetworkVideoTransmitter", nullptr);
    while (soap_wsdd_listen(soap, 1000) == SOAP_OK) {
        for (auto &match : matches.ProbeMatch) {
            DeviceBindingProxy proxy(match.XAddrs);
            _tds__GetCapabilitiesResponse caps;
            if (proxy.GetCapabilities(nullptr, caps) == SOAP_OK) {
                add_channel({
                    id: channels.size() + 1,
                    name: "IPC_" + std::to_string(channel_id),
                    rtsp_url: get_ipc_stream_url(proxy), // 调用IPC的GetStreamUri
                    profile_token: "Profile_IPC_" + std::to_string(channel_id),
                    is_local: false,
                    is_online: true
                });
            }
        }
    }
}

模块2:鉴权与安全机制
实现思路
  1. 服务端鉴权

    • 实现WS-Security Digest认证,验证UsernameToken中的Nonce和PasswordDigest。
    • 兼容Basic Auth(旧设备)和TLS 1.3(2021版)。
  2. 客户端鉴权

    • 动态添加鉴权头,适配不同IPC设备的认证方式。
关键代码
// 服务端:密码校验
int validate_password(...) {
    std::string stored_pwd = query_db(username);
    return (sha1(nonce + timestamp + stored_pwd) == password) ? SOAP_OK : SOAP_FAULT;
}

// 客户端:添加动态鉴权头
void add_auth_header(soap* ctx, const IPCConfig &ipc) {
    if (ipc.version >= 2018) {
        soap_wsse_add_UsernameTokenDigest(ctx, "user", ipc.user.c_str(), ipc.pwd.c_str());
    } else {
        // Basic Auth
        soap->auth = soap_wsse_add_BasicAuth(ctx, ipc.user.c_str(), ipc.pwd.c_str());
    }
}

模块3:多通道视频流管理
实现思路
  1. 本地通道

    • NVR直接管理物理摄像头,通过FFmpeg/Live555发布RTSP流。
  2. IPC通道

    • 拉取IPC的RTSP流,转封装为NVR的RTSP服务。
    • 支持负载均衡:限制最大并发流数量(如16路)。
关键代码
// 统一视频流入口
void start_all_streams() {
    thread_pool pool(MAX_STREAMS); // 限制最大并发数
    for (auto &[id, channel] : channels) {
        pool.enqueue([&channel] {
            if (channel.is_local) {
                // 发布本地摄像头流
                system(fmt::format("ffmpeg -i /dev/video{} -c h264 -f rtsp rtsp://nvr_ip:554/local_ch{}", 
                    id, id).c_str());
            } else {
                // 拉取IPC流并转发
                system(fmt::format("ffmpeg -i {} -c copy -f rtsp rtsp://nvr_ip:554/ipc_ch{}", 
                    channel.rtsp_url, id).c_str());
            }
        });
    }
}

// 服务端:GetStreamUri接口
int MediaService::GetStreamUri(...) {
    auto ch = find_channel_by_token(req->ProfileToken);
    resp->MediaUri->Uri = ch.is_local ? 
        fmt::format("rtsp://nvr_ip:554/local_ch{}", ch.id) :
        fmt::format("rtsp://nvr_ip:554/ipc_ch{}", ch.id);
    return SOAP_OK;
}

模块4:事件双向传递
实现思路
  1. 客户端事件订阅

    • 订阅所有IPC的MotionDetector事件,解析事件中的通道ID。
  2. 服务端事件转发

    • 将IPC事件转换为NVR事件格式,添加通道标识后推送给上级平台。
  3. 控制指令传递

    • 解析上级平台的PTZ指令,转发到对应IPC。
关键代码
// 客户端:订阅IPC事件
void subscribe_ipc_events(const IPCConfig &ipc) {
    EventBindingProxy proxy(ipc.xaddr);
    _tev__CreatePullPointSubscriptionResponse sub;
    if (proxy.CreatePullPointSubscription(nullptr, sub) == SOAP_OK) {
        event_threads.emplace_back([sub, ipc] {
            while (true) {
                _tev__PullMessagesResponse msgs;
                if (proxy.PullMessages(..., msgs) == SOAP_OK) {
                    for (auto &msg : msgs.NotificationMessage) {
                        // 添加通道ID标识
                        std::string enriched_msg = fmt::format(
                            "<nvr:ChannelID>{}</nvr:ChannelID>{}", ipc.channel_id, msg.Message);
                        event_queue.push(enriched_msg); // 进入全局事件队列
                    }
                }
            }
        });
    }
}

// 服务端:事件推送线程
void event_push_thread() {
    EventBindingProxy upper_proxy(upper_platform_url);
    while (true) {
        auto msg = event_queue.pop();
        _tev__Notify notify;
        notify.NotificationMessage.push_back(build_tev_message(msg));
        upper_proxy.Notify(notify); // 推送至上级平台
    }
}

// PTZ指令转发
int PTZService::ContinuousMove(...) {
    int ch_id = extract_channel_id(req->ProfileToken);
    auto &ipc = get_ipc_by_channel(ch_id);
    PTZBindingProxy proxy(ipc.xaddr);
    add_auth_header(proxy.soap, ipc); // 添加IPC鉴权
    return proxy.ContinuousMove(req, resp); // 转发指令
}

模块5:协议兼容性处理
实现思路
  1. 多版本代码隔离

    • 使用不同命名空间生成2.0/2018/2021版代码。
  2. 动态接口适配

    • 根据GetCapabilities返回的命名空间选择接口版本。
关键代码
// 接口工厂
template<typename T20, typename T18>
auto create_service_proxy(const std::string &xaddr) {
    if (xaddr.find("ver20") != string::npos) 
        return std::make_unique<T18>(xaddr);
    else 
        return std::make_unique<T20>(xaddr);
}

// 动态调用示例
void get_device_info(const std::string &xaddr) {
    auto proxy = create_service_proxy<DeviceBindingProxy_2_0, DeviceBindingProxy_2018>(xaddr);
    _tds__GetDeviceInformationResponse resp;
    proxy->GetDeviceInformation(nullptr, resp);
}

三、验证与测试

1. 测试用例设计
测试场景验证方法预期结果
设备发现使用ONVIF Device Manager搜索NVR和IPCNVR和IPC均可见
多通道视频流VLC同时播放NVR的local_ch1和ipc_ch2两路流均流畅无卡顿
事件级联触发IPC移动侦测,查看上级平台日志10秒内收到带通道ID的事件
级联PTZ控制在上级平台控制NVR通道,观察IPC摄像头转动IPC云台按指令运动
断线重连拔掉IPC网线,5分钟后恢复NVR日志显示通道状态变化
2. 性能压测
# 模拟16路1080P@25fps流
for i in {1..16}; do
    ffmpeg -re -i test.mp4 -c copy -f rtsp rtsp://nvr_ip:554/stress_ch$i &
done

# 监控资源占用
top -b | grep nvr_server

http://www.kler.cn/a/560039.html

相关文章:

  • FPGA学习规划
  • DPVS-5: 后端服务监控原理与测试
  • LeetCode 热题100 2. 两数相加
  • 我们需要学习和掌握基本的健康知识---秋浦四郎
  • 分布式之CAP BASE理论
  • java23种设计模式-建造者模式
  • 第十四:路由器工作的模式
  • HTML之JavaScript DOM操作元素(2)
  • hot100---day3
  • 一、C#基础入门课程【学习20天】01-07
  • 企业数据分析-投资回报能力分析ROE核心指标
  • 基于Qlearning强化学习的2DoF机械臂运动控制系统matlab仿真
  • 在使用ragflow时docker desktop出现内存不足的问题
  • sailwind 安装提示找不到mfc140.dll安装Visual C++ Redistributable for Visual Studio 2015
  • 【SQLI】sqlmap测试过滤规则和tamper有效性的方法
  • 《Netty 基础:构建高性能网络应用的基石》
  • 华山论剑之JAVA中的“方法论”
  • 嵌入式学习第二十一天--线程
  • 基于CNN的FashionMNIST数据集识别3——模型验证
  • Java多线程与高并发专题——深入synchronized