ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

多摄像头测试工具:多路USB与RTSP同屏、录像和断流重连实践

多摄像头测试工具:多路USB与RTSP同屏、录像和断流重连实践

简介:MVideoTest这款多摄像头测试工具面向监控系统开发者、智能安防调试人员及车载、无人机视觉应用测试者,核心目标是在多设备并行接入时验证摄像头的分辨率、帧率、曝光、色彩与视频流同步表现。压缩包共268个文件、约17.88MB,以C++头文件、动态库、cpp源码、编译目标文件及Qt工程配置为主,头文件与源码体现功能结构,动态库提供运行依赖,工程配置便于复现调试,并包含FFmpeg相关库与播放、编解码封装代码,便于理解多路视频采集、显示与控制链路的模块组织。已有512人学习下载,说明其在摄像头联调与监控测试场景中具有一定参考价值。读者可从中获得多摄像头管理、视频质量检测、不同品牌设备兼容性验证、网络延迟与带宽评估、故障诊断及日志报告等功能的实现线索,也可借鉴自动化测试与界面组织的思路,用于搭建自己的监控测试环境或排查视频丢失、信号中断、编码错误等问题。

1. 多摄像头测试工具到底测什么:从单路预览到八路同屏的落地判断

多摄像头测试工具不是把几个预览窗口拼在一起就完事。它首先是一套测试工具,用来验证多路 USB 摄像头、RTSP 流在同时预览、录像、断流重连时的真实表现;把它放到自有设备的轻量监控场景里,路数一多,CPU、带宽、缓冲区和设备节点都会出来找麻烦。很多人第一次跑多摄,八路里总有一两路黑屏,最后发现不是代码写错,而是 USB 控制器带宽和采集线程互相拖死。

这类工具适合三类人:正在做多摄采集联调的开发者、要给实验室或门店自有摄像头做集中预览的运维、以及课程设计或项目验收前需要压一轮长时间运行的学生。它解决的不是“把画面显示出来”这一件事,而是把设备枚举、分辨率与帧率探测、同屏渲染、录像分段、断流重连和资源占用观察串成一条可重复的测试链路。

如果只想临时看一路画面,系统自带相机应用就够了;多摄测试工具的价值在“多路同时”和“可观察”。下面按采集链路、同屏与录制、监控化使用、避坑排查、进阶验证的顺序拆,参数都落到能改的代码和命令上。

2. 环境与采集链路:把 USB 摄像头和 RTSP 流拉进同一张画布

多摄测试工具的第一道坎不是界面,而是采集链路。USB 摄像头走本地采集接口,RTSP 走网络解码,两者的打开方式、缓冲策略、错误恢复完全不同。我一般先把两路都抽象成“能 read 的最新帧源”,再谈同屏。否则 UI 一接进来,排查就变成黑匣子。这里选 Python 加 OpenCV 做主采集,FFmpeg 做录制和转封装,PySide6 只负责渲染;GStreamer 留到低延迟硬解场景再上。理由很直接:OpenCV 的VideoCapture上手快,适合做测试工具;FFmpeg 命令行在分段录像和转封装上成熟;渲染层不要碰采集,才能把卡顿定位清楚。

组件负责的事不负责的事常见误用
OpenCV打开 USB 摄像头、RTSP 流,读帧,做轻量图像处理长时间分段录像、复杂转封装在 UI 线程里直接read()
FFmpeg录制、转封装、拉流参数控制、合成测试源精细的界面渲染用-c copy录不兼容的封装格式
PySide6多画面布局、缩略图刷新、按钮和状态显示采集和解码每个画面一个定时器各自读流
GStreamer低延迟硬解、复杂管道快速搭一个可改参数的测试工具小项目里过早引入,打包依赖变重

2.1 依赖选型:OpenCV、FFmpeg、PySide6 各管哪一段

OpenCV 的 RTSP 后端通常走 FFmpeg,但CAP_PROP_BUFFERSIZE在部分后端里只是建议值,设成 1 不代表网络缓冲真的只有 1 帧。血泪经验是:代码里设了缓冲区,延迟还是越跑越大,最后要回到 FFmpeg 参数上找原因。常见做法是在启动前设置采集选项,让 RTSP 走 TCP,并尽量压低缓冲。下面这行环境变量适合在 Linux 或桌面系统的启动脚本里先设好,再启动 Python 进程:

