You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
9.8 KiB
267 lines
9.8 KiB
import cv2
|
|
import asyncio
|
|
import time
|
|
from . import api
|
|
from quart import jsonify, request,websocket
|
|
from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack,RTCConfiguration
|
|
from core.ModelManager import mMM
|
|
from core.DBManager import mDBM
|
|
from myutils.ConfigManager import myCongif
|
|
from fractions import Fraction
|
|
import threading
|
|
import logging
|
|
from quart.helpers import Response
|
|
|
|
# 配置日志
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
#-------------------基于WEBRTC实现拉流
|
|
#pcs = set() #创建一个空的集合,去重复且无序
|
|
pcs = {}
|
|
|
|
'''
|
|
--------------基础信息--后续封装在函数里---------------
|
|
'''
|
|
# 打开摄像头
|
|
# def get_camera():
|
|
# cap = cv2.VideoCapture(0)
|
|
# if not cap.isOpened():
|
|
# raise IOError("Cannot open webcam")
|
|
# return cap
|
|
#
|
|
# cap = get_camera()
|
|
# last_image = None
|
|
# read_lock = threading.Lock()
|
|
# def camera_thread():
|
|
# global cap, last_image
|
|
# while True:
|
|
# try:
|
|
# ret, frame = cap.read()
|
|
# if not ret:
|
|
# logger.warning("Camera disconnected. Reconnecting...")
|
|
# cap.release()
|
|
# cap = get_camera()
|
|
# continue
|
|
# frame = cv2.resize(frame, (640, 480)) # 降低视频分辨率
|
|
# with read_lock:
|
|
# last_image = frame
|
|
# time.sleep(1.0/20)
|
|
# except Exception as e:
|
|
# logger.error(f"Error in camera thread: {e}")
|
|
# continue
|
|
|
|
#threading.Thread(target=camera_thread, daemon=True).start()
|
|
'''
|
|
---------------------传输--------------------------
|
|
'''
|
|
# class VideoTransformTrack(VideoStreamTrack):
|
|
# kind = "video"
|
|
# def __init__(self,cid,itype=1): #0-usb,1-RTSP,2-海康SDK --itype已没有作用
|
|
# super().__init__()
|
|
# self.channel_id = cid
|
|
#
|
|
# self.frame_rate = myCongif.get_data("frame_rate")
|
|
# self.time_base = Fraction(1, self.frame_rate)
|
|
# self.frame_count = 0
|
|
# self.start_time = time.time()
|
|
#
|
|
# async def recv(self):
|
|
# new_frame = None
|
|
# time.sleep(1.0 / self.frame_rate)
|
|
# new_frame = mMM.verify_list[self.channel_id][4]
|
|
# # while True:
|
|
# # new_frame = mMM.verify_list[self.channel_id][4]
|
|
# # #new_frame = av.VideoFrame.from_ndarray(img, format="rgb24")
|
|
# # if new_frame is not None:
|
|
# # break
|
|
# # else:
|
|
# # time.sleep(1.0 / self.frame_rate)
|
|
# # 设置时间戳和时间基数 -- 根据耗时实时计算帧率
|
|
# # elapsed_time = time.time() - self.start_time
|
|
# # self.frame_count += 1
|
|
# # actual_frame_rate = self.frame_count / elapsed_time
|
|
# # self.time_base = Fraction(1, int(actual_frame_rate))
|
|
# #设置时间戳和帧率
|
|
# self.frame_count += 1
|
|
# if self.frame_count > myCongif.get_data("RESET_INTERVAL"):
|
|
# self.frame_count = 0
|
|
# new_frame.pts = self.frame_count
|
|
# new_frame.time_base = self.time_base
|
|
# #print(f"{self.channel_id} -- Frame pts: {new_frame.pts}, time_base: {new_frame.time_base}")
|
|
#
|
|
# # 将检测结果图像转换为帧
|
|
# # new_frame.pts = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
|
|
# # new_frame.time_base = self.time_base
|
|
# # end_time = time.time() # 结束时间
|
|
# # print(f"Processing time: {end_time - start_time} seconds")
|
|
# #print("webRTC recv at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
|
|
# return new_frame
|
|
|
|
async def get_stats(peer_connection):
|
|
stats = await peer_connection.getStats()
|
|
for report in stats.values():
|
|
if report.type == 'outbound-rtp':
|
|
print(f"RTT: {report.roundTripTime} seconds")
|
|
print(f"Packets Sent: {report.packetsSent}")
|
|
print(f"Bytes Sent: {report.bytesSent}")
|
|
|
|
# @api.route('/offer', methods=['POST'])
|
|
# async def offer():
|
|
# #接收客户端的连接请求
|
|
# params = await request.json
|
|
# channel_id = params["cid"] #需要传参过来
|
|
# itype = params["mytype"]
|
|
# element_id = params["element_id"]
|
|
# reStatus = 0
|
|
# reMsg = ""
|
|
# ret = False
|
|
# if itype == 1:
|
|
# #添加通道和页面控件ID的关系
|
|
# strsql = f"select * from channel where element_id = {element_id}"
|
|
# data = mDBM.do_select(strsql,1)
|
|
# if data:
|
|
# reStatus = 0
|
|
# reMsg = '该控件已经关联某一视频通道,请确认数据是否正确!'
|
|
# return jsonify({'status': reStatus, 'msg': reMsg})
|
|
# strsql = f"update channel set element_id = {element_id} where ID = {channel_id};"
|
|
# ret = mDBM.do_sql(strsql)
|
|
# if ret == True:
|
|
# reStatus = 1
|
|
# reMsg = '播放画面成功,请稍等!'
|
|
# else:
|
|
# reStatus = 0
|
|
# reMsg = '播放画面失败,请联系技术支持!'
|
|
# #提取客户端发来的SDP,生成服务器端的SDP
|
|
# offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
|
|
# # # 配置 STUN 服务器
|
|
# #ice_servers = [{"urls": []}]# 禁用 STUN 服务器
|
|
# # pc = RTCPeerConnection(configuration={"iceServers": ice_servers})
|
|
# config = RTCConfiguration(iceServers=[])
|
|
# pc = RTCPeerConnection(configuration=config)
|
|
# # t = threading.Thread(target=get_stats,args=(pc,))
|
|
# # t.start()
|
|
#
|
|
# #pc = RTCPeerConnection() # 实例化一个rtcp对象
|
|
# pcs[channel_id] = pc # 集合中添加一个对象,若已存在则不添
|
|
#
|
|
# @pc.on("datachannel")
|
|
# def on_datachannel(channel):
|
|
# @channel.on("message")
|
|
# def on_message(message):
|
|
# print(f'Message received: {message}')
|
|
# # 处理接收到的数据
|
|
# if message == 'pause':
|
|
# # 执行暂停操作
|
|
# pass
|
|
# elif message == 'resume':
|
|
# # 执行继续操作
|
|
# pass
|
|
#
|
|
# #监听RTC连接状态
|
|
# @pc.on("iconnectionstatechange") #当ice连接状态发生变化时
|
|
# async def iconnectionstatechange():
|
|
# if pc.iceConnectionState == "failed":
|
|
# await pc.close()
|
|
# pcs.pop(channel_id, None) #移除对象
|
|
#
|
|
# # 添加视频轨道
|
|
# video_track = VideoTransformTrack(channel_id)
|
|
# pc.addTrack(video_track)
|
|
#
|
|
# # @pc.on('track') --- stream.getTracks().forEach(track => pc.addTrack(track, stream)); 猜测是这里触发的添加了摄像头通道
|
|
# # def on_track(track):
|
|
# # if track.kind == 'video':
|
|
# # local_video = VideoTransformTrack(track)
|
|
# # pc.addTrack(local_video)
|
|
#
|
|
# # 记录客户端 SDP
|
|
# await pc.setRemoteDescription(offer)
|
|
# # 生成本地 SDP
|
|
# answer = await pc.createAnswer()
|
|
# # 记录本地 SDP
|
|
# await pc.setLocalDescription(answer)
|
|
#
|
|
# print("返回sdp")
|
|
# return jsonify({
|
|
# "sdp": pc.localDescription.sdp,
|
|
# "type": pc.localDescription.type,
|
|
# "status":reStatus, #itype =1 的时候才有意义
|
|
# "msg":reMsg
|
|
# })
|
|
|
|
async def generate_frames(stream_id):
|
|
#video_capture = streams[stream_id]
|
|
start_time = time.time()
|
|
icount = 0
|
|
frame_rate_time = 1.0 / myCongif.get_data("frame_rate")
|
|
while True:
|
|
try:
|
|
await asyncio.sleep(frame_rate_time)
|
|
frame = mMM.verify_list[int(stream_id)][4]
|
|
if frame is not None:
|
|
img = frame.to_ndarray(format="bgr24")
|
|
ret, buffer = cv2.imencode('.jpg', img)
|
|
if not ret:
|
|
continue
|
|
frame = buffer.tobytes()
|
|
yield (b'--frame\r\n'
|
|
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
|
|
end_time = time.time() # 结束时间
|
|
icount += 1
|
|
print(f"icount:{icount},Processing time: {end_time - start_time} seconds")
|
|
except asyncio.CancelledError:
|
|
print("Task was cancelled")
|
|
break # 处理任务取消
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
break # 在出现异常时退出循环
|
|
print("退出循环")
|
|
|
|
@api.route('/video_feed/<stream_id>')
|
|
async def video_feed(stream_id):
|
|
if int(stream_id) != 2:
|
|
return "None"
|
|
print("执行一次:video_feed")
|
|
return Response(generate_frames(stream_id),mimetype='multipart/x-mixed-replace; boundary=frame')
|
|
|
|
@api.websocket('/ws/video_feed/<int:channel_id>')
|
|
async def ws_video_feed(channel_id):
|
|
while True:
|
|
frame = mMM.verify_list[int(channel_id)][4]
|
|
if frame is not None:
|
|
img = frame.to_ndarray(format="bgr24")
|
|
ret, buffer = cv2.imencode('.jpg', img)
|
|
if not ret:
|
|
continue
|
|
frame = buffer.tobytes()
|
|
await websocket.send(frame)
|
|
await asyncio.sleep(1.0 / myCongif.get_data("frame_rate")) # Adjust based on frame rate
|
|
|
|
@api.route('/shutdown', methods=['POST'])
|
|
async def shutdown():#这是全关
|
|
coros = [pc.close() for pc in pcs]
|
|
await asyncio.gather(*coros)
|
|
pcs.clear()
|
|
return 'Server shutting down...'
|
|
|
|
@api.route('/close_stream', methods=['POST'])
|
|
async def close_stream():
|
|
channel_id = (await request.form)['channel_id']
|
|
reStatus = 0
|
|
reMsg =""
|
|
if channel_id in pcs:
|
|
await pcs[channel_id].close()
|
|
pcs.pop(channel_id, None)
|
|
print(f'Stream {channel_id} closed.')
|
|
#数据库中该通道的关联关系要清除
|
|
strsql = f"update channel set element_id = NULL where ID={channel_id};"
|
|
ret = mDBM.do_sql(strsql)
|
|
if ret == True:
|
|
reStatus = 1
|
|
reMsg = '关闭画面成功!'
|
|
else:
|
|
reStatus = 0
|
|
reMsg = '删除通道与组件关联关系失败,请联系技术支持!'
|
|
else:
|
|
reMsg = "通道编号不在系统内,请检查!"
|
|
return jsonify({'status': reStatus, 'msg': reMsg})
|
|
|