Qt+FFmpeg 实现摄像头采集并录制 YUV 格式视频

一、前言

本文主要记录通过 Qt 结合 FFmpeg 实现从摄像头采集视频数据,并将采集到的数据保存为 YUV 格式文件的过程。涉及 FFmpeg 设备操作、Qt 多线程编程等核心知识点,适合有一定 Qt 和 FFmpeg 基础的开发者学习参考。

二、环境准备

  1. 开发框架:Qt 5/6(本文以 Qt 为例,核心逻辑不依赖具体版本)
  2. FFmpeg 库 :需包含libavdevicelibavformatlibavutillibavcodec等核心库
  3. 系统环境:本文以 Windows 系统为例,使用 dshow(DirectShow)作为设备输入格式

三、核心代码解析

3.1 线程类头文件(audiothread.h)

由于视频采集是耗时操作,需放在子线程中执行,避免阻塞主线程(UI 线程)。

cpp 复制代码
#ifndef AUDIOTHREAD_H
#define AUDIOTHREAD_H

#include <QThread>

class audioThread : public QThread
{
    Q_OBJECT
private:
    void run(); // 线程执行函数,重写QThread的run方法
public:
    explicit audioThread(QObject *parent = nullptr);
    ~audioThread();
};

#endif // AUDIOTHREAD_H

3.2 线程实现文件(audiothread.cpp)

核心逻辑集中在该文件,包含 FFmpeg 设备打开、数据采集、文件写入、资源释放等步骤。

3.2.1 头文件与宏定义
cpp 复制代码
#include "audiothread.h"
#include <qdebug.h>
#include <qfile.h>

// 引入FFmpeg的C语言接口
extern "C"{
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavcodec/avcodec.h>
}

// Windows系统下的设备配置
#ifdef Q_OS_WIN
#define FMT_NAME "dshow"          // 设备输入格式(dshow)
#define DEVICE_NAME "video=Integrated Webcam" // 摄像头设备名称
#define FILENAME "D:/out.yuv"     // YUV文件保存路径
#endif

// 错误信息格式化宏
#define ERROR_BUF(ret) \
    char errbuf[1024]; \
    av_strerror(ret,errbuf,sizeof(errbuf));
3.2.2 构造与析构函数
cpp 复制代码
audioThread::audioThread(QObject *parent):QThread{parent}
{
    // 线程结束时自动回收内存
    connect(this,&audioThread::finished,
            this,&audioThread::deleteLater);
}

audioThread::~audioThread()
{
    disconnect(); // 断开所有信号槽连接
    requestInterruption(); // 请求线程中断
    quit(); // 退出事件循环
    wait(); // 等待线程结束
    qDebug() << this << "析构(内存被回收)";
}
3.2.3 核心采集逻辑(run 方法)
cpp 复制代码
void audioThread::run()
{
    qDebug() << this << "开始执行------";

    // 1. 获取输入格式对象(dshow)
    AVInputFormat *fmt = av_find_input_format(FMT_NAME);
    if(!fmt){
        qDebug() << "av_find_input_format error" << FMT_NAME;
        return;
    }

    // 2. 初始化格式上下文(操作设备的核心上下文)
    AVFormatContext *ctx = nullptr;
    AVDictionary *options = nullptr;
    // 设置采集参数:分辨率、像素格式、帧率
    av_dict_set(&options,"video_size","1280x720",0);
    av_dict_set(&options,"pixel_format","yuyv422",0);
    av_dict_set(&options,"framerate","10",0);

    // 3. 打开摄像头设备
    int ret = avformat_open_input(&ctx,DEVICE_NAME,fmt,&options);
    if(ret < 0){
        ERROR_BUF(ret);
        qDebug() << "avformat_open_input error" << errbuf;
        return;
    }

    // 4. 打开YUV文件用于写入
    QFile file(FILENAME);
    if(!file.open(QFile::WriteOnly)){
        qDebug() << "file open error" << FILENAME;
        avformat_close_input(&ctx); // 失败时关闭设备
        return;
    }

    // 5. 计算一帧视频数据的大小
    AVCodecParameters *params = ctx->streams[0]->codecpar;
    AVPixelFormat pixFmt = (AVPixelFormat)params->format;
    int imageSize = av_image_get_buffer_size(
        pixFmt,
        params->width,
        params->height,
        1);

    // 6. 循环采集视频数据
    AVPacket *pkt = av_packet_alloc(); // 分配数据包
    while(!isInterruptionRequested()){ // 检查是否需要中断
        ret = av_read_frame(ctx,pkt); // 读取一帧数据

        if(ret == 0){ // 读取成功
            // 将数据写入YUV文件
            file.write((const char*)pkt->data,imageSize);
            av_packet_unref(pkt); // 释放数据包引用
        }else if(ret == AVERROR(EAGAIN)){ // 资源临时不可用,继续循环
            continue;
        }else{ // 其他错误,退出循环
            ERROR_BUF(ret);
            qDebug() << "av_read_frame error" << errbuf << ret;
            break;
        }
    }

    // 7. 释放资源
    av_packet_free(&pkt); // 释放数据包
    file.close(); // 关闭文件
    avformat_close_input(&ctx); // 关闭设备

    qDebug() << this << "正常结束------";
}

