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

使用Cudacodec VideoReader同步多路RTSP流的技术问题咨询

多路RTSP流(Cudacodec::VideoReader)同步方案

核心问题排查

你当前代码的首要问题是串行拉取4路流:nextFrame()是阻塞调用,每路流等待帧返回的时间差会直接导致画面不同步,就算拿到时间戳也解决不了这个架构问题。
其次纠正一个误解:OpenCV 4.5.3的cv::cudacodec::VideoReader支持获取帧时间戳,通过get()接口即可读取流自带的PTS(编码端FPGA生成,已通过NTP同步)。

具体实现步骤

1. 定义带时间戳的帧缓冲结构

每个流对应独立的拉流线程和环形缓冲区,避免串行拉流的延迟差:

// 全局/类成员定义
struct SyncFrame {
    cv::cuda::GpuMat frame;
    double pts; // 帧的PTS时间戳,单位毫秒
};
const int BUFFER_SIZE = 30; // 可容纳约1秒的帧(按30fps计算)
std::queue<SyncFrame> frameBuffers[4];
std::mutex bufferMutex[4];
std::atomic<bool> isRunning = false;
const double SYNC_THRESHOLD = 30; // 同步误差允许范围,单位毫秒

2. 独立拉流线程实现

每路流单独开线程拉帧,读取PTS并存入对应缓冲区:

void streamThread(int streamIdx, std::string rtspUrl) {
    cv::Ptr<cv::cudacodec::VideoReader> reader = cv::cudacodec::createVideoReader(rtspUrl);
    while (isRunning) {
        cv::cuda::GpuMat frame;
        try {
            if (!reader->nextFrame(frame)) {
                // 重连逻辑和你原有代码一致,此处省略
                continue;
            }
            // 读取当前帧PTS,单位转换为毫秒
            double pts = reader->get(cv::CAP_PROP_POS_MSEC);
            std::lock_guard<std::mutex> lock(bufferMutex[streamIdx]);
            // 缓冲区满则丢弃最早的帧
            if (frameBuffers[streamIdx].size() >= BUFFER_SIZE) {
                frameBuffers[streamIdx].pop();
            }
            frameBuffers[streamIdx].push({frame.clone(), pts});
        } catch (const std::exception& e) {
            // 异常重连逻辑
        }
    }
}

3. 主渲染同步逻辑

主线程只做同步取帧和拼接渲染:

void Widget::on_StartStream_clicked(){
    cv::namedWindow("OpenGL Output", cv::WINDOW_OPENGL);
    cv::setWindowProperty("OpenGL Output", cv::WND_PROP_FULLSCREEN, cv::WINDOW_FULLSCREEN);
    cv::cuda::GpuMat MergedOutput(3120 * 2, 4208 * 2, CV_8UC4);
    isRunning = true;

    // 启动4路拉流线程
    std::thread threads[4];
    for(int i = 0; i <4; i++){
        int ipSuffix = 1 + 2*i;
        std::string rtspPathFpga = "rtsp://172.16.1.10" + std::to_string(ipSuffix) + ":900" + std::to_string(ipSuffix) + "/camera_1";
        threads[i] = std::thread(streamThread, i, rtspPathFpga);
    }

    // 等待所有缓冲区都有至少1帧
    bool allBufferReady = false;
    while(!allBufferReady){
        allBufferReady = true;
        for(int i=0;i<4;i++){
            std::lock_guard<std::mutex> lock(bufferMutex[i]);
            if(frameBuffers[i].empty()){
                allBufferReady = false;
                break;
            }
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }

    while (isRunning) {
        // 第一步:获取所有缓冲区最新帧的时间戳,找到基准时间
        double latestPts[4];
        for(int i=0;i<4;i++){
            std::lock_guard<std::mutex> lock(bufferMutex[i]);
            latestPts[i] = frameBuffers[i].back().pts;
        }
        double basePts = *std::min_element(latestPts, latestPts+4);

        // 第二步:每个缓冲区取最接近basePts的帧
        cv::cuda::GpuMat frames[4];
        for(int i=0;i<4;i++){
            std::lock_guard<std::mutex> lock(bufferMutex[i]);
            // 丢弃早于基准时间超过阈值的帧
            while(!frameBuffers[i].empty() && basePts - frameBuffers[i].front().pts > SYNC_THRESHOLD){
                frameBuffers[i].pop();
            }
            frames[i] = frameBuffers[i].front().frame;
        }

        // 拼接渲染逻辑和原有代码一致
        frames[0].copyTo(MergedOutput(cv::Rect(0, 0, frames[0].cols, frames[0].rows)));
        frames[1].copyTo(MergedOutput(cv::Rect(frames[0].cols, 0, frames[1].cols, frames[1].rows)));
        frames[2].copyTo(MergedOutput(cv::Rect(0, frames[1].rows, frames[2].cols, frames[2].rows)));
        frames[3].copyTo(MergedOutput(cv::Rect(frames[2].cols, frames[3].rows, frames[3].cols, frames[3].rows)));
        cv::imshow("OpenGL Output",MergedOutput);

        if (cv::waitKey(1) == 'q') {
            isRunning = false;
            break;
        }
    }

    // 回收线程资源
    for(int i=0;i<4;i++){
        if(threads[i].joinable()) threads[i].join();
    }
}

特殊情况适配

如果你的RTSP流没有封装PTS(FPGA编码时未写入),可以用NTP同步后的本地系统时间替代:拉到帧后立刻取当前系统时间作为帧的时间戳,同步逻辑完全不变,误差在百毫秒级,普通监控场景足够使用。

内容的提问来源于stack exchange,提问作者murat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 04:18:03