|
|
@ -2,7 +2,7 @@ import cv2 |
|
|
|
import asyncio |
|
|
|
import time |
|
|
|
from . import api |
|
|
|
from quart import jsonify, request |
|
|
|
from quart import jsonify, request,websocket |
|
|
|
from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack,RTCConfiguration |
|
|
|
from core.ModelManager import mMM |
|
|
|
from core.DBManager import mDBM |
|
|
@ -10,6 +10,7 @@ from myutils.ConfigManager import myCongif |
|
|
|
from fractions import Fraction |
|
|
|
import threading |
|
|
|
import logging |
|
|
|
from quart.helpers import Response |
|
|
|
|
|
|
|
# 配置日志 |
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
@ -53,48 +54,48 @@ pcs = {} |
|
|
|
''' |
|
|
|
---------------------传输-------------------------- |
|
|
|
''' |
|
|
|
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 |
|
|
|
# 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() |
|
|
@ -104,89 +105,137 @@ async def get_stats(peer_connection): |
|
|
|
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) |
|
|
|
# @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 |
|
|
|
# }) |
|
|
|
|
|
|
|
# @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) |
|
|
|
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("退出循环") |
|
|
|
|
|
|
|
# 记录客户端 SDP |
|
|
|
await pc.setRemoteDescription(offer) |
|
|
|
# 生成本地 SDP |
|
|
|
answer = await pc.createAnswer() |
|
|
|
# 记录本地 SDP |
|
|
|
await pc.setLocalDescription(answer) |
|
|
|
@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') |
|
|
|
|
|
|
|
print("返回sdp") |
|
|
|
return jsonify({ |
|
|
|
"sdp": pc.localDescription.sdp, |
|
|
|
"type": pc.localDescription.type, |
|
|
|
"status":reStatus, #itype =1 的时候才有意义 |
|
|
|
"msg":reMsg |
|
|
|
}) |
|
|
|
@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():#这是全关 |
|
|
|