概述
本篇实践介绍如何使用TopicRecorder 将订阅的数据流保存为 MCAP 文件。应用场景包括记录实验数据、回看采集过程和准备离线分析数据。
对应的 Python 示例为 2.recording.py。 录制接口见 TopicRecorder 接口清单。
C SDK 没有对应的录制接口,本页介绍 Python 用法。
前置条件
硬件
本示例只读取,不写任何设备参数。
软件
前序步骤
先看懂数据订阅:回调。录制的输入就是subscribe() 返回的订阅句柄,队列语义完全一致。
数据流
文件结构实测
录 6 条设备流加 2 条全局流、5 秒,产出文件的通道如下:
消息编码是
json,每条通道自带一份 schema。用 Foxglove Studio 打开能直接看到字段名,不需要额外的消息定义文件。
文件头的 library 字段实测为 mcap-rs-0.24.0,写入器是 SDK 内置的 Rust 实现。
压缩模式实测
同样录 3 条流、5 秒、约 1970 帧:
JSON 编码的重复字段名压缩率很高。长时间录制默认选
zstd,对写入延迟敏感的场合选 lz4,需要边录边被别的进程流式读取时才用 none。
单独看 lz4 的一个 chunk,实测 3101 字节原始数据压到 938 字节。
质量指标实测
handle.stop() 返回的 summary 带一个 quality 对象:
三处值得注意:
sync_rate 在混入 tf 后掉到 0.857。 六条设备流都在 130 Hz 附近,彼此对得齐。tf 实测 872 Hz、tf_static 只有 1 Hz,与其他通道的节奏差一个量级,同步判定自然失败。录制里同时放不同频段的流,这个数字偏低是预期行为,不代表数据有问题。
max_sync_offset_ms 可能出现一个很大的孤立值。 3 条流那轮实测到 2966 ms,同一轮的 avg 只有 7.89 ms。这是录制刚启动、各通道首帧尚未对齐造成的。判断录制质量看 avg 和 frame_drop_rate,不要被 max 的启动毛刺带偏。
duration_s 是挂钟时长,不是有效录制时长。 用了暂停就会对不上,见下一节。
暂停与恢复
handle 除了 stop() 还有 pause() 和 resume(),示例没有展示。实测录 2 秒、暂停 2 秒、再录 2 秒,3 条流:
帧数对应的是 4 秒有效录制,暂停期间一帧不写。
duration_s 仍然按挂钟走,所以拿 total_frames / duration_s 反推输出率会偏低。
暂停恢复这轮 spc_alert_count 变成 1。暂停期间通道静默,统计过程控制把它识别成一次异常。这是预期的,不是故障。
handle 还提供 subscribe_alerts()、subscribe_metrics()、subscribe_status() 三个订阅接口,可以在录制过程中实时拿到告警、质量指标和状态,不用等到 stop()。
集成步骤
完整示例见 Python 示例。以下片段按接入顺序展示关键操作,参数解析、信号处理和完整错误分支见示例。1. 连接目标手套
将serial_number 替换为目标手套的序列号。连接成功后,后续步骤复用同一个设备句柄。C 片段需包含 wuji_sdk.h。
2. 登记录制通道
创建录制器,再把需要保存的订阅登记进去。以下操作放在异步函数中,设备连接在外层finally 中断开。
3. 启动录制
先创建输出目录,再启动录制。保留返回的句柄,用它停止写入并取得录制摘要。4. 等待文件写完再断开设备
停止录制会完成文件写入。沿用 Python 示例 的取消保护,确保任务取消时仍等待stop() 完成,然后在外层清理设备。
运行与输出
注意事项
示例里这段是有意为之:
asyncio.shield 让 stop() 不被外层取消打断。少了这层保护,Ctrl+C 可能在 MCAP 写完索引之前就把任务砍掉,文件读不出摘要。
读取录好的文件
装了mcap 命令行工具直接看:
json.loads(message.data) 就能拿到与在线订阅一致的字段结构。
扩展用法
按需登记通道,别全开
八条流全录、zstd 压缩,实测 5 秒 6.01 MB,折算每分钟约 72 MB。tf 一条就占了 4361 条消息,比六条设备流加起来还多。录长时间数据前先想清楚哪些流真的要。
降低输出率再录
订阅句柄支持降频。把 130 Hz 降到 30 Hz,文件体积直接掉到四分之一以下,见输出率调节。录制期间监控质量
不想等stop() 才知道是否丢帧,用 handle.subscribe_metrics() 在线拿指标,handle.subscribe_alerts() 拿告警。长时间无人值守的采集应该接上这两个。