export OPENCV_FFMPEG_CAPTURE_OPTIONS="rtsp_transport;tcp|fflags;nobuffer|flags;low_delay|max_delay;500000"

这段配置用分号分隔键值,rtsp_transport;tcp让 RTSP 走 TCP,减少 UDP 丢包导致的花屏;fflags;nobuffer让解复用阶段少攒数据;flags;low_delay偏向低延迟;max_delay;500000是微秒单位,表示最大延迟约束。参数不是越大越好,max_delay放得太宽,断流恢复会变慢;放得太紧,弱网下容易读不到帧。不同 OpenCV 构建对这些选项的识别程度不一样,失败时先看cap.isOpened()和read()的返回值,再换后端验证,不要只盯 UI。

提示:RTSP 的CAP_PROP_BUFFERSIZE在 OpenCV 里经常只是建议值,真正生效要靠 FFmpeg 的fflags和flags参数;不要只看代码里设了 1 就以为不会堆积。

FFmpeg 在这套工具里主要干两件事:录制时做分段转封装,以及在没有真实摄像头时生成合成视频源。PySide6 只拿最新帧做缩放显示,不参与采集。这样分工以后,UI 卡顿和采集断流可以分开排查:UI 卡顿看刷新定时器和缩放,采集断流看read()返回值和重连日志。常见误用是把三个组件的职责混在一起,比如在按钮回调里打开摄像头、在定时器里读帧、在同一个线程里写录像文件,最后任何一个环节慢都会拖死整个界面。

2.2 摄像头枚举与能力探测:先确认设备节点和编码格式

Linux 环境下,同一物理摄像头可能暴露多个/dev/video*节点,不是每个节点都能读到图像。先枚举能出帧的节点,再记录分辨率和 FOURCC。下面这段探测代码先用低分辨率打开,读一帧成功才保留,避免一上来就高分辨率把 USB 带宽打满:

import glob import cv2 def fourcc_to_str(value): # FOURCC 是 32 位整数,按字节转成可读的编码名 return "".join(chr((value >> (8 * i)) & 0xFF) for i in range(4)) def probe_usb_cameras(): found = [] for dev in sorted(glob.glob("/dev/video*")): cap = cv2.VideoCapture(dev, cv2.CAP_V4L2) if not cap.isOpened(): cap.release() continue # 探测阶段先用低分辨率,减少 USB 带宽占用 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cap.set(cv2.CAP_PROP_FPS, 15) ok, frame = cap.read() if ok and frame is not None: found.append({ "device": dev, "width": int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), "height": int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)), "fps": round(cap.get(cv2.CAP_PROP_FPS), 1), "fourcc": fourcc_to_str(int(cap.get(cv2.CAP_PROP_FOURCC))), }) cap.release() return found

逻辑很直白:VideoCapture用CAP_V4L2打开设备节点,打开失败就跳过;设置探测分辨率后读一帧,读不到也跳过;只有真正出帧的节点才进入结果列表。fourcc字段用来判断当前是 MJPG 还是 YUYV 一类格式。MJPG 通常比未压缩格式省 USB 带宽,多路同开时更友好,但会增加一点解码开销;YUYV 对 CPU 轻,但带宽吃得多。参数上,探测阶段用 640x480、15 帧足够,不要拿 4K 去枚举,八路同时探测很容易把控制器带宽打满,表现就是设备能打开但读帧超时。

真正接多路之前,建议先逐路把分辨率升到目标值,确认单路能读、帧率符合预期,再开第二路。常见误用是直接写一个循环同时打开八路,然后怪工具“不支持多摄”;实际上第一路高分辨率就把 USB 控制器带宽占完了。失败时看两个地方:cap.isOpened()为 false 先查设备权限和节点是否存在;打开成功但read()为 false,先降分辨率、换 MJPG、换 USB 口,再查驱动和线材。

2.3 统一采集线程模型:每路一个 reader,主线程只做渲染

多摄测试工具的核心不是“读帧”,而是“每路独立读帧,主线程只取最新帧”。下面这个 reader 用独立线程打开和重连设备,读到的帧加锁保存,UI 只调用read_latest()拿一份拷贝。这样某一路 RTSP 卡住时,其他路不会被拖死:

