邢台县教育局五库建设网站枸橼酸西地那非片
Onvif协议NVR开发方案指南
一、架构设计
1. 双重角色功能
角色 | 功能描述 | 协议交互对象 |
---|---|---|
服务端 | 提供多通道视频流、事件订阅、PTZ控制,向上级平台暴露ONVIF接口 | 上级监控平台、第三方客户端 |
客户端 | 发现并接入IPC设备,拉取视频流、订阅事件,将数据整合到本地服务 | 下级IPC设备 |
2. 数据流逻辑
二、核心模块实现
模块1:设备发现与动态通道管理
实现思路
-
服务端发现
- NVR作为设备响应WS-Discovery Probe请求,返回服务地址和Profile信息。
- 使用UDP多播(端口3702)实现,兼容ONVIF 2.0/2018版本。
-
客户端发现
- NVR主动探测局域网IPC,通过
GetCapabilities
协商能力。 - 动态注册IPC到通道表,支持通道增删改查。
- NVR主动探测局域网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的GetStreamUriprofile_token: "Profile_IPC_" + std::to_string(channel_id),is_local: false,is_online: true});}}}
}
模块2:鉴权与安全机制
实现思路
-
服务端鉴权
- 实现WS-Security Digest认证,验证
UsernameToken
中的Nonce和PasswordDigest。 - 兼容Basic Auth(旧设备)和TLS 1.3(2021版)。
- 实现WS-Security Digest认证,验证
-
客户端鉴权
- 动态添加鉴权头,适配不同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 Authsoap->auth = soap_wsse_add_BasicAuth(ctx, ipc.user.c_str(), ipc.pwd.c_str());}
}
模块3:多通道视频流管理
实现思路
-
本地通道
- NVR直接管理物理摄像头,通过FFmpeg/Live555发布RTSP流。
-
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:事件双向传递
实现思路
-
客户端事件订阅
- 订阅所有IPC的
MotionDetector
事件,解析事件中的通道ID。
- 订阅所有IPC的
-
服务端事件转发
- 将IPC事件转换为NVR事件格式,添加通道标识后推送给上级平台。
-
控制指令传递
- 解析上级平台的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:协议兼容性处理
实现思路
-
多版本代码隔离
- 使用不同命名空间生成2.0/2018/2021版代码。
-
动态接口适配
- 根据
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和IPC | NVR和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}; doffmpeg -re -i test.mp4 -c copy -f rtsp rtsp://nvr_ip:554/stress_ch$i &
done# 监控资源占用
top -b | grep nvr_server