Python(Qt)应用中基于FFmpeg同时录制带音频摄像头并实时预览的实现方案咨询
Python(Qt)应用中基于FFmpeg同时录制带音频摄像头并实时预览的实现方案咨询
嘿,刚好我之前也折腾过类似的需求,来给你捋捋在Python(Qt)应用里实现FFmpeg录制带音频摄像头+实时预览的方案!
首先,你的核心需求是让FFmpeg同时做两件事:把摄像头+音频存成文件,同时输出预览画面。FFmpeg里实现这个的关键是用tee多路复用器,它能把输入流拆成两个分支,分别处理录制和预览。
方案一:快速实现录制+独立预览窗口(基于SDL)
如果只是先验证功能,不需要把预览嵌入Qt窗口,直接修改你的现有代码,用tee分流到文件和SDL预览窗口就行。SDL是FFmpeg默认支持的轻量显示窗口,修改后的代码如下:
import ffmpeg import time process = ( ffmpeg .input('video=Integrated Camera:audio=Microphone Array (Realtek(R) Audio)', format='dshow') # 用tee分流:一路存MP4文件,一路输出到SDL预览窗口 .output( 'tee:[f=mp4]out.mp4|[f=sdl]摄像头预览', pix_fmt='yuv420p', vcodec='libx264', acodec='aac', overwrite_output=True ) .run_async(pipe_stdin=True, quiet=True) ) time.sleep(10) # 模拟录制时长 process.stdin.write('q'.encode()) process.communicate() process.wait() print('录制完成')
运行后会弹出一个SDL的独立预览窗口,同时后台录制到out.mp4,缺点是这个窗口是独立的,没法直接嵌入Qt界面。
方案二:整合到Qt应用的深度实现
如果要把预览画面嵌入Qt窗口,就得把FFmpeg的预览分支输出导到管道,再在Qt里读取管道流、解码成帧,最后渲染到Qt的UI组件上。这里给你写个完整的PyQt5示例:
import ffmpeg import time import threading import sys from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtCore import pyqtSignal, QObject # 用信号槽解决线程更新UI的安全问题 class FrameSignal(QObject): new_frame = pyqtSignal(QImage) class CameraRecorder(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Qt摄像头录制+预览") self.preview_label = QLabel() layout = QVBoxLayout() layout.addWidget(self.preview_label) self.setLayout(layout) # 初始化信号槽 self.frame_signal = FrameSignal() self.frame_signal.new_frame.connect(self.update_preview) # 1. 启动主FFmpeg进程:分流到MP4文件和Nut格式管道(Nut适合流式传输) self.main_process = ( ffmpeg .input( 'video=Integrated Camera:audio=Microphone Array (Realtek(R) Audio)', format='dshow', s='640x480' # 固定摄像头分辨率,避免帧大小不匹配 ) .output( 'tee', map='0:v', map='0:a', filename='[f=mp4]out.mp4|[f=nut]pipe:1', format='tee', vcodec='libx264', acodec='aac', pix_fmt='yuv420p', overwrite_output=True ) .run_async(pipe_stdout=True, pipe_stdin=True, quiet=True) ) # 2. 启动解码进程:读取管道的Nut流,解码成BGR888格式的原始帧 self.decode_process = ( ffmpeg .input('pipe:0', format='nut') .output( 'pipe:1', format='rawvideo', pix_fmt='bgr24', s='640x480' ) .run_async(pipe_stdin=self.main_process.stdout, pipe_stdout=True, quiet=True) ) # 3. 启动线程读取解码后的帧 self.running = True self.preview_thread = threading.Thread(target=self.read_frames) self.preview_thread.start() def read_frames(self): frame_size = 640 * 480 * 3 # BGR888格式的帧大小 while self.running: raw_frame = self.decode_process.stdout.read(frame_size) if not raw_frame: break # 转换成QImage,通过信号传给主线程更新UI q_img = QImage(raw_frame, 640, 480, QImage.Format_BGR888) self.frame_signal.new_frame.emit(q_img) def update_preview(self, img): self.preview_label.setPixmap(QPixmap.fromImage(img)) self.preview_label.resize(img.size()) self.resize(img.size()) def closeEvent(self, event): # 停止所有进程和线程 self.running = False self.main_process.stdin.write('q'.encode()) self.main_process.communicate() self.decode_process.terminate() self.main_process.wait() self.decode_process.wait() self.preview_thread.join() event.accept() if __name__ == "__main__": app = QApplication(sys.argv) window = CameraRecorder() window.show() sys.exit(app.exec_())
这个示例里用了线程读取解码后的帧,再通过Qt的信号槽安全更新UI,避免了多线程操作UI的问题,预览画面会直接嵌入Qt窗口里。
方案三:更简单的替代——Qt原生多媒体API
如果你不想折腾FFmpeg的管道和分流,Qt本身就有一套成熟的多媒体API,能直接实现摄像头录制+预览,代码更简洁,也不需要依赖FFmpeg:
import sys from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton from PyQt5.QtMultimedia import QCamera, QMediaRecorder from PyQt5.QtMultimediaWidgets import QVideoWidget from PyQt5.QtCore import QUrl class QtCameraRecorder(QWidget): def __init__(self): super().__init__() self.setWindowTitle("Qt原生摄像头录制") self.video_widget = QVideoWidget() self.record_btn = QPushButton("开始录制") layout = QVBoxLayout() layout.addWidget(self.video_widget) layout.addWidget(self.record_btn) self.setLayout(layout) # 初始化摄像头和录制器 self.camera = QCamera() self.recorder = QMediaRecorder(self.camera) self.recorder.setOutputLocation(QUrl.fromLocalFile("out.mp4")) # 绑定预览和录制按钮 self.camera.setViewfinder(self.video_widget) self.record_btn.clicked.connect(self.toggle_recording) self.is_recording = False self.camera.start() def toggle_recording(self): if not self.is_recording: self.recorder.record() self.record_btn.setText("停止录制") self.is_recording = True else: self.recorder.stop() self.record_btn.setText("开始录制") self.is_recording = False if __name__ == "__main__": app = QApplication(sys.argv) window = QtCameraRecorder() window.show() sys.exit(app.exec_())
这个方案的优点是完全原生,不需要额外依赖FFmpeg,缺点是对系统解码器有要求(比如Windows上需要Media Foundation支持)。
备注:内容来源于stack exchange,提问作者Jan Oechsler
相关产品推荐
相关产品推荐

