You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 18:08:04