项目实战——Qt实现FFmpeg音视频转码器

文章目录

  • 前言
  • 一、移植 FFmpeg 相关文件
  • 二、绘制 ui 界面
  • 三、实现简单的转码
  • 四、功能优化
    • 1、控件布局及美化
    • 2、缩放界面
    • 3、实现拖拽
    • 4、解析文件
    • 5、开启独立线程
    • 6、开启定时器
    • 7、最终运行效果
  • 五、附录
  • 六、资源自取


前言

本文记录使用 Qt 实现 FFmepg 音视频转码器项目的开发过程。


一、移植 FFmpeg 相关文件

1、首先创建一个 Qt 项目,选择 MSVC2017 32bit 作为其编译器
在这里插入图片描述
2、将 FFmpeg 相关库及源文件拷贝到当前目录下
在这里插入图片描述
3、注释 prepare_app_arguments 函数(这里方便后面我们运行时可以指定相应的转码参数)
在这里插入图片描述
4、将所需的一些 dll 动态库文件拷贝到 debug 目录下
在这里插入图片描述
5、将音视频素材文件拷贝到 build-QtVideoConverterFFmpeg431-Desktop_Qt_5_14_2_MinGW_32_bit-Debug目录下(点击运行自动生成的目录)
在这里插入图片描述

二、绘制 ui 界面

绘制一个简单的 ui 界面,效果如下:
在这里插入图片描述
里面包括 Frame、Push Button、Progress Bar、Label、Table Widget、Combo Box、Line Edit 等相关控件。

三、实现简单的转码

1、在开始转码按键的 clicked 槽函数加入以下代码:

void Widget::on_pushButton_Running_clicked()
{qDebug() << "hello,ffmpeg";QString currentPath = QDir::current().path();qDebug() << "Current path:" << currentPath;char* arrParams[10] = { 0 };for (int k = 0; k < 10; k++) {arrParams[k] = new char[64]();}strcpy(arrParams[0], "QtVideoConverter.exe");strcpy(arrParams[1], "-i");strcpy(arrParams[2], "SampleVideo_1280x720_20mb.mp4");strcpy(arrParams[3], "-vcodec");strcpy(arrParams[4], "libx264");strcpy(arrParams[5], "-acodec");strcpy(arrParams[6], "copy");strcpy(arrParams[7], "-y");strcpy(arrParams[8], "SampleVideo_1280x720_20mb.flv");main_ffmpeg431(9, arrParams);AVGeneralMediaInfo* avmi = new AVGeneralMediaInfo();for (int k = 0; k < 10; k++) {delete[] arrParams[k];avmi = NULL;}
}

2、点击运行,可以看到如下的界面
在这里插入图片描述
目前进度条功能还未实现,点击转码可以在 build-QtVideoConverter-Desktop_Qt_5_14_2_MSVC2017_32bit-Debug 目录下看到转码成功的 flv 文件
在这里插入图片描述

四、功能优化

1、控件布局及美化

Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);this->setStyleSheet("background-color:#F0F0F0;");   // 设置组件窗口的外观// qss,类似于cssui->lblLogoText->setStyleSheet("color:#009100;font-style:italic;font-weight:bold;font-size:30px;");// frame 背景色ui->frameTop->setStyleSheet("background-color:#C4E1FF;");// 按钮背景色ui->pushButton_Running->setStyleSheet("background-color:#C4E1FF;font-weight:bold;font-size:30px;color:#009100;border:2px groove gray;border-radius:10px;padding:2px 4px;");
}// 隐藏栅格线、单元格不可编辑ui->tableWidget_FileList->verticalHeader()->setHidden(true); // 设置行名隐藏(注意是行名,不是整行)ui->tableWidget_FileList->setShowGrid(false); // 控制视图中数据项之间是否显示网格ui->tableWidget_FileList->setEditTriggers(QAbstractItemView::NoEditTriggers); // 让这个表格对用户只读

