You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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++;
}

咨询问题

  1. 是否可跳过OpenCV,直接用GStreamer采集H264视频同时记录时间戳?
  2. 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参数仅为参考值,实际写入帧率受编码性能、系统负载影响波动,但你的代码逻辑是每读取一帧才记录一个时间戳,理论上数量应完全一致。
  • 数量差异更可能来自以下几点:
    1. appsink drop=1导致丢帧:当GStreamer缓冲区满时,appsink会丢弃旧帧,被丢弃的帧不会触发cap.read()成功,自然不会记录时间戳,导致时间戳数量少于实际采集的帧。
    2. 缓冲区未完全写入:代码仅在nSeq为nFlushInterval倍数时才刷新缓冲区,若程序异常退出或循环结束时未做最后一次flush,缓冲区中剩余的时间戳会丢失。
    3. ROS节点循环阻塞:ros::ok()响应不及时或cap.read()偶尔失败但未触发break,可能导致部分帧未被处理。

修复建议

  • 循环结束后强制写入缓冲区剩余内容:
    // 循环结束后执行
    outputFile << timestampBuffer.str();
    timestampBuffer.str("");
    outputFile.close();
    
  • 统计cap.read()的失败次数,确认是否存在丢帧;可尝试将appsink drop=0(可能增加延迟)或调整GStreamer缓冲区大小。
  • 对比视频文件实际帧数与nSeq最终值,明确是时间戳漏写还是帧被丢弃。

内容的提问来源于stack exchange,提问作者schneider-daniel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 07:54:54