import threading import time import cv2 class CameraReader(threading.Thread): def __init__(self, src, backend=cv2.CAP_ANY, fps=15): super().__init__(daemon=True) self.src = src self.backend = backend self.fps = max(1, int(fps)) self.cap = None self.frame = None self.last_ts = 0.0 self.lock = threading.Lock() self.running = threading.Event() self.running.set() def open(self): cap = cv2.VideoCapture(self.src, self.backend) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) cap.set(cv2.CAP_PROP_FPS, self.fps) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) self.cap = cap return cap.isOpened() def run(self): interval = 1.0 / self.fps while self.running.is_set(): if self.cap is None or not self.cap.isOpened(): if not self.open(): time.sleep(1.0) continue ok, frame = self.cap.read() if not ok: self.close() time.sleep(1.0) continue with self.lock: self.frame = frame self.last_ts = time.time() # 控制拉流节奏,避免空转吃满 CPU time.sleep(interval) def read_latest(self): with self.lock: if self.frame is None: return None, 0.0 return self.frame.copy(), self.last_ts def close(self): if self.cap is not None: self.cap.release() self.cap = None def stop(self): self.running.clear() self.close()

这段代码的关键点在三个地方。第一,run()里每路自己打开、自己读、自己释放,某一路打开失败只影响自己;第二,read_latest()返回的是copy(),避免 UI 线程和采集线程同时操作同一块 NumPy 内存;第三,time.sleep(interval)控制拉流节奏,不是精确帧率,真实帧率还要看设备和网络。参数上,fps=15对多数监控预览够用,720p 下八路以内比 1080p 更容易跑;CAP_PROP_BUFFERSIZE设 1 是意图,实际是否生效要看后端。如果某一路总是超时,先把fps降到 10,再把分辨率降到 960x540 做对比,不要一上来就改 UI。

常见误用是把cap.read()放在 Qt 的定时器里。定时器一密集,UI 线程就被读帧阻塞,表现是按钮点不动、画面一起卡。正确做法是采集线程一直跑,UI 定时器只拿最新帧。失败时看read_latest()返回的last_ts:超过 3 秒没有新帧,就在画面上显示“超时”,而不是继续显示旧画面让人误判。线程退出也要走stop()加join(),否则设备句柄没释放,下一次打开同一路会失败。

3. 多路同屏与录制:分辨率、帧率、缓冲区的参数怎么定

同屏和录制是多摄测试工具最容易“看起来能用、跑久就翻车”的两段。预览阶段大家容易把刷新频率拉得很高,觉得画面顺滑;录制阶段又容易直接拿原始帧写文件,觉得省事。实际跑起来,UI 刷新、编码、磁盘写入会互相抢资源。我的做法是:预览只取最新帧,按固定间隔刷新;录制尽量交给 FFmpeg 做转封装或分段,Python 进程只负责发起和监控。下面把参数和代码拆开说。

3.1 同屏布局与渲染节流:别让 UI 拖垮采集

多路同屏先定布局,再定刷新。4 路用 2x2,6 路用 3x2,9 路用 3x3;超过 9 路不建议继续缩小单画面,改用轮巡或分组。每路一个QLabel,UI 定时器每 33 到 50 毫秒拉一次最新帧,缩放到标签大小后显示。下面这段是刷新核心,省略了窗口搭建,重点看“只取最新帧”和“拷贝后再交给 Qt”:

import time from PySide6.QtCore import QTimer, Qt from PySide6.QtGui import QImage, QPixmap class PreviewGrid: def __init__(self, readers, labels, interval_ms=40): self.readers = readers self.labels = labels self.timer = QTimer() self.timer.timeout.connect(self.refresh) self.timer.start(interval_ms) @staticmethod def to_pixmap(frame): h, w, ch = frame.shape # QImage 不拥有 NumPy 内存,copy() 后再交给 Qt image = QImage(frame.data, w, h, ch * w, QImage.Format_BGR888) return QPixmap.fromImage(image.copy()) def refresh(self): for reader, label in zip(self.readers, self.labels): frame, ts = reader.read_latest() if frame is None: label.setText("无信号") continue if ts and (time.time() - ts) > 3.0: label.setText("超时") continue pix = self.to_pixmap(frame) label.setPixmap(pix.scaled( label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation, ))

