rtsp_player.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import av
  2. import numpy as np
  3. import time
  4. import cv2
  5. import logging
  6. from PySide6.QtCore import QThread, Signal, QMutex
  7. from PySide6.QtGui import QImage, QPixmap
  8. from typing import List, Dict, Any
  9. logger = logging.getLogger(__name__)
  10. class RTSPPlayer(QThread):
  11. frame_ready = Signal(QPixmap)
  12. first_frame_received = Signal()
  13. timeout = Signal()
  14. def __init__(self, rtsp_url: str, target_width: int = 640, target_height: int = 360, timeout_sec: float = 5.0):
  15. super().__init__()
  16. self.rtsp_url = rtsp_url
  17. self.target_width = target_width
  18. self.target_height = target_height
  19. self.timeout_sec = timeout_sec
  20. self.running = False
  21. self.ai_objects: List[Dict[str, Any]] = []
  22. self.target_fps = 25
  23. self.mutex = QMutex()
  24. def set_target_size(self, width: int, height: int):
  25. self.mutex.lock()
  26. self.target_width = width
  27. self.target_height = height
  28. self.mutex.unlock()
  29. def set_ai_objects(self, objects):
  30. self.ai_objects = objects
  31. def run(self):
  32. self.running = True
  33. options = {
  34. 'rtsp_transport': 'tcp',
  35. 'buffer_size': '1024',
  36. 'max_delay': '50000',
  37. 'flags': 'low_delay',
  38. 'fflags': 'nobuffer+discardcorrupt',
  39. 'analyzeduration': '100000',
  40. 'probesize': '100000',
  41. 'timeout': str(int(self.timeout_sec * 1000000)),
  42. 'stimeout': str(int(self.timeout_sec * 1000000)),
  43. }
  44. start_time = time.time()
  45. first_packet = True
  46. container = None
  47. try:
  48. container = av.open(self.rtsp_url, options=options)
  49. video_stream = container.streams.video[0]
  50. video_stream.thread_type = 'AUTO'
  51. video_stream.thread_count = 1
  52. last_emit = 0
  53. frame_interval = 1.0 / self.target_fps
  54. for packet in container.demux(video_stream):
  55. if not self.running:
  56. break
  57. if first_packet:
  58. first_packet = False
  59. self.first_frame_received.emit()
  60. for frame in packet.decode():
  61. if not self.running:
  62. break
  63. img = frame.to_rgb().to_ndarray()
  64. now = time.time()
  65. if now - last_emit >= frame_interval:
  66. self.process_frame(img)
  67. last_emit = now
  68. if first_packet and (time.time() - start_time) > self.timeout_sec:
  69. logger.warning("RTSP连接超时(未收到任何包)")
  70. self.timeout.emit()
  71. self.running = False
  72. break
  73. if not self.running:
  74. break
  75. except Exception as e:
  76. logger.error(f"RTSP错误: {e}")
  77. if first_packet:
  78. self.timeout.emit()
  79. finally:
  80. if container:
  81. container.close()
  82. self.running = False
  83. def stop(self):
  84. self.running = False
  85. self.wait()
  86. def process_frame(self, frame):
  87. # 先缩放到目标尺寸
  88. self.mutex.lock()
  89. tw, th = self.target_width, self.target_height
  90. self.mutex.unlock()
  91. if tw > 0 and th > 0 and (frame.shape[1] != tw or frame.shape[0] != th):
  92. frame = cv2.resize(frame, (tw, th), interpolation=cv2.INTER_LINEAR)
  93. # 再在缩放后的图像上绘制 AI 标注
  94. for obj in self.ai_objects:
  95. x = obj.get('x', 0)
  96. y = obj.get('y', 0)
  97. w = obj.get('w', 0)
  98. h = obj.get('h', 0)
  99. label = obj.get('label', '')
  100. color = obj.get('color', (0, 255, 0))
  101. cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
  102. cv2.putText(frame, label, (x, y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
  103. # 转换为 QPixmap
  104. h, w, ch = frame.shape
  105. bytes_per_line = ch * w
  106. qt_img = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
  107. pixmap = QPixmap.fromImage(qt_img)
  108. self.frame_ready.emit(pixmap)