MacOS Sonoma下Python+PyQt6录屏时长不匹配问题求助
屏幕录制时长与实际视频时长不同步的问题分析与解决
问题描述
在MacOS Sonoma操作系统中,使用Python、PyQt6和QtWebEngineWidgets实现屏幕录制功能时出现异常:设置20秒录制时长,生成的视频却不足1秒。测试显示20秒仅录制了32帧,尝试强制设置每秒20或24帧也无法解决问题。
相关代码
父脚本启停录制代码
def start_recording(self): applicazione_log(f"BROWSER: Start recording") self.record_button.setText("STOP RECORDING AND SAVE ACQUISITION") self.recording = True self.recording_task = VideoRecorder(self.unique_code) self.recording_thread = RecordingThread(self.recording_task) self.recording_thread.start() def stop_recording(self): applicazione_log(f"BROWSER: Stop recording") self.recording = False self.record_button.setText("START RECORDING") if self.recording_task: self.recording_task.stop() self.recording_thread.quit() self.recording_thread.wait()
录屏脚本代码
import os import ffmpeg import numpy as np import cv2 from mss import mss import pyautogui from PyQt6.QtCore import * from PyQt6.QtWidgets import * from PyQt6.QtGui import * import time from .monitoraggio_applicazione import applicazione_log class VideoRecorder(QObject): frameAcquired = pyqtSignal(int) def __init__(self, unique_code): super().__init__() self.running = True self.last_frame_time = 0 self.frame_count = 0 self.width, self.height = pyautogui.size() applicazione_log(f"pyautogui: width = {str(self.width)} width = {str(self.height)}") self.script_directory = os.path.dirname(os.path.abspath(__file__)) self.parent_directory = os.path.dirname(self.script_directory) self.folder_documents = 'documents' self.folder_documents_path = os.path.join(self.parent_directory, self.folder_documents, unique_code) self.filename = os.path.join(self.folder_documents_path, "recorded_video.mp4") # 帧缓冲 self.buffer_size = 30 self.buffer = [] self.frame_rate = 24 self.frame_duration = 1 / self.frame_rate try: self.video_stream = ( ffmpeg.input('pipe:', format='rawvideo', pix_fmt='bgr24', s=f'{self.width}x{self.height}', r=self.frame_rate) .output(self.filename, pix_fmt='yuv420p', vcodec='libx264', crf=20) .run_async(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True) ) except Exception as e: applicazione_log(f"Errore durante la configurazione di ffmpeg: {str(e)}") def stop(self): try: self.running = False # 写入缓冲中剩余的所有帧 while self.buffer: frame = self.buffer.pop(0) self.video_stream.stdin.write(frame.tobytes('C')) self.video_stream.stdin.close() self.video_stream.wait() except Exception as e: if "write to closed file" in str(e): applicazione_log(f"Errore di scrittura su file chiuso: {str(e)}") else: raise e @pyqtSlot() def run(self): with mss() as sct: # 获取主显示器区域 monitor = sct.monitors[1] self.last_frame_time = time.perf_counter() while self.running: try: # 内存直接获取截图,跳过磁盘IO screenshot = sct.grab(monitor) # 转换为cv2兼容的BGR格式 frame = np.array(screenshot) frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR) # 严格控制帧率间隔 current_time = time.perf_counter() elapsed_time = current_time - self.last_frame_time if elapsed_time < self.frame_duration: time.sleep(self.frame_duration - elapsed_time) self.last_frame_time = time.perf_counter() # 帧缓冲处理 self.buffer.append(frame) if len(self.buffer) > self.buffer_size: self.buffer.pop(0) # 写入单帧到ffmpeg if self.buffer: frame_to_write = self.buffer.pop(0) self.video_stream.stdin.write(frame_to_write.tobytes('C')) self.frame_count += 1 except Exception as e: applicazione_log(f"Errore durante l'acquisizione e l'elaborazione dei frame: {str(e)}") # 收尾时清空缓冲 while self.buffer: frame_to_write = self.buffer.pop(0) self.video_stream.stdin.write(frame_to_write.tobytes('C')) self.video_stream.stdin.close() self.video_stream.wait() applicazione_log(f"录制完成,总帧数: {self.frame_count}") class RecordingThread(QThread): def __init__(self, video_recorder): super().__init__() self.video_recorder = video_recorder def run(self): self.video_recorder.run()
问题根源
- 磁盘IO拖慢采集速度:原代码用
sct.shot()将截图保存为PNG文件,再通过cv2.imread()读取,磁盘读写的高耗时导致每秒仅能采集1-2帧,20秒仅生成32帧左右。而ffmpeg按24fps封装视频,32帧对应时长仅约1.3秒,因此出现时长不符。 - 无帧率控制逻辑:原循环未控制帧采集间隔,完全依赖硬件和IO速度,实际帧率远低于设定值。
- 缓冲逻辑无效:原代码每次添加帧到缓冲后立刻弹出写入,缓冲未起到平滑帧率的作用,反而增加冗余操作。
解决措施
- 内存直连采集:改用
sct.grab()直接获取内存中的截图,再转换为cv2格式,彻底消除磁盘IO开销,大幅提升采集速度。 - 强制帧率控制:通过
time.perf_counter()计算帧间隔,用time.sleep()确保每帧的采集/写入间隔严格匹配设定帧率(24fps),保证实际帧率与预期一致。 - 优化缓冲与收尾:停止录制时先写入缓冲剩余帧,避免数据丢失;调整缓冲逻辑实现帧率平滑。
内容的提问来源于stack exchange,提问作者gc75
相关产品推荐
相关产品推荐