效果如下:
在这里插入图片描述

2、缩放界面

事件过滤器:(双击,全屏)

// 事件过滤器:(双击,全屏)
bool Widget::eventFilter(QObject *obj, QEvent *event)
{// 指定某个控件if (obj == ui->frameTop || obj == ui->lblLogoText || obj == ui->lblLogoImage) {//  QEvent::MouseButtonPress,QEvent::MouseButtonDblClickif (event->type() == QEvent::MouseButtonDblClick) {QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);if (mouseEvent->button() == Qt::LeftButton) {// QMessageBox::information(this, "点击", "点击了我", QMessageBox::Yes | QMessageBox::No | QMessageBox::Yes);if (!this->isMaximized()) {this->showMaximized();} else {this->showNormal();}return true;} else {return false;}} else {return false;}} else {// pass the event on to the parent classreturn Widget::eventFilter(obj, event);}
}

效果:
请添加图片描述
ESC 键退出全屏

// 按键:(esc--退出全屏)
void Widget::keyPressEvent(QKeyEvent *event)
{switch (event->key()) {case Qt::Key_Escape:if (this->isMaximized()) {this->showNormal();}break;default:QWidget::keyPressEvent(event);}
}

3、实现拖拽

鼠标按下不松开,然后移动鼠标实现拖拽,松开鼠标拖拽结束

// 拖拽操作---begin
void Widget::mousePressEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton) {m_bDrag = true;// 获得鼠标的初始位置mouseStartPoint = event->globalPos(); // 事件发生时鼠标相对于我们整个屏幕的左上角(0,0)的偏移值// mouseStartPoint = event->pos(); // 事件发生时鼠标相对于当前active widget的左上角(0,0)的偏移值// 获得窗口的初始位置windowTopLeftPoint = this->frameGeometry().topLeft(); // 仍然表示整个屏幕的左上角qDebug() << "mouseStartPoint" << mouseStartPoint.x() << mouseStartPoint.y();qDebug() << "windowTopLeftPoint" << windowTopLeftPoint.x() << windowTopLeftPoint.y();}
}void Widget::mouseMoveEvent(QMouseEvent *event)
{if (m_bDrag) {// 获得鼠标移动的距离QPoint distance = event->globalPos() - mouseStartPoint;// QPoint distance = event->pos() - mouseStartPoint;// 改变窗口的位置this->move(windowTopLeftPoint + distance);qDebug() << "move" << windowTopLeftPoint + distance;}
}void Widget::mouseReleaseEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton) {m_bDrag = false;}
}
// 拖拽操作--end

效果如下:
在这里插入图片描述

4、解析文件

点击 选择文件 按钮,选择待转码的文件,可以将所选文件的相关信息解析出来

void Widget::on_pushButton_AddFile_clicked()
{// 定义文件对话框类QFileDialog *fileDialog = new QFileDialog(this);// 定义文件对话框标题fileDialog->setWindowTitle(tr("打开文件")); // tr()函数:Qt会根据当前的语言环境自动选择相应的翻译文件,并将字符串翻译成对应的语言。// 设置默认路径fileDialog->setDirectory(".");// 设置文件过滤器fileDialog->setNameFilter(tr("video(*.mp4 *.flv *.mkv);;All files(*.*)"));// 设置可以选择多个文件,默认只能选择一个文件 QFileDialog::ExistingFilesfileDialog->setFileMode(QFileDialog::ExistingFile);// 设置视图模式fileDialog->setViewMode(QFileDialog::Detail);if (fileDialog->exec()) {QString strFileName = fileDialog->selectedFiles()[0];qDebug() << strFileName;QFileInfo fileinfo;fileinfo = QFileInfo(strFileName);// 插入数据项ui->tableWidget_FileList->setRowCount(1);ui->tableWidget_FileList->setItem(0, 0, new QTableWidgetItem(fileinfo.fileName())); // 文件名ui->tableWidget_FileList->setItem(0, 1, new QTableWidgetItem(fileinfo.suffix()));   // 后缀AVGeneralMediaInfo avmi;std::string str = strFileName.toStdString();const char *chFilename = str.c_str();get_avgeneral_mediainfo(&avmi, chFilename);ui->tableWidget_FileList->setItem(0, 2, new QTableWidgetItem(QString(QLatin1String(avmi.videoCodecName))));ui->tableWidget_FileList->setItem(0, 3, new QTableWidgetItem(QString(QLatin1String(avmi.audioCodecName))));char chDuration[128] = {0};sprintf(chDuration, "%lld", avmi.duration);ui->tableWidget_FileList->setItem(0, 4, new QTableWidgetItem(QString(QLatin1String(chDuration))));ui->tableWidget_FileList->setItem(0, 5, new QTableWidgetItem(strFileName));}
}

