项目实战——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,类似于css
    ui->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::MouseButtonDblClick
        if (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 class
        return 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::ExistingFiles
    fileDialog->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,欢迎交流!


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nginx代理本机多个端口应用

说明&#xff1a; 需求&#xff1a;我当前只有一个端口的授权&#xff0c;比如80&#xff0c;但是我的服务有多个且在不同的端口&#xff0c;比如8081&#xff0c;8082&#xff0c;我希望所有的请求通过都通过80端口进来&#xff0c;然后根据不同的文根怼到不同的端口&#xff…

专业ScrumMaster(高级)- PSM II 认证班,Scrum.org认证PSM II官方认证班

课程简介 Scrum是目前运用最为广泛的敏捷开发方法&#xff0c;是一个轻量级的项目管理和产品研发管理框架&#xff0c;旨在最短时间内交付最大价值。根据2022年全球敏捷状态报告&#xff0c;Scrum的应用占比已经达到87%。 Scrum.org 由 Scrum 的联合创始人 Ken Schwaber 创立…

让代码在键盘上跳“华尔兹”的10大原则

大家好&#xff0c;我是小❤&#xff0c;一个漂泊江湖多年的 985 非科班程序员&#xff0c;曾混迹于国企、互联网大厂和创业公司的后台开发攻城狮。 引言 编程界都知道&#xff0c;代码是一种艺术 —— 它是对技术职责和美学眼光的完美融合。 正如一名工匠在雕琢他的作品&am…

qt+sqllite小区管理系统

&#x1f482; 个人主页:pp不会算法^ v ^ &#x1f91f; 版权: 本文由【pp不会算法v】原创、在CSDN首发、需要转载请联系博主 &#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 最近接了单子把小区管理系统改改分享出来吧&#xff0c;比较适合q…

python04-变量命名规则

python需要使用标识符来给变量命名。 标识符&#xff0c;我来解释下&#xff0c;就是给程序中变量、类、方法命名的符号&#xff0c;简单理解就是起一个名字&#xff0c;这个名字必须是合法的名字&#xff0c; 对于Python来说&#xff0c;标识符必须是以字母、下划线(_)开头&…

Oracle 19c rac集群管理 -------- 集群启停操作过程

Oracle rac集群启停操作过程 首先查看数据库的集群的db_unique_name SQL> show parameter nameNAME TYPE VALUE ------------------------------------ ----------- --------------------------- cdb_cluster_name …

vue2中CesiumV1.113.0加载离线地形数据

离线地形数据可以放在vue项目下的public/data/sjzTerrain文件下 由于地形离线数据数量太大&#xff0c;在vue项目编译时会报如下错误&#xff1a; ERROR in EMFILE: too many open files, open D:\test_project\vue_cesium_demo\public\data\sjzTerrain\.tmp\14\26787\11669.h…

《WebKit 技术内幕》学习之十二(1):安全机制

第12章 安全机制 安全机制对于浏览器和渲染引擎来说至关重要。一个不考虑安全机制的HTML5规范体系肯定不会受到广泛地使用&#xff0c;同时一个不安全的浏览器也不会得到广大用户的青睐。本章介绍的安全机制分成两个不同的部分&#xff0c;第一个部分是网页的安全&#xff0c;…

web 应用常见的安全问题

一xss攻击 人们经常将跨站脚本攻击&#xff08;Cross Site Scripting&#xff09;缩写为CSS&#xff0c;但这会与层叠样式表&#xff08;Cascading Style Sheets&#xff0c;CSS&#xff09;的缩写混淆。因此&#xff0c;有人将跨站脚本攻击缩写为XSS。 跨站脚本攻击&#xff…

ubuntu 20.04 使用 webrtc-streamer自动退出,报错GLIBC 问题解决方法

文章目录 前言Ubuntu 20.4中使用webrtc-streamer报错总结 前言 前端vue2 项目需要播放海康的视频流&#xff0c;本地启动起来了&#xff0c;现在需要的服务器上部署&#xff0c;服务器是Ubuntu 20.04&#xff0c;下面是部署时遇到的问题及解决方法&#xff0c;总耗时2天。 不知…

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

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

3.【SpringBoot3】文章分类接口开发

序言 在文章分类模块&#xff0c;有以下接口需要开发&#xff1a; 新增文章分类文章分类列表获取文章分类详情更新文章分类删除文章分类 数据库表字段和实体类属性&#xff1a; 在数据库表中&#xff0c;create_user 来自于 user 表中的主键 id&#xff0c;是用来记录当前文…

使用AFPN渐近特征金字塔网络优化YOLOv8改进小目标检测效果(不适合新手)

目录 简单概述 算法概述 优化效果 参考文献 文献地址&#xff1a;paper 废话少说&#xff0c;上demo源码链接&#xff1a; 简单概述 AFPN的核心思想&#xff1a;AFPN主要通过引入渐近的特征融合策略&#xff0c;逐步整合底层、高层和顶层的特征到目标检测过程中。这种融合…

正信晟锦:亲戚借了钱怎么要回来

亲戚间的金钱借贷&#xff0c;往往是情感与金钱交织的微妙话题。在中国传统文化中&#xff0c;家族关系至关重要&#xff0c;因此处理此类事宜时需要格外细致和谨慎。要回借出的钱&#xff0c;不仅要考虑到资金的回流&#xff0c;更要维护和谐的家庭关系。 沟通是解决问题的关键…

v-for中使用v-model的坑点

问题场景: 有这样一种场景,比如我们需要根据数据创建多个input输入框 <template><div v-for"(item, index) in list" :key"index"><a-input v-model"item"></a-input></div></template><script>expo…

Git--创建仓库(1)

git init Git 使用 git init 命令来初始化一个 Git 仓库&#xff0c;Git 的很多命令都需要在 Git 的仓库中运行&#xff0c;所以 git init 是使用 Git 的第一个命令。 在执行完成 git init 命令后&#xff0c;Git 仓库会生成一个 .git 目录&#xff0c;该目录包含了资源的所有…

别再局限于Android和iOS了尝试鸿蒙APP系统开发吧!

最近&#xff0c;多家互联网公司也发布了鸿蒙OS的App开发工程师的岗位&#xff0c;开启了抢人大战&#xff0c;有的企业开出了近百万的年薪招聘鸿蒙OS工程师&#xff0c;而华为甚至为鸿蒙OS资深架构师开出了100万元—160万元的年薪。 「纯血」鸿蒙开启&#xff0c;欲与 Andori…

css clip-path

1 解释 clip-path 属性使用裁剪方式创建元素的可显示区域。区域内的部分显示&#xff0c;区域外的隐藏。浏览器会裁剪掉裁剪区域以外的内容&#xff0c;包括: 背景&#xff0c;内容&#xff0c;边框&#xff0c;阴影等&#xff0c;另外也不会捕获裁剪区域之外的hover click等事…

《JavaScript权威指南》读书笔记1

服创要开始了&#xff0c;选题基本上都是关于ai的&#xff0c;之前写的项目没有和ai有关的&#xff0c;也没有学习过这方面的知识。现在我们组基本上确认的选题是&#xff1a;【A01】基于文心大模型的智能阅卷平台设计与开发【百度】【A01】基于文心大模型的智能阅卷平台设计与…