逻辑说明:interval_ms=40表示 UI 每秒最多刷新 25 次,和采集帧率解耦;read_latest()只拿当前最新帧,不排队,所以网络延迟不会在 UI 侧累积;QImage用copy()是因为原始 NumPy 数组会被采集线程复用,不拷贝会出现花屏或崩溃。参数上,4 路以下 33 毫秒可以;6 到 9 路建议 50 到 100 毫秒,画面少一点顺滑,但 CPU 明显下降。Qt.SmoothTransformation在路数多时也会吃 CPU,可以换成快速缩放,等布局稳定后再开平滑。

常见误用是每个QLabel自己起一个定时器,各自调用read()。这样刷新频率和采集频率绑死,任何一路慢都会让对应定时器堆积。另一个误用是直接把原始frame交给QImage而不拷贝,短时间看不出问题,跑几分钟后开始随机花屏,排查起来很玄学。失败时先看是“全部卡”还是“单路卡”:全部卡查 UI 定时器和缩放,单路卡查该路last_ts和采集日志。

3.2 录像与截图:用 VideoWriter 还是 FFmpeg 子进程

录制先问自己:录的是 RTSP 流还是 USB 摄像头。RTSP 流如果编码格式固定,优先用 FFmpeg 做-c copy分段录制,不转码,CPU 占用低;USB 摄像头如果拿不到压缩流,只能用 OpenCV 的VideoWriter或 FFmpeg 的本地采集输入。下面这条命令适合把 RTSP 流按 5 分钟分段写成 MP4,每段独立时间戳:

ffmpeg -rtsp_transport tcp -i "rtsp://user:pass@192.168.1.10:554/stream1" \ -c copy -f segment -segment_time 300 -reset_timestamps 1 \ -movflags +faststart "record_%03d.mp4"

参数说明:-rtsp_transport tcp让拉流走 TCP;-c copy不重新编码,CPU 省,但要求输入编码和 MP4 封装兼容;-f segment开分段;-segment_time 300每段 300 秒;-reset_timestamps 1让每段从零开始,播放器时长显示更正常;-movflags +faststart把索引放到文件头,方便边录边看。失败时先看 FFmpeg 日志里有没有“Non-monotonous DTS”或“Could not find codec parameters”,前者通常是时间戳不连续,后者是流参数变化。不要在不支持 copy 的编码上硬套,必要时去掉-c copy,用-c:v libx264 -preset veryfast转码换兼容性。

USB 摄像头用VideoWriter更直接,但要注意写文件和读帧不能在同一个线程里死等。常见做法是采集线程读到帧后放进有界队列,录制线程从队列取帧写文件;队列长度控制在 30 到 60 帧,满了就丢旧帧并记日志,避免内存一直涨。截图则简单得多,在 UI 上对当前帧做cv2.imwrite即可,但文件名要带路号和毫秒时间戳,否则多路同时截图会覆盖。

3.3 断流重连与时间戳:监控场景最容易被忽略的两件事

监控化使用最怕“画面停在最后一帧,但界面不报错”。所以每路 reader 要记录last_ts,UI 超过 3 秒没新帧就显示超时;重连不要立刻狂试,用指数退避,避免把设备和网络打满。下面是一个重连循环的骨架:

import time def reconnect_loop(reader, max_delay=30.0): delay = 1.0 while reader.running.is_set(): frame, ts = reader.read_latest() if frame is not None and (time.time() - ts) <= 5.0: delay = 1.0 time.sleep(1.0) continue reader.close() time.sleep(delay) delay = min(delay * 2, max_delay)

逻辑说明:每 5 秒检查一次有没有新帧;有就重置退避;没有就关闭当前句柄,等待后重开。delay从 1 秒开始翻倍,最大 30 秒,避免断网时每秒重连几十次。参数上,max_delay不要小于 10 秒,否则网络恢复慢时重连次数还是偏多;5.0秒的超时阈值可以按帧率改,低帧率场景放到 8 秒更合适。时间戳用time.time()做展示够用,但如果要统计延迟,建议另存time.monotonic(),避免系统时间调整导致负数。

参数建议起点说明
预览分辨率1280x720八路以内优先 720p,先保路数再谈清晰度
采集帧率10 到 15监控预览不需要 30 帧,低帧率更省 CPU 和带宽
UI 刷新间隔40 到 100 毫秒路数越多越往 100 毫秒靠
录像分段300 秒单段太大不好回看,太小文件多
重连最大间隔30 秒避免断网时频繁重连

