import os import queue import subprocess import threading import cv2 import numpy as np from .ffmpeg_cli import FFmpegCLI class FramePrefetcher: """ 后台线程预解码视频帧,使 I/O 与模型推理重叠。 接口兼容 cv2.VideoCapture(read/release)。 """ def __init__(self, video_cap, buffer_size=10): self.cap = video_cap self._buffer = queue.Queue(maxsize=buffer_size) self._stopped = False self._thread = threading.Thread(target=self._read_loop, daemon=True) self._thread.start() def _read_loop(self): while not self._stopped: ret, frame = self.cap.read() self._buffer.put((ret, frame)) if not ret: break def read(self): """读取下一帧,接口与 cv2.VideoCapture.read() 一致。""" return self._buffer.get() def get(self, propId): return self.cap.get(propId) def stop(self): """停止预读取,不释放底层 video_cap。""" self._stopped = True try: while not self._buffer.empty(): self._buffer.get_nowait() except queue.Empty: pass self._thread.join(timeout=5) def release(self): self.stop() self.cap.release() class FFmpegVideoWriter: """ 通过 FFmpeg 管道写入帧,使用 libx264 编码。 接口兼容 cv2.VideoWriter(write/release)。 """ def __init__(self, output_path, fps, size): w, h = size cmd = [ FFmpegCLI.instance().ffmpeg_path, '-y', '-f', 'rawvideo', '-vcodec', 'rawvideo', '-s', f'{w}x{h}', '-pix_fmt', 'bgr24', '-r', str(fps), '-i', '-', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '18', '-preset', 'fast', '-loglevel', 'error', output_path ] self._process = subprocess.Popen( cmd, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) def write(self, frame): """写入一帧(numpy BGR 数组)。""" if frame.dtype != np.uint8: frame = np.clip(frame, 0, 255).astype(np.uint8) try: self._process.stdin.write(frame.tobytes()) except BrokenPipeError: pass def release(self): """关闭管道并等待编码完成。""" try: self._process.stdin.close() except BrokenPipeError: pass self._process.wait()