一个现代 C++17 header-only ONVIF 客户端库,用于与 IP 摄像机和视频监控设备通信。
- Header-Only 设计:无需编译,直接包含头文件即可使用
- 零拷贝 XML 解析:使用
string_view高效处理 XML 数据 - 类型安全 API:从 ONVIF WSDL 架构自动生成,完整类型安全
- 异步优先:基于回调的非阻塞 API
- 灵活的 HTTP 后端:需要自定义 HTTP 客户端, libonvif_client不提供http客户端
- 完整 ONVIF 支持:Device、Media、PTZ、Imaging、Events、Analytics 等
- WS-Security:支持摘要算法的用户名/密码认证
- 跨平台:Linux、Windows、macOS
- libxml2 (>= 2.9):XML 解析
- libcurl:curl 示例的 HTTP 客户端
- Drogon:drogon 示例的 HTTP 客户端
- jsoncpp:代码生成工具 (wsdl_parser2)
由于 libonvif_client 是 header-only 库,无需编译。只需包含头文件并链接 libxml2:
find_package(LibXml2 REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE LibXml2::LibXml2)
target_include_directories(my_app PRIVATE /path/to/libonvif_client/include)mkdir build && cd build
cmake .. -DBUILD_TOOLS=ON -DBUILD_EXAMPLES=ON
make -j$(nproc)| 选项 | 描述 | 默认值 |
|---|---|---|
BUILD_TOOLS |
构建代码生成工具 (wsdl_parser2) | OFF |
BUILD_EXAMPLES |
构建示例程序 | OFF |
#include <libonvif_client/core/onvif_client.h>
#include <libonvif_client/client/DeviceClient.h>
#include <libonvif_client/client/MediaClient.h>
// 实现 IHttpClient 接口(或使用提供的实现)
#include "your_http_client.h"
using namespace libonvif_client;
int main() {
// 创建 HTTP 客户端
auto http_client = std::make_shared<YourHttpClient>();
// 创建 ONVIF 客户端
OnvifClient client(
"http://192.168.1.100/onvif/device_service",
"admin",
"password",
http_client
);
// 初始化(发现服务)
client.initialize([](OnvifResult<tds_GetServicesResponse>&& result) {
if (result.is_success()) {
std::cout << "连接成功!" << std::endl;
}
});
// 获取设备信息
auto device_client = client.create_device_client();
tds_GetDeviceInformationRequest request;
device_client->GetDeviceInformation(request,
[](OnvifResult<tds_GetDeviceInformationResponse>&& result) {
if (result.is_success()) {
std::cout << "制造商: "
<< result.data->Manufacturer << std::endl;
}
});
}libonvif_client 支持WS-UsernameToken认证模式,通过 username 和 password 参数控制:
提供用户名和密码时,自动使用 ONVIF 标准的 WS-UsernameToken 认证:
OnvifClient client(
"http://192.168.1.100/onvif/device_service",
"admin", // 用户名
"password123", // 密码
http_client
);认证机制:
- 自动生成随机 Nonce
- 获取当前时间戳 Created
- 计算 SHA1 摘要:
Base64(SHA1(Nonce + Created + Password)) - 在 SOAP 头中添加 WS-Security 安全令牌
不提供用户名和密码(或留空)时,不使用 WS-Security 认证:
// 方式1:使用空字符串
OnvifClient client(
"http://192.168.1.100/onvif/device_service",
"", // 无用户名
"", // 无密码
http_client
);
// 方式2:不传递认证参数
OnvifClient client(
"http://192.168.1.100/onvif/device_service",
http_client
);适用于:
- 设备不需要认证
- 通过其他方式(如 IP 白名单)授权访问
如果设备使用 HTTP Digest Authentication 而非 WS-Security,需要自行在 HTTP 客户端中实现:
class DigestHttpClient : public IHttpClient {
public:
DigestHttpClient(const std::string& username, const std::string& password)
: username_(username), password_(password) {}
void request_async(Request request, Callback callback) override {
// 第一次请求,获取 401 响应和 WWW-Authenticate 头
// 解析 realm、nonce、qop 等参数
// 计算摘要响应
// 重新发送带 Authorization 头的请求
// ...
}
private:
std::string username_;
std::string password_;
};- WS-UsernameToken:由 ONVIF 客户端在 SOAP 头中添加
- HTTP Basic/Digest:由 HTTP 客户端在 HTTP 头中添加
库需要实现 IHttpClient 接口的 HTTP 客户端:
class IHttpClient {
public:
struct Request {
std::string url;
std::string action;
std::shared_ptr<xmlDoc> xml_doc;
int timeout_ms = 5000;
};
struct Response {
int status_code = 0;
bool timeout = false;
std::string_view body;
std::string error;
bool is_success() const;
bool has_http_error() const;
};
using Callback = std::function<void(const Response&)>;
virtual void request_async(Request request, Callback callback) = 0;
virtual ~IHttpClient() = default;
};示例实现(在 examples 目录):
| 实现 | 位置 | 依赖 | 适用场景 |
|---|---|---|---|
DrogonHttpClient |
examples/drogon/ |
Drogon | Drogon 框架项目 |
CurlHttpClient |
examples/curl/ |
libcurl | 独立应用程序 |
注意:libonvif_client 不提供 HTTP 客户端实现,需要用户自行实现或参考示例。
注意:客户端回调Http Response时, Response的Buffer信息必需确保在回调执行结束前有效。
查看 examples/ 目录获取完整使用示例:
cd build
./examples/onvif_client_demo http://192.168.1.100/onvif/device_service admin passwordcd build
./examples/onvif_client_curl_demo http://192.168.1.100/onvif/device_service admin password| 类 | 描述 |
|---|---|
OnvifClient |
主客户端,用于服务发现和管理 |
OnvifServiceBase |
所有 ONVIF 服务客户端的基类 |
IHttpClient |
HTTP 客户端接口 |
OnvifResult<T> |
结果封装,包含错误处理 |
| 客户端 | 服务 | 描述 |
|---|---|---|
DeviceClient |
Device | 设备信息、能力、网络设置 |
MediaClient / Media2Client |
Media | 配置文件、流、快照 |
PTZClient |
PTZ | 云台控制 |
ImagingClient |
Imaging | 图像设置调整 |
EventsClient |
Events | 事件订阅和通知 |
AnalyticsClient |
Analytics | 视频分析 |
RecordingClient |
Recording | 录像控制 |
ReplayClient |
Replay | 录像回放 |
所有操作返回 OnvifResult<T>:
template<typename T>
struct OnvifResult {
std::unique_ptr<T> data; // 成功数据
std::unique_ptr<SoapFault> soap_fault; // 错误信息
std::shared_ptr<xmlDoc> xml_doc; // 原始 XML
xmlNodePtr response_node; // 响应节点
bool is_success() const; // data != nullptr
bool is_error() const; // soap_fault != nullptr
operator bool() const; // 同 is_success()
std::string get_error_message() const;
std::string get_raw_xml() const;
};void 特化(无返回数据):
OnvifResult<void> result;
if (result.is_success()) {
// 操作成功
} else {
std::cout << result.get_error_message() << std::endl;
}ONVIF 客户端代码由 WSDL 架构自动生成,使用 wsdl_parser2 工具:
# 构建代码生成器
cmake .. -DBUILD_TOOLS=ON
make wsdl_parser2
# 从 WSDL 生成代码
./wsdl_parser2 input.wsdl output_dir该工具生成:
- XSD 架构的类型定义
- WSDL 操作的客户端类
- XML 序列化/反序列化代码
libonvif_client/
├── include/libonvif_client/
│ ├── client/ # 生成的 ONVIF 服务客户端
│ │ ├── DeviceClient.h
│ │ ├── MediaClient.h
│ │ ├── PTZClient.h
│ │ └── ...
│ ├── core/
│ │ ├── http_client.h # HTTP 客户端接口
│ │ ├── onvif_client.h # 主客户端类
│ │ ├── onvif_service_base.h # 服务客户端基类
│ │ └── onvif_result.h # 结果封装
│ ├── client/
│ │ └── namespaces.h # ONVIF 命名空间映射
│ └── utils/
│ └── common.h # 工具函数(WS-Security)
├── examples/
│ ├── drogon/ # Drogon HTTP 客户端 + 示例
│ └── curl/ # libcurl HTTP 客户端 + 示例
└── tools/wsdl_parser2/ # 代码生成工具
GetDeviceInformation- 设备制造商、型号、固件GetSystemDateAndTime- 系统时钟GetCapabilities- 可用服务GetServices- 所有服务端点GetHostname- 网络主机名SetHostname- 配置主机名- 网络配置
GetProfiles- 视频编码配置文件GetStreamUri- RTSP 流 URLGetSnapshotUri- JPEG 快照 URL- 视频源配置
- 视频编码器配置
- 音频配置
ContinuousMove- 连续云台移动AbsoluteMove- 移动到绝对位置RelativeMove- 相对当前位置移动Stop- 停止移动GotoPreset- 移动到预置点GetPresets- 列出预置点SetPreset- 保存预置点
GetImagingSettings- 当前图像设置SetImagingSettings- 配置图像设置GetOptions- 可用选项Move- 镜头移动(聚焦、光圈等)
Subscribe- 订阅事件Unsubscribe- 取消订阅PullMessages- 接收事件通知- 事件属性
所有错误通过 OnvifResult<T> 返回:
client->GetDeviceInformation(request,
[](OnvifResult<tds_GetDeviceInformationResponse>&& result) {
if (result.is_error()) {
// 检查错误类型
auto& fault = result.soap_fault;
std::cout << "SOAP Fault: " << fault->code << "\n";
std::cout << "Reason: " << fault->reason << "\n";
// 获取原始 XML 用于调试
std::cout << "Raw XML:\n" << result.get_raw_xml() << "\n";
}
});| 错误 | 描述 |
|---|---|
Client |
HTTP/网络错误、解析失败 |
Sender |
请求格式无效 |
Receiver |
设备无法处理请求 |
env:Sender |
认证失败 |
- 零拷贝设计:XML 数据通过
string_view引用,无复制 - 共享 XML 文档:原始 XML 保留用于调试
- Header-Only:模板在每个编译单元实例化
- 优化建议:
# Release 构建 target_compile_options(my_app PRIVATE -flto -finline-functions)
- 异步 API:非阻塞,适合事件循环
- 线程安全:HTTP 回调可能在任何线程执行
- HTTP 客户端生命周期:确保 HTTP 客户端比 ONVIF 操作更长寿
- 错误处理:访问
data前始终检查is_error() - 回调安全:使用值捕获或谨慎使用
shared_ptr - 线程安全:在回调中使用适当的同步
- 资源管理:使用智能指针自动清理
// 正确:捕获 shared_ptr
auto device = client.create_device_client();
device->GetInfo([device](auto&& result) { /* device 仍然有效 */ });
// 错误:捕获可能被销毁的原始指针
DeviceClient* raw_ptr = device.get();
device->GetInfo([raw_ptr](auto&& result) { /* 未定义行为 */ });#include "curl_http_client.h"
auto http_client = std::make_shared<CurlHttpClient>(true, 4);
http_client->set_ssl_verify(false);
OnvifClient client(url, username, password, http_client);"No root element in SOAP XML"
- 设备返回空响应
- 检查 HTTP 客户端响应 body 生命周期
"Authentication failed"
- 验证用户名/密码
- 某些设备需要特定的认证方法
- 检查设备是否支持 WS-UsernameToken
"Timeout"
- 增加 HTTP 客户端超时时间
- 检查网络连接
- 某些设备响应较慢