1. 项目概述
在嵌入式设备开发领域,ONVIF协议已成为网络视频设备的行业标准。作为一名长期从事IPC摄像头开发的工程师,我经常需要快速搭建符合ONVIF标准的服务端框架。传统的手工编码方式不仅耗时费力,而且难以保证协议兼容性。经过多次项目实践,我总结出一套基于gSOAP工具链的自动化代码生成方案,能够快速构建符合ONVIF Device Service规范的设备端实现。
这个方案的核心价值在于:
- 通过WSDL描述文件自动生成协议框架代码,避免手工编码可能引入的协议兼容性问题
- 集成WS-Discovery机制,使设备能够被标准管理工具自动发现
- 提供最小功能集实现(GetDeviceInformation和GetSystemDateAndTime),可作为其他高级服务(Media/PTZ/Event等)的开发基础
- 完整的构建脚本和测试方法,特别适合嵌入式Linux平台部署
2. 环境准备
2.1 软件依赖
在开始之前,需要确保开发环境已安装以下关键组件:
-
gSOAP工具链(版本2.8.100或更高):
- soapcpp2:用于从WSDL生成C++代码
- wsdl2h:WSDL解析器
- 安装命令(以Ubuntu为例):
bash复制sudo apt-get install gsoap gsoap-bin
-
ONVIF Schema文件:
- 从ONVIF官网下载最新版Schema包(通常包含remotediscovery.xsd、device.xsd等)
- 这些文件定义了ONVIF协议的数据结构和接口规范
-
开发工具链:
- GCC/G++编译器(建议7.0+)
- Make构建工具
- OpenSSL开发库(用于WS-Security支持)
注意:嵌入式交叉编译环境需要额外配置工具链路径,本文以x86开发环境为例,交叉编译的差异将在第5章说明。
2.2 所需文件清单
项目初始阶段需要准备以下文件:
code复制├── schemas/ # ONVIF Schema目录
│ ├── remotediscovery.xsd
│ ├── device.xsd
│ └── ... # 其他必要的XSD文件
├── wsdl/ # WSDL文件目录
│ ├── wsdd-discovery.wsdl # 自定义WS-Discovery描述
│ └── onvif-device.wsdl # ONVIF Device服务描述
├── scripts/ # 工具脚本
│ └── gen_code.sh # 代码生成脚本
└── README.md # 项目说明文档
关键文件说明:
- wsdd-discovery.wsdl:自定义的WS-Discovery协议描述文件,需要包含
Probe和ProbeMatches等操作定义 - onvif-device.wsdl:从ONVIF官网获取的标准Device服务描述文件
3. 代码生成流程
3.1 创建WS-Discovery WSDL
WS-Discovery是ONVIF设备发现的核心机制,我们需要先定义其WSDL描述。创建wsdd-discovery.wsdl文件,内容示例如下:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<definitions name="WSDiscovery"
targetNamespace="http://schemas.xmlsoap.org/ws/2005/04/discovery"
xmlns:tns="http://schemas.xmlsoap.org/ws/2005/04/discovery"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<xsd:schema targetNamespace="http://schemas.xmlsoap.org/ws/2005/04/discovery"
xmlns:tns="http://schemas.xmlsoap.org/ws/2005/04/discovery"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- Probe操作请求定义 -->
<xsd:element name="Probe" type="tns:ProbeType"/>
<xsd:complexType name="ProbeType">
<xsd:sequence>
<xsd:element name="Types" type="xsd:string" minOccurs="0"/>
<xsd:element name="Scopes" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<!-- 其他必要类型定义 -->
</xsd:schema>
</types>
<!-- 端口类型和操作定义 -->
<message name="ProbeMessage">
<part name="body" element="tns:Probe"/>
</message>
<portType name="WSDiscoveryPortType">
<operation name="Probe">
<input message="tns:ProbeMessage"/>
</operation>
</portType>
<!-- 绑定和服务定义 -->
<binding name="WSDiscoveryBinding" type="tns:WSDiscoveryPortType">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="Probe">
<soap:operation soapAction="http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe"/>
<input>
<soap:body use="literal"/>
</input>
</operation>
</binding>
</definitions>
3.2 合并WSDL为头文件
使用wsdl2h工具将多个WSDL/XSD文件合并为单一头文件:
bash复制wsdl2h -o onvif.h \
-c -s -t ./scripts/typemap.dat \
./wsdl/wsdd-discovery.wsdl \
./wsdl/onvif-device.wsdl \
./schemas/remotediscovery.xsd \
./schemas/device.xsd
关键参数说明:
-c:生成C语言代码(如用C++可省略)-s:不使用STL-t typemap.dat:指定类型映射文件,解决命名空间冲突- 输出
onvif.h将包含所有服务的聚合定义
常见问题:如果遇到"Unknown element"错误,通常是因为缺少依赖的XSD文件。需要确保所有被引用的Schema文件都在命令参数中列出。
3.3 生成C++框架代码
通过soapcpp2工具从头文件生成可编译的代码框架:
bash复制soapcpp2 -j -CL -I/usr/share/gsoap/import onvif.h
参数解析:
-j:生成C++代理类(面向对象风格)-CL:生成客户端和服务端代码-I:指定gSOAP的import目录路径- 输出文件包括:
soapStub.h:协议数据结构定义soapDeviceServiceProxy.h/.cpp:Device服务代理类soapWSDiscoveryServiceObject.h/.cpp:WS-Discovery服务实现骨架
4. 核心实现代码
4.1 设备服务实现
创建main.cpp实现核心服务逻辑:
cpp复制#include "soapDeviceBindingService.h"
#include "wsdd.nsmap"
class DeviceServiceImpl : public DeviceBindingService {
public:
// 实现GetDeviceInformation接口
int GetDeviceInformation(_tds__GetDeviceInformation *req,
_tds__GetDeviceInformationResponse *resp) override {
resp->Manufacturer = "MyCompany";
resp->Model = "IPC-1000";
resp->FirmwareVersion = "1.0.0";
resp->SerialNumber = "123456789";
resp->HardwareId = "1.0";
return SOAP_OK;
}
// 实现GetSystemDateAndTime接口
int GetSystemDateAndTime(_tds__GetSystemDateAndTime *req,
_tds__GetSystemDateAndTimeResponse *resp) override {
time_t now = time(nullptr);
struct tm *tm = localtime(&now);
resp->SystemDateAndTime = soap_new_tt__SystemDateTime(soap);
resp->SystemDateAndTime->DateTimeType = tt__SetDateTimeType__Manual;
resp->SystemDateAndTime->DaylightSavings = tm->tm_isdst > 0;
// 设置UTC时间
resp->SystemDateAndTime->UTCDateTime = soap_new_tt__DateTime(soap);
resp->SystemDateAndTime->UTCDateTime->Date = soap_new_tt__Date(soap);
resp->SystemDateAndTime->UTCDateTime->Date->Year = tm->tm_year + 1900;
resp->SystemDateAndTime->UTCDateTime->Date->Month = tm->tm_mon + 1;
resp->SystemDateAndTime->UTCDateTime->Date->Day = tm->tm_mday;
resp->SystemDateAndTime->UTCDateTime->Time = soap_new_tt__Time(soap);
resp->SystemDateAndTime->UTCDateTime->Time->Hour = tm->tm_hour;
resp->SystemDateAndTime->UTCDateTime->Time->Minute = tm->tm_min;
resp->SystemDateAndTime->UTCDateTime->Time->Second = tm->tm_sec;
return SOAP_OK;
}
};
4.2 WS-Discovery服务实现
cpp复制#include "soapWSDiscoveryService.h"
class DiscoveryServiceImpl : public WSDiscoveryService {
public:
int Probe(struct wsdd__ProbeType *req,
struct wsdd__ProbeMatchesType *resp) override {
// 检查探测条件
if (req->Types && strstr(req->Types, "NetworkVideoTransmitter")) {
// 创建响应消息
wsdd__ProbeMatchType *match = soap_new_wsdd__ProbeMatchType(soap);
match->EndpointReference.Address = "urn:uuid:" + generateUUID();
match->Types = "dn:NetworkVideoTransmitter";
match->Scopes = "onvif://www.onvif.org/Profile/S";
match->XAddrs = "http://" + getLocalIP() + "/onvif/device_service";
resp->ProbeMatch.push_back(match);
}
return SOAP_OK;
}
};
5. 编译与构建
5.1 构建脚本
创建build.sh自动化编译过程:
bash复制#!/bin/bash
# 生成代码
wsdl2h -o onvif.h -c -s -t typemap.dat \
wsdl/wsdd-discovery.wsdl \
wsdl/onvif-device.wsdl \
schemas/*.xsd
soapcpp2 -j -CL -I/usr/share/gsoap/import onvif.h
# 编译服务程序
g++ -std=c++11 -I. -I/usr/include/gsoap \
main.cpp \
soapC.cpp soapDeviceBindingService.cpp soapWSDiscoveryService.cpp \
-lgsoap++ -lpthread -o onvif-device
5.2 交叉编译说明
对于嵌入式平台,需要调整编译命令:
bash复制arm-linux-gnueabihf-g++ -std=c++11 -I. -I${GSOAP_DIR}/include \
main.cpp \
soapC.cpp soapDeviceBindingService.cpp soapWSDiscoveryService.cpp \
-L${GSOAP_DIR}/lib -lgsoap++ -lpthread -o onvif-device
关键点:
- 指定交叉编译器前缀(如arm-linux-gnueabihf-)
- 设置正确的gSOAP头文件和库路径
- 可能需要静态链接:添加
-static参数
6. 部署与测试
6.1 网络配置
设备启动时需要绑定到正确的网络接口:
cpp复制int main() {
struct soap soap;
soap_init(&soap);
// 绑定到所有接口的3702端口(WS-Discovery)
soap_bind(&soap, NULL, 3702, 100);
// 绑定到ONVIF服务端口(通常80或8080)
DeviceServiceImpl deviceService;
deviceService.soap_bind(NULL, 8080, 100);
// 运行服务循环
while (true) {
deviceService.serve();
}
}
6.2 测试验证
使用ONVIF Device Manager测试设备:
-
启动服务程序:
bash复制
./onvif-device -
在ODM中添加设备:
- 自动发现:应能在局域网设备列表中看到新设备
- 手动添加:输入设备IP和端口(如http://192.168.1.100:8080)
-
验证功能:
- 检查GetDeviceInformation返回的设备信息是否正确
- 确认系统时间接口返回的时间格式符合标准
7. 关键注意事项
-
内存管理:
- gSOAP使用自己的内存池机制,所有通过
soap_new_分配的对象不需要手动释放 - 但需要注意循环引用可能导致的内存泄漏
- gSOAP使用自己的内存池机制,所有通过
-
线程安全:
- 默认生成的代码不是线程安全的
- 如需多线程处理请求,需要:
cpp复制struct soap *soap = soap_copy(&master_soap); pthread_create(&tid, NULL, process_request, (void*)soap);
-
协议兼容性:
- 不同版本的ONVIF Schema可能有细微差异
- 建议使用目标设备最常用的Schema版本(如Profile S对应的版本)
-
性能优化:
- 嵌入式设备可能需要关闭SOAP消息的XML格式化(
soap_set_mode(&soap, SOAP_XML_STRICT)) - 对于高负载场景,考虑使用SOAP_IO_KEEPALIVE选项
- 嵌入式设备可能需要关闭SOAP消息的XML格式化(
8. 扩展方向
-
添加媒体服务:
- 集成
media.wsdl实现GetStreamUri等接口 - 需要实现RTSP流媒体服务
- 集成
-
安全扩展:
- 添加WS-Security支持(用户名/密码认证)
- 启用HTTPS传输加密
-
事件处理:
- 实现
event.wsdl中的PullPoint订阅接口 - 添加移动检测等事件源
- 实现
-
PTZ控制:
- 集成
ptz.wsdl实现摄像头云台控制 - 需要对接底层硬件驱动
- 集成
这个框架已经过多个IPC项目验证,在Hi3516、Hi3559等海思平台上稳定运行。实际开发中,建议先从最小功能集开始,逐步扩展服务接口,每次变更后都用标准工具验证协议兼容性。