Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

1000 rindas
43KB

  1. # -*- coding: utf-8 -*-
  2. from json import loads
  3. from time import time
  4. from traceback import format_exc
  5. import cv2
  6. import subprocess as sp
  7. import numpy as np
  8. from loguru import logger
  9. from common import Constant
  10. from exception.CustomerException import ServiceException
  11. from enums.ExceptionEnum import ExceptionType
  12. class Cv2Util:
  13. __slots__ = [
  14. 'pullUrl',
  15. 'pushUrl',
  16. 'orFilePath',
  17. 'aiFilePath',
  18. 'p',
  19. 'or_video_file',
  20. 'ai_video_file',
  21. 'fps',
  22. 'width',
  23. 'height',
  24. 'wh',
  25. 'h',
  26. 'w',
  27. 'all_frames',
  28. 'bit_rate',
  29. 'pull_p',
  30. 'requestId',
  31. 'p_push_retry_num',
  32. 'isGpu',
  33. 'read_w_h',
  34. 'context',
  35. 'p_push_time'
  36. ]
  37. def __init__(self, pullUrl=None, pushUrl=None, orFilePath=None, aiFilePath=None, requestId=None, context=None,
  38. gpu_ids=None):
  39. self.pullUrl = pullUrl
  40. self.pushUrl = pushUrl
  41. self.orFilePath = orFilePath
  42. self.aiFilePath = aiFilePath
  43. # self.cap = None
  44. self.p = None
  45. self.or_video_file = None
  46. self.ai_video_file = None
  47. self.fps = None
  48. self.width = None
  49. self.height = None
  50. self.wh = None
  51. self.h = None
  52. self.w = None
  53. self.all_frames = None
  54. self.bit_rate = None
  55. self.pull_p = None
  56. self.requestId = requestId
  57. self.p_push_time = 0
  58. self.p_push_retry_num = 0
  59. self.isGpu = False
  60. self.read_w_h = None
  61. self.context = context
  62. if gpu_ids is not None and len(gpu_ids) > 0:
  63. self.isGpu = True
  64. def getFrameConfig(self, fps, width, height):
  65. if self.fps is None or self.width != width or self.height != height:
  66. self.fps = fps
  67. self.width = width
  68. self.height = height
  69. if width > Constant.width:
  70. self.h = int(self.height // 2)
  71. self.w = int(self.width // 2)
  72. else:
  73. self.h = int(self.height)
  74. self.w = int(self.width)
  75. def clear_video_info(self):
  76. self.fps = None
  77. self.width = None
  78. self.height = None
  79. '''
  80. 获取视频信息
  81. '''
  82. def get_video_info(self):
  83. try:
  84. if self.pullUrl is None or len(self.pullUrl) == 0:
  85. logger.error("拉流地址不能为空, requestId:{}", self.requestId)
  86. raise ServiceException(ExceptionType.PULL_STREAM_URL_EXCEPTION.value[0],
  87. ExceptionType.PULL_STREAM_URL_EXCEPTION.value[1])
  88. args = ['ffprobe', '-show_format', '-show_streams', '-of', 'json', self.pullUrl]
  89. p = sp.Popen(args, stdout=sp.PIPE, stderr=sp.PIPE)
  90. out, err = p.communicate(timeout=20)
  91. if p.returncode != 0:
  92. raise Exception("未获取视频信息!!!!!requestId:" + self.requestId)
  93. probe = loads(out.decode('utf-8'))
  94. if probe is None or probe.get("streams") is None:
  95. raise Exception("未获取视频信息!!!!!requestId:" + self.requestId)
  96. # 视频大小
  97. # format = probe['format']
  98. # size = int(format['size'])/1024/1024
  99. video_stream = next((stream for stream in probe['streams'] if stream.get('codec_type') == 'video'), None)
  100. if video_stream is None:
  101. raise Exception("未获取视频信息!!!!!requestId:" + self.requestId)
  102. width = video_stream.get('width')
  103. height = video_stream.get('height')
  104. nb_frames = video_stream.get('nb_frames')
  105. fps = video_stream.get('r_frame_rate')
  106. # duration = video_stream.get('duration')
  107. # bit_rate = video_stream.get('bit_rate')
  108. if width is not None and int(width) != 0 and height is not None and int(height) != 0:
  109. self.width = int(width)
  110. self.height = int(height)
  111. self.wh = self.width * self.height * 3
  112. if width > Constant.width:
  113. self.h = int(self.height // 2)
  114. self.w = int(self.width // 2)
  115. else:
  116. self.h = int(self.height)
  117. self.w = int(self.width)
  118. if nb_frames:
  119. self.all_frames = int(nb_frames)
  120. up, down = str(fps).split('/')
  121. self.fps = int(eval(up) / eval(down))
  122. if self.fps > 30:
  123. logger.info("获取视频FPS大于30帧, FPS:{}, requestId:{}", self.fps, self.requestId)
  124. self.fps = 30
  125. if self.fps < 25:
  126. logger.info("获取视频FPS小于25帧, FPS:{}, requestId:{}", self.fps, self.requestId)
  127. self.fps = 25
  128. # if duration:
  129. # self.duration = float(video_stream['duration'])
  130. # self.bit_rate = int(bit_rate) / 1000
  131. logger.info("视频信息, width:{}|height:{}|fps:{}|all_frames:{}, requestId:{}",
  132. self.width, self.height, self.fps, self.all_frames, self.requestId)
  133. except ServiceException as s:
  134. logger.error("获取视频信息异常: {}, requestId:{}", s.msg, self.requestId)
  135. self.clear_video_info()
  136. raise s
  137. except Exception:
  138. logger.error("获取视频信息异常:{}, requestId:{}", format_exc(), self.requestId)
  139. self.clear_video_info()
  140. '''
  141. 录屏任务获取视频信息
  142. '''
  143. def get_recording_video_info(self):
  144. try:
  145. video_info = 'ffprobe -show_format -show_streams -of json %s' % self.pullUrl
  146. p = sp.Popen(video_info, stdout=sp.PIPE, stderr=sp.PIPE, shell=True)
  147. out, err = p.communicate(timeout=17)
  148. if p.returncode != 0:
  149. raise Exception("未获取视频信息!!!!!requestId:" + self.requestId)
  150. probe = loads(out.decode('utf-8'))
  151. if probe is None or probe.get("streams") is None:
  152. raise Exception("未获取视频信息!!!!!requestId:" + self.requestId)
  153. video_stream = next((stream for stream in probe['streams'] if stream.get('codec_type') == 'video'), None)
  154. if video_stream is None:
  155. raise Exception("未获取视频信息!!!!!requestId:" + self.requestId)
  156. width = video_stream.get('width')
  157. height = video_stream.get('height')
  158. nb_frames = video_stream.get('nb_frames')
  159. fps = video_stream.get('r_frame_rate')
  160. if width and int(width) > 0:
  161. self.width = int(width)
  162. if height and int(height) > 0:
  163. self.height = int(height)
  164. if self.width and self.height:
  165. self.wh = int(width * height * 3)
  166. self.read_w_h = ([self.height, self.width, 3])
  167. if nb_frames and int(nb_frames) > 0:
  168. self.all_frames = int(nb_frames)
  169. if fps:
  170. up, down = str(fps).split('/')
  171. self.fps = int(eval(up) / eval(down))
  172. logger.info("视频信息, width:{}|height:{}|fps:{}|all_frames:{}, requestId:{}", self.width,
  173. self.height, self.fps, self.all_frames, self.requestId)
  174. except ServiceException as s:
  175. logger.error("获取视频信息异常: {}, requestId:{}", s.msg, self.requestId)
  176. self.clear_video_info()
  177. raise s
  178. except Exception:
  179. logger.error("获取视频信息异常:{}, requestId:{}", format_exc(), self.requestId)
  180. self.clear_video_info()
  181. def getRecordingFrameConfig(self, fps, width, height):
  182. self.fps = fps
  183. self.width = width
  184. self.height = height
  185. '''
  186. 录屏拉取视频
  187. '''
  188. def recording_pull_p(self):
  189. try:
  190. # 如果视频信息不存在, 不初始化拉流
  191. if self.checkconfig():
  192. return
  193. # 如果已经初始化, 不再初始化
  194. if self.pull_p:
  195. return
  196. command = ['ffmpeg -re', '-y', '-an'
  197. # '-hide_banner',
  198. ]
  199. if self.pullUrl.startswith('rtsp://'):
  200. command.extend(['-rtsp_transport', 'tcp'])
  201. if self.isGpu:
  202. command.extend(['-hwaccel', 'cuda'])
  203. command.extend(['-i', self.pullUrl,
  204. '-f', 'rawvideo',
  205. '-pix_fmt', 'bgr24',
  206. '-r', '25',
  207. '-'])
  208. self.pull_p = sp.Popen(command, stdout=sp.PIPE)
  209. except ServiceException as s:
  210. logger.exception("构建拉流管道异常: {}, requestId:{}", s.msg, self.requestId)
  211. self.clear_video_info()
  212. if self.pull_p:
  213. logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
  214. self.pull_p.stdout.close()
  215. self.pull_p.terminate()
  216. self.pull_p.wait()
  217. self.pull_p = None
  218. raise s
  219. except Exception as e:
  220. logger.error("构建拉流管道异常:{}, requestId:{}", e, self.requestId)
  221. self.clear_video_info()
  222. if self.pull_p:
  223. logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
  224. self.pull_p.stdout.close()
  225. self.pull_p.terminate()
  226. self.pull_p.wait()
  227. self.pull_p = None
  228. def recording_read(self):
  229. result = None
  230. try:
  231. self.recording_pull_p()
  232. in_bytes = self.pull_p.stdout.read(self.wh)
  233. if in_bytes is not None and len(in_bytes) > 0:
  234. try:
  235. result = np.frombuffer(in_bytes, np.uint8).reshape(self.read_w_h)
  236. except Exception:
  237. logger.error("视频格式异常:{}, requestId:{}", format_exc(), self.requestId)
  238. raise ServiceException(ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[0],
  239. ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[1])
  240. except ServiceException as s:
  241. if self.pull_p:
  242. logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
  243. self.pull_p.stdout.close()
  244. self.pull_p.terminate()
  245. self.pull_p.wait()
  246. self.pull_p = None
  247. raise s
  248. except Exception:
  249. logger.error("读流异常:{}, requestId:{}", format_exc(), self.requestId)
  250. if self.pull_p:
  251. logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
  252. self.pull_p.stdout.close()
  253. self.pull_p.terminate()
  254. self.pull_p.wait()
  255. self.pull_p = None
  256. return result
  257. '''
  258. 拉取视频
  259. '''
  260. def build_pull_p(self):
  261. try:
  262. command = ['ffmpeg']
  263. if self.pullUrl.startswith("rtsp://"):
  264. command.extend(['-rtsp_transport', 'tcp'])
  265. command.extend(['-re',
  266. '-y',
  267. '-an',
  268. # '-hwaccel', 'cuda', cuvid
  269. '-c:v', 'h264_cuvid',
  270. # '-resize', self.wah,
  271. '-i', self.pullUrl,
  272. '-f', 'rawvideo',
  273. '-pix_fmt', 'bgr24',
  274. '-r', '25',
  275. '-'])
  276. self.pull_p = sp.Popen(command, stdout=sp.PIPE)
  277. except ServiceException as s:
  278. logger.exception("构建拉流管道异常: {}, requestId:{}", s.msg, self.requestId)
  279. raise s
  280. except Exception as e:
  281. logger.error("构建拉流管道异常:{}, requestId:{}", format_exc(), self.requestId)
  282. self.clear_video_info()
  283. if self.pull_p:
  284. logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
  285. self.pull_p.stdout.close()
  286. self.pull_p.terminate()
  287. self.pull_p.wait()
  288. self.pull_p = None
  289. def checkconfig(self):
  290. if self.width is None or self.height is None or self.fps is None:
  291. return True
  292. return False
  293. def read(self):
  294. result = None
  295. try:
  296. if self.pull_p is None:
  297. self.build_pull_p()
  298. in_bytes = self.pull_p.stdout.read(self.wh)
  299. if in_bytes is not None and len(in_bytes) > 0:
  300. try:
  301. result = (np.frombuffer(in_bytes, np.uint8).reshape([self.height, self.width, 3]))
  302. # img = (np.frombuffer(in_bytes, np.uint8)).reshape((self.h, self.w))
  303. except Exception as ei:
  304. logger.error("视频格式异常:{}, requestId:{}", format_exc(), self.requestId)
  305. raise ServiceException(ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[0],
  306. ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[1])
  307. # result = cv2.cvtColor(img, cv2.COLOR_YUV2BGR_NV12)
  308. # result = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
  309. if self.width > Constant.width:
  310. result = cv2.resize(result, (self.w, self.h), interpolation=cv2.INTER_LINEAR)
  311. except ServiceException as s:
  312. raise s
  313. except Exception as e:
  314. self.clear_video_info()
  315. if self.pull_p:
  316. logger.info("关闭拉流管道, requestId:{}", self.requestId)
  317. self.pull_p.stdout.close()
  318. self.pull_p.terminate()
  319. self.pull_p.wait()
  320. self.pull_p = None
  321. logger.error("读流异常:{}, requestId:{}", format_exc(), self.requestId)
  322. return result
  323. def close(self):
  324. self.clear_video_info()
  325. if self.pull_p:
  326. if self.pull_p.stdout:
  327. self.pull_p.stdout.close()
  328. self.pull_p.terminate()
  329. self.pull_p.wait()
  330. self.pull_p = None
  331. logger.info("关闭拉流管道完成, requestId:{}", self.requestId)
  332. if self.p:
  333. if self.p.stdin:
  334. self.p.stdin.close()
  335. self.p.terminate()
  336. self.p.wait()
  337. self.p = None
  338. # self.p.communicate()
  339. # self.p.kill()
  340. logger.info("关闭管道完成, requestId:{}", self.requestId)
  341. if self.or_video_file:
  342. self.or_video_file.release()
  343. self.or_video_file = None
  344. logger.info("关闭原视频写入流完成, requestId:{}", self.requestId)
  345. if self.ai_video_file:
  346. self.ai_video_file.release()
  347. self.ai_video_file = None
  348. logger.info("关闭AI视频写入流完成, requestId:{}", self.requestId)
  349. # 构建 cv2
  350. # def build_cv2(self):
  351. # try:
  352. # if self.cap is not None:
  353. # logger.info("重试, 关闭cap, requestId:{}", self.requestId)
  354. # self.cap.release()
  355. # if self.p is not None:
  356. # logger.info("重试, 关闭管道, requestId:{}", self.requestId)
  357. # self.p.stdin.close()
  358. # self.p.terminate()
  359. # self.p.wait()
  360. # if self.pullUrl is None:
  361. # logger.error("拉流地址不能为空, requestId:{}", self.requestId)
  362. # raise ServiceException(ExceptionType.PULL_STREAM_URL_EXCEPTION.value[0],
  363. # ExceptionType.PULL_STREAM_URL_EXCEPTION.value[1])
  364. # if self.pushUrl is None:
  365. # logger.error("推流地址不能为空, requestId:{}", self.requestId)
  366. # raise ServiceException(ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[0],
  367. # ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[1])
  368. # self.cap = cv2.VideoCapture(self.pullUrl)
  369. # if self.fps is None or self.fps == 0:
  370. # self.fps = int(self.cap.get(cv2.CAP_PROP_FPS))
  371. # if self.width is None or self.width == 0:
  372. # self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
  373. # if self.height is None or self.height == 0:
  374. # self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
  375. # command = ['/usr/bin/ffmpeg',
  376. # # '-y', # 不经过确认,输出时直接覆盖同名文件。
  377. # '-f', 'rawvideo',
  378. # '-vcodec', 'rawvideo',
  379. # '-pix_fmt', 'bgr24', # 显示可用的像素格式
  380. # # '-s', "{}x{}".format(self.width * 2, self.height),
  381. # '-s', "{}x{}".format(int(self.width), int(self.height/2)),
  382. # # '-r', str(15),
  383. # '-i', '-', # 指定输入文件
  384. # '-g', '25',
  385. # '-b:v', '3000k',
  386. # '-tune', 'zerolatency', # 加速编码速度
  387. # '-c:v', 'libx264', # 指定视频编码器
  388. # '-sc_threshold', '0',
  389. # '-pix_fmt', 'yuv420p',
  390. # '-an',
  391. # '-preset', 'ultrafast', # 指定输出的视频质量,会影响文件的生成速度,有以下几个可用的值 ultrafast,
  392. # # superfast, veryfast, faster, fast, medium, slow, slower, veryslow。
  393. # '-f', 'flv',
  394. # self.pushUrl]
  395. # # 管道配置
  396. # logger.info("fps:{}|height:{}|width:{}|requestId:{}", self.fps, self.height, self.width, self.requestId)
  397. # self.p = sp.Popen(command, stdin=sp.PIPE)
  398. # except ServiceException as s:
  399. # logger.exception("构建cv2异常: {}, requestId:{}", s, self.requestId)
  400. # raise s
  401. # except Exception as e:
  402. # logger.exception("初始化cv2异常:{}, requestId:{}", e, self.requestId)
  403. # 构建 cv2
  404. def build_p(self):
  405. try:
  406. if self.pushUrl is None or len(self.pushUrl) == 0:
  407. logger.error("推流地址不能为空, requestId:{}", self.requestId)
  408. raise ServiceException(ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[0],
  409. ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[1])
  410. command = ['ffmpeg',
  411. # '-loglevel', 'debug',
  412. '-re',
  413. '-y',
  414. "-an",
  415. '-f', 'rawvideo',
  416. '-vcodec', 'rawvideo',
  417. '-pix_fmt', 'bgr24',
  418. '-thread_queue_size', '1024',
  419. '-s', "{}x{}".format(self.w * 2, self.h),
  420. '-i', '-', # 指定输入文件
  421. '-r', str(25),
  422. '-g', str(25),
  423. '-maxrate', '6000k',
  424. # '-profile:v', 'high',
  425. '-b:v', '5000k',
  426. # '-crf', '18',
  427. # '-rc:v', 'vbr',
  428. # '-cq:v', '25',
  429. # '-qmin', '25',
  430. # '-qmax', '25',
  431. '-c:v', 'h264_nvenc', #
  432. '-bufsize', '5000k',
  433. # '-c:v', 'libx264', # 指定视频编码器
  434. # '-tune', 'zerolatency', # 加速编码速度
  435. # '-sc_threshold', '0',
  436. '-pix_fmt', 'yuv420p',
  437. # '-flvflags', 'no_duration_filesize',
  438. # '-preset', 'fast', # 指定输出的视频质量,会影响文件的生成速度,有以下几个可用的值 ultrafast,
  439. '-preset', 'p6', # 指定输出的视频质量,会影响文件的生成速度,有以下几个可用的值 ultrafast,
  440. '-tune', 'll',
  441. '-f', 'flv',
  442. self.pushUrl]
  443. logger.info("fps:{}|height:{}|width:{}|requestId:{}", self.fps, self.height, self.width,
  444. self.requestId)
  445. self.p = sp.Popen(command, stdin=sp.PIPE, shell=False)
  446. except ServiceException as s:
  447. if self.p:
  448. if self.p.stdin:
  449. self.p.stdin.close()
  450. self.p.terminate()
  451. self.p.wait()
  452. logger.exception("构建p管道异常: {}, requestId:{}", s.msg, self.requestId)
  453. raise s
  454. except Exception as e:
  455. if self.p:
  456. if self.p.stdin:
  457. self.p.stdin.close()
  458. self.p.terminate()
  459. self.p.wait()
  460. logger.error("初始化p管道异常:{}, requestId:{}", format_exc(), self.requestId)
  461. def push_stream(self, frame):
  462. current_retry_num = 0
  463. while True:
  464. try:
  465. if self.p is None:
  466. self.build_p()
  467. self.p.stdin.write(frame.tostring())
  468. break
  469. except ServiceException as s:
  470. raise s
  471. except Exception as ex:
  472. if self.p_push_time == 0:
  473. self.p_push_time = time.time()
  474. if time.time() - self.p_push_time < 2:
  475. self.p_push_retry_num += 1
  476. self.p_push_time = time.time()
  477. if time.time() - self.p_push_time > 60:
  478. self.p_push_retry_num = 0
  479. self.p_push_time = time.time()
  480. logger.error("推流管道异常:{}, requestId: {}", format_exc(), self.requestId)
  481. if self.p:
  482. try:
  483. if self.p.stdin:
  484. self.p.stdin.close()
  485. self.p.terminate()
  486. self.p.wait()
  487. except:
  488. logger.error("推流管道异常:{}, requestId: {}", format_exc(), self.requestId)
  489. self.p = None
  490. current_retry_num += 1
  491. if self.p_push_retry_num > 100:
  492. logger.error("推流进管道异常:{}, requestId: {}", format_exc(), self.requestId)
  493. raise ServiceException(ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[0],
  494. ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[1])
  495. if current_retry_num > 3:
  496. logger.error("推流进管道异常:{}, requestId: {}", format_exc(), self.requestId)
  497. raise ServiceException(ExceptionType.PUSH_STREAM_EXCEPTION.value[0],
  498. ExceptionType.PUSH_STREAM_EXCEPTION.value[1])
  499. def build_or_write(self):
  500. try:
  501. if self.orFilePath is not None and self.or_video_file is None:
  502. self.or_video_file = cv2.VideoWriter(self.orFilePath, cv2.VideoWriter_fourcc(*'mp4v'), 25,
  503. (self.w, self.h))
  504. # self.or_video_file.set(cv2.CAP_PROP_BITRATE, 5000)
  505. if self.or_video_file is None:
  506. logger.error("or_video_file为空, requestId:{}", self.requestId)
  507. raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
  508. ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
  509. except ServiceException as s:
  510. if self.or_video_file:
  511. self.or_video_file.release()
  512. self.or_video_file = None
  513. logger.error("构建OR文件写对象异常: {}, requestId:{}", s.msg, self.requestId)
  514. raise s
  515. except Exception as e:
  516. if self.or_video_file:
  517. self.or_video_file.release()
  518. self.or_video_file = None
  519. logger.error("构建OR文件写对象异常: {}, requestId:{}", format_exc(), self.requestId)
  520. raise e
  521. except:
  522. if self.or_video_file:
  523. self.or_video_file.release()
  524. self.or_video_file = None
  525. logger.exception("构建OR文件写对象异常:{}, requestId:{}", format_exc(), self.requestId)
  526. raise Exception("构建OR文件写对象异常")
  527. def build_ai_write(self):
  528. try:
  529. if self.aiFilePath is not None and self.ai_video_file is None:
  530. self.ai_video_file = cv2.VideoWriter(self.aiFilePath, cv2.VideoWriter_fourcc(*'mp4v'), 25,
  531. (self.w * 2, self.h))
  532. # self.ai_video_file.set(cv2.CAP_PROP_BITRATE, 5000)
  533. if self.ai_video_file is None:
  534. logger.error("ai_video_file为空, requestId:{}", self.requestId)
  535. raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
  536. ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
  537. except ServiceException as s:
  538. if self.ai_video_file:
  539. self.ai_video_file.release()
  540. self.ai_video_file = None
  541. logger.error("构建AI文件写对象异常: {}, requestId:{}", s.msg, self.requestId)
  542. raise s
  543. except Exception as e:
  544. if self.ai_video_file:
  545. self.ai_video_file.release()
  546. self.ai_video_file = None
  547. logger.error("构建AI文件写对象异常: {}, requestId:{}", format_exc(), self.requestId)
  548. raise e
  549. except:
  550. if self.ai_video_file:
  551. self.ai_video_file.release()
  552. self.ai_video_file = None
  553. logger.error("构建AI文件写对象异常:{}, requestId:{}", format_exc(), self.requestId)
  554. raise Exception("构建AI文件写对象异常")
  555. def video_or_write(self, frame):
  556. ai_retry_num = 0
  557. while True:
  558. try:
  559. if self.or_video_file is None:
  560. self.build_or_write()
  561. self.or_video_file.write(frame)
  562. break
  563. except ServiceException as s:
  564. raise s
  565. except Exception as ex:
  566. if ai_retry_num > 3:
  567. logger.error("重新写入原视频视频到本地, 重试失败:{}, requestId: {}", format_exc(),
  568. self.requestId)
  569. raise ex
  570. finally:
  571. ai_retry_num += 1
  572. def video_ai_write(self, frame):
  573. ai_retry_num = 0
  574. while True:
  575. try:
  576. if self.ai_video_file is None:
  577. self.build_ai_write()
  578. self.ai_video_file.write(frame)
  579. break
  580. except ServiceException as s:
  581. raise s
  582. except Exception as ex:
  583. if ai_retry_num > 3:
  584. logger.exception("重新写入分析后的视频到本地,重试失败:{}, requestId: {}", format_exc(),
  585. self.requestId)
  586. raise ex
  587. finally:
  588. ai_retry_num += 1
  589. def video_merge(self, frame1, frame2):
  590. # frameLeft = cv2.resize(frame1, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
  591. # frameRight = cv2.resize(frame2, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
  592. # frame_merge = np.hstack((frameLeft, frameRight))
  593. frame_merge = np.hstack((frame1, frame2))
  594. return frame_merge
  595. def getP(self):
  596. if self.p is None:
  597. logger.error("获取管道为空, requestId:{}", self.requestId)
  598. raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
  599. ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
  600. return self.p
  601. def getOrVideoFile(self):
  602. if self.or_video_file is None:
  603. logger.error("获取原视频写入对象为空, requestId:{}", self.requestId)
  604. raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
  605. ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
  606. return self.or_video_file
  607. def getAiVideoFile(self):
  608. if self.ai_video_file is None:
  609. logger.error("获取AI视频写入对象为空, requestId:{}", self.requestId)
  610. raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
  611. ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
  612. return self.ai_video_file
  613. def check_video_stream(width, height):
  614. if width is None or height is None:
  615. return True
  616. return False
  617. def build_video_info(pull_url, requestId):
  618. try:
  619. if pull_url is None or len(pull_url) == 0:
  620. logger.error("拉流地址不能为空, requestId:{}", requestId)
  621. raise ServiceException(ExceptionType.PULL_STREAM_URL_EXCEPTION.value[0],
  622. ExceptionType.PULL_STREAM_URL_EXCEPTION.value[1])
  623. args = ['ffprobe', '-show_format', '-show_streams', '-of', 'json', pull_url]
  624. pp = sp.Popen(args, stdout=sp.PIPE, stderr=sp.PIPE)
  625. out, err = pp.communicate(timeout=20)
  626. if pp.returncode != 0:
  627. raise Exception("未获取视频信息!!!!")
  628. probe = loads(out.decode('utf-8'))
  629. # 视频大小
  630. # format = probe['format']
  631. # size = int(format['size'])/1024/1024
  632. video_stream = next((stream for stream in probe['streams'] if stream.get('codec_type') == 'video'), None)
  633. if video_stream is None:
  634. raise Exception("未获取视频信息!!!!")
  635. width_new = video_stream.get('width')
  636. height_new = video_stream.get('height')
  637. nb_frames = video_stream.get('nb_frames', 0)
  638. # fps = video_stream.get('r_frame_rate')
  639. duration = video_stream.get('duration')
  640. if duration is not None and float(duration) != float(0):
  641. nb_frames = int(float(duration) * 25)
  642. # bit_rate = video_stream.get('bit_rate')
  643. if width_new is not None and int(width_new) != 0 and height_new is not None and int(height_new) != 0:
  644. width_o = int(width_new)
  645. height_o = int(height_new)
  646. # width_height_3 = width * height * 3
  647. width_height_3 = width_o * height_o * 3 // 2
  648. width = width_o
  649. height = height_o * 3 // 2
  650. all_frames = int(nb_frames)
  651. w_2 = width_o
  652. h_2 = height_o
  653. if width > Constant.width:
  654. w_2 = width_o // 2
  655. h_2 = height_o // 2
  656. # up, down = str(fps).split('/')
  657. # self.fps = int(eval(up) / eval(down))
  658. # if duration:
  659. # self.duration = float(video_stream['duration'])
  660. # self.bit_rate = int(bit_rate) / 1000
  661. logger.info("视频信息, width:{}|height:{}|all_frames:{}, requestId:{}", width_o, height_o, all_frames,
  662. requestId)
  663. return width, height, width_height_3, all_frames, w_2, h_2
  664. raise Exception("未获取视频信息!!!!")
  665. except ServiceException as s:
  666. logger.error("获取视频信息异常: {}, requestId:{}", s.msg, requestId)
  667. raise s
  668. except Exception:
  669. logger.error("获取视频信息异常:{}, requestId:{}", format_exc(), requestId)
  670. return None, None, None, 0, None, None
  671. def start_pull_p(pull_url, requestId):
  672. try:
  673. command = ['ffmpeg']
  674. if pull_url.startswith("rtsp://"):
  675. command.extend(['-rtsp_transport', 'tcp'])
  676. command.extend(['-re',
  677. '-y',
  678. '-an',
  679. # '-hwaccel', 'cuda', cuvid
  680. '-c:v', 'h264_cuvid',
  681. # '-resize', self.wah,
  682. '-i', pull_url,
  683. '-f', 'rawvideo',
  684. # '-pix_fmt', 'bgr24',
  685. '-r', '25',
  686. '-'])
  687. return sp.Popen(command, stdout=sp.PIPE)
  688. except ServiceException as s:
  689. logger.error("构建拉流管道异常: {}, requestId:{}", s.msg, requestId)
  690. raise s
  691. except Exception as e:
  692. logger.error("构建拉流管道异常:{}, requestId:{}", format_exc(), requestId)
  693. raise e
  694. def clear_pull_p(pull_p, requestId):
  695. try:
  696. if pull_p:
  697. logger.info("关闭拉流管道, requestId:{}", requestId)
  698. pull_p.stdout.close()
  699. pull_p.terminate()
  700. pull_p.wait()
  701. logger.info("拉流管道已关闭, requestId:{}", requestId)
  702. except Exception:
  703. logger.error("关闭拉流管道异常: {}, requestId:{}", format_exc(), requestId)
  704. def pull_read_video_stream(pull_p, pull_url, width, height, width_height_3, w_2, h_2, requestId):
  705. result = None
  706. try:
  707. if pull_p is None:
  708. pull_p = start_pull_p(pull_url, requestId)
  709. in_bytes = pull_p.stdout.read(width_height_3)
  710. if in_bytes is not None and len(in_bytes) > 0:
  711. try:
  712. # result = (np.frombuffer(in_bytes, np.uint8).reshape([height * 3 // 2, width, 3]))
  713. result = (np.frombuffer(in_bytes, np.uint8)).reshape((height, width))
  714. except Exception:
  715. logger.error("视频格式异常:{}, requestId:{}", format_exc(), requestId)
  716. raise ServiceException(ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[0],
  717. ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[1])
  718. result = cv2.cvtColor(result, cv2.COLOR_YUV2BGR_NV12)
  719. # result = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
  720. if width > Constant.width:
  721. result = cv2.resize(result, (w_2, h_2), interpolation=cv2.INTER_LINEAR)
  722. except ServiceException as s:
  723. clear_pull_p(pull_p, requestId)
  724. raise s
  725. except Exception:
  726. clear_pull_p(pull_p, requestId)
  727. pull_p = None
  728. width = None
  729. height = None
  730. width_height_3 = None
  731. logger.error("读流异常:{}, requestId:{}", format_exc(), requestId)
  732. return result, pull_p, width, height, width_height_3
  733. def video_conjuncing(frame1, frame2):
  734. # frameLeft = cv2.resize(frame1, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
  735. # frameRight = cv2.resize(frame2, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
  736. # frame_merge = np.hstack((frameLeft, frameRight))
  737. frame_merge = np.hstack((frame1, frame2))
  738. return frame_merge
  739. def build_push_p(push_url, width, height, requestId):
  740. push_p = None
  741. try:
  742. if push_url is None or len(push_url) == 0:
  743. logger.error("推流地址不能为空, requestId:{}", requestId)
  744. raise ServiceException(ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[0],
  745. ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[1])
  746. command = ['ffmpeg',
  747. # '-loglevel', 'debug',
  748. # '-re',
  749. '-y',
  750. "-an",
  751. '-f', 'rawvideo',
  752. '-vcodec', 'rawvideo',
  753. '-pix_fmt', 'bgr24',
  754. '-thread_queue_size', '1024',
  755. '-s', "{}x{}".format(width * 2, height),
  756. '-i', '-', # 指定输入文件
  757. '-r', str(25),
  758. '-g', str(25),
  759. '-maxrate', '6000k',
  760. # '-profile:v', 'high',
  761. '-b:v', '5000k',
  762. # '-crf', '18',
  763. # '-rc:v', 'vbr',
  764. # '-cq:v', '25',
  765. # '-qmin', '25',
  766. # '-qmax', '25',
  767. '-c:v', 'h264_nvenc', #
  768. '-bufsize', '5000k',
  769. # '-c:v', 'libx264', # 指定视频编码器
  770. # '-tune', 'zerolatency', # 加速编码速度
  771. # '-sc_threshold', '0',
  772. # '-rc', 'cbr_ld_hq',
  773. # '-zerolatency', '1',
  774. '-pix_fmt', 'yuv420p',
  775. # '-flvflags', 'no_duration_filesize',
  776. # '-preset', 'fast', # 指定输出的视频质量,会影响文件的生成速度,有以下几个可用的值 ultrafast,
  777. '-preset', 'p6', # 指定输出的视频质量,会影响文件的生成速度,有以下几个可用的值 ultrafast,
  778. '-tune', 'll',
  779. '-f', 'flv',
  780. push_url]
  781. logger.info("height:{}|width:{}|requestId:{}", height, width, requestId)
  782. push_p = sp.Popen(command, stdin=sp.PIPE, shell=False)
  783. except ServiceException as s:
  784. if push_p:
  785. if push_p.stdin:
  786. push_p.stdin.close()
  787. push_p.terminate()
  788. push_p.wait()
  789. logger.exception("构建p管道异常: {}, requestId:{}", s.msg, requestId)
  790. raise s
  791. except Exception:
  792. if push_p:
  793. if push_p.stdin:
  794. push_p.stdin.close()
  795. push_p.terminate()
  796. push_p.wait()
  797. push_p = None
  798. logger.error("初始化p管道异常:{}, requestId:{}", format_exc(), requestId)
  799. return push_p
  800. def push_video_stream(frame, push_p, push_url, width, height, p_push_array, requestId):
  801. current_retry_num = 0
  802. while True:
  803. try:
  804. if push_p is None:
  805. push_p = build_push_p(push_url, width, height, requestId)
  806. push_p.stdin.write(frame.tostring())
  807. return push_p
  808. except ServiceException as s:
  809. clear_push_p(push_p, requestId)
  810. raise s
  811. except Exception:
  812. if p_push_array[0] == 0:
  813. p_push_array[0] = time()
  814. if time() - p_push_array[0] < 2:
  815. p_push_array[1] += 1
  816. p_push_array[0] = time()
  817. if time() - p_push_array[0] > 60:
  818. p_push_array[1] = 0
  819. p_push_array[0] = time()
  820. logger.error("推流管道异常:{}, requestId: {}", format_exc(), requestId)
  821. clear_push_p(push_p, requestId)
  822. push_p = None
  823. current_retry_num += 1
  824. if p_push_array[1] > 20:
  825. logger.error("推流进管道异常:{}, requestId: {}", format_exc(), requestId)
  826. raise ServiceException(ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[0],
  827. ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[1])
  828. if current_retry_num > 3:
  829. logger.error("推流进管道异常:{}, requestId: {}", format_exc(), requestId)
  830. raise ServiceException(ExceptionType.PUSH_STREAM_EXCEPTION.value[0],
  831. ExceptionType.PUSH_STREAM_EXCEPTION.value[1])
  832. def clear_push_p(push_p, requestId):
  833. if push_p:
  834. try:
  835. if push_p.stdin:
  836. push_p.stdin.close()
  837. push_p.terminate()
  838. push_p.wait()
  839. except Exception:
  840. logger.error("推流管道异常:{}, requestId: {}", format_exc(), requestId)
  841. def close_or_write_stream(or_video_file, requestId):
  842. try:
  843. if or_video_file:
  844. or_video_file.release()
  845. except Exception:
  846. logger.info("关闭原视频写流管道异常:{}, requestId:{}", format_exc(), requestId)
  847. def close_ai_write_stream(ai_video_file, requestId):
  848. try:
  849. if ai_video_file:
  850. ai_video_file.release()
  851. except Exception:
  852. logger.info("关闭AI视频写流管道异常:{}, requestId:{}", format_exc(), requestId)
  853. def close_all_p(push_p, or_video_file, ai_video_file, requestId):
  854. logger.info("开始停止推流、写流管道!requestId:{}", requestId)
  855. clear_push_p(push_p, requestId)
  856. close_or_write_stream(or_video_file, requestId)
  857. close_ai_write_stream(ai_video_file, requestId)
  858. logger.info("停止推流、写流管道完成!requestId:{}", requestId)
  859. def build_or_video(orFilePath, width, height, requestId):
  860. or_video_file = None
  861. try:
  862. or_video_file = cv2.VideoWriter(orFilePath, cv2.VideoWriter_fourcc(*'mp4v'), 25, (width, height))
  863. if or_video_file is None:
  864. logger.error("or_video_file为空, requestId:{}", requestId)
  865. raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
  866. ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
  867. return or_video_file
  868. except ServiceException as s:
  869. if or_video_file:
  870. or_video_file.release()
  871. logger.error("构建OR文件写对象异常: {}, requestId:{}", s.msg, requestId)
  872. raise s
  873. except Exception as e:
  874. if or_video_file:
  875. or_video_file.release()
  876. logger.error("构建OR文件写对象异常: {}, requestId:{}", format_exc(), requestId)
  877. raise e
  878. def write_or_video(frame, orFilePath, or_video_file, width, height, requestId):
  879. retry_num = 0
  880. while True:
  881. try:
  882. if or_video_file is None:
  883. or_video_file = build_or_video(orFilePath, width, height, requestId)
  884. or_video_file.write(frame)
  885. return or_video_file
  886. except ServiceException as s:
  887. if or_video_file:
  888. or_video_file.release()
  889. raise s
  890. except Exception as ex:
  891. if retry_num > 3:
  892. if or_video_file:
  893. or_video_file.release()
  894. logger.error("重新写入原视频视频到本地, 重试失败:{}, requestId: {}", format_exc(), requestId)
  895. raise ex
  896. finally:
  897. retry_num += 1
  898. def build_ai_video(aiFilePath, width, height, requestId):
  899. ai_video_file = None
  900. try:
  901. ai_video_file = cv2.VideoWriter(aiFilePath, cv2.VideoWriter_fourcc(*'mp4v'), 25, (width * 2, height))
  902. if ai_video_file is None:
  903. logger.error("ai_video_file为空, requestId:{}", requestId)
  904. raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
  905. ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
  906. return ai_video_file
  907. except ServiceException as s:
  908. if ai_video_file:
  909. ai_video_file.release()
  910. logger.error("构建AI文件写对象异常: {}, requestId:{}", s.msg, requestId)
  911. raise s
  912. except Exception as e:
  913. if ai_video_file:
  914. ai_video_file.release()
  915. logger.error("构建AI文件写对象异常: {}, requestId:{}", format_exc(), requestId)
  916. raise e
  917. def write_ai_video(frame, aiFilePath, ai_video_file, width, height, requestId):
  918. retry_num = 0
  919. while True:
  920. try:
  921. if ai_video_file is None:
  922. ai_video_file = build_ai_video(aiFilePath, width, height, requestId)
  923. ai_video_file.write(frame)
  924. return ai_video_file
  925. except ServiceException as s:
  926. if ai_video_file:
  927. ai_video_file.release()
  928. raise s
  929. except Exception as ex:
  930. if retry_num > 3:
  931. if ai_video_file:
  932. ai_video_file.release()
  933. logger.error("重新写入分析后的视频到本地,重试失败:{}, requestId: {}", format_exc(), requestId)
  934. raise ex
  935. finally:
  936. retry_num += 1