效果如下:
在这里插入图片描述

5、开启独立线程

tcworkthread.h

#ifndef TCWORKTHREAD_H
#define TCWORKTHREAD_H#include <QThread>
extern "C" {
#include "ffmpeg.h"
}#define MAX_CMDLINE_ARGC_COUNT 100// 转码参数
typedef struct __TCParams {char inFilename[512];char videoCodecName[256];char audioCodecName[256];char muxerName[256];// 定义了一个无参数的构造函数__TCParams(),在该构造函数中调用了一个名为__init()的私有成员函数。// 构造函数在创建结构体实例时会被自动调用,因此当创建TCParams对象时,会自动执行__init()函数。__TCParams() {__init();}void __init() {memset(inFilename, 0, 512);memset(videoCodecName, 0, 256);memset(audioCodecName, 0, 256);memset(muxerName, 0, 256);}} TCParams;class TCWorkThread : public QThread
{
public:TCWorkThread();private:virtual void run(); // 任务处理线程TCParams *m_pTCParams;public:int workCount;  // 计数void SetTCParams(TCParams *params);signals:public slots:};

tcworkthread.c

#include "tcworkthread.h"
#include <QDebug>TCWorkThread::TCWorkThread()
{workCount = 0;m_pTCParams = nullptr;
}void TCWorkThread::SetTCParams(TCParams *params)
{m_pTCParams = params;
}// run() 重新实现
void TCWorkThread::run()
{if (m_pTCParams == nullptr) {return;}// by lp,参数都是写死的,仅供参考char* arrParams[MAX_CMDLINE_ARGC_COUNT] = { 0 };for (int k = 0; k < MAX_CMDLINE_ARGC_COUNT; k++) {arrParams[k] = new char[1024]();}char strOutName[512] = {0};strcpy(arrParams[0], "QtVideoConverter.exe");strcpy(arrParams[1], "-i");strcpy(arrParams[2], m_pTCParams->inFilename);strcpy(arrParams[3], "-vcodec");strcpy(arrParams[4], m_pTCParams->videoCodecName);strcpy(arrParams[5], "-acodec");strcpy(arrParams[6], m_pTCParams->audioCodecName);strcpy(arrParams[7], "-y");sprintf(strOutName, "SampleVideo_1280x720_20mb.%s", m_pTCParams->muxerName);strcpy(arrParams[8], strOutName);// 准备参数main_ffmpeg431(9, arrParams);for (int k = 0; k < MAX_CMDLINE_ARGC_COUNT; k++) {delete[] arrParams[k];  // 切记要释放申请的内存arrParams[k] = NULL;}
}

6、开启定时器

// 定时器事件处理函数
// 获取实时转码进度
// 当前进度为 1.00 时,killTimer
void Widget::timerEvent(QTimerEvent *event)
{int nPrg = (int)(get_tc_progress() * 100);qDebug() << "progress:" << nPrg;ui->progressBar_tcprg->setValue(nPrg);if (nPrg >= 100) {killTimer(m_TimerID1);}
}

7、最终运行效果

将本地 mp3 文件转换成 flv 文件
请添加图片描述

五、附录

附上一个十六进制颜色码的网站:十六进制颜色代码表,图表及调色板

六、资源自取

链接:基于QT和ffmpeg的音视频转码器
在这里插入图片描述


我的qq:2442391036,欢迎交流!


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

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

相关文章

Kubernetes/k8s之HPA,命名空间资源限制

Horizontal Pod Autoscaling:po的水平自动伸缩 这是k8s自带的模块 pod占用cpu比例达到一定的阀值&#xff0c;会触发伸缩机制。 根据cpu的阀值触发伸缩机制 replication controller 副本控制器 控制pod的副本数 deployment controller 节点控制器 部署pod hpa控制副本的数…

深度学习(3)--递归神经网络(RNN)和词向量模型Word2Vec

目录 一.递归神经网络基础概念 二.自然语言处理-词向量模型Word2Vec 2.1.词向量模型 2.2.常用模型对比 2.3.负采样方案 2.4.词向量训练过程 一.递归神经网络基础概念 递归神经网络(Recursive Neural Network, RNN)可以解决有时间序列的问题&#xff0c;处理诸如树、图这样…

蓝桥杯(C++ 最大开支 优先队列)

优先队列&#xff1a; 蓝桥杯&#xff08;C 整数删除 优先队列 &#xff09;-CSDN博客 思路&#xff1a; 1、每个人依此选择项目&#xff0c;每个人选项目时都&#xff08;选择当下花费增加最多的项目&#xff09;&#xff0c;若项目i的门票价格为kxb&#xff0c;那么增加一个…

HTML+JavaScript-02

数组 JavaScript中的数组用于在单一变量存储多个值&#xff0c;其实跟java中的数组是一样的&#xff0c;都是通过索引来访问这些值的。 创建数组 方式一&#xff1a;&#xff08;推荐&#xff09; var cars ["北京现代", "丰田", "五菱宏光"…

EDA-数据探索-pandas自带可视化-iris

# 加载yellowbrick数据集 import os import pandas as pd FIXTURES os.path.join(os.getcwd(), "data") df pd.read_csv(os.path.join(FIXTURES,"iris.csv")) df.head()sepal_lengthsepal_widthpetal_lengthpetal_widthspecies05.13.51.40.2setosa14.93…

最新AI系统ChatGPT网站系统源码,支持AI绘画,GPT语音对话,ChatFile文档对话总结,DALL-E3文生图,MJ绘画局部编辑重绘

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

数仓建设学习路线(三)元数据管理

什么是元数据&#xff1f; 简单来说就是描述数据的数据&#xff0c;更直白来说就是描述表名、表制作者、表字段、表生命周期、表存粗等信息的数据 元数据该如何管理 工具化 开源&#xff1a; 可通过atlas获取表依赖及信息做二次开发&#xff0c;或者完成可视化界面 平台化&am…

【网络安全 -> 防御与保护】专栏文章索引

为了方便 快速定位 和 便于文章间的相互引用等 作为一个快速准确的导航工具 网络安全——防御与保护 &#xff08;一&#xff09;.信息安全概述 &#xff08;二&#xff09;.防火墙组网

计组与原理:系统总线

大家好啊&#xff0c;这里来到计组第二部分内容&#xff1a;系统总线 跳转上一篇&#xff1a;计组原理&#xff1a;系统概论与基本组成 系统总线 1.总线的基本概念单总线结构框图面向 CPU 的双总线结构框图以存储器为中心的双总线结构框图 2.总线的分类片内总线系统总线通信总线…

Linux之权限(内容详细,细节满满)

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 数据结构初阶 Linux 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力 目录 一.前言 二.权限修改的两种方法 …

Python学习02—Python开发环境的配置

1.Python语言的简单概述 Python的诞生离不开一个人&#xff0c;他就是Guido van Rossum&#xff0c;他在1989年的圣诞节期间&#xff0c;在荷兰的阿姆斯特丹度假&#xff0c;为了打发假期时间&#xff0c;就想着设计一种编程语言&#xff0c;后期便诞生了Python Python语言拥…

掌握技术脉搏:程序员如何在技术变革中保持领先,避免被裁员

笔者刚入行IT业的时候&#xff0c;有个前辈老猿工就语重心长的说&#xff0c;写代码要与时俱进&#xff0c;永远不能停止学习&#xff0c;如果不能时刻走在前面&#xff0c;就会被淘汰。哪天感觉到力不从心&#xff0c;就要考虑努力往管理层走啦。随着计算机技术和互联网技术的…

MySQL-B-tree和B+tree区别

B-tree&#xff08;平衡树&#xff09;和Btree&#xff08;平衡树的一种变种&#xff09;是两种常见的树状数据结构&#xff0c;用于构建索引以提高数据库的查询性能。它们在一些方面有相似之处&#xff0c;但也有一些关键的区别。以下是B-tree和Btree的主要区别&#xff1a; …

Java Server-Sent Events通信

Server-Sent Events特点与优势 后端可以向前端发送信息&#xff0c;类似于websocket&#xff0c;但是websocket是双向通信&#xff0c;但是sse为单向通信&#xff0c;服务器只能向客户端发送文本信息&#xff0c;效率比websocket高。 单向通信&#xff1a;SSE只支持服务器到客…

Leetcode—22.括号生成【中等】

2023每日刷题&#xff08;七十九&#xff09; Leetcode—22.括号生成 算法思想 实现代码 class Solution { public:vector<string> generateParenthesis(int n) {vector<string> ans;int m n * 2;string path(m, 0);function<void(int, int)> dfs [&…

小红书多模态团队建立新「扩散模型」:解码脑电波,高清还原人眼所见

近些年&#xff0c;研究人员们对探索大脑如何解读视觉信息&#xff0c;并试图还原出原始图像一直孜孜不倦。去年一篇被 CVPR 录用的论文&#xff0c;通过扩散模型重建视觉影像&#xff0c;给出了非常炸裂的效果—— AI 不光通过脑电波知道你看到了什么&#xff0c;并且帮你画了…

技术浪潮中的职业变革:程序员面临的裁员挑战与应对策略

目录 前言 冲破时空壁垒&#xff1a;探索技术变革的酷时代&#xff01; 逆境成长&#xff1a;一个互联网人战胜失业困境的故事 裁员的双面影响&#xff1a;挑战与机遇 应对裁员潮危机&#xff1a;程序员的超级英雄之路 前言 惊人的新闻传来&#xff0c;一对来自中国的工程师…

基于神经网络的电力系统的负荷预测

一、背景介绍&#xff1a; 电力系统负荷预测是生产部门的重要工作之一&#xff0c;通过准确的负荷预测&#xff0c;可以经济合理地安排机组的启停、减少旋转备用容量、合理安排检修计划、降低发电成本和提高经济效益。负荷预测按预测的时间可以分为长期、中期和短期负荷预测。…

​​快速排序(四)——挖坑法,前后指针法与非递归

目录 ​一.前言 二.挖坑法 三.前后指针法 四.递归优化 五.非递归 六.结语 一.前言 本文我们接着上篇文章的重点快排&#xff0c;现在继续讲解对快排优化的挖坑法&#xff0c;前后指针法以及非递归方法&#xff0c;下面是上篇文章快排链接&#xff1a;https://mp.csdn.net…

C#,字符串匹配(模式搜索)有限自动机(Finite Automata)算法的源代码

一、有限状态自动机 图中两个圆圈&#xff0c;也叫节点&#xff0c;用于表示状态&#xff0c;从图中可以看成&#xff0c;它有两个状态&#xff0c;分别叫0和1。从每个节点出发&#xff0c;都会有若干条边。当处于某个状态时&#xff0c;如果输入的字符跟该节点出发的某条边的内…