09 时间戳同步
任务目标
实现 APS 帧尾时间戳与 EVS 事件帧的精确配对——通过回调满速喂环形缓存、按传感器型号(003/014)统一换算到微秒、二分查找最近邻帧并统计残差,必要时做迟到补配。
1. 初始化相机与解码器
Shimeta::hv::Camera cam;
Shimeta::hv::DeviceConfig cfg;
cfg.backend = Shimeta::hv::Backend::Usb;
cam.Init(cfg);
cam.StartStream();
Shimeta::codec::MipiRaw8Decoder dec;
// dec 内部含 014 自校准状态,实例需跨帧复用(不可每帧新建)2. 注册回调满速喂环形缓存
必须用回调,不要用 GetFrame
GetFrame 返回"最新帧"——紧循环消费时中间帧会被跳过。014 在 600fps 下如果主循环每秒只处理 ~60 帧,环形缓存只被喂了 1/10 的帧,配对会退化为稀疏网格最近邻。
std::deque<EvsFrame> ring; // 按时间戳排序的事件帧缓存
std::mutex ring_mutex;
cam.SetEventCallback([&](const Shimeta::hv::EventPacket& pkt) {
// 回调线程:每个派发帧调用一次,满速解码
std::vector<Shimeta::EventCD> events;
dec.Decode(pkt.data.data, pkt.data.size, events);
// 按时间戳分组为帧,追加到 ring(持锁)
std::lock_guard<std::mutex> lk(ring_mutex);
appendEvsFrames(ring, events);
});
cam.SetImageCallback([&](const Shimeta::hv::ImageData& img) {
// 存最新 APS 帧(NV12 字节 + 尾时间戳)
latest_aps = img;
});3. 从 Frame 中获取 APS 尾时间戳
uint64_t aps_ts_raw = uint64_t(latest_aps.ts.aps_ts_ns);
// 注意:003 时此值为纳秒(µs×1000);014 时为原始 tick(非纳秒)4. 判断传感器并统一换算到微秒
const bool is014 = (latest_aps.width == 640);
uint64_t aps_us;
if (aps_ts_raw == 0) {
// 尾值为 0 = 无效(设备端侧信道 2 秒超时),跳过配对
skipped++;
} else if (!is014) {
// 003:纳秒 → 微秒,直接配对
aps_us = aps_ts_raw / 1000;
} else if (dec.calibrated() && dec.tickHz() > 0) {
// 014:原始 tick → 微秒(需校准完成后才能换算)
const uint32_t diff = uint32_t(aps_ts_raw) - dec.rawTickBase();
aps_us = uint64_t(double(diff) * 1e6 / dec.tickHz());
} else {
// 014 校准中,暂无法配对
}5. 二分查找配对帧
int nearestFrame(const std::deque<EvsFrame>& ring, uint64_t target) {
// ring 按时间戳升序排列
// 返回 ts ≤ target 的最大帧索引(或最近的)
int lo = 0, hi = ring.size() - 1;
// ... 标准二分 ...
return best_index;
}
int idx = nearestFrame(ring, aps_us);
uint64_t evs_ts = ring[idx].ts;
uint64_t residual = (evs_ts > aps_us) ? evs_ts - aps_us : aps_us - evs_ts;残差含义:
residual == 0:精确命中——APS 尾时间戳与该帧事件时间戳逐微秒相等residual > 0:指向的事件帧未被送达(设备端丢帧),配对落到最近邻
6. 迟到补配(可选优化)
APS 帧可能比它引用的事件帧先到达主机(一拍赛跑)。如果首轮配对差一拍,等下一帧事件解码后再重试:
uint64_t pending_aps_us = 0; // 上一帧未精确命中的尾 ts
// 每轮处理新事件帧后,先重试 pending
if (pending_aps_us != 0) {
int retry = nearestFrame(ring, pending_aps_us);
if (ring[retry].ts == pending_aps_us) {
// 补配成功,残差为 0
pending_aps_us = 0;
}
}
// 本帧配对后,若差一拍(evs < aps 且 residual > 0),存入 pending 留待下轮时间戳单位速查
| 位置 | APX003 | APX014 |
|---|---|---|
Frame.ts.aps_ts_ns | 纳秒(真 ns) | 原始 tick(非纳秒) |
EventCD.t | 微秒 | 微秒(自校准) |
| 换算方式 | ns ÷ 1000 = µs | (tick − rawTickBase) × 10⁶ ÷ tickHz |
注意事项
- USB 3.0 必须——014 满速需 ~158 MB/s,USB 2.0 下帧率骤降且配对紊乱(用
lsusb -t确认5000M) - 014 校准期前 64 帧——
Decode()返回 0 事件(内部缓冲),校准完成后一次性补发,此期间配对不可用 - 解码器实例跨帧复用——014 校准状态存储在
MipiRaw8Decoder实例内,每帧新建实例校准永远完不成 - 环形缓存容量——建议 ~900 帧(014@600fps 约 1.5 秒),太小会导致配对时引用帧已被淘汰
- 尾值为 0 跳过——设备端 EVS 未启动时侧信道 2 秒超时后写 0,这是无效标记
