当前位置: 首页 > news >正文

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 线程)。

#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 头文件与宏定义
#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 构造与析构函数
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 方法)
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)
#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)
#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)

#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)等进阶知识点。

http://www.jsqmd.com/news/449303/

相关文章:

  • 教师-学生模型自学习:小数据场景下YOLO河道排口检测的工程实践
  • 宝塔面板PHP无法启动的N中场景和解决方案
  • WinForm项目完美适配麒麟系统全攻略
  • ssm基于java的东风锻造有限公司点检管理系统(源码+文档+调试+jsp)
  • 为什么 OpenClaw 更适合具备工程系统思维的人?一次实际使用后的思考
  • Git merge 策略
  • 抽样方法(常用)
  • QML学习笔记(六十二)动画相关:PathAnimation路径动画
  • 112312
  • 第二十九天--我的心,我的眼,我的身
  • 二分法入门到进阶:从“查数字”到“二分答案”,一篇真正讲明白
  • C++保留几位小数
  • HBuilderX新手入门:快速搭建网页指南
  • 从0到1:Android组件化架构搭建秘籍
  • nodejs基于vue的集客物料物资盘点管理系统vue
  • OpenClaw 全平台安装部署教程(Windows/macOS/云服务器)
  • Spring AI Alibaba + Nacos 分布式架构实战教程(非常详细),企业级 MCP 从入门到精通,收藏这一篇就够了!
  • 别再疯狂百度了!AI时代,程序员如何优雅地“带薪摸鱼”
  • ssm基于java的电影购票系统(源码+文档+调试+vue+前后端分离)
  • 打开软件就弹出mfc71u.dll如何修复? 附免费下载方法分享
  • Git 二进制文件管理
  • 计算机毕业设计源码:电商商品数据智能分析系统 Django requests爬虫 ARIMA预测 可视化 分布式计算 数据可视化 人工智能 deepseek 大模型 大数据 agent(建议收藏)✅
  • GDPR合规检查表:AI应用的差分隐私实现
  • 2026年安阳商祺网络市场口碑如何,选购要点揭秘 - 工业设备
  • ssm基于java的青少年体质健康数据管理与分析系统(源码+文档+调试+vue+前后端分离)
  • 论文“瘦身”新革命:书匠策AI如何用“智能手术刀”精准降重+去AI味
  • 靠谱的跨境电商退税申报系统,浙江地区有哪些推荐的厂商? - 工业品牌热点
  • Agent 时代的身份危机:为什么传统身份模型已经失效
  • 学术写作的“分子料理师”:书匠策AI如何将重复文本“提纯”为原创佳肴
  • 2026年用户口碑最佳的固态硬盘品牌推荐:五款高可靠性产品真实评价对比 - 品牌推荐