Qt 基于FFmpeg的视频转换器 - 转GIF动图

Qt 基于FFmpeg的视频转换器 - 转GIF动图

  • 引言
  • 一、设计思路
  • 二、核心源码
  • 三、参考链接

引言

在这里插入图片描述 在这里插入图片描述

gif格式的动图可以通过连续播放一系列图像或视频片段来展示动态效果,使信息更加生动形象,可以很方便的嵌入到网页或者ppt中。上图展示了视频的前几帧转为gif动图的效果 (转了7%直接取消了)。

之前写过一个基于python的 MP4视频转GIF动图,速度略慢且不容易打包 (体积很大),故基于c++写一个小程序,方便日常使用. (这里推荐几个gif生成的小工具 - GifCamScreenGif.exeLICEcap.exe等等 or 直接使用ffmpeg提供的小工具)

  • 本文思路:基于FFmpeg进行视频的读取解码成一张张图片,调用gif.h将图片写入gif

gif-h官方git地址:https://github.com/charlietangora/gif-h

一、设计思路

可参考之前的博客:Qt 基于FFmpeg的视频播放器 - QtFFmpegPlayer

    1. 和之前的视频播放器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);
    1. 创建新的FFmpegVideo类和新的处理线程,避免与播放线程冲突
m_FFmpegProcessing = new FFmpegVideo();
m_ProcessingThread = new QThread(this);
m_FFmpegProcessing->moveToThread(m_ProcessingThread);  // 移动到线程中
    1. 创建非模态的进度条,发送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

    1. 子线程中会判断m_stopProcessing - 是否点击了进度条的退出按钮. 如果点击了按钮,最后也会执行GifEnd生成一个不完整的gif
while(this->m_stopProcessing == false)
GifEnd(&writer);   // 取消之后是否需要保存不完整的gif?  暂时保存

使用事件循环,信号,stop变量,sleep阻塞,QWaitCondition+QMutex条件变量,退出子线程工作:https://blog.csdn.net/u012999461/article/details/127204493

    1. 进度条在函数中new的,子线程结束之后需释放deleteLater。 还有一些小问题… 比如点两次另存为gif,可以同时弹出两个进度条等等 - 进度条没必要每次都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

  1. 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);
}
    1. 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();}
}

三、参考链接

    1. 直接调用工具:

用ffmpeg提供的工具将视频转成gif动图:https://blog.csdn.net/xindoo/article/details/127603896
Android录屏并利用FFmpeg转换成gif:https://blog.csdn.net/MingHuang2017/article/details/79186527

    1. 代码实现:

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

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

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

相关文章

基于Paraformer的alpha-token强制对齐

1. 基本原理 CIF 作为Parafoemr的核心模块&#xff0c;用于预测字数和生成声学向量&#xff0c;从而实现了单轮非自回归解码。其中字数的预测主要通过encoder输出系数alpha的累计得分&#xff0c;满足通关阈值β1.0即可产生一个token&#xff0c;其中alpha曲线在一定程度上呈现…

CSS:浮动

▐ 文档流&#xff1a; 由于网页默认是一个二维平面&#xff0c;当我们在网页中一行行摆放标签时&#xff0c;块标签会独占一行&#xff0c;行标签则只占自身大小&#xff0c;这种情况下要实现网页布局就很麻烦了&#xff0c;所以我们就需要通过一些方法来改变这种默认的布局方…

centos7离线安装pthon3.8

centos7离线安装pthon3.8 因服务器无外网环境&#xff0c;所以事先需要把所有离线的依赖都准备好。 安装前的准备 先在有外网环境的机器上准备依赖 安装 centos-release-scl 第三方yum源 yum install centos-release-scl安装 yum 依赖下载插件 yum install yum-plugin-do…

GO语言 gin框架 简述

原文地址 基本路由 Go语言中文文档 一、简介 Gin是一个golang的轻量级web框架&#xff0c;性能不错&#xff0c;API友好。 Gin支持Restful风格的API&#xff0c;可以直接从URL路径上接收api参数或者URL参数&#xff0c;也可是使用json或者表单 数据绑定的方式接收参数。 Gin响…

【传知代码】BERT论文解读及情感分类实战-论文复现

文章目录 概述原理介绍BERT模型架构任务1 Masked LM&#xff08;MLM&#xff09;任务2 Next Sentence Prediction (NSP)模型输入下游任务微调GLUE数据集SQuAD v1.1 和 v2.0NER 情感分类实战IMDB影评情感数据集数据集构建模型构建 核心代码超参数设置训练结果注意事项 小结 本文…

AIOps在线评测基准首阶段建设完成,面向社区发布真实运维数据!

本文根据必示科技算法研究员、产品总监聂晓辉博士在2024 CCF国际AIOps挑战赛线下宣讲会上的演讲整理成文。 2024年1月份OpenAIOps社区成立&#xff0c;随着越来越多的社区成员加入&#xff0c;各项工作在有条不紊的推进中。在线评测基准系统&#xff08;AIOps Live Benchmark&a…

积鼎CFDPro水文水动力模型,专为中小流域洪水“四预”研发的流体仿真技术

水动力模型与水文模型是水利工程与水文学研究中不可或缺的两大工具。水动力模型着重于流体运动的动力学机制&#xff0c;通过一系列方程组捕捉水流的时空变化&#xff0c;而概念性水文模型则侧重于流域尺度的水文循环过程&#xff0c;利用物理概念与经验关系进行近似模拟。两者…

