# libonvif_client **Repository Path**: cxs1992/libonvif_client ## Basic Information - **Project Name**: libonvif_client - **Description**: C++17 header-only ONVIF 客户端库 - **Primary Language**: C++ - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-01-15 - **Last Updated**: 2026-01-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: onvif, onvif-client, modren, camera-onvif, header-only ## README # libonvif_client 一个现代 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) ## 构建 ### 使用库(Header-Only) 由于 libonvif_client 是 header-only 库,无需编译。只需包含头文件并链接 libxml2: ```cmake 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) ``` ### 构建示例和工具 ```bash mkdir build && cd build cmake .. -DBUILD_TOOLS=ON -DBUILD_EXAMPLES=ON make -j$(nproc) ``` ### 构建选项 | 选项 | 描述 | 默认值 | |------|------|--------| | `BUILD_TOOLS` | 构建代码生成工具 (wsdl_parser2) | `OFF` | | `BUILD_EXAMPLES` | 构建示例程序 | `OFF` | ## 快速开始 ### 基本用法 ```cpp #include #include #include // 实现 IHttpClient 接口(或使用提供的实现) #include "your_http_client.h" using namespace libonvif_client; int main() { // 创建 HTTP 客户端 auto http_client = std::make_shared(); // 创建 ONVIF 客户端 OnvifClient client( "http://192.168.1.100/onvif/device_service", "admin", "password", http_client ); // 初始化(发现服务) client.initialize([](OnvifResult&& result) { if (result.is_success()) { std::cout << "连接成功!" << std::endl; } }); // 获取设备信息 auto device_client = client.create_device_client(); tds_GetDeviceInformationRequest request; device_client->GetDeviceInformation(request, [](OnvifResult&& result) { if (result.is_success()) { std::cout << "制造商: " << result.data->Manufacturer << std::endl; } }); } ``` ## 认证说明 libonvif_client 支持`WS-UsernameToken`认证模式,通过 `username` 和 `password` 参数控制: ### 1. WS-UsernameToken 认证(推荐) 提供用户名和密码时,自动使用 ONVIF 标准的 WS-UsernameToken 认证: ```cpp 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 安全令牌 ### 2. 无认证模式 不提供用户名和密码(或留空)时,不使用 WS-Security 认证: ```cpp // 方式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 白名单)授权访问 ### 3. HTTP 摘要认证 如果设备使用 HTTP Digest Authentication 而非 WS-Security,需要自行在 HTTP 客户端中实现: ```cpp 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 头中添加 ### HTTP 客户端实现 库需要实现 `IHttpClient` 接口的 HTTP 客户端: ```cpp class IHttpClient { public: struct Request { std::string url; std::string action; std::shared_ptr 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; 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/` 目录获取完整使用示例: ### Drogon 示例 ```bash cd build ./examples/onvif_client_demo http://192.168.1.100/onvif/device_service admin password ``` ### libcurl 示例 ```bash cd build ./examples/onvif_client_curl_demo http://192.168.1.100/onvif/device_service admin password ``` ## API 概览 ### 核心类 | 类 | 描述 | |-------|-------------| | `OnvifClient` | 主客户端,用于服务发现和管理 | | `OnvifServiceBase` | 所有 ONVIF 服务客户端的基类 | | `IHttpClient` | HTTP 客户端接口 | | `OnvifResult` | 结果封装,包含错误处理 | ### 服务客户端 | 客户端 | 服务 | 描述 | |--------|---------|-------------| | `DeviceClient` | Device | 设备信息、能力、网络设置 | | `MediaClient` / `Media2Client` | Media | 配置文件、流、快照 | | `PTZClient` | PTZ | 云台控制 | | `ImagingClient` | Imaging | 图像设置调整 | | `EventsClient` | Events | 事件订阅和通知 | | `AnalyticsClient` | Analytics | 视频分析 | | `RecordingClient` | Recording | 录像控制 | | `ReplayClient` | Replay | 录像回放 | ### 结果处理 所有操作返回 `OnvifResult`: ```cpp template struct OnvifResult { std::unique_ptr data; // 成功数据 std::unique_ptr soap_fault; // 错误信息 std::shared_ptr 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 特化(无返回数据):** ```cpp OnvifResult result; if (result.is_success()) { // 操作成功 } else { std::cout << result.get_error_message() << std::endl; } ``` ## 代码生成 ONVIF 客户端代码由 WSDL 架构自动生成,使用 `wsdl_parser2` 工具: ```bash # 构建代码生成器 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/ # 代码生成工具 ``` ## 支持的 ONVIF 操作 ### Device 服务 - `GetDeviceInformation` - 设备制造商、型号、固件 - `GetSystemDateAndTime` - 系统时钟 - `GetCapabilities` - 可用服务 - `GetServices` - 所有服务端点 - `GetHostname` - 网络主机名 - `SetHostname` - 配置主机名 - 网络配置 ### Media 服务 - `GetProfiles` - 视频编码配置文件 - `GetStreamUri` - RTSP 流 URL - `GetSnapshotUri` - JPEG 快照 URL - 视频源配置 - 视频编码器配置 - 音频配置 ### PTZ 服务 - `ContinuousMove` - 连续云台移动 - `AbsoluteMove` - 移动到绝对位置 - `RelativeMove` - 相对当前位置移动 - `Stop` - 停止移动 - `GotoPreset` - 移动到预置点 - `GetPresets` - 列出预置点 - `SetPreset` - 保存预置点 ### Imaging 服务 - `GetImagingSettings` - 当前图像设置 - `SetImagingSettings` - 配置图像设置 - `GetOptions` - 可用选项 - `Move` - 镜头移动(聚焦、光圈等) ### Events 服务 - `Subscribe` - 订阅事件 - `Unsubscribe` - 取消订阅 - `PullMessages` - 接收事件通知 - 事件属性 ## 错误处理 所有错误通过 `OnvifResult` 返回: ```cpp client->GetDeviceInformation(request, [](OnvifResult&& 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**:模板在每个编译单元实例化 - **优化建议**: ```cmake # Release 构建 target_compile_options(my_app PRIVATE -flto -finline-functions) ``` ### 运行时 - **异步 API**:非阻塞,适合事件循环 - **线程安全**:HTTP 回调可能在任何线程执行 ## 最佳实践 1. **HTTP 客户端生命周期**:确保 HTTP 客户端比 ONVIF 操作更长寿 2. **错误处理**:访问 `data` 前始终检查 `is_error()` 3. **回调安全**:使用值捕获或谨慎使用 `shared_ptr` 4. **线程安全**:在回调中使用适当的同步 5. **资源管理**:使用智能指针自动清理 ```cpp // 正确:捕获 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) { /* 未定义行为 */ }); ``` ## 集成示例 ```cpp #include "curl_http_client.h" auto http_client = std::make_shared(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 客户端超时时间 - 检查网络连接 - 某些设备响应较慢 ## 参考资料 - [ONVIF 规范](https://www.onvif.org/specs/) - [ONVIF 核心规范](https://www.onvif.org/specs/core/ONVIF-Core-Specification.pdf) - [libxml2 文档](http://xmlsoft.org/) ---