Skip to content

chengxiaosheng/libonvif_client

Repository files navigation

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:

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认证模式,通过 usernamepassword 参数控制:

1. WS-UsernameToken 认证(推荐)

提供用户名和密码时,自动使用 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 安全令牌

2. 无认证模式

不提供用户名和密码(或留空)时,不使用 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 白名单)授权访问

3. HTTP 摘要认证

如果设备使用 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 头中添加

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/ 目录获取完整使用示例:

Drogon 示例

cd build
./examples/onvif_client_demo http://192.168.1.100/onvif/device_service admin password

libcurl 示例

cd build
./examples/onvif_client_curl_demo http://192.168.1.100/onvif/device_service admin password

API 概览

核心类

描述
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/            # 代码生成工具

支持的 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<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 回调可能在任何线程执行

最佳实践

  1. HTTP 客户端生命周期:确保 HTTP 客户端比 ONVIF 操作更长寿
  2. 错误处理:访问 data 前始终检查 is_error()
  3. 回调安全:使用值捕获或谨慎使用 shared_ptr
  4. 线程安全:在回调中使用适当的同步
  5. 资源管理:使用智能指针自动清理
// 正确:捕获 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 客户端超时时间
  • 检查网络连接
  • 某些设备响应较慢

参考资料


Releases

No releases published

Packages

No packages published