2026.7.1小更新

This commit is contained in:
2026-07-01 09:18:10 +08:00
parent 2378972214
commit b66b96ad9b
+108 -33
View File
@@ -64,11 +64,10 @@ class VideoStreaming:
""" 后台不断读取帧,存入 frame_buffer """
while self.is_running:
# 如果摄像头未打开或已断开,尝试连接
# _open_camera() 内部自带带退避的无限重试,阻塞直到成功或被停止
if self.cap is None or not self.cap.isOpened():
self._open_camera()
if self.cap is None or not self.cap.isOpened():
time.sleep(1) # 等待重连
continue
continue # 回到 while 头部检查 is_running 并读取帧
ret, frame = self.cap.read()
if not ret:
@@ -93,40 +92,116 @@ class VideoStreaming:
if self.cap is not None:
self.cap.release()
@staticmethod
def _interruptible_sleep(duration, interval=1.0):
"""
分段睡眠,每 interval 秒检查一次线程退出条件。
替代 time.sleep(duration),确保能秒级响应停止信号。
Args:
duration: 总睡眠时间(秒)
interval: 每段睡眠时长(秒),默认 1 秒
"""
end_time = time.time() + duration
while time.time() < end_time:
remaining = min(interval, end_time - time.time())
if remaining <= 0:
break
time.sleep(remaining)
def _open_camera(self):
""" 打开摄像头或 RTSP 流,并设置低延迟参数 """
try:
self.logger.info("正在连接视频源: %s", self.video_source)
os.environ['OPENCV_FFMPEG_CAPTURE_OPTIONS'] = 'rtsp_transport;tcp'
self.cap = cv2.VideoCapture(self.video_source, cv2.CAP_FFMPEG)
"""
打开摄像头或 RTSP 流,带自动退避重试机制
if self.is_rtsp:
# 设置 RTSP 缓冲区大小(只保留 1 帧,降低延迟)
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
# 设置读取超时(OpenCV 没有直接超时参数,但可尝试设置后端属性)
# 某些平台支持以下设置:
self.cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('H', '2', '6', '4'))
# 强制使用 tcp 协议(某些 rtsp 默认 udp 会丢包严重)
# 如果 rtsp 地址支持,可以改成 "rtsp://...?tcp"
if "?" not in self.video_source:
self.video_source += "?tcp"
# 注意:有些相机需要重新初始化,这里简单打印提示
self.logger.info("建议使用 TCP 传输: 在 RTSP URL 后添加 ?tcp")
else:
# USB 摄像头也可设置缓冲区
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
【重试策略】
1. 首轮正常尝试(INFO 日志,不打印 Warn)
2. 每轮最多 5 次重试,每次间隔 1 秒,释放旧资源后再试
3. 5 次全部失败 → 进入 30 秒冷却期(分段 sleep,可秒级响应停止信号)
4. 无限外层 while self.is_running 循环
→ 硬件恢复后自动重连,手动停止时立即退出
# 可选:强制指定分辨率(根据你的相机调整)
# self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
# self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
【日志规范】
- 首次成功打开 → INFO
- 每轮重试开始 → WARN(标明轮次)
- 每次重试失败 → WARN(标明第几次/共5次)
- 进入 30 秒冷却期 → ERROR
- 重连成功后 → INFO(带 ✅ 标记)
"""
RETRIES_PER_ROUND = 5
RETRY_INTERVAL = 1.0 # 小循环重试间隔(秒)
COOLDOWN = 30.0 # 大循环冷却期(秒)
if self.cap.isOpened():
self.logger.info("视频源连接成功: %s", self.video_source)
else:
self.logger.error("视频源打开失败: %s", self.video_source)
except Exception as e:
self.logger.error("打开视频源异常: %s", e)
self.cap = None
first_attempt = True # 首轮特殊处理:不打印 Warn
round_num = 0
while self.is_running:
round_num += 1
if not first_attempt:
self.logger.warning("摄像头重连第 %d 轮开始(共 %d 次尝试)...",
round_num, RETRIES_PER_ROUND)
for attempt in range(1, RETRIES_PER_ROUND + 1):
if not self.is_running:
return
# ---- 每次重试前释放旧资源,防止描述符泄漏 ----
if self.cap is not None:
try:
self.cap.release()
except Exception:
pass
self.cap = None
try:
self.logger.info("正在连接视频源: %s (尝试 %d/%d)",
self.video_source, attempt, RETRIES_PER_ROUND)
os.environ['OPENCV_FFMPEG_CAPTURE_OPTIONS'] = 'rtsp_transport;tcp'
self.cap = cv2.VideoCapture(self.video_source, cv2.CAP_FFMPEG)
if self.is_rtsp:
# 设置 RTSP 缓冲区大小(只保留 1 帧,降低延迟)
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
self.cap.set(cv2.CAP_PROP_FOURCC,
cv2.VideoWriter_fourcc('H', '2', '6', '4'))
if "?" not in self.video_source:
self.video_source += "?tcp"
else:
# USB 摄像头也可设置缓冲区
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
if self.cap.isOpened():
if first_attempt:
self.logger.info("视频源连接成功: %s", self.video_source)
else:
self.logger.info("✅ 摄像头重连成功: %s", self.video_source)
return # 成功!退出 _open_camera
else:
self.cap.release()
self.cap = None
self.logger.warning("摄像头打开失败 (尝试 %d/%d)",
attempt, RETRIES_PER_ROUND)
except Exception as e:
if self.cap is not None:
try:
self.cap.release()
except Exception:
pass
self.cap = None
self.logger.warning("摄像头打开异常: %s (尝试 %d/%d)",
e, attempt, RETRIES_PER_ROUND)
# 小循环内重试间隔(分段 sleep,可响应停止信号)
if attempt < RETRIES_PER_ROUND and self.is_running:
self._interruptible_sleep(RETRY_INTERVAL)
first_attempt = False
# ---- 5 次全部失败 → 进入冷却期 ----
if self.is_running:
self.logger.error("摄像头打开失败,进入 %.0f 秒冷却期...", COOLDOWN)
self._interruptible_sleep(COOLDOWN)
def read_frame(self):
""" 主循环调用:从缓冲区获取最新一帧(非阻塞) """