3.3 主窗口逻辑(mainwindow 相关)

3.3.1 头文件(mainwindow.h)
cpp 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "audiothread.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_playBtn_clicked(); // 按钮点击槽函数

private:
    Ui::MainWindow *ui;
    audioThread *_audioThread = nullptr; // 采集线程指针
};
#endif // MAINWINDOW_H
3.3.2 实现文件(mainwindow.cpp)
cpp 复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_playBtn_clicked()
{
    if(!_audioThread){ // 开始采集
        _audioThread = new audioThread(this);
        _audioThread->start(); // 启动线程

        // 线程结束后重置指针并恢复按钮文字
        connect(_audioThread,&audioThread::finished,
                [this](){
                    _audioThread = nullptr;
                    ui->playBtn->setText("开始录视频");
        });

        ui->playBtn->setText("结束录视频");
    }else{ // 停止采集
        _audioThread->requestInterruption(); // 请求线程中断
        _audioThread = nullptr;
        ui->playBtn->setText("开始录视频");
    }
}

3.4 程序入口(main.cpp)

cpp 复制代码
#include "mainwindow.h"
#include <QApplication>

extern "C"{
#include <libavdevice/avdevice.h>
}

int main(int argc, char *argv[])
{
    avdevice_register_all(); // 注册FFmpeg所有设备

    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

四、关键知识点总结

4.1 FFmpeg 核心操作流程

  1. avdevice_register_all():注册所有 FFmpeg 设备,必须在操作设备前调用;
  2. av_find_input_format():根据格式名称(如 dshow)获取输入格式对象;
  3. avformat_open_input():打开设备 / 文件,初始化格式上下文;
  4. av_read_frame():从设备读取一帧视频数据(封装在 AVPacket 中);
  5. avformat_close_input():关闭设备,释放格式上下文资源;
  6. av_image_get_buffer_size():计算指定分辨率、像素格式的单帧视频数据大小。

4.2 Qt 多线程注意事项

  1. 耗时操作(如视频采集)必须放在QThread::run()方法中,避免阻塞 UI;
  2. 线程中断通过requestInterruption() + isInterruptionRequested()配合实现,优雅停止线程;
  3. 线程结束后通过finished信号关联deleteLater(),自动回收线程内存,避免内存泄漏;
  4. 主线程与子线程的交互尽量通过信号槽,避免直接操作子线程对象。

4.3 YUV 文件验证

采集完成后,可通过 FFmpeg 的 ffplay 工具验证 YUV 文件是否有效:

复制代码
ffplay -f rawvideo -pixel_format yuyv422 -video_size 1280x720 -framerate 10 D:/out.yuv

五、常见问题与解决

  1. avformat_open_input 失败

    • 检查设备名称是否正确(Windows 下可通过ffmpeg -list_devices true -f dshow -i dummy查看摄像头名称);
    • 确认 FFmpeg 编译时包含了 dshow 模块;
    • 检查权限(摄像头是否被其他程序占用)。
  2. 采集数据写入文件后无法播放

    • 确认像素格式、分辨率、帧率与 ffplay 播放参数一致;
    • 检查单帧数据大小计算是否正确,避免写入数据长度错误。
  3. 线程退出时崩溃

    • 确保退出时释放所有 FFmpeg 资源(AVPacket、AVFormatContext 等);
    • 析构函数中先请求中断,再调用 quit () 和 wait (),等待线程完全结束后再释放资源。

六、总结

本文通过 Qt+FFmpeg 实现了摄像头视频采集并保存为 YUV 格式文件,核心是掌握 FFmpeg 设备操作流程和 Qt 多线程编程规范。YUV 作为原始视频格式,是音视频开发的基础,理解其采集过程有助于后续学习编码(如 H.264/H.265)、封装(如 MP4)等进阶知识点。

相关推荐
软件架构师-叶秋2 小时前
FFMPEG之完整编译指南
ffmpeg
食指Shaye2 小时前
免费视频编辑软件FFmpeg 在windows上使用CMD命令行的操作说明
windows·ffmpeg
kisshuan123962 小时前
[特殊字符] RollingDepth:单目视频深度估计算法解析
算法·音视频
学术 学术 Fun2 小时前
图生视频还带声音?我把LTX 2.3塞进了自己的显卡里
音视频
艾莉丝努力练剑2 小时前
【QT】常用控件(一):初识控件,熟悉QWidget
android·linux·数据库·qt·学习·mysql·qt5
娇娇yyyyyy2 小时前
QT编程(5):几种常用的对话框
windows·qt·microsoft
王家视频教程图书馆2 小时前
测试开源视频播放器在RN webview中的运行方式
开源·音视频
奔跑吧 android2 小时前
【车载audio】【AudioService 01】【Android 音频子系统分析:按键音(Sound Effects)开启与关闭机制深度解析】
android·音视频·audioflinger·audioservice·audiohal
Ronin3053 小时前
【Qt常用控件】按钮类控件
开发语言·qt·常用控件·按钮类控件