Windows系统部署YOLOv5 v6.1版本的训练与推理环境保姆级教程

文章目录 一 概述二 依赖环境(prerequisites)2.1 硬件环境2.2 软件环境 三 环境安装3.1 创建并激活虚拟环境3.2 安装Pytorch与torchvision3.3 校验Pytorch安装3.4 下载 YOLOv5 v6.1 源码3.5 安装 YOLOv5 依赖3.6 下载预训练模型3.7 安装其他依赖3.8 测试环境安装3.9 测试训练流…

jupyter notebook更改位置

1.找到jupyer的配置文件 一般在c盘用户的.jupter文件夹下 2. 用记事本打开这个配置文件&#xff0c;定位到c.NotebookApp.notebook_dir /path_to_your_directory 替换你的位置 3.找到jupyer图标的位置&#xff0c;打开属性 添加要存放的位置在目标文件的末尾&#xff0c;重新…

一个全面了解Xilinx FPGA IP核的窗口:《Xilinx系列FPGA芯片IP核详解》(可下载)

随着摩尔定律的逐渐放缓&#xff0c;传统的芯片设计方法面临着越来越多的挑战。而FPGA以其并行处理能力和可编程性&#xff0c;为解决复杂问题提供了新的途径。它允许设计者在同一个芯片上实现多种不同的功能模块&#xff0c;极大地提高了资源的利用率和系统的综合性能。 FPGA…

HCIA-ARP

ARP的由来 ARP这一种协议它会是在我们HCIA中第一个需要完全掌握的一个协议&#xff0c;不然对于数据通讯来说大家都会一直觉得很绕圈 协议栈&#xff0c;网线&#xff0c;网卡&#xff0c;它们组成了我们最小的数据通信的小脉络注&#xff1a;可以了解ARP攻击&#xff08;冒充访…

文献解读-群体基因组第二期|《中国人群中PAX2新生突变的检测及表型分析:一项单中心研究》

关键词&#xff1a;应用遗传流行病学&#xff1b;群体测序&#xff1b;群体基因组&#xff1b;基因组变异检测&#xff1b; 文献简介 标题&#xff08;英文&#xff09;&#xff1a;Detection of De Novo PAX2 Variants and Phenotypes in Chinese Population: A Single-Cente…

new CCDIKSolver( OOI.kira, iks ); // 创建逆运动学求解器

demo案例 new CCDIKSolver(OOI.kira, iks); 在使用某个特定的库或框架来创建一个逆运动学&#xff08;Inverse Kinematics, IK&#xff09;求解器实例。逆运动学在机器人学、动画和计算机图形学等领域中非常重要&#xff0c;它用于根据期望的末端执行器&#xff08;如机器人的…

建设现代智能工业-智能化、数字化、自动化节能减排

建设现代智能工业-智能化节能减排 遵循“一体化”能源管理(Integrated Energy Management)的设计宗旨&#xff0c;集成城市各领域(如工业.交通、建筑等&#xff09;的能源生产和消费信息&#xff0c;面向城市政府、企业、公众三类实体&#xff0c;提供“一体化”的综合能源管理…

uniapp h5项目切换导航栏及动态渲染按钮颜色

1.效果图 2.html,动态渲染按钮样式---三元判断 <!-- 切换栏 --><view class"statusList"><block v-for"(item,index) in list" :key"index"><view class"swiper-tab-list" :class"current item.id?activ…

CEEMDAN +组合预测模型(CNN-Transfromer + XGBoost)

注意&#xff1a;本模型继续加入 组合预测模型全家桶 中&#xff0c;之前购买的同学请及时更新下载! 往期精彩内容&#xff1a; 时序预测&#xff1a;LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较-CSDN博客 VMD CEEMDAN 二次分解&#xff0c;Transformer-BiGRU预测模…

【JAVA WEB实用与优化技巧】如何自己封装一个自定义UI的Swagger组件,包含Swagger如何处理JWT无状态鉴权自动TOKEN获取

目录 一、Swagger 简介1. 什么是 Swagger&#xff1f;2. 如何使用 Swagger3. Springboot 中swagger的使用示例1. maven 引入安装2. java配置 二、Swagger UI存在的缺点1.不够方便直观2.请求的参数没有缓存3.不够美观4.如果是JWT 无状态登录&#xff0c;Swagger使用起来就没有那…

STL-stack的使用及其模拟实现

在C标准库中&#xff0c;stack是一种容器适配器&#xff0c;它以后进先出的方式组织数据&#xff0c;其删除只能从容器的栈顶进行元素的插入与取出操作。 stack的使用 stack的构造函数 stack的成员函数 empty&#xff1a;判断栈是否为空back&#xff1a;返回当前栈中元素的数量…

推荐一个 Java 开源企业级新能源汽车智能共享充电桩管理平台

文末可获取 Orise 平台源码 01 Orise 智能充电桩管理平台 奥升( Orise ) 新能源汽车充电桩管理 Saas 云平台是一个集充电设备管理、用户充电管理、线上小程序内容管理于一体的综合管理平台。Orise充电桩平台支持高并发业务、业务动态伸缩、桩通信负载均衡&#xff0c;通过Docke…

Golang项目代码组织架构实践

Golang在项目结构上没有强制性规范&#xff0c;虽然这给了开发者很大的自由度&#xff0c;但也需要自己沉淀一套可行的架构。本文介绍了一种项目布局&#xff0c;可以以此为参考设计适合自己的 Golang 项目组织模式。原文: Golang Project Layout Go 有很多强制的或是约定俗成的…