使用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
相关产品推荐
相关产品推荐

