Qt 基于FFmpeg的视频转换器 - 转GIF动图
Qt 基于FFmpeg的视频转换器 - 转GIF动图
- 引言
- 一、设计思路
- 二、核心源码
- 三、参考链接
引言

gif格式的动图可以通过连续播放一系列图像或视频片段来展示动态效果,使信息更加生动形象,可以很方便的嵌入到网页或者ppt中。上图展示了视频的前几帧转为gif动图的效果 (转了7%直接取消了)。
之前写过一个基于python的 MP4视频转GIF动图,速度略慢且不容易打包 (体积很大),故基于c++写一个小程序,方便日常使用. (这里推荐几个gif生成的小工具 - GifCam、ScreenGif.exe、LICEcap.exe等等 or 直接使用ffmpeg提供的小工具)
- 本文思路:基于
FFmpeg进行视频的读取解码成一张张图片,调用gif.h将图片写入gif
gif-h官方git地址:https://github.com/charlietangora/gif-h
一、设计思路
可参考之前的博客:Qt 基于FFmpeg的视频播放器 - QtFFmpegPlayer
-
- 和之前的视频播放器
play()函数类似,实现savetoGif()函数,将视频的一帧解码成图片后,立即写入gif文件
- 和之前的视频播放器
GifWriteFrame(&writer, image.bits(),static_cast<uint32_t>(avcodec_context->width),static_cast<uint32_t>(avcodec_context->height),static_cast<uint32_t>(100/this->m_fps), // 单位是1/100秒,即10ms8, true);frame_id++;qDebug()<<QString("当前转换第 %1 帧").arg(frame_id);emit sig_SendFrameNum(frame_id);
-
- 创建新的
FFmpegVideo类和新的处理线程,避免与播放线程冲突
- 创建新的
m_FFmpegProcessing = new FFmpegVideo();
m_ProcessingThread = new QThread(this);
m_FFmpegProcessing->moveToThread(m_ProcessingThread); // 移动到线程中
-
- 创建非模态的进度条,发送
sig_SendFrameNum帧数信号设置进度条进度 同时判断是否点击了进度条的按钮 (稳妥起见此连接设置为Qt::BlockingQueuedConnection- 确定同步执行对m_stopProcessing及时赋值)
- 创建非模态的进度条,发送
// 进度条progressDialog = new QProgressDialog();progressDialog->setMinimumWidth(300); // 设置最小宽度progressDialog->setWindowModality(Qt::NonModal); // 非模态,其它窗口正常交互 Qt::WindowModal 模态progressDialog->setMinimumDuration(0); // 等待0秒后显示progressDialog->setWindowTitle(tr("进度条框")); // 标题名progressDialog->setLabelText(tr("正在转换")); // 标签的progressDialog->setCancelButtonText(tr("放弃")); // 取消按钮progressDialog->setRange(0, static_cast<int>(m_FFmpegProcessing->m_frame_num)); // 考虑是否移换种方式显示进度条进度... 不使用帧数
// 进度条绑定
connect(m_FFmpegProcessing, &FFmpegVideo::sig_SendFrameNum, this, [&](int num){if(progressDialog->wasCanceled()){ // 弹窗的取消按钮m_FFmpegProcessing->m_stopProcessing = true;return;}progressDialog->setValue(num);}, Qt::BlockingQueuedConnection); // 发送信号后,先执行此内容 再继续执行线程,保证线程可以及时推出
使用
lambda表达式接收信号,需要注意其默认参数… 建议写完整防止奇奇怪怪的问题
Qt使用connect连接信号与lambda表达式需要注意:https://blog.csdn.net/qq_17769915/article/details/132609165
qt 如何使用 lamda 表达式接收线程中发射的数据,并在里面更新 UI ?https://www.cnblogs.com/cheungxiongwei/p/10895172.html
-
- 子线程中会判断
m_stopProcessing- 是否点击了进度条的退出按钮. 如果点击了按钮,最后也会执行GifEnd生成一个不完整的gif
- 子线程中会判断
while(this->m_stopProcessing == false)
GifEnd(&writer); // 取消之后是否需要保存不完整的gif? 暂时保存
使用事件循环,信号,stop变量,sleep阻塞,QWaitCondition+QMutex条件变量,退出子线程工作:https://blog.csdn.net/u012999461/article/details/127204493
-
- 进度条在函数中new的,子线程结束之后需释放
deleteLater。 还有一些小问题… 比如点两次另存为gif,可以同时弹出两个进度条等等 - 进度条没必要每次都new… 后续继续改进
- 进度条在函数中new的,子线程结束之后需释放
// 开始转换 在这里连接需注意Qt::UniqueConnection 使得连接唯一
connect(m_ProcessingThread, SIGNAL(started()), m_FFmpegProcessing, SLOT(savetoGif()), Qt::UniqueConnection);
connect(m_ProcessingThread, &QThread::finished, progressDialog, &QProgressDialog::deleteLater, Qt::UniqueConnection);
m_ProcessingThread->start();
m_ProcessingThread->quit();
二、核心源码
其他源码可参考我之前的博客:Qt 基于FFmpeg的视频播放器 - QtFFmpegPlayer
FFmpegVideo::savetoGif()
void FFmpegVideo::savetoGif()
{qDebug()<<"savetoGif";//avformat_seek_file()GifWriter writer = {};GifBegin(&writer, this->m_outfilename.toStdString().c_str(),static_cast<uint32_t>(avcodec_context->width),static_cast<uint32_t>(avcodec_context->height),static_cast<uint32_t>(100/this->m_fps), // 单位是1/100秒,即10ms8, true );// 初始化临时变量AVPacket* av_packet = static_cast<AVPacket*>(av_malloc(sizeof(AVPacket)));AVFrame *pFramein = av_frame_alloc(); //输入和输出的帧数据AVFrame *pFrameRGB = av_frame_alloc();uint8_t * pOutbuffer = static_cast<uint8_t *>(av_malloc( //缓冲区分配内存static_cast<quint64>(av_image_get_buffer_size(AV_PIX_FMT_RGBA,avcodec_context->width,avcodec_context->height,1))));// 初始化缓冲区av_image_fill_arrays(pFrameRGB->data,pFrameRGB->linesize,pOutbuffer,AV_PIX_FMT_RGB32,avcodec_context->width, avcodec_context->height, 1);// 格式转换SwsContext* pSwsContext = sws_getContext(avcodec_context->width, // 输入宽avcodec_context->height, // 输入高avcodec_context->pix_fmt, // 输入格式avcodec_context->width, // 输出宽avcodec_context->height, // 输出高AV_PIX_FMT_RGBA, // 输出格式SWS_BICUBIC, ///todonullptr,nullptr,nullptr);int ret=0;int frame_id = 0;this->m_stopProcessing = false;// 开始循环while(this->m_stopProcessing == false){if (av_read_frame(avformat_context, av_packet) >= 0){if (av_packet->stream_index == av_stream_index){avcodec_send_packet(avcodec_context, av_packet); // 解码ret = avcodec_receive_frame(avcodec_context, pFramein); // 获取解码输出if (ret == 0){sws_scale(pSwsContext, //图片格式的转换static_cast<const uint8_t* const*>(pFramein->data),pFramein->linesize, 0, avcodec_context->height,pFrameRGB->data, pFrameRGB->linesize);QImage *tmpImg = new QImage(static_cast<uchar *>(pOutbuffer),avcodec_context->width,avcodec_context->height,QImage::Format_RGBA8888);QImage image = tmpImg->copy();GifWriteFrame(&writer, image.bits(),static_cast<uint32_t>(avcodec_context->width),static_cast<uint32_t>(avcodec_context->height),static_cast<uint32_t>(100/this->m_fps), // 单位是1/100秒,即10ms8, true);frame_id++;qDebug()<<QString("当前转换第 %1 帧").arg(frame_id);emit sig_SendFrameNum(frame_id);//break;}}}}GifEnd(&writer); // 取消之后是否需要保存不完整的gif? 暂时保存av_packet_unref(av_packet);
}
-
MainWindow::saveVideo()
void MainWindow::saveVideo()
{if(!m_FFmpegVideo){return;}m_FFmpegProcessing->loadVideoFile(m_FFmpegVideo->m_filename); // 读取视频QFileInfo fileInfo(m_FFmpegProcessing->m_filename);QString filePath = QFileDialog::getSaveFileName(this, QObject::tr("Open File"),fileInfo.completeBaseName() + ".gif",QObject::tr("gif (*.gif) ;; All Files (*)"));m_FFmpegProcessing->m_outfilename = filePath; // 输出文件fileInfo.setFile(filePath);// 转GIF ------------int ret = fileInfo.suffix().compare(QString("gif"), Qt::CaseInsensitive);// 进度条progressDialog = new QProgressDialog();progressDialog->setMinimumWidth(300); // 设置最小宽度progressDialog->setWindowModality(Qt::NonModal); // 非模态,其它窗口正常交互 Qt::WindowModal 模态progressDialog->setMinimumDuration(0); // 等待0秒后显示progressDialog->setWindowTitle(tr("进度条框")); // 标题名progressDialog->setLabelText(tr("正在转换")); // 标签的progressDialog->setCancelButtonText(tr("放弃")); // 取消按钮progressDialog->setRange(0, static_cast<int>(m_FFmpegProcessing->m_frame_num)); // 考虑是否移换种方式显示进度条进度... 不使用帧数// 转换if(ret == 0){// 进度条绑定connect(m_FFmpegProcessing, &FFmpegVideo::sig_SendFrameNum, this, [&](int num){if(progressDialog->wasCanceled()){ // 弹窗的取消按钮m_FFmpegProcessing->m_stopProcessing = true;return;}progressDialog->setValue(num);}, Qt::BlockingQueuedConnection); // 发送信号后,先执行此内容 再继续执行线程,保证线程可以及时推出// 开始转换 在这里连接需注意Qt::UniqueConnection 使得连接唯一connect(m_ProcessingThread, SIGNAL(started()), m_FFmpegProcessing, SLOT(savetoGif()), Qt::UniqueConnection);connect(m_ProcessingThread, &QThread::finished, progressDialog, &QProgressDialog::deleteLater, Qt::UniqueConnection);m_ProcessingThread->start();m_ProcessingThread->quit();}
}
三、参考链接
-
- 直接调用工具:
用ffmpeg提供的工具将视频转成gif动图:https://blog.csdn.net/xindoo/article/details/127603896
Android录屏并利用FFmpeg转换成gif:https://blog.csdn.net/MingHuang2017/article/details/79186527
-
- 代码实现:
Qt项目中,实现屏幕截图并生成gif的详细示例:https://www.zhihu.com/tardis/bd/art/194303756
Qt编写自定义控件35-GIF录屏控件:https://developer.aliyun.com/article/712842
ffmpeg生成gif动图:https://www.jianshu.com/p/d9652fc2e3fd
FFmpeg进阶: 截取视频生成gif动图:https://zhuanlan.zhihu.com/p/628705382
相关文章:
Qt 基于FFmpeg的视频转换器 - 转GIF动图
Qt 基于FFmpeg的视频转换器 - 转GIF动图 引言一、设计思路二、核心源码三、参考链接 引言 gif格式的动图可以通过连续播放一系列图像或视频片段来展示动态效果,使信息更加生动形象,可以很方便的嵌入到网页或者ppt中。上图展示了视频的前几帧转为gif动图的…...
HTML新春烟花盛宴
目录 写在前面 烟花盛宴 完整代码 修改文字...
第十四届蓝桥杯c++研究生组
A 混乘数字 关键思路是求每个十进制数的数字以及怎么在一个数组中让判断所有的数字次数相等。 求每个十进制的数字 while(n!0){int x n%10;//x获取了n的每一个位数字n/10;}扩展:求二进制的每位数字 (注意:进制转换、1的个数、位运算&#…...
KDD 2024|基于隐空间因果推断的微服务系统根因定位
简介:本文介绍了由清华大学、南开大学、eBay、微软、中国科学院计算机网络信息中心等单位共同合作的论文《基于隐空间因果推断的受限可观测性场景的微服务系统根因定位》。该论文已被KDD 2024会议录用。 论文标题:Microservice Root Cause Analysis Wit…...
白鹭群优化算法,原理详解,MATLAB代码免费获取
白鹭群优化算法(Egret Swarm Optimization Algorithm,ESOA)是一种受自然启发的群智能优化算法。该算法从白鹭和白鹭的捕食行为出发,由三个主要部分组成:坐等策略、主动策略和判别条件。将ESOA算法与粒子群算法(PSO)、遗传算法(GA)…...
【源码】2024完美运营版商城/拼团/团购/秒杀/积分/砍价/实物商品/虚拟商品等全功能商城
后台可以自由拖曳修改前端UI页面 还支持虚拟商品自动发货等功能 前端UNIAPP 后端PHP 一键部署版本 获取方式: 微:uucodes...
Java-数组内存解析
文章目录 1.内存的主要结构:栈、堆2.一维数组的内存解析3.二维数组的内存解析 1.内存的主要结构:栈、堆 2.一维数组的内存解析 举例1:基本使用 举例2:两个变量指向一个数组 3.二维数组的内存解析 举例1: 举例2&am…...
Spring Cache --学习笔记
一、概述 Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。 Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如: EHCache Caffeine Redis(常…...
NTP服务的DDoS攻击:原理和防御
NTP协议作为一种关键的互联网基础设施组件,旨在确保全球网络设备间的时钟同步,对于维护数据一致性和安全性至关重要。然而,其设计上的某些特性也为恶意行为者提供了发动大规模分布式拒绝服务(DDoS)攻击的机会。以下是NTP服务DDoS攻击及其防御…...
【面试干货】事务的并发问题(脏读、不可重复读、幻读)与解决策略
【面试干货】事务的并发问题(脏读、不可重复读、幻读)与解决策略 一、脏读(Dirty Read)二、不可重复读(Non-repeatable Read)三、幻读(Phantom Read)四、总结 💖The Begi…...
函数式接口:现代编程的利器
1. 引言 在软件开发的演进过程中,函数式编程(Functional Programming, FP)逐渐显露头角,成为解决复杂问题的有效工具之一。函数式接口作为函数式编程的核心概念之一,其重要性不言而喻。本文将深入探讨函数式接口的概念…...
2022职称继续教育--深入实施新时代人才强国战略 加快建设世界重要人才中心和创新高地
单选题(共7题,每题5分) 1、()实行职位职级制工资为主。 D、中长线科研重要岗位人员 2、建设世界重要人才中心和创新高地有()个阶段目标。 B、三 3、综合国力竞争说到底是(…...
kube-prometheus-stack 识别 k8s 集群内所有的 ServiceMonitor 和 PrometheusRule
默认情况下,kube-prometheus-stack 只自己创建的 ServiceMonitor,如果 k8s 集群内有多个非 kube-prometheus-stack 创建的 ServiceMonitor,不会被识别到。PrometheusRule 同理。 要识别所有的 ServiceMonitor 和 PrometheusRule ,…...
Android 图片加载glide库 一次通关
前言 Glide是一个由Bumptech开发的开源图片加载库,专门用于Android平台。它被广泛应用于Android应用中,以简化图片加载过程,并提高性能和效率。 Glide能够快速加载图片,同时减少页面加载时间和内存消耗。Glide具有强大的缓存机制…...
Spring OAuth2:开发者的安全盾牌!(上)
何利用Spring OAuth2构建坚不可摧的安全体系?如何使用 OAuth2 从跨域挑战到性能优化,每一个环节都为你的应用保驾护航? 文章目录 Spring OAuth2 详解1. 引言简述OAuth2协议的重要性Spring Framework对OAuth2的支持概述 2. 背景介绍2.1 OAuth2…...
设计模式使用(成本扣除)
前言 名词解释 基础名词 订单金额:用户下单时支付的金额,这个最好理解 产品分成:也就是跟其他人合做以后我方能分到的金额,举个例子,比如用户订单金额是 100 块,我方的分成是 80%,那么也就是…...
输入输出(2)——C++的标准输出流
目录 一、C的标准输出流 (一)cout、cerr和clog流对象 1、cout 流对象 2、cerr 流对象 3、clog流对象 (二)用函数put输出字符 (三)用函数 write 输出字符 一、C的标准输出流 标准输出流——流向标准输…...
C语言序列化和反序列化--TPL(一)
TPL TPL说明网站 C语言中高效的序列化 您可以使用tpl快速轻松地存储和重新加载C数据。Tpl是一个用于序列化C数据的库。数据以自然二进制形式存储。该API很小,并试图保持“不碍事”。Tpl可以序列化许多C数据类型,包括结构。Tpl与文件、内存缓冲区和文件…...
Session + JWT + Cookie
00:HTTP无状态(为了保持状态,前端好麻烦,又要自己存,又要想办法带出去,于是使用cookie) 01:Cookie 将用户信息,在每次请求时候 带给后端(但是自己存储大小有…...
PaddleOCR2.7+Qt5
章节一:Windows 下的 PIP 安装 官网安装教程地址 按照里面的教程去安装 如果使用cuda版本的还要安装tensorrt,不然后面运行demo程序的程序会报如下错。 下载TensorRT 8版本,tensorrt下载地址 章节二:编译源码 进入官网源码地址 下…...
EPWM模块影子寄存器的加载机制与应用场景解析
1. EPWM模块影子寄存器基础概念 第一次接触EPWM模块的影子寄存器时,我也被这个"影子"的概念绕晕了。后来在实际项目中调试电机控制才发现,这个机制简直是PWM波形控制的"安全气囊"。简单来说,影子寄存器就是活动寄存器的&…...
【ZGC性能调优终极指南】:20年JVM专家亲授5大实战瓶颈突破法
第一章:ZGC核心机制与性能边界全景透视ZGC(Z Garbage Collector)是JDK 11引入的低延迟垃圾收集器,专为处理TB级堆内存与毫秒级停顿目标而设计。其核心突破在于并发标记、并发重定位与着色指针(Colored Pointers&#x…...
Ryzen SDT调试工具:解锁AMD处理器潜能的系统级配置平台
Ryzen SDT调试工具:解锁AMD处理器潜能的系统级配置平台 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://g…...
MAI-UI-8B入门:Node.js环境配置与自动化测试
MAI-UI-8B入门:Node.js环境配置与自动化测试 1. 开篇:为什么选择MAI-UI-8B进行自动化测试 如果你正在寻找一个能够真正理解图形界面、像真人一样操作应用的自动化测试方案,MAI-UI-8B绝对值得关注。这个由阿里通义实验室开源的GUI智能体模型…...
南京大学发布“视频侦探“系统:让AI像侦探一样从长视频中找线索
这项由南京大学与中科院自动化所联合进行的研究发表于2026年的计算机视觉与模式识别(CVPR)会议,论文编号为arXiv:2603.22285。有兴趣深入了解的读者可以通过该编号查询完整论文内容。当我们观看一部两小时的电影时,想要回答"主角在什么时候第一次露…...
MinerU智能文档理解镜像:财务报表自动识别实战体验
MinerU智能文档理解镜像:财务报表自动识别实战体验 1. 引言:财务文档处理的痛点与机遇 在财务工作中,我们经常需要处理各种格式的财务报表——PDF扫描件、Excel截图、纸质文档照片等。传统的手工录入方式不仅效率低下,还容易出错…...
Pixel Language Portal效果展示:多轮对话上下文跨语种一致性保持
Pixel Language Portal效果展示:多轮对话上下文跨语种一致性保持 1. 产品概览 **像素语言跨维传送门(Pixel Language Portal)**是一款突破性的多语言交互工具,基于腾讯Hunyuan-MT-7B核心引擎构建。不同于传统翻译工具的机械感,它将语言转换…...
MIKE URBAN中污水处理厂如何进行概化
01 前言应用厂网一体化耦合模型研究水厂间调度和厂前溢流入河污染量等内容时,由于不需要关注污水处理厂内部的具体处理工艺,需要对污水处理厂的关键设施进行概化处理。02 水厂资料收集收集污水处理的工艺流程图和设施设计参数。依据厂网一体化模型的研究…...
Agent上线后有专人运营支持吗?深度解析AI Agent的全生命周期运维保障体系
随着AI Agent(智能体)在企业业务场景中的深度渗透,从简单的流程自动化到复杂的跨境贸易、研发辅助,企业对“数字员工”的期待已不再局限于单次的开发交付,而是转向了长期的稳定运行与持续进化。对于许多决策者而言&…...
告别僵硬数字人:用InfiniteTalk V2的WebUI,让照片开口唱歌(保姆级参数设置指南)
告别僵硬数字人:用InfiniteTalk V2的WebUI,让照片开口唱歌(保姆级参数设置指南) 当一张静态照片突然流畅地唱起你上传的歌曲,嘴角弧度与歌词节奏完美匹配,甚至伴随旋律自然摆动头部——这种魔法般的体验&am…...