注意:录制文件“能播但时长不对”多半不是播放器问题,而是时间戳或帧率元数据不连续;先用 FFmpeg 看日志,再决定要不要转码。

4. 监控化使用:轮巡、移动侦测和资源占用的边界

把多摄测试工具当轻量监控用,边界要提前说清:它适合自有设备的集中预览、录像分段和简单画面变化提醒,不适合替代专业录像系统。轮巡解决“屏幕放不下”,移动侦测解决“没人一直盯”,资源占用观察解决“跑一晚上会不会崩”。这三件事都不难,难的是参数别拍脑袋。下面按可配置、可验证的方式拆。

4.1 轮巡与多画面切换:把“看不过来”变成可配置策略

轮巡不要写死在代码里,做成配置表。每路有组号、停留秒数、是否参与轮巡、是否录像。比如 8 路分两组,每组 4 路轮巡,单路停留 8 秒,主画面固定显示 2x2。配置表可以用 JSON 或 Python 字典,启动时读入。下面是一个最小配置结构,字段名直接对应界面逻辑:

CAMERAS = [ {"name": "入口-1", "src": "/dev/video0", "group": "A", "dwell": 8, "record": True}, {"name": "入口-2", "src": "/dev/video2", "group": "A", "dwell": 8, "record": True}, {"name": "通道-1", "src": "rtsp://user:pass@192.168.1.11:554/stream1", "group": "B", "dwell": 10, "record": False}, ]

逻辑说明:group决定轮巡分组,dwell是停留秒数,record控制是否拉起录像子进程。建议轮巡组不超过 6 路,停留 8 到 12 秒;停留太短人眼还没看清就切走,太长又失去轮巡意义。切换时不要销毁再重建采集线程,reader 一直跑,只换 UI 上绑定的那一路;否则每次切换都重新打开设备,延迟和失败率都会上来。失败时看切换瞬间是否出现“无信号”,如果是,先查该路last_ts,再查是不是在切换时重复打开了同一个设备节点。

轮巡参数建议值适用场景
每组路数4 到 6屏幕再大也有限,组太多切不过来
单路停留8 到 12 秒轻量查看,不是专业矩阵
切换方式复用 reader,只换绑定避免重复打开设备
录像策略关键路常录,轮巡路不录降低磁盘和编码压力

4.2 轻量移动侦测:帧差法的阈值和误报控制

移动侦测用帧差法就够做第一版,不要一上来上模型。把当前帧转灰度、高斯模糊、和上一帧做绝对差、二值化、膨胀,再按轮廓面积过滤。下面这段可以直接挂到某一路 reader 上,每隔几帧算一次:

import cv2 class MotionDetector: def __init__(self, diff_threshold=25, area_threshold=800, blur_size=5): self.prev = None self.diff_threshold = diff_threshold self.area_threshold = area_threshold self.blur_size = blur_size def detect(self, frame): gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (self.blur_size, self.blur_size), 0) if self.prev is None: self.prev = gray return False, [] diff = cv2.absdiff(self.prev, gray) _, th = cv2.threshold(diff, self.diff_threshold, 255, cv2.THRESH_BINARY) th = cv2.dilate(th, None, iterations=2) contours, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) boxes = [] for c in contours: if cv2.contourArea(c) >= self.area_threshold: boxes.append(cv2.boundingRect(c)) self.prev = gray return len(boxes) > 0, boxes

逻辑说明:diff_threshold=25控制灰度差多少算变化,光照变化大的场景要往上调;area_threshold=800是 720p 下的起点,分辨率越高这个值越要跟着涨;blur_size=5用来压噪点,但太大会把小块变化抹掉。失败时先看误报是不是集中在开灯、关灯、树影晃动时段,如果是,给画面加 ROI 只检测通道区域,或者把侦测频率从每帧降到每 5 帧一次。不要用帧差法直接触发录像 30 秒,先做“连续 2 次检测到才触发”,能过滤不少瞬时误报。

4.3 资源占用实测方法:CPU、内存、句柄和网络

