NVIDIA Jetson平台GStreamer/OpenCV录屏帧与时间戳不匹配问题咨询
问题背景
在NVIDIA Jetson平台上使用GStreamer与OpenCV(C++)采集相机视频,通过ROS Noetic中间件获取ROS时间戳,收到每帧图像后将时间戳写入文本文件。但发现视频帧数量与时间戳数量不匹配:一段约37.46分钟的录屏中,采集到68087帧,仅记录67800个时间戳,录屏后需将视频拆解为单帧做后续处理。
简化采集代码
sPipeLine = "v4l2src device=/dev/video0 ! video/x-raw ! nvvidconv flip-method=0 ! video/x-raw,width=(int)nWidth, height=(int)nHeight, format=(string)BGRx ! videoconvert ! appsink drop=1"; // 获取GStreamer视频数据 cv::VideoCapture cap(sPipeline, cv::CAP_GSTREAMER); // 创建VideoWriter对象,文件后缀为.mkv cv::VideoWriter videoWriter(sVideoFileName, cv::VideoWriter::fourcc('m', 'p', '4', 'v'), 30, cv::Size(nWidth, nHeight)); int nSeq = 0; std::ofstream outputFile(sTextFileName); while (ros::ok()) { // 读取图像数据 if (!cap.read(img)) { std::cout << "Capture read error" << std::endl; break; } // 时间戳记录 tTimeStamp.data = ros::Time::now(); // 将帧写入VideoWriter videoWriter.write(img); // 将时间消息转换为字符串 std::string timeString = std::to_string(tTimeStamp.data.sec) + "." + std::to_string(tTimeStamp.data.nsec); timestampBuffer << nSeq << "," << timeString << std::endl; // 示例格式:1234,1701357893.213058856 // 定期刷新缓冲区到文件,减少IO操作 if (nSeq % nFlushInterval == 0) { outputFile << timestampBuffer.str(); // 清空缓冲区 timestampBuffer.str(std::string()); } // 递增序列计数器 nSeq++; }
咨询问题
- 是否可跳过OpenCV,直接用GStreamer采集H264视频同时记录时间戳?
- OpenCV VideoWriter要求整数FPS,但实际帧率在29.xx-30.xx波动,是否为帧与时间戳不匹配的原因?
回答
问题1:跳过OpenCV直接用GStreamer采集并记录时间戳
完全可以,这种方式能更精准地控制帧与时间戳的对应关系,避免OpenCV中间层的潜在丢帧或延迟问题。
实现思路
- 用GStreamer的
v4l2src采集原始视频,通过Jetson硬件编码元素omxh264enc生成H264流,同时借助identity元素挂载自定义probe函数捕获每帧的时间戳。 - 时间戳可直接取GStreamer缓冲区的
pts(显示时间戳),或结合ros::Time::now()记录,确保与帧严格同步。 - 视频流通过
filesink写入本地文件,时间戳在probe函数中写入文本文件,两者同步处理。
关键代码片段示例
// 为identity元素的src pad注册probe函数 GstPad* pad = gst_element_get_static_pad(identity, "src"); gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, (GstPadProbeCallback)frame_probe, NULL, NULL); gst_object_unref(pad); // probe函数实现 static GstPadProbeReturn frame_probe(GstPad* pad, GstPadProbeInfo* info, gpointer user_data) { GstBuffer* buffer = GST_PAD_PROBE_INFO_BUFFER(info); if (buffer) { // 将GStreamer PTS转换为ROS时间 GstClockTime pts = GST_BUFFER_PTS(buffer); ros::Time ts; ts.fromNSec(pts); // 线程安全地写入时间戳到文件 static std::mutex mtx; static int nSeq = 0; std::lock_guard<std::mutex> lock(mtx); timestampFile << nSeq << "," << ts.sec << "." << ts.nsec << std::endl; nSeq++; } return GST_PAD_PROBE_OK; }
问题2:帧率波动是否导致帧与时间戳不匹配?
不是直接原因,但可能间接影响。
分析
- OpenCV
VideoWriter的整数FPS参数仅为参考值,实际写入帧率受编码性能、系统负载影响波动,但你的代码逻辑是每读取一帧才记录一个时间戳,理论上数量应完全一致。 - 数量差异更可能来自以下几点:
appsink drop=1导致丢帧:当GStreamer缓冲区满时,appsink会丢弃旧帧,被丢弃的帧不会触发cap.read()成功,自然不会记录时间戳,导致时间戳数量少于实际采集的帧。- 缓冲区未完全写入:代码仅在
nSeq为nFlushInterval倍数时才刷新缓冲区,若程序异常退出或循环结束时未做最后一次flush,缓冲区中剩余的时间戳会丢失。 - ROS节点循环阻塞:
ros::ok()响应不及时或cap.read()偶尔失败但未触发break,可能导致部分帧未被处理。
修复建议
- 循环结束后强制写入缓冲区剩余内容:
// 循环结束后执行 outputFile << timestampBuffer.str(); timestampBuffer.str(""); outputFile.close(); - 统计
cap.read()的失败次数,确认是否存在丢帧;可尝试将appsink drop=0(可能增加延迟)或调整GStreamer缓冲区大小。 - 对比视频文件实际帧数与
nSeq最终值,明确是时间戳漏写还是帧被丢弃。
内容的提问来源于stack exchange,提问作者schneider-daniel
相关产品推荐
相关产品推荐

