Forráskód Böngészése

refactor(video_lock): 重构视频锁机制为线程安全的读取函数

将原有的上下文管理器锁机制重构为直接提供线程安全的读取函数
增加异常处理防止线程意外退出
在PTZ和全景相机模块中更新调用方式
wenhongquan 4 napja
szülő
commit
7fc9a211bf

+ 21 - 11
dual_camera_system/panorama_camera.py

@@ -18,7 +18,7 @@ from dataclasses import dataclass
 
 from config import PANORAMA_CAMERA, DETECTION_CONFIG
 from dahua_sdk import DahuaSDK, PTZCommand
-from video_lock import ff_lock
+from video_lock import safe_read, safe_is_opened
 
 
 @dataclass
@@ -126,10 +126,22 @@ class PanoramaCamera:
             rtsp_url = self.config.get('rtsp_url') or f"rtsp://{self.config['username']}:{self.config['password']}@{self.config['ip']}:{self.config.get('rtsp_port', 554)}/h264/ch{self.config['channel']}/main/av_stream"
         
         try:
+            # 先尝试FFmpeg后端
             self.rtsp_cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG)
             if not self.rtsp_cap.isOpened():
-                print(f"无法打开RTSP流: {rtsp_url}")
-                return False
+                # FFmpeg失败,尝试GStreamer后端
+                print(f"FFmpeg后端无法打开RTSP流,尝试GStreamer后端...")
+                try:
+                    gst_cap = cv2.VideoCapture(rtsp_url, cv2.CAP_GSTREAMER)
+                    if gst_cap.isOpened():
+                        self.rtsp_cap = gst_cap
+                        print(f"使用GStreamer后端打开RTSP流成功")
+                    else:
+                        print(f"无法打开RTSP流: {rtsp_url}")
+                        return False
+                except Exception as ge:
+                    print(f"GStreamer后端也不可用: {ge}")
+                    return False
             
             self.rtsp_cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
             
@@ -161,9 +173,8 @@ class PanoramaCamera:
                         pass
                 
                 # RTSP 模式获取帧 (推荐方式)
-                if self.rtsp_cap is not None and self.rtsp_cap.isOpened():
-                    with ff_lock:
-                        ret, frame = self.rtsp_cap.read()
+                if self.rtsp_cap is not None and safe_is_opened(self.rtsp_cap):
+                    ret, frame = safe_read(self.rtsp_cap)
                     if ret and frame is not None:
                         with self.frame_lock:
                             self.current_frame = frame.copy()
@@ -185,7 +196,7 @@ class PanoramaCamera:
                             self.rtsp_cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG)
                             self.rtsp_cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)  # 减少缓冲延迟
                         
-                        if self.rtsp_cap.isOpened():
+                        if safe_is_opened(self.rtsp_cap):
                             retry_count = 0
                             continue
                     except Exception as e:
@@ -234,12 +245,11 @@ class PanoramaCamera:
         
         while self.running:
             try:
-                if self.rtsp_cap is None or not self.rtsp_cap.isOpened():
+                if self.rtsp_cap is None or not safe_is_opened(self.rtsp_cap):
                     time.sleep(0.1)
                     continue
                 
-                with ff_lock:
-                    ret, frame = self.rtsp_cap.read()
+                ret, frame = safe_read(self.rtsp_cap)
                 if not ret or frame is None:
                     error_count += 1
                     if error_count > max_consecutive_errors:
@@ -283,7 +293,7 @@ class PanoramaCamera:
         
         try:
             self.rtsp_cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG)
-            if self.rtsp_cap.isOpened():
+            if safe_is_opened(self.rtsp_cap):
                 self.rtsp_cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
                 print("全景RTSP流重连成功")
             else:

+ 4 - 5
dual_camera_system/ptz_camera.py

@@ -19,7 +19,7 @@ import numpy as np
 
 from config import PTZ_CAMERA, PTZ_CONFIG
 from dahua_sdk import DahuaSDK, PTZCommand
-from video_lock import ff_lock
+from video_lock import safe_read, safe_is_opened
 
 
 @dataclass
@@ -129,12 +129,11 @@ class PTZCamera:
         
         while self.running_stream:
             try:
-                if self.rtsp_cap is None or not self.rtsp_cap.isOpened():
+                if self.rtsp_cap is None or not safe_is_opened(self.rtsp_cap):
                     time.sleep(0.1)
                     continue
                 
-                with ff_lock:
-                    ret, frame = self.rtsp_cap.read()
+                ret, frame = safe_read(self.rtsp_cap)
                 if not ret or frame is None:
                     error_count += 1
                     if error_count > max_consecutive_errors:
@@ -175,7 +174,7 @@ class PTZCamera:
         
         try:
             self.rtsp_cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG)
-            if self.rtsp_cap.isOpened():
+            if safe_is_opened(self.rtsp_cap):
                 self.rtsp_cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
                 print("[PTZCamera] RTSP流重连成功")
             else:

+ 22 - 22
dual_camera_system/video_lock.py

@@ -13,25 +13,25 @@ import threading
 _ffmpeg_lock = threading.Lock()
 
 
-def acquire_ffmpeg_lock():
-    """获取FFmpeg全局锁"""
-    _ffmpeg_lock.acquire()
-
-
-def release_ffmpeg_lock():
-    """释放FFmpeg全局锁"""
-    _ffmpeg_lock.release()
-
-
-class FFLockContext:
-    """with语句用法: with ff_lock(): cap.read()"""
-    def __enter__(self):
-        _ffmpeg_lock.acquire()
-        return self
-
-    def __exit__(self, *args):
-        _ffmpeg_lock.release()
-
-
-# 便捷别名
-ff_lock = FFLockContext
+def safe_read(cap) -> tuple:
+    """
+    线程安全的VideoCapture.read()
+    持有全局锁调用cap.read(),防止FFmpeg并发崩溃。
+    如果发生异常,返回(False, None)而非让异常传播导致线程退出。
+    """
+    with _ffmpeg_lock:
+        try:
+            return cap.read()
+        except Exception:
+            return (False, None)
+
+
+def safe_is_opened(cap) -> bool:
+    """
+    线程安全的VideoCapture.isOpened()检查
+    """
+    with _ffmpeg_lock:
+        try:
+            return cap.isOpened()
+        except Exception:
+            return False