import queue from http.server import BaseHTTPRequestHandler, HTTPServer from threading import Lock, Thread import cv2 _latest_frame = None _frame_lock = Lock() _encode_queue = queue.Queue(maxsize=2) class MJPEGHandler(BaseHTTPRequestHandler): def do_GET(self): if self.path != '/stream' and self.path != '/': self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(b"") return self.send_response(200) self.send_header('Content-type', 'multipart/x-mixed-replace; boundary=FRAME') self.end_headers() try: while True: with _frame_lock: frame = _latest_frame if frame: self.wfile.write(b'--FRAME\r\n') self.wfile.write(b'Content-Type: image/jpeg\r\n') self.wfile.write(f'Content-Length: {len(frame)}\r\n\r\n'.encode()) self.wfile.write(frame) self.wfile.write(b'\r\n') try: self.wfile.flush() except Exception: pass except Exception: return def _encoding_worker(quality: int, scale_factor: float): """Background thread to handle JPEG compression without blocking the GPU loop.""" global _latest_frame while True: try: frame_to_encode = _encode_queue.get(timeout=1) if scale_factor < 1.0: h, w = frame_to_encode.shape[:2] frame_to_encode = cv2.resize(frame_to_encode, (int(w * scale_factor), int(h * scale_factor))) _, enc = cv2.imencode('.jpg', frame_to_encode, [int(cv2.IMWRITE_JPEG_QUALITY), quality]) with _frame_lock: _latest_frame = enc.tobytes() except queue.Empty: continue def start_http_server(port: int, quality: int, scale_factor: float): server = HTTPServer(('', port), MJPEGHandler) Thread(target=server.serve_forever, daemon=True).start() Thread(target=_encoding_worker, args=(quality, scale_factor), daemon=True).start() return server def submit_stream_frame(frame) -> None: """Non-blocking frame submission for HTTP MJPEG stream.""" try: _encode_queue.put_nowait(frame) except queue.Full: pass