import math import cv2 import threading import time from myutils.ConfigManager import myCongif from myutils.MyDeque import MyDeque from myutils.MyLogger_logger import LogHandler class CapManager: def __init__(self): self.logger = LogHandler().get_logger("CapManager") self.mycap_map = {} #source,VideoCaptureWithFPS self.lock = threading.Lock() def __del__(self): pass def start_get_video(self,source,type=1): vcf = None with self.lock: if source in self.mycap_map: vcf = self.mycap_map[source] vcf.addcount() else: vcf = VideoCaptureWithFPS(source,type) self.mycap_map[source] = vcf return vcf def stop_get_video(self,source): with self.lock: if source in self.mycap_map: vcf = self.mycap_map[source] vcf.delcount() if vcf.icount == 0: del self.mycap_map[source] else: self.logger.error("数据存在问题!") mCap = CapManager() class VideoCaptureWithFPS: '''视频捕获的封装类,是一个通道一个 打开摄像头 0--USB摄像头,1-RTSP,2-海康SDK ''' def __init__(self, source,type=1): self.source = self.ensure_udp_transport(source) self.width = None self.height = None self.bok = False self.icount = 1 #引用次数 # GStreamer --- 内存占用太高,且工作环境的部署也不简单 # self.pipeline = ( # "rtspsrc location=rtsp://192.168.3.102/live1 protocols=udp latency=100 ! " # "rtph264depay !" # " h264parse !" # " avdec_h264 !" # " videoconvert !" # " appsink" # ) #self.cap = cv2.VideoCapture(self.pipeline, cv2.CAP_GSTREAMER) # opencv -- 后端默认使用的就是FFmpeg -- 不支持UDP self.running = True #self.frame_queue = queue.Queue(maxsize=1) self.frame_queue = MyDeque(5) self.frame = None self.read_lock = threading.Lock() self.thread = threading.Thread(target=self.update) self.thread.start() def addcount(self): self.icount += 1 def delcount(self): self.icount -= 1 if self.icount ==0: #结束线程 self.release() def openViedo_opencv(self,source): self.cap = cv2.VideoCapture(source,cv2.CAP_FFMPEG) # self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 3) if self.cap.isOpened(): # 若没有打开成功,在读取画面的时候,已有判断和处理 -- 这里也要检查下内存的释放情况 self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # self.fps = fps # 线程保持最大帧率的刷新画面---过高的帧率会影响CPU性能,但过地的帧率会造成帧积压 self.fps = math.ceil( self.cap.get(cv2.CAP_PROP_FPS) / float(myCongif.get_data("verify_rate"))) - 1 # 向上取整。 #print(self.width, self.height, self.fps) else: raise ValueError("无法打开视频源") def ensure_udp_transport(self,source): #使用udp拉流时使用 # 检查 source 是否已经包含 '?transport=udp' # if not source.endswith("?transport=udp"): # # 如果没有,则添加 '?transport=udp' # if "?" in source: # # 如果已有其他查询参数,用 '&' 拼接 # source += "&transport=udp" # else: # # 否则直接添加 '?transport=udp' # source += "?transport=udp" return source def update(self): sleep_time = myCongif.get_data("cap_sleep_time") reconnect_attempts = myCongif.get_data("reconnect_attempts") frame_interval = 1 / (myCongif.get_data("verify_rate")+1) last_frame_time = time.time() while self.running: try: # ffmpeg_process = subprocess.Popen( # ['ffmpeg', '-i', self.source, '-f', 'image2pipe', '-pix_fmt', 'bgr24', '-vcodec', 'rawvideo', '-'], # stdout=subprocess.PIPE, stderr=subprocess.PIPE # ) #读取帧后,对帧数据进行处理 # frame = np.frombuffer(raw_frame, np.uint8).reshape((480, 640, 3)) # frame = np.copy(frame) # 创建一个可写的副本 self.openViedo_opencv(self.source) if not self.cap.isOpened(): raise RuntimeError("视频源打开失败") failure_count = 0 self.bok = True while self.running: #subprocess-udp 拉流 #raw_frame = ffmpeg_process.stdout.read(640 * 480 * 3) if self.cap.grab(): current_time = time.time() if current_time - last_frame_time > frame_interval: last_frame_time = current_time ret, frame = self.cap.retrieve() if ret: # resized_frame = cv2.resize(frame, (int(self.width / 2), int(self.height / 2))) #self.frame_queue.myappend(frame) with self.read_lock: self.frame = frame failure_count = 0 # 重置计数 else: failure_count += 1 time.sleep(0.1) # 休眠一段时间后重试 if failure_count >= reconnect_attempts: with self.read_lock: self.frame = None raise RuntimeError("无法读取视频帧") continue #正常结束,关闭进程,释放资源 #ffmpeg_process.terminate() self.cap.release() self.bok = False except Exception as e: print(f"发生异常:{e}") #ffmpeg_process.terminate() self.cap.release() self.bok = False print(f"{self.source}视频流,将于{sleep_time}秒后重连!") # 分段休眠,检测 self.running total_sleep = 0 while total_sleep < sleep_time: if not self.running: return time.sleep(2) total_sleep += 2 def read(self): with self.read_lock: frame = self.frame.copy() if self.frame is not None else None if frame is not None: return True, frame else: return False, None # if not self.frame_queue.empty(): # try: # frame = self.frame_queue.get(timeout=0.05) # except queue.Empty: # #print("cap-frame None") # return False, None # else: # #print("cap-frame None") # return False, None # ret = False # frame = None # if self.bok: #连接状态再读取 # frame = self.frame_queue.mypopleft() # if frame is not None: # ret = True # else: # print("____读取cap帧为空,采集速度过慢___") # return ret, frame def release(self): self.running = False self.thread.join() self.cap.release()