Qt+FFmpeg简单实现录屏并保存为MP4视频

news2024/11/13 3:38:11

一、前言

        最近需要实现一个录屏功能,网上查了好多资料,最可靠的方案当然还是用FFmpeg实现,但是也踩了很多坑,包括FFmpeg版本问题,vs2019里相关编译问题,FFmpeg也不太熟悉,很多代码不太容易看懂,想要按自己熟悉的方式实现录屏功能,花了一番功夫。

        如果你进来了,可以不用走了,应该能帮到你。

二、环境

        VS2019 + Qt5 + FFmpeg4.2.2

        FFmpeg的版本比较重要,不同的版本很多函数没法通用。

0、查看FFmpeg版本:

extern "C"
{
#include "libavutil/version.h"
}

const char* versionInfo = av_version_info();
qDebug() << "FFmpeg Version: " << versionInfo;

1、vs2019配置FFmpeg

        右键点击项目——>属性——>VC++目录——>添加包含目录和库目录为FFmpeg的include和lib目录。

         右键点击项目——>属性——>链接器——>输入——>添加附加依赖项,avcodec.lib,avformat.lib,avutil.lib,avdevice.lib,avfilter.lib,postproc.lib,swresample.lib,swscale.lib

三、实现代码

1、main.cpp

#include "screenRecord.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    screenRecord w;
    w.show();
    return a.exec();
}

2、screenRecord.h

#pragma once
#pragma execution_character_set("utf-8")

#include <QtWidgets/QMainWindow>
#include "ui_screenRecord.h"
#include "ffmpegVideoThread.h"
#include <QDateTime>
#include <QTimer>
#include <QFileDialog>
#include <QMessageBox>

class screenRecord : public QMainWindow
{
    Q_OBJECT

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

private slots:
    void on_pushButton_startRecord_clicked();
    void on_pushButton_savePath_clicked();

    void slotShowTime();

private:
    Ui::screenRecordClass ui;

    ffmpegVideoThread* m_ffmpegVideoThread;
    bool hasScreenRecord = false;
    QTimer* m_timer;
    int _h, _m, _s;
};

3、screenRecord.cpp.

#include "screenRecord.h"

screenRecord::screenRecord(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);

    this->setWindowTitle("录屏");
    this->setWindowFlags(Qt::WindowCloseButtonHint);

    ui.pushButton_startRecord->setEnabled(false);

    m_timer = new QTimer();
    connect(m_timer, &QTimer::timeout, this, &screenRecord::slotShowTime);
    _h = 0;
    _m = 0;
    _s = 0;
}

screenRecord::~screenRecord()
{}

void screenRecord::on_pushButton_startRecord_clicked()
{
    if (ui.pushButton_startRecord->text() == "开始录屏") {
        ui.pushButton_startRecord->setText("停止录屏");
        ui.pushButton_savePath->setEnabled(false);
        //开始录屏
        m_ffmpegVideoThread = new ffmpegVideoThread();
        connect(m_ffmpegVideoThread, &ffmpegVideoThread::sigRecordError, this, [=](QString errorStr) {
            QMessageBox::warning(this, "错误", errorStr, "确定");
            m_timer->stop();
            });
        m_ffmpegVideoThread->start();
        m_ffmpegVideoThread->setOutMpeFileName(ui.lineEdit->text().trimmed().toStdString().c_str());
        hasScreenRecord = true;
        _h = 0;
        _m = 0;
        _s = 0;
        if (m_timer->isActive())
            m_timer->stop();
        m_timer->start(1000);
    }
    else {
        ui.pushButton_savePath->setEnabled(true);
        ui.pushButton_startRecord->setEnabled(true);
        ui.pushButton_startRecord->setText("开始录屏");
        //停止录屏
        m_ffmpegVideoThread->setStoped(true);
        m_ffmpegVideoThread->quitThread();
        m_ffmpegVideoThread->quit();
        m_ffmpegVideoThread->wait();
        m_ffmpegVideoThread->deleteLater();
        m_ffmpegVideoThread = NULL;
        hasScreenRecord = false;
        m_timer->stop();
    }
}

void screenRecord::on_pushButton_savePath_clicked()
{
    QString dirpath = QFileDialog::getExistingDirectory(this, "选择目录", "./", QFileDialog::ShowDirsOnly);
    ui.lineEdit->setText(dirpath);
    ui.pushButton_startRecord->setEnabled(true);
}