资源占用不要靠感觉。跑起来后用top -H -p <pid>看线程级 CPU,用ps -o pid,rss,vsz,nlwp -p <pid>看内存和线程数,用ls /proc/<pid>/fd | wc -l看句柄数,用ss -tnp看 RTSP 连接数。观察顺序是:先空载跑 10 分钟,再逐路增加,每加两路稳定观察 5 分钟。重点看三个曲线:CPU 是否随路数线性涨、内存是否持续爬、句柄数是否只增不减。内存持续爬通常是帧队列没设上限,句柄只增不减通常是VideoCapture没有 release。

参数建议:720p、15 帧、8 路以内,先把 UI 刷新降到 100 毫秒,录像只开关键路;如果 CPU 还是高,优先降分辨率而不是降帧率,因为解码和缩放对分辨率更敏感。网络侧看 RTSP 连接是否频繁重连,频繁重连会把 CPU 和日志都拖上去。失败时不要只看总 CPU,要看每个采集线程的 CPU;某一路占用异常高,先查是不是拿了高分辨率却没用上,或者解码后端选错。

5. 避坑与排查:多摄测试工具最容易翻车的五件事

多摄测试工具的问题很少是“代码不会写”,更多是环境、带宽、时间戳和线程退出这些边角。下面五条是我在联调里反复遇到的翻车记录,每条按现象、原因、解决写。你不需要全部踩一遍,但接真实摄像头之前最好对着查一遍。

5.1 设备节点漂移与权限问题

现象:昨天还是/dev/video0的摄像头,重启后变成/dev/video2,工具里配置的路径全部打不开;或者手动能打开,工具进程提示权限不足。

原因:USB 设备枚举顺序不固定,同一物理摄像头可能暴露多个节点;权限则取决于当前用户是否在视频设备组里,或者桌面会话是否占用了设备。

解决:不要写死/dev/videoN,优先用/dev/v4l/by-id/下的持久路径,启动时枚举并匹配名称。权限问题先用ls -l /dev/video*看属组,再把运行用户加入对应组并重新登录。工具里加一个“重新枚举”按钮,枚举时用read()验证节点真能出帧,别只看文件存在。

ls -l /dev/v4l/by-id/ ls -l /dev/video*

第一行看持久化路径,第二行看设备节点和权限。注意:如果同一摄像头有多个节点,用 by-id 路径也要逐个read()验证,不能假设第一个就是采集节点。

5.2 RTSP 花屏、延迟与缓冲堆积

现象:画面偶尔花屏,跑几分钟后延迟从 1 秒涨到十几秒;暂停再开,延迟又恢复正常。

原因:UDP 丢包导致花屏;消费端读得慢,缓冲区堆满;关键帧间隔太大,解码器等关键帧。

解决:拉流改 TCP,设置fflags;nobuffer和flags;low_delay;消费端只取最新帧,不排队;确认摄像头的 GOP 不要设得过大,监控场景常见 2 到 4 秒一个关键帧。延迟还是大,就在 FFmpeg 侧加-fflags nobuffer -flags low_delay做对比,确认是采集端还是显示端堆积。

提示:不要用“加大缓冲区”解决延迟,缓冲区越大,堆积越隐蔽;监控预览要的是最新帧,不是完整帧序列。

5.3 多路同时打开就掉帧

现象:单路 1080p 很顺,开到第四路开始掉帧,第八路直接有几路黑屏;降低分辨率后明显好转。

原因:USB 控制器带宽共享,高分辨率未压缩格式把带宽吃满;多路同时打开时枚举和读帧竞争;CPU 软解扛不住。

解决:优先用 MJPG 降低 USB 带宽,或者把分辨率降到 720p;把摄像头分到不同控制器或不同 USB 口;打开时串行初始化,每路间隔 200 到 500 毫秒,不要八路同时VideoCapture。如果 CPU 高,先降分辨率,再考虑硬解或换 GStreamer 管道。

5.4 录制文件能播但时长不对

现象:录了 10 分钟,播放器显示 2 分钟;拖动进度条花屏;分段文件第一段正常,后面时间戳跳变。

原因:-c copy遇到时间戳不连续;输入流参数变化;分段时没有重置时间戳;帧率元数据和实际帧数不一致。

解决:分段录制加-reset_timestamps 1;时间戳异常时加-fflags +genpts重新生成;确认封装格式兼容输入编码,不兼容就去掉

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进