librealsense 视频流元数据在 DDS 网络中的传输与同步机制详解

librealsense 视频流元数据在 DDS 网络中的传输与同步机制详解 librealsense 视频流元数据在 DDS 网络中的传输与同步机制详解【免费下载链接】librealsenseRealSense SDK项目地址: https://gitcode.com/GitHub_Trending/li/librealsense本篇文章基于 third-party/realdds/doc/metadata.md 展开系统讲解 RealSense 相机通过 DDSData Distribution Service网络对外发布视频流时如何单独承载每一帧的元数据曝光、增益等并借助timestamp与图像帧完成跨网络同步。读完本文你将掌握 metadata 主题的完整协议格式QoS、JSON 字段语义、服务端发布与客户端接收的实现路径以及丢包、失步等异常场景下的行为约定。流的两大类别Video 与 Motion在 realdds 的流模型中所有流被划分为两类Video 流携带图像数据其内容由**元数据metadata**描述例如每一帧的曝光时间、亮度、增益等与图像生成直接相关的信息Motion 流如加速度计、陀螺仪产生的 IMU 数据。两者在元数据处理上有着本质区别Motion 流不携带元数据。文档给出的理由非常明确——Motion 流以高频输出、追求极致效率其频率使得它与其它流很难同步而为了等待同步而将数据滞留所引入的任何延迟都是有害的。这一设计在源码中同样得到印证在 src/dds/rs-dds-sensor-proxy.cpp 处理运动数据的分支中注释直接写明 No metadata for motion streams, therefore no syncer运动流没有元数据因此没有同步器运动数据在收到后直接通过invoke_new_frame派发出去不经过任何元数据匹配队列。为什么元数据必须“单独走一个 Topic”视频流的元数据用于描述图像理论上应与图像数据打包在一起发送。但这里存在一个硬约束ROS2 的 Image 消息格式sensor_msgs/Image除时间戳外无法携带任何附加信息。流数据的传输格式可参考 third-party/realdds/doc/streaming.md——视频流使用 ROS2 Image 格式其结构只有headerstampframe_id、height、width、encoding、is_bigendian、step和data字节序列确实没有任何容纳逐帧元数据的字段。因此元数据必须通过独立的主题单独发送再通过与图像帧的时间戳匹配来完成同步。Metadata 主题Topic协议主题名称整个设备使用单一主题向客户端传递所有视频流的元数据主题路径为device-topic-root/metadata该主题同时承载设备上全部视频流的元数据客户端依据消息中的stream-name区分它们属于哪条流。主题名的构造可以在源码中找到直接定义third-party/realdds/include/realdds/topics/dds-topic-names.h 中声明了constexpr char const * METADATA_TOPIC_NAME /metadata;它会被拼接到设备的 topic root例如realsense/...之后。在服务端初始化代码 third-party/realdds/src/dds-device-server.cpp 中可以看到当任意一条流开启了元数据stream-metadata_enabled()时服务端就会创建一个挂载在topic-root/metadata上的 flexible 主题写入器dds_topic_writer。服务质量QoSmetadata 主题的 QoS 为可靠性ReliabilityBEST_EFFORT尽力而为不保证送达持久性DurabilityVOLATILE易失不保存历史数据这一点与常规 flexible 控制类主题不同。在 third-party/realdds/include/realdds/topics/flexible/readme.md 中明确指出所有 flexible 主题默认是RELIABLE可靠传输唯一著名的例外就是 metadata 主题。这是因为元数据是高速、逐帧、允许丢弃的信息采用尽力而为可以避免阻塞数据通路。服务端实现也与此一致third-party/realdds/src/dds-device-server.cpp 为 metadata 写入器显式设置了BEST_EFFORT_RELIABILITY_QOS并将历史深度history depth从默认值 1 提升到 10允许在短暂拥塞时多缓冲几条元数据消息。消息格式flexible 消息metadata 主题使用flexible灵活消息传输相关类型定义位于 third-party/realdds/include/realdds/topics/flexible/。flexible 的设计初衷就是“内容灵活、易于变更、无需预定义结构”。其底层 IDL 定义flexible.idl为enum flexible_data_format { FLEXIBLE_DATA_JSON, FLEXIBLE_DATA_CBOR, FLEXIBLE_DATA_CUSTOM }; struct flexible { flexible_data_format data_format; octet version[4]; // 重要性递减version[0] 最高 sequenceoctet,32768 data; // 数据上限 32KB };data_format声明数据编码格式当前实际使用的是JSONCBORJSON 的二进制表示与CUSTOM由客户端自解释的裸字节虽已定义但尚未真正投入使用version为 4 字节版本号目前始终为 0data为字节序列承载实际负载——在 JSON 格式下即每个字符占 1 字节的 JSON 文本。flexible 消息的便捷封装见 flexible-msg.h它提供了从rsutils::json构造消息、将消息写往dds_topic_writer、以及按自定义类型解读CUSTOM数据custom_dataT()等能力。消息内容与字段语义一条 metadata 消息的典型 JSON 示例如下{ stream-name: Color, header: {frame-number: 1234, timestamp: 123456789, timestamp-domain: 0}, metadata: {Exposure: 123, Gain: 456} }三个顶层字段的含义字段名常量可在 dds-topic-names.h 的topics::metadata命名空间中找到定义stream-name本元数据应与之同步的流名称客户端据此将元数据分发给对应流的处理逻辑header包含对客户端正确处理至关重要的“非元数据”信息frame-number帧的序号可选字段timestamp关键字段即帧的时间戳与图像消息携带的时间戳一致是图像与元数据同步的匹配依据timestamp-domain告诉客户端如何解释时间戳可选字段若缺失则默认按硬件时间戳hardware timestamp处理。值得补充的是header 中还允许出现depth_units键——在 src/dds/rs-dds-depth-sensor-proxy.cpp 中深度传感器代理会专门从元数据中读取header/depth_units用于换算深度帧的真实距离单位metadata实际的可定制元数据内容表现为名称-值name-value映射name是元数据字段的字符串名称value是其取值。与 librealsense 的字段名约定协议对元数据的内容本身不设任何强制要求——任何键值对都可以承载。但要想让数据真正可用服务端与客户端之间必须就字段名与取值格式达成一致对于 librealsense 而言元数据字段的名称必须与rs2_frame_metadata_to_string返回的名称一致元数据的值必须是整数型long long。rs2_frame_metadata_to_string是 librealsense C API 中的标准函数声明于 include/librealsense2/h/rs_frame.h与之配套的rs2_frame_metadata_value枚举定义了完整的受支持元数据项例如枚举项含义RS2_FRAME_METADATA_FRAME_COUNTER按流管理的顺序帧计数整数值RS2_FRAME_METADATA_FRAME_TIMESTAMP设备时钟在数据读出并开始传输时设定的时间戳微秒RS2_FRAME_METADATA_ACTUAL_EXPOSURE传感器曝光宽度AE 开启时由固件控制微秒RS2_FRAME_METADATA_GAIN_LEVEL增益系数AE 开启时由固件控制整数值RS2_FRAME_METADATA_WHITE_BALANCE白平衡色温开尔文RS2_FRAME_METADATA_TEMPERATURE帧采集时设备温度摄氏度RS2_FRAME_METADATA_ACTUAL_FPS实际帧率乘以 100030.1 fps 表示为 30100完整的枚举清单见 include/librealsense2/h/rs_frame.h。文档示例中的Exposure、Gain正是这类字段名的直观示例。容错语义缺失的元数据会被标记为“不存在not-there”而客户端无法识别的元数据名称会被直接忽略不会报错也不会影响流的正常处理。发送顺序约定协议建议先发送图像再发送元数据。原因在于元数据消息远比图像小得多甚至可能只占一个数据包因此即便后发也几乎必然在图像完整传输完成之前到达接收端。这种“大块头先行、小块头殿后”的顺序能最大限度地缩小两者在网络上到达的时间差有利于后续的时间戳匹配。消息丢失与不完整场景的处理由于 metadata 主题采用BEST_EFFORT可靠性消息存在丢失的可能此外服务端也可能根本没有发送某条元数据。协议对这类异常给出了明确的降级约定无法与元数据同步的图像无论是时间戳不匹配还是元数据本身丢失将简单地不带任何元数据交付给上层没有frame-number时客户端必须自行推断帧序号timestamp-domain可能丢失但视频流本身会继续正常工作不受影响。这套约定在客户端同步器dds_metadata_syncer中有非常具体的工程落地见下文。源码纵深服务端如何发布、客户端如何同步服务端按需创建写入器逐条发布在 third-party/realdds/src/dds-device-server.cpp 中服务端在初始化时遍历所有流只要存在一条metadata_enabled()的流就为topic-root/metadata创建 flexible 主题写入器并以BEST_EFFORT 历史深度 10 的 QoS 启动。实际的发布入口是publish_metadatadds-device-server.cpp它将传入的 JSON 负载包装为topics::flexible_msg随后调用std::move(msg).write_to(*_metadata_writer)写入 DDS 网络。has_metadata_readers()dds-device-server.cpp则用于查询当前是否存在订阅者以便上层决定是否需要实际发送。客户端基于时间戳的双队列同步器客户端一侧的核心组件是dds_metadata_syncer实现位于 third-party/realdds/src/dds-metadata-syncer.cpp。它维护两个有界队列帧队列_frame_queue上限max_frame_queue_size 2元数据队列_metadata_queue上限max_md_queue_size 8图像帧与元数据分别通过enqueue_framedds-metadata-syncer.cpp与enqueue_metadatadds-metadata-syncer.cpp入队两者都使用同一把锁、按时间戳递增的顺序入队若发现乱序会直接抛出运行时错误。入队后都会触发search_for_match匹配逻辑若帧时间戳 元数据时间戳说明更新的元数据已到当前帧已不可能找到匹配于是调用handle_frame_without_metadata不带元数据地释放该帧dds-metadata-syncer.cpp——这正是文档“无法同步的图像就没有元数据”约定的代码级体现若两者相等调用handle_match将帧与元数据配对后一起交付给_on_frame_ready回调dds-metadata-syncer.cpp若帧时间戳 元数据时间戳说明该元数据已过时帧序号只会持续增长调用drop_metadata丢弃最旧的元数据dds-metadata-syncer.cpp并可通知上层发生了丢弃。队列的容量上限即是“尽力而为”语义的落点帧队列过载时优先以“无元数据”方式释放帧元数据队列过载时优先丢弃最旧的元数据从而保证主数据通路始终畅通。librealsense 侧的对接链路在 librealsense 的 DDS 设备代理中整条链路是订阅元数据主题src/dds/rs-dds-device-proxy.cpp 中若设备支持元数据_dds_dev-supports_metadata()客户端会订阅元数据消息解析其中的stream-name并将消息分发给对应流的handle_new_metadata帧入队src/dds/rs-dds-sensor-proxy.cpp 中视频帧到达时若元数据已启用_md_enabled帧会被以timestamp.to_ns()为键放入同步器的帧队列等待与元数据配对元数据入队src/dds/rs-dds-sensor-proxy.cpp 中handle_new_metadata从消息的header/timestamp中取出时间戳作为匹配键调用enqueue_metadata入队帧数据装配src/dds/rs-dds-sensor-proxy.cpp 中frame_additional_data的各项字段默认不含元数据timestamp_domain、depth_units、frame_number等均需在元数据配对完成后才被填充——即“帧号只有在元数据已知后才被补全”与文档中“没有 frame-number 时客户端必须自行假定”的表述完全一致。此外从 third-party/realdds/doc/streaming.md 的流式章节可知ROS2 图像格式中没有帧号字段帧号只能靠元数据携带若元数据被禁用可选则帧将不会被编号或由客户端自行编号librealsense 正是这样做的。这也解释了为什么metadata-enabled会被写入流头通知third-party/realdds/src/dds-device-server.cpp使客户端在发现阶段就能知晓每条流是否开启元数据。小结RealSense 视频流的元数据通过独立的device-topic-root/metadata主题、以 flexibleJSON消息格式、BEST_EFFORT/VOLATILE的 QoS 发布图像与元数据依靠一致的timestamp在客户端双队列同步器中进行配对。整个机制在“尽量高效、允许丢弃”与“尽力同步、保证主链路畅通”之间取得了明确平衡而字段名与rs2_frame_metadata_to_string的约定则保证了跨实现服务端、librealsense 客户端、ROS2 客户端的互操作性。若希望进一步了解流式数据本身的传输格式与open-streams控制可继续阅读 third-party/realdds/doc/streaming.md 与 third-party/realdds/doc/device.md。【免费下载链接】librealsenseRealSense SDK项目地址: https://gitcode.com/GitHub_Trending/li/librealsense创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考