void screenRecord::slotShowTime()
{
    _s++;
    if (_s >= 60) {
        _m++;
        _s = 0;
    }
    if (_m >= 60) {
        _h++;
        _m = 0;
    }
    QString showTime = QString("%1:%2:%3").arg(_h).arg(_m).arg(_s);
    ui.label_recordTime->setText(showTime);
}

4、screenRecord.ui

 5、ffmpegVideoThread.h

#pragma once
#pragma execution_character_set("utf-8")
#include <QThread>
#include <QImage>
#include <QDebug>
#include <QDateTime>

#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <conio.h>
#include <SDKDDKVer.h>

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "libavutil/audio_fifo.h"
#include "libavutil/version.h"
};

class ffmpegVideoThread : public QThread
{
    Q_OBJECT
public:
    ffmpegVideoThread(QObject* parent = Q_NULLPTR);
    ~ffmpegVideoThread();
    void setStoped(bool);
    void quitThread();
    void setOutMpeFileName(QString outFileName);

signals:
    void sigRecordError(QString);

protected:
    virtual void run();

private:
    bool stopped = false;
    bool quited = false;
    QString m_outFileName;
    //ffmpeg
    AVFormatContext* pFormatCtx_Video = NULL,* pFormatCtx_Out = NULL;
    AVCodecContext* pCodecCtx_Video;
    AVCodec* pCodec_Video;
    AVFifoBuffer* fifo_video = NULL;
    int VideoIndex;
    CRITICAL_SECTION VideoSection;
    SwsContext* img_convert_ctx;
    int frame_size = 0;
    uint8_t* picture_buf = NULL, * frame_buf = NULL;
    bool bCap = true;

    static DWORD WINAPI ScreenCapThreadProc(LPVOID lpParam);

    int OpenVideoCapture();
    int OpenOutPut(const char* outFileName);
};

6、ffmpegVideoThread.cpp部分代码

ffmpegVideoThread.cpp部分代码暂不完全开放,有需要私聊我。

void ffmpegVideoThread::run()
{
	av_register_all();
	avdevice_register_all();
	if (OpenVideoCapture() < 0)
	{
		emit sigRecordError("视频捕获打开错误!");
		return;
	}

	if (m_outFileName.isEmpty())
		m_outFileName = "./";
	QDateTime current_date_time = QDateTime::currentDateTime();
	QString current_date = current_date_time.toString("/yyyyMMdd_hhmmss");
	QString SaveFile = m_outFileName + current_date + ".mp4";
	if (OpenOutPut(SaveFile.toStdString().c_str()) < 0)
	{
		emit sigRecordError("不能打开输出文件句柄!\n输出文件:" + SaveFile);
		return;
	}

	InitializeCriticalSection(&VideoSection);

	AVFrame* picture = av_frame_alloc();
	int size = avpicture_get_size(pFormatCtx_Out->streams[VideoIndex]->codec->pix_fmt,
		pFormatCtx_Out->streams[VideoIndex]->codec->width, pFormatCtx_Out->streams[VideoIndex]->codec->height);
	picture_buf = new uint8_t[size];

	avpicture_fill((AVPicture*)picture, picture_buf,
		pFormatCtx_Out->streams[VideoIndex]->codec->pix_fmt,
		pFormatCtx_Out->streams[VideoIndex]->codec->width,
		pFormatCtx_Out->streams[VideoIndex]->codec->height);

	//开始捕获屏幕线程
	CreateThread(NULL, 0, ScreenCapThreadProc, this, 0, NULL);

	int64_t cur_pts_v = 0, cur_pts_a = 0;
	int VideoFrameIndex = 0, AudioFrameIndex = 0;

	while (!quited)
	{
		while (!stopped) {
			//从fifo读取数据
			if (av_fifo_size(fifo_video) < frame_size && !bCap)
			{
				cur_pts_v = 0x7fffffffffffffff;
			}
			if (av_fifo_size(fifo_video) >= size)
			{
				EnterCriticalSection(&VideoSection);
				av_fifo_generic_read(fifo_video, picture_buf, size, NULL);
				LeaveCriticalSection(&VideoSection);

				avpicture_fill((AVPicture*)picture, picture_buf,
					pFormatCtx_Out->streams[VideoIndex]->codec->pix_fmt,
					pFormatCtx_Out->streams[VideoIndex]->codec->width,
					pFormatCtx_Out->streams[VideoIndex]->codec->height);

				picture->pts = VideoFrameIndex * ((pFormatCtx_Video->streams[0]->time_base.den / pFormatCtx_Video->streams[0]->time_base.num) / 15);

				int got_picture = 0;
				AVPacket pkt;
				av_init_packet(&pkt);

				pkt.data = NULL;
				pkt.size = 0;
				int ret = avcodec_encode_video2(pFormatCtx_Out->streams[VideoIndex]->codec, &pkt, picture, &got_picture);
				if (ret < 0)
				{
					//编码错误,不理会此帧
					continue;
				}

				if (got_picture == 1)
				{
					pkt.stream_index = VideoIndex;
					pkt.pts = av_rescale_q_rnd(pkt.pts, pFormatCtx_Video->streams[0]->time_base,
						pFormatCtx_Out->streams[VideoIndex]->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
					pkt.dts = av_rescale_q_rnd(pkt.dts, pFormatCtx_Video->streams[0]->time_base,
						pFormatCtx_Out->streams[VideoIndex]->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));

					pkt.duration = ((pFormatCtx_Out->streams[0]->time_base.den / pFormatCtx_Out->streams[0]->time_base.num) / 15);

					cur_pts_v = pkt.pts;

					ret = av_interleaved_write_frame(pFormatCtx_Out, &pkt);

					av_free_packet(&pkt);
				}
				VideoFrameIndex++;
			}			
		}
		bCap = false;
		Sleep(2000);//等待采集线程关闭
	}

	delete[] picture_buf;
	av_fifo_free(fifo_video);
	av_write_trailer(pFormatCtx_Out);
	avio_close(pFormatCtx_Out->pb);
	avformat_free_context(pFormatCtx_Out);

	if (pFormatCtx_Video != NULL)
	{
		avformat_close_input(&pFormatCtx_Video);
		pFormatCtx_Video = NULL;
	}
}

