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

news2024/11/8 21:49:59

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秒,即10ms
                      8, 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秒,即10ms
             8, 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,               ///todo
                                             nullptr,
                                             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秒,即10ms
                                  8, 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.coloradmin.cn/o/1714667.html

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

相关文章

大摩“凑热闹”:当前氧化铝紧平衡,任何供给冲击都将导致价格急剧波动

大摩认为&#xff0c;全球有590万吨氧化铝供应受阻&#xff0c;相等于扣除中国后全球供应约一成&#xff0c;而氧化铝需求可能有所上升&#xff0c;同时氧化铝库存缓冲有限&#xff0c;因此任何供给冲击都将导致价格急剧波动。 文章内容 今年以来氧化铝期货价格一路上行&#…

STM32-13-MPU

STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 STM32-12-OLED模块 文章目录 STM32-12-MPU1. 内存保护单元MPU1. M…

JVM之性能优化

1.JVM优化什么 由博客JVM之垃圾回收-CSDN博客我们已经了解到了数据存储是在方法区和堆区&#xff0c;而堆区的使用更为频繁。堆区有什么呢?老年代、新生代、GC。因此JVM性能优化&#xff0c;优化什么&#xff1f; 我们猜想一下&#xff0c;新生代的大小设置&#xff1b;老年代…

模型实战(22)之 C++ - tensorRT部署yolov8-cls 目标分类

C++ - tensorRT部署yolov8-cls 目标分类 在检测应用场景中如果有同等类别不同形态的目标,单纯的目标检测可能达不到实用或者想要的精度,这就需要衔接一步分类python环境下如何直接调用推理模型转换并导出:pt -> onnx ->.engineC++ tensorrt 部署分类模型1.Python环境下…

【OrangePi AIpro】香橙派 AIpro 为AI而生

产品简介 OrangePi AIpro(8T)&#xff1a;定义边缘智能新纪元的全能开发板 在当今人工智能与物联网技术融合发展的浪潮中&#xff0c;OrangePi AIpro(8T)凭借其强大的硬件配置与全面的接口设计&#xff0c;正逐步成为开发者手中的创新利器。这款开发板不仅代表了香橙派与华为…

2024最新 Jenkins + Docker实战教程(七)- Jenkins实现远程传输和自动部署

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

powershell 配合aria2实现简单的图片爬取

powershell 配合aria2实现简单的图片爬取 01 前言 现如今&#xff0c;提到爬虫&#xff0c;令人不得不提到Python&#xff0c;确实简单&#xff0c;也强大&#xff0c;到处都可以找到教程。故而今天换换口味&#xff0c;用powershell来实现&#xff0c;配合aria2的强大下载功…

鸿蒙开发接口图形图像:【@ohos.window (窗口)】

窗口 窗口提供管理窗口的一些基础能力&#xff0c;包括对当前窗口的创建、销毁、各属性设置&#xff0c;以及对各窗口间的管理调度。 该模块提供以下窗口相关的常用功能&#xff1a; [Window]&#xff1a;当前窗口实例&#xff0c;窗口管理器管理的基本单元。[WindowStage]&…

【单片机毕设选题】-智能语音控制风扇

一. 系统功能 此设计采用STM32和SU-03T离线语音模块来控制风扇, 主要功能如下: 1. 通过DHT11温湿度模块来采集环境温湿度。 2. 通过SU-03T语音模块来控制风扇启停加减速等。 3. 通过OLED显示系统状态。 4. 可以通过按键控制风扇启停加减速等。 5. 通过蓝牙模块监视系统状…

内网安全之证书模版的管理

证书模板 Certificate templates 是 CA 证书颁发机构的一个组成部分&#xff0c;是证书策略中的重要元素&#xff0c;是用于证书注册、使用和管理的一组规则和格式。当 CA 收到对证书的请求时&#xff0c;必须对该请求应用一组规则和设置&#xff0c;以执行所请求的功能&#x…

【创作活动】探索 GPT-4o:下一代语言模型的技术革命

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Thingsboard规则链:Customer Details节点详解

在物联网&#xff08;IoT&#xff09;平台Thingsboard的规则引擎体系中&#xff0c;Customer Details节点是一个功能强大的组件&#xff0c;它专为处理与客户&#xff08;Customer&#xff09;实体相关的综合信息而设计。这个节点不仅能够读取客户的基本属性&#xff0c;还能提…

VBA技术资料MF159:实现某个区域内的数据滚动

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

【微服务】springboot 构建docker镜像多模式使用详解

目录 一、前言 二、微服务常用的镜像构建方案 3.1 使用Dockerfile 3.2 使用docker plugin插件 3.3 使用docker compose 编排文件 三、环境准备 3.1 服务器 3.2 安装JDK环境 3.2.1 创建目录 3.2.2 下载安装包 3.2.3 配置环境变量 2.2.4 查看java版本 3.3 安装maven …

F. Longest Strike[双指针详解]

Longest Strike 题面翻译 给你一个长度为 n n n 的序列 a a a 和一个整数 k k k&#xff0c;你要求一个区间 [ l , r ] [l,r] [l,r] 满足&#xff1a; 对于任何整数 x ∈ [ l , r ] x∈[l,r] x∈[l,r]&#xff0c; x x x 在 a a a 中的出现次数不少于 k k k 次。最大…

【Postman接口测试】第四节.Postman接口测试项目实战(上)

文章目录 前言一、项目介绍 1.1 项目界面功能介绍 1.2 项目测试接口介绍 1.3 项目测试接口流程二、HTTP协议三、接口测试中接口规范四、项目合同新增业务介绍 4.1 登录接口调试 4.1 登录接口自动关联 4.1 添加课程接口调试 4.1 上传合同…

排序(前篇)

1.排序的概念及其运用 2.插入排序的概念及实现 3.希尔排序的概念及实现 4.选择排序概念及实现 总代码&#xff08;对比各个排序在大量的数据情况排序所化的时间&#xff09;&#xff1a; 1.排序的概念及其运用 1.1排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使…

linux中逻辑卷管理与扩展

逻辑卷管理与扩展 逻辑卷 作用&#xff1a; 1.整合分散的空间2.空间支持扩大 逻辑卷制作过程&#xff1a;将众多的物理卷&#xff08;PV&#xff09;组建成卷组&#xff08;VG&#xff09;&#xff0c;再从卷组中划分出逻辑卷&#xff08;LV&#xff09; 逻辑卷的逻辑思路 …

前端项目开发,3个HTTP请求工具

这一小节&#xff0c;我们介绍一下前端项目开发中&#xff0c;HTTP请求会用到的3个工具&#xff0c;分别是fetch、axios和js-tool-big-box中的jsonp请求。那么他们都有哪些小区别呢&#xff1f;我们一起来看一下。 目录 1 fetch 2 axios 3 js-tool-big-box 的 jsonp 请求 …

一个月速刷leetcodeHOT100 day13 二叉树结构 以及相关简单题

树是一种分层数据的抽象模型 二叉树 二叉树中的节点最多只能有两个子节点&#xff0c;一个是左侧子节点&#xff0c;另一个是右侧子节点 二叉搜索树 二叉搜索树&#xff08;BST&#xff09;是二叉树的一种&#xff0c;但是只允许你在左侧节点存储&#xff08;比父节点&…