|
|
|
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()
|