四、运行效果

ffmpeg实现录屏功能

五、最后

      FFmpeg4.2.2编译好了的,直接使用。FFmpeg4.2.2已编译好的

      FFmpeg我并不熟悉,只求能正常跑通使用就行了,花了好多时间踩坑,在此记录一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/443162.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot+vue学生毕业离校系统(源码+说明文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的学生毕业离校系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风…

Flutter Android问题记录 - 升级Android Studio 2022.2.1版本后运行项目报错

文章目录 前言开发环境问题描述问题分析解决方案补充内容最后 前言 最近一个Flutter项目有新需求&#xff0c;开发时一直是在iOS设备上运行&#xff0c;花了几天做完后运行到Android设备测试&#xff0c;结果项目构建失败了。 开发环境 Flutter: 3.7.11Android Studio: 2022…

java工程师前景分析

本篇文章主要讲解java工程师的职业就业环境及职业剖析 作者&#xff1a;任聪聪 日期&#xff1a;2023年4月18日 java工程师目前属于很饱和的一个岗位&#xff08;2023年4月18日&#xff09;&#xff0c;但也会伴随劳动市场的变化出现不饱和的情况的。 实际上对于想入行it行业的…

PHP下的MySQL的基础学习

文章目录 一、MySQL LIKE 子句二、MySQL UNION 操作符三、MySQL 排序四、MySQL GROUP BY 语句五、MySQL 连接的使用总结 一、MySQL LIKE 子句 我们知道在 MySQL 中使用 SQL SELECT 命令来读取数据&#xff0c; 同时我们可以在 SELECT 语句中使用 WHERE 子句来获取指定的记录。…

virsh dump 内核转储 crash 分析swapper内核进程

为了节约时间&#xff0c;虚拟机配置4G内存&#xff0c;避免dump时间过长、文件过大 <memory>4194304</memory><currentMemory>4194304</currentMemory> //memory这两个值最好设成一样<vcpu>4</vcpu>vnc登录虚拟机 编写一个CPU消耗程序a.…

Word控件Spire.Doc 【字体】教程(1):在 Word 中更改字体颜色

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

【AI生产力工具】ChatPDF:将 PDF 文档转化为交互式阅读体验的利器

文章目录 简介一、ChatPDF 是什么&#xff1f;二、ChatPDF 的优势三、ChatPDF 的应用场景四、如何使用 ChatPDF&#xff1f;五、结语 简介 随着数字化时代的发展&#xff0c;PDF 文件已经成为了日常工作和学习中不可或缺的一部分。然而&#xff0c;仅仅将 PDF 文件上传或下载并…

网络抓包分析【IP,ICMP,ARP】以及 IP数据报,MAC帧,ICMP报和ARP报的数据报格式

网络抓包分析&#xff0c;IP数据报&#xff0c;MAC帧&#xff0c;ICMP报&#xff0c;ARP报格式以及不同网络通信的过程。 网络抓包工具 wireshark以太网v2MAC帧IP数据报格式ICMP报文格式ARP协议及ARP报文格式抓包分析IP数据报抓包分析icmp数据报的抓包分析ARP数据报的抓包分析 …

Windows下Release版本Qt程序生成日志和dump文件(用于程序异常崩溃检测)

文章目录 前言一、基于qInstallMessageHandler生成输出日志二、基于qBreakpad生成dump文件三、基于DbgHelp和SetUnhandledExceptionFilter生成dump文件四、示例完整代码五、下载链接总结 前言 在实际项目开发时&#xff0c;一般打包发布给客户的程序是release版本Qt程序&#…

Spark大数据处理学习笔记(2.2)搭建Spark Standalone集群

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/DrziJ】 文章目录 一、在master虚拟机上安装配置Spark1.1 将spark安装包上传到master虚拟机1.2 将spark安装包解压到指定目录1.3 配置spark环境变量1.4 编辑spark环境配置文件1.5 创建slaves文件&…

Unity记录3.3-地图-柏林噪声生成 2D 地图

文章首发及后续更新&#xff1a;https://mwhls.top/4486.html&#xff0c;无图/无目录/格式错误/更多相关请至首发页查看。 新的更新内容请到mwhls.top查看。 欢迎提出任何疑问及批评&#xff0c;非常感谢&#xff01; 汇总&#xff1a;Unity 记录 摘要&#xff1a;柏林噪声生成…

再学C语言51:C库中的字符串函数(3)

一、strcpy()函数 功能&#xff1a;复制字符串&#xff0c;在字符串中的作用等价于赋值运算符 示例代码&#xff1a; /* test of strcpy() function */ #include <stdio.h> #include <string.h>int main(void) {char arr1[] "Easy doesnt enter into grow…

基于html+css的图片展示19

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

【AI生产力工具】Midjourney:为创意人士提供创造性灵感和支持的工具

文章目录 一、Midjourney是什么&#xff1f;二、Midjourney的优势三、Midjourney的应用四、结语 在现代社会&#xff0c;创意和创新成为越来越重要的能力。然而&#xff0c;创意灵感的获取却不是一件容易的事情&#xff0c;这就需要我们使用一些辅助工具来帮助我们发现和实现创…

照片尺寸怎么修改,3大工具推荐

照片尺寸怎么修改&#xff1f;对于许多人来说&#xff0c;调整图片的尺寸可能是一个日常任务&#xff0c;无论是个人或者工作上都可能会遇到这个需求。适当地调整图片的尺寸可以让图片更具专业性和美观性&#xff0c;而且能够减小文件大小&#xff0c;提高图片的加载速度。在电…

2023年4月北京/西安/郑州/深圳CDGA/CDGP数据治理认证考试报名

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

如何用 Leangoo领歌做迭代规划及迭代执行。

迭代是敏捷开发的核心&#xff0c;正确的迭代可以帮助敏捷团队提高工作交付速度&#xff0c;今天&#xff0c;我们深度看下如何用Leangoo领歌敏捷工具进行迭代规划和迭代执行&#xff0c;高效落地 Scrum。 1、确定迭代需要做的需求 在需求看板中&#xff0c;将已经梳理好的用…

C++ -3- 类和对象 (中) | 构造函数与析构函数(一)

文章目录 1.类的6个默认成员函数2.构造函数3.析构函数构造函数与析构函数应用场景缺省值初始化 1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自…

网络安全必学渗透测试流程

这个靶场是一个对渗透新手很友好的靶场。而且&#xff0c;该靶场包含了渗透测试的信息收集&#xff0c;漏洞利用和权限提升的全过程&#xff0c;对新手理解渗透测试的流程有很好的帮助。 靶场地址&#xff1a;https://hackmyvm.eu/machines/machine.php?vmHundred 靶场基本情…

关系数据库(查询优化)

选择操作的实现select * from student where Sno201212128; 简单的全表扫描法优点&#xff1a;对于小表简单有效 缺点&#xff1a;对于大表顺序扫描浪费时间效率低下索引扫描方法 连接操作的实现 连接操作是查询处理中最耗时的操作之一 select *from student,sc where s…