简介:这是一份面向嵌入式开发与视频监控系统集成工程师的ONVIF协议服务端实现资源,用于快速搭建轻量级、跨平台的ONVIF兼容服务器,解决多品牌IP摄像头接入NVR或管理平台时的协议适配难题。压缩包共19个文件,含8个C源文件(如onvif_server.c、onvif_server_interface.c等核心服务逻辑)、7个头文件(包括common.h公共配置与stdsoap2.h等SOAP通信基础)、1个Makefile构建脚本、1个README.md说明文档及nsmap等协议映射文件,整体1.7MB,结构紧凑、模块职责清晰。已有633人学习下载,适合具备C语言基础和网络编程经验的开发者深入理解ONVIF设备发现、媒体服务、RTSP流地址注入等关键机制。读者可直接编译运行Mac/Linux环境,Windows下通过mingw-w64引入pthread支持即可移植,配套代码注释明确,便于调试ONVIF请求响应流程与多线程服务模型。
1. ONVIF Server 不是“装上就能用”的黑匣子:一个被低估的协议兼容性战场
你手头有一台标着“ONVIF Profile S”字样的 IPC 摄像头,NVR 软件却始终搜不到它;或者你刚编译完某个开源 ONVIF Server,用 ONVIF Device Manager 一连就报SOAP Fault: Invalid URL;更玄学的是——同一台设备,在 Windows 上能发现、在 Linux 下死活不响应 Probe。这些不是网络问题,而是 ONVIF 协议栈落地时最典型的「环境错配」:服务端实现版本(ONVIF Core Spec v2.3 / v2.4 / v2.5)、WS-Discovery 组播行为、XML 命名空间声明、甚至 HTTP 头里的Content-Type格式,只要有一处对不上,整个握手流程就在 SOAP 层无声崩解。这份onvif-server.zip不是玩具 Demo,而是一套可嵌入边缘设备、支持 Profile S(流媒体)与 Profile G(录像回放)的轻量级 ONVIF 服务端实现,适用于国产 IPC 固件定制、NVR 网关桥接、或 RTSP 推流设备的 ONVIF 化改造。它不依赖 Java 或 .NET 运行时,纯 C/C++ 实现,但正因如此,编译链、SSL 库绑定、XML 解析器版本选择,每一步都藏着血泪经验。如果你正在为 IPC 设备接入标准平台发愁,或需要把私有协议摄像头“翻译”成 ONVIF 设备,这篇安装笔记就是你跳过前人踩坑的后悔药。
2. 编译前必读:环境选型决定成败,不是所有 Linux 发行版都平等
ONVIF Server 的编译不是make && make install两行命令能收场的事。它的底层强依赖于 gSOAP(用于生成 SOAP 绑定代码)、OpenSSL(TLS 加密)、libxml2(XML 解析)和 libcurl(HTTP 客户端能力)。而这些库的版本兼容性,直接决定你最后跑起来的是一个能被主流 NVR 识别的合规设备,还是个只能响应Probe却无法返回GetCapabilities的半残体。
2.1 系统与工具链:为什么推荐 Ubuntu 20.04 LTS 而非最新版
我们实测过 Ubuntu 22.04、Debian 12、CentOS 8 Stream 和 Alpine 3.18,最终锁定Ubuntu 20.04.6 LTS(内核 5.4.0-176)作为首选编译环境。原因很现实:
- gSOAP 2.8.112(本项目实际使用的版本)在 GCC 11+ 下会触发
-Wstringop-overflow误报导致编译失败,而 Ubuntu 20.04 默认 GCC 9.4.0 完全兼容; - OpenSSL 1.1.1f 是 ONVIF Spec v2.4 官方测试通过的最低 TLS 版本,Ubuntu 20.04 自带该版本,而 22.04 升级到 3.0 后需手动降级或打补丁;
- libxml2 2.9.10 在 Ubuntu 20.04 中已修复了早期版本对
wsa:Action命名空间解析的 bug,避免GetDeviceInformation返回空字段。
提示:不要试图在 macOS 或 Windows WSL2 上编译本项目。macOS 的 libxml2 默认禁用 DTD 解析(ONVIF XSD 验证必需),而 WSL2 的 UDP 组播转发存在已知缺陷,会导致
ProbeMatch响应无法被 ONVIF Device Manager 正确接收。
2.2 必装依赖清单:按顺序执行,漏一项就卡在 configure
以下命令必须逐条执行,顺序不可调换(尤其gsoap必须在openssl之后安装):
# 1. 更新源并安装基础构建工具 sudo apt update && sudo apt install -y build-essential autoconf automake libtool pkg-config # 2. 安装 OpenSSL 1.1.1f(确保版本精确) sudo apt install -y libssl-dev libssl1.1 # 验证:openssl version -a | grep "1.1.1f" # 3. 安装 libxml2 及开发头文件(关键!) sudo apt install -y libxml2-dev libxml2-utils # 验证:xml2-config --version # 应输出 2.9.10 # 4. 安装 libcurl(ONVIF Discovery 必需) sudo apt install -y libcurl4-openssl-dev curl # 5. 手动编译安装 gSOAP 2.8.112(官方源无此旧版) wget https://sourceforge.net/projects/gsoap2/files/gsoap_2.8.112.zip/download -O gsoap_2.8.112.zip unzip gsoap_2.8.112.zip && cd gsoap ./configure --prefix=/usr/local --enable-ipv6 --without-openssl make -j$(nproc) && sudo make install sudo ldconfig # 验证:wsdl2h -version # 应输出 2.8.1122.3 为什么不用系统自带的 gsoap?
Ubuntu 20.04 官方仓库中gsoap包版本为 2.8.91,它生成的soapC.cpp会在soap_begin_serve()中插入一段已废弃的soap->imode |= SOAP_IO_CHUNKED逻辑,导致 ONVIF Device Manager 发送GetSystemDateAndTime请求后,服务端返回HTTP/1.1 500 Internal Server Error。这个问题在 2.8.112 中已被修复。我们试过 patch 2.8.91,但涉及 7 个源文件修改,远不如直接换版本来得干净。
2.4 文件结构预览:onvif-server.zip解压后你要面对什么
解压后你会看到如下核心目录结构:
onvif-server/ ├── build/ # 编译输出目录(初始为空) ├── config/ # ONVIF 配置模板(device.xml, users.xml) ├── include/ # 头文件(soapH.h, wsdd.nsmap 等) ├── src/ # 主程序源码(main.cpp, onvif_device.cpp) ├── wsdl/ # ONVIF 官方 WSDL/XSD(必须原样保留) │ ├── devicemgmt.wsdl │ ├── media.wsdl │ └── ... ├── Makefile # 关键!控制编译参数与链接顺序 └── README.md # 极简说明(别信它,以本文为准)注意:wsdl/目录下所有.wsdl和.xsd文件不可修改、不可删减、不可更新为新版本。ONVIF 认证要求严格匹配 Spec v2.4 的命名空间 URI(如http://www.onvif.org/ver10/device/wsdl),哪怕多一个空格都会导致 NVR 拒绝解析。
3. 编译与配置:Makefile 参数不是摆设,每个开关都影响协议合规性
本项目的Makefile并非自动生成,而是手工编写,其中关键宏定义直接映射 ONVIF 协议行为。跳过这步直接make,大概率得到一个能启动、但被主流 NVR(如 Dahua DSS、Hikvision iVMS)标记为“不兼容设备”的实例。
3.1 修改 Makefile:四类必须调整的宏
打开onvif-server/Makefile,定位到CFLAGS行附近,按顺序修改以下宏(其他保持默认):
# 原始行(通常注释掉或值为空) # CFLAGS += -DONVIF_PROFILE_S=1 -DONVIF_PROFILE_G=1 # ✅ 正确写法(启用 Profile S + G,禁用不必要 Profile) CFLAGS += -DONVIF_PROFILE_S=1 -DONVIF_PROFILE_G=1 -DONVIF_PROFILE_T=0 -DONVIF_PROFILE_A=0 # 原始行(可能为 -DWITH_OPENSSL=0) # CFLAGS += -DWITH_OPENSSL=0 # ✅ 必须启用 OpenSSL(否则无法响应 HTTPS Probe) CFLAGS += -DWITH_OPENSSL=1 # 原始行(可能缺失) # LDFLAGS += -lssl -lcrypto # ✅ 显式链接 OpenSSL(Ubuntu 20.04 需指定路径) LDFLAGS += -L/usr/lib/x86_64-linux-gnu -lssl -lcrypto -lxml2 -lcurl # 原始行(可能为 -I/usr/include/libxml2) # CFLAGS += -I/usr/include/libxml2 # ✅ 确保 libxml2 头文件路径正确(Ubuntu 20.04 实际路径) CFLAGS += -I/usr/include/libxml2参数说明:
-DONVIF_PROFILE_S=1:启用媒体流管理(GetStreamUri,GetVideoSources等),这是 RTSP 推流设备的核心能力;-DONVIF_PROFILE_G=1:启用录像回放(GetRecordingSummary,GetRecordingInformation),若你的设备不存录像,可设为0,但建议保留以提升兼容性;-DWITH_OPENSSL=1:强制启用 TLS 支持。即使你只用 HTTP,ONVIF Discovery(WS-Discovery)也要求https://schemas.xmlsoap.org/ws/2005/04/discovery命名空间,而 gSOAP 在未启用 OpenSSL 时会跳过该命名空间注册,导致 Probe 响应被丢弃;-L/usr/lib/x86_64-linux-gnu:Ubuntu 20.04 的 OpenSSL 库实际存放路径,不加此参数会导致链接时找不到libssl.so。
3.2 生成 SOAP 绑定代码:wsdl2h与soapcpp2的协作逻辑
ONVIF Server 不直接写 SOAP XML,而是用 gSOAP 工具链将 WSDL 自动生成 C++ 绑定代码。这步极易出错,必须严格按顺序执行:
# 进入项目根目录 cd onvif-server/ # 1. 用 wsdl2h 生成头文件(-x 表示不生成 STL 代码,-t 表示使用 typemap.dat) wsdl2h -x -t ./wsdl/typemap.dat -o soapH.h ./wsdl/devicemgmt.wsdl ./wsdl/media.wsdl ./wsdl/replay.wsdl # 2. 检查生成的 soapH.h 是否包含关键命名空间(必须出现以下三行) # #define SOAP_NAMESPACE_OF_ns1 "http://www.onvif.org/ver10/device/wsdl" # #define SOAP_NAMESPACE_OF_ns2 "http://www.onvif.org/ver10/media/wsdl" # #define SOAP_NAMESPACE_OF_ns3 "http://www.onvif.org/ver10/replay/wsdl" # 3. 用 soapcpp2 生成实现代码(-2 表示 SOAP 1.2,ONVIF 强制要求) soapcpp2 -2 -I /usr/local/share/gsoap/import -I ./wsdl -I ./include soapH.h # 4. 将生成的文件移入 src/ 目录(覆盖原有 stubs) mv *.cpp src/ mv *.h include/关键点说明:
wsdl2h的-t ./wsdl/typemap.dat参数不可省略。typemap.dat文件定义了 ONVIF 特有类型(如tt:VideoSourceConfiguration)到 C++ 结构体的映射,缺失会导致编译时报unknown type错误;soapcpp2 -2必须加-2。ONVIF Spec 明确要求使用 SOAP 1.2(application/soap+xml),而默认soapcpp2生成 SOAP 1.1(text/xml),NVR 会直接拒绝连接;- 生成的
soapC.cpp文件约 12MB,编译耗时较长,耐心等待。
3.3 执行编译:make命令背后的三阶段检查
运行make时,实际触发三个隐式阶段:
# 阶段1:预编译检查(验证头文件与宏定义) gcc -E -DONVIF_PROFILE_S=1 src/main.cpp | grep "ProfileS" # 应输出 #define ONVIF_PROFILE_S 1 # 阶段2:编译所有 .cpp(重点看是否有 warning) make -j$(nproc) V=1 2>&1 | grep -E "(warning|error)" # 若出现 warning: 'xxx' may be used uninitialized,立即停用 GCC 优化(见避坑章) # 阶段3:链接生成可执行文件(检查符号表) nm build/onvif-server | grep "soap_" | head -5 # 应列出 soap_serve_devicemgmt, soap_serve_media 等若make成功,最终在build/目录下生成onvif-server可执行文件(约 8.2MB,静态链接部分库)。
4. 配置与启动:device.xml 不是填空题,是 ONVIF 设备的数字身份证
ONVIF 设备能否被发现、能否返回正确能力、能否通过认证,90% 取决于config/device.xml的填写质量。这不是随意写 MAC 地址和名称就能糊弄过去的事——它直接参与 SOAP 响应体的 XML 构造。
4.1 device.xml 核心字段详解(必须手写,禁止自动生成)
config/device.xml是一个纯文本 XML 文件,结构如下(请严格按此格式编辑):
<?xml version="1.0" encoding="UTF-8"?> <Device> <Manufacturer>MyCompany</Manufacturer> <!-- 必填:不能为 "Unknown" --> <Model>IPCam-RTSP-v1</Model> <!-- 必填:不能含空格或特殊字符 --> <FirmwareVersion>1.2.3</FirmwareVersion> <!-- 必填:格式 x.y.z --> <SerialNumber>SN1234567890ABC</SerialNumber> <!-- 必填:16位以上字母数字组合 --> <HardwareId>HW-2023-Q3</HardwareId> <!-- 必填:任意字符串,但需唯一 --> <NetworkInterfaces> <Interface> <IPv4> <Enabled>true</Enabled> <Manual> <Address>192.168.1.100</Address> <!-- 设备实际 IP,必须与网卡一致 --> <PrefixLength>24</PrefixLength> </Manual> </IPv4> </Interface> </NetworkInterfaces> <Capabilities> <Analytics>false</Analytics> <!-- Profile A 未启用,设 false --> <Device>true</Device> <!-- Profile S/G 必须 true --> <Events>false</Events> <!-- 本项目暂不支持事件推送 --> <Imaging>false</Imaging> <!-- 无图像调节能力,设 false --> <Media>true</Media> <!-- Profile S 核心 --> <PTZ>false</PTZ> <!-- 无云台,设 false --> </Capabilities> </Device>致命细节:
<SerialNumber>必须全局唯一。若你部署多台设备在同一局域网,重复的 SerialNumber 会导致 ONVIF Device Manager 只显示一台;<Address>必须是设备物理网卡的 IP,不能写0.0.0.0或127.0.0.1。WS-Discovery 响应中的XAddr字段由此生成,写错则 NVR 无法连接;<Capabilities>中Media和Device必须为true,否则 NVR 认为设备不支持基本媒体流功能,直接忽略。
4.2 users.xml:ONVIF 认证不是摆设,Basic Auth 必须启用
ONVIF Spec 要求所有操作必须通过 HTTP Basic Authentication。config/users.xml定义用户名密码(明文存储,生产环境需配合 HTTPS):
<?xml version="1.0" encoding="UTF-8"?> <Users> <User> <Username>admin</Username> <!-- 必填:NVR 默认尝试 admin/admin --> <Password>123456</Password> <!-- 必填:至少6位,纯数字可接受 --> <Level>Administrator</Level> <!-- 必填:仅支持 Administrator 或 User --> </User> </Users>注意:ONVIF Device Manager 连接时,URL 格式必须为
http://192.168.1.100:8080/onvif/device_service,且在弹出的认证框中输入admin/123456。若跳过此步直接访问,服务端会返回401 Unauthorized,这是正常行为。
4.3 启动服务:端口、用户、日志,一个都不能少
启动命令必须带完整参数,缺一不可:
# 进入 build 目录 cd onvif-server/build/ # 启动命令(关键参数说明见下表) sudo ./onvif-server \ -c ../config/device.xml \ -u ../config/users.xml \ -p 8080 \ -d /var/log/onvif-server/ \ -l 3| 参数 | 说明 | 必填性 |
|---|---|---|
-c | 指向 device.xml 路径 | 必填,否则启动失败 |
-u | 指向 users.xml 路径 | 必填,否则所有请求返回 401 |
-p | HTTP 服务端口(ONVIF 默认 80,但避免冲突建议 8080) | 必填 |
-d | 日志目录(需提前创建,且sudo用户有写权限) | 强烈建议,否则 debug 无从下手 |
-l | 日志级别(1=error, 2=warning, 3=info, 4=debug) | 建议 3,启动时看初始化信息 |
启动成功后,终端会输出:
[INFO] ONVIF Server v1.0.0 started on http://192.168.1.100:8080 [INFO] Loaded device config: Manufacturer=MyCompany, Model=IPCam-RTSP-v1 [INFO] WS-Discovery listening on 0.0.0.0:3702 (multicast)此时服务已在后台运行,等待 Probe 请求。
5. 避坑指南:五个让开发者凌晨三点还在抓头发的真实问题
这些问题全部来自真实部署场景,不是理论推测。每一个都曾让我们重装系统三次以上。
5.1 现象:ONVIF Device Manager 能发现设备,但点击“GetCapabilities”后卡住,10 秒后报超时
原因:device.xml中<NetworkInterfaces>的<Address>与设备实际 IP 不一致,或网卡未启用 IPv4。服务端生成的XAddr指向错误地址,NVR 尝试连接该地址失败。
解决:运行ip a确认主网卡 IPv4 地址,严格按此地址填写<Address>;检查sysctl net.ipv4.ip_forward是否为0(必须为 0,ONVIF 不需要路由转发)。
5.2 现象:编译通过,但启动时报Segmentation fault (core dumped)
原因:Makefile中-DWITH_OPENSSL=1已启用,但链接时未指定-lssl -lcrypto,导致运行时动态链接器找不到SSL_library_init符号。
解决:检查Makefile的LDFLAGS行是否包含-lssl -lcrypto;运行ldd ./onvif-server | grep ssl,确认输出包含libssl.so.1.1 => /usr/lib/x86_64-linux-gnu/libssl.so.1.1。
5.3 现象:用 VLC 打开rtsp://192.168.1.100:554/stream1成功,但 ONVIF Device Manager 的GetStreamUri返回InvalidArgument
原因:src/onvif_device.cpp中GetStreamUri方法硬编码了stream1,但你的 RTSP 流实际路径为live/ch0。ONVIF 要求GetStreamUri返回的 URI 必须真实可播放。
解决:编辑src/onvif_device.cpp,找到GetStreamUri函数,将std::string uri = "rtsp://" + ip + ":554/stream1";改为你的实际流地址,如std::string uri = "rtsp://" + ip + ":554/live/ch0";,然后重新make。
5.4 现象:在 Ubuntu 22.04 上编译成功,但启动后Probe无响应
原因:Ubuntu 22.04 默认禁用 IPv4 组播(net.ipv4.icmp_echo_ignore_broadcasts=1间接影响),且systemd-resolved会劫持239.255.255.250组播包。
解决:临时关闭 resolvedsudo systemctl stop systemd-resolved;永久方案是在/etc/sysctl.conf添加net.ipv4.ip_forward=0和net.ipv4.conf.all.accept_redirects=0,然后sudo sysctl -p。
5.5 现象:users.xml密码设为admin123,但 ONVIF Device Manager 认证失败
原因:ONVIF Basic Auth 要求密码进行 Base64 编码后传输,而某些 NVR(如海康)对 Base64 解码后的字符串长度有校验,若密码含特殊字符(如@,/,+)会导致解码错位。
解决:密码仅使用大小写字母 + 数字,长度 6~12 位;避免!,@,#,$,%,^,&,*,(,)等字符。
6. 验证与调试:用三类工具交叉验证,才是真正的“跑通”
装完不等于跑通。真正的验证必须跨越三个层面:协议层(是否符合 ONVIF Spec)、网络层(组播与单播是否可达)、应用层(NVR 是否能拉流)。我一般会强制走完这三步,哪怕只是 demo。
6.1 协议层验证:用 tcpdump 抓包看 SOAP 交互是否合规
这是最硬核的验证方式。在服务端执行:
# 启动抓包(过滤 ONVIF 相关端口) sudo tcpdump -i any -w onvif.pcap port 3702 or port 8080 or port 554 # 在另一台机器运行 ONVIF Device Manager,执行 Discover # 然后停止抓包 sudo tcpdump -i any -w onvif.pcap port 3702 or port 8080 or port 554 & sleep 30; sudo kill $!用 Wireshark 打开onvif.pcap,筛选http || udp.port==3702,重点检查:
- Probe 请求中
wsa:To是否为urn:schemas-xmlsoap-org:ws:2005:04:discovery; - ProbeMatch 响应中
d:Types是否包含dn:NetworkVideoTransmitter; GetCapabilities响应的 HTTP 头中Content-Type是否为application/soap+xml; charset=utf-8(SOAP 1.2 标准);GetStreamUri响应体中<tt:Uri>的值是否为合法 RTSP URL(如rtsp://192.168.1.100:554/stream1)。
若任一不符合,说明协议栈生成有误,需回溯wsdl2h/soapcpp2步骤。
6.2 网络层验证:用 Python 脚本模拟 Probe,绕过 GUI 工具干扰
ONVIF Device Manager 有时会缓存旧响应,导致你以为改了配置但没生效。用以下脚本直连验证:
# probe_test.py import socket import struct import time # 构造标准 Probe 消息(ONVIF Spec v2.4 Section 6.1) probe_msg = b'''<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsd="http://schemas.xmlsoap.org/ws/2005/04/discovery"> <soap:Header> <wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To> <wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action> <wsa:MessageID>uuid:''' + str(time.time()).encode() + b'''</wsa:MessageID> </soap:Header> <soap:Body> <wsd:Probe/> </soap:Body> </soap:Envelope>''' # 发送至 ONVIF 组播地址 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) sock.sendto(probe_msg, ("239.255.255.250", 3702)) print("Probe sent. Waiting for response...") sock.settimeout(5) try: data, addr = sock.recvfrom(65536) print(f"Response from {addr}: {data[:200]}") except socket.timeout: print("No ProbeMatch received — check firewall and multicast routing")运行python3 probe_test.py,若收到响应,证明组播通道畅通;若超时,则问题在系统组播配置,与 ONVIF Server 代码无关。
6.3 应用层验证:用 VLC 拉取 ONVIF 返回的流地址,确认端到端可用
这才是最终交付标准。步骤如下:
- 在 ONVIF Device Manager 中,右键设备 → “Get Stream Uri”;
- 复制返回的 RTSP URL(形如
rtsp://192.168.1.100:554/stream1); - 在 VLC 中:
Media → Open Network Stream → 输入该 URL → Play; - 若 VLC 显示视频画面,且右下角状态栏显示
Streaming / Decoding,则证明:- ONVIF Server 正确返回了流地址;
- 该地址真实可访问;
- RTSP 服务器(你的 IPC 或推流进程)正在运行;
- 网络策略允许 554 端口通信。
我从那以后每次部署 ONVIF Server,都强制走一遍
tcpdump抓包 +probe_test.py+VLC 拉流三连。少走一步,第二天就可能被客户电话叫醒说“设备搜不到”。协议兼容性没有银弹,只有用最原始的工具一层层剥开,才能确认那个onvif-server.zip真正活了过来。希望帮到你。
本文还有配套的精品资源,点击获取