【Qt学习】 FFmpeg 解码播放本地视频

目录

一:效果展示

二:源码分享


一:效果展示

【Qt学习】 FFmpeg 解码播放本地视频_第1张图片

【Qt学习】 FFmpeg 解码播放本地视频_第2张图片

二:源码分享

videodecode.h .cpp [解码线程的封装]

#ifndef VIDEODECODE_H
#define VIDEODECODE_H

#include 
#include//使用线程
#include

//当前C++兼容C语言
extern "C"
{
//avcodec:编解码(最重要的库)
#include 
//avformat:封装格式处理
#include 
//swscale:视频像素数据格式转换
#include 
//avdevice:各种设备的输入输出
#include 
//avutil:工具库(大部分库都需要这个库的支持)
#include 
}

class videoDecodeThread : public QThread//利用线程基类
{
    Q_OBJECT
public:
    //    explicit videoDecodeThread(QObject *parent = 0);
    videoDecodeThread(QString videoname);
    QString videoname;

    //视频文件上下文格式
    AVFormatContext* avformat_context;
    //编解码器上下文格式
    AVCodecContext* avcodec_context;
    //解码器上下文格式
    AVCodec* avcodec;

    AVPacket* av_packet;
    //流的标志位
    int av_stream_index;

    //线程函数的引入
    void run();
    virtual ~videoDecodeThread();
    //定义停止的标志位
    bool m_stop;

    void videoStop();


    AVFrame *pFramein;
    AVFrame *pFrameRGB;
    uint8_t * pOutbuffer;



signals:
    //发送信号数据
    void SendOneData(int num);
    void sigGetOneFrame(QImage image);

public slots:

};

#endif // VIDEODECODE_H
#include "videodecode.h"
#include
#include
#include
#include

//解码初始化操作
//1.注册所有组件
//2.打开视频输入文件
//3.查找视频流信息
//4.查找解码器
//5.打开解码器

//videoDecode::videoDecode(QObject *parent) : QObject(parent)
//{
//}

videoDecodeThread::videoDecodeThread(QString videoname)
{
    //初始线程未启动
    this->m_stop = false;
    qDebug()<<"1.注册所有组件";
    av_register_all();

    qDebug()<<"2.打开视频输入文件";
    QString filename = QCoreApplication::applicationDirPath();
    qDebug()<<"获取程序运行目录 "<AVFormatContext->包含了视频信息(视频格式、大小等等...)
    //参数二:打开文件(入口文件)->url
    qDebug()<<"打开"<AVFormatContext
    //参数二:配置
    //返回值:0>=返回OK,否则失败
    int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
    if (avformat_find_stream_info_result < 0){
        //获取失败
        char* error_info = new char[32];
        av_strerror(avformat_find_stream_info_result, error_info, 1024);
        qDebug()<找到了视频流
    //音频解码器、视频解码器、字幕解码器等等...
    //获取视频解码器流引用
    av_stream_index = -1;
    for (int i = 0; i < avformat_context->nb_streams; ++i) {
        //循环遍历每一流
        //视频流、音频流、字幕流等等...
        if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
            //找到了
            av_stream_index = i;
            break;
        }
    }
    if (av_stream_index == -1)
    {
        qDebug()<查找到视频解码器上下文->视频压缩数据
    //编解码器上下文
    avcodec_context = avformat_context->streams[av_stream_index]->codec;

    //第三点:根据解码器上下文->获取解码器ID
    avcodec = avcodec_find_decoder(avcodec_context->codec_id);
    if (avcodec == NULL)
    {
        qDebug()<缓存到packet里面
    av_packet = (AVPacket*)av_malloc(sizeof(AVPacket));

    //输入->环境一帧数据->缓冲区->类似于一张图
    pFramein = av_frame_alloc();
    //输出->帧数据->数据格式->RGB
    pFrameRGB = av_frame_alloc();
    //只有指定了AVFrame的像素格式、画面大小才能真正分配内存
    //缓冲区分配内存
    pOutbuffer = (uint8_t *)av_malloc(avpicture_get_size(
                                          AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height));
    //初始化缓冲区 类似于memset
    avpicture_fill((AVPicture *)pFrameRGB, pOutbuffer,
                   AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height);

    //解码的状态类型(0:表示解码完毕,非0:表示正在解码)
    //    int current_frame_index = 0;

    //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
    //准备一个视频像素数据格式上下文
    //参数一:输入帧数据宽
    //参数二:输入帧数据高
    //参数三:输入帧数据格式
    //参数四:输出帧数据宽
    //参数五:输出帧数据高
    //参数六:输出帧数据格式->AV_PIX_FMT_RGB32
    //参数七:视频像素数据格式转换算法类型
    //参数八:字节对齐类型(C/C++里面)->提高读取效率
    SwsContext* pSwsContext = sws_getContext(avcodec_context->width,
                                             avcodec_context->height,
                                             avcodec_context->pix_fmt,
                                             avcodec_context->width,
                                             avcodec_context->height,
                                             AV_PIX_FMT_RGB32,
                                             SWS_BICUBIC,NULL,NULL,NULL);
    int ret;
    //解码的状态类型(0:表示解码完毕,非0:表示正在解码)
    int current_frame_index = 0;

    while (m_stop == false)
    {
        //>=0:说明有数据,继续读取   <0:说明读取完毕,结束
        //从视频文件上下文中读取包--- 有数据就一直读取
        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, (const unsigned char* const*)pFramein->data, pFramein->linesize, 0, avcodec_context->height,
                              pFrameRGB->data,  pFrameRGB->linesize);

                    QImage  *tmpImg  = new QImage((uchar *)pOutbuffer, avcodec_context->width,
                                                  avcodec_context->height,QImage::Format_RGB32);

                    QImage image=tmpImg->copy();

                    qDebug()<<"接收图片信号"<

videoplaywin.h .cpp [视频播放界面设计]

信号槽 发送接收 数据信息

#ifndef VIDEOPLAYWIN_H
#define VIDEOPLAYWIN_H

#include 
#include
#include
#include
#include"videodecode.h"
#include


class VideoPlayWin : public QWidget
{
    Q_OBJECT
public:
    explicit VideoPlayWin(QString videoname,QWidget *parent = 0);
    QPushButton *playBtn;
    QLabel *showLab;

    videoDecodeThread *playThread;
    //重绘事件
    void paintEvent(QPaintEvent *event);

    int temp;
    QImage tempimage;

signals:

public slots:
    void videoPlaySlot();
    void ReceOneData(int num);
    void ReceOneImage(QImage img);
};

#endif // VIDEOPLAYWIN_H
#include "videoplaywin.h"
#include


VideoPlayWin::VideoPlayWin(QString videoname, QWidget *parent) : QWidget(parent)
{
    this->setWindowTitle(videoname);
    this->setGeometry(200,200,800,500);
    QVBoxLayout *layout = new QVBoxLayout(this);

    showLab = new QLabel();
    playBtn = new QPushButton();
    layout->addWidget(showLab);
    layout->addWidget(playBtn);

    playBtn->setText("暂停");

    QString cinputFilePath = videoname+".avi";

    playThread = new videoDecodeThread(cinputFilePath);
    //类之间的数据传递 -- 信号槽机制
    connect(playThread,SIGNAL(SendOneData(int)),this,SLOT(ReceOneData(int)));
    connect(playThread,SIGNAL(sigGetOneFrame(QImage)),this,SLOT(ReceOneImage(QImage)));

    connect(playBtn,SIGNAL(clicked(bool)),this,SLOT(videoPlaySlot()));

}

void VideoPlayWin::paintEvent(QPaintEvent *event)
{
    //    showLab->setText(QString("接收%1帧数据").arg(temp));
    QPainter painter(this);
    if(tempimage.isNull())
    {
        return ;
    }
    if(tempimage.size().width()<=0) return;
    QImage img=tempimage.scaled(showLab->size(),Qt::KeepAspectRatio);
//    painter.drawImage(0,20,img);
    showLab->setPixmap(QPixmap::fromImage(img));

}

void VideoPlayWin::videoPlaySlot()
{
    static int inum = 0;
    inum++;
    if(inum % 2 == 0)
    {
        playBtn->setText("暂停");
        //停止线程自己定义
        playThread->videoStop();
    }
    else
    {
        playBtn->setText("开始");
        //启动线程
        playThread->start();
        playThread->m_stop = false;
    }
}

void VideoPlayWin::ReceOneData(int num)
{
    temp = num;
    qDebug()<<"接收"<

indexwin.h .cpp [视频播放器界面设计]

#ifndef INDEXWIN_H
#define INDEXWIN_H

#include 
#include//垂直布局
#include//水平布局
#include
#include
#include
#include//制作视频页面列表

class IndexWin : public QWidget
{
    Q_OBJECT
public:
    explicit IndexWin(QWidget *parent = 0);

    QWidget *leftwin;         //左边窗口
    QWidget *topwin;          //顶部窗口
    QWidget *indexwin;
    QWidget *rightwin;        //右边窗口

    QLineEdit *searchEdit;    //搜索编辑框
    QPushButton *userBtn;     //用户按钮
    QPushButton *vipBtn;      //会员按钮
    QPushButton *recordBtn;   //观看记录按钮
    QPushButton *downloadBtn; //下载视频记录按钮
    QPushButton *likeBtn;     //喜欢按钮
    QPushButton *shrinkBtn;   //缩小按钮
    QPushButton *enlargeBtn;  //放大按钮
    QPushButton *closeBtn;    //关闭按钮
    QPushButton *searchBtn;   //搜索按钮
    QLabel *logo1Lab;         //视频logo
    QPushButton *return1Btn;  //返回登录界面按钮
    QPushButton *cutBtn;      //最小化按钮

    QListWidget *videowinlist;//制作视频页面列表

signals:

public slots:
    void returnLoginSlot();   //回到登录界面
    void goNewwinSlot();      //点击不同的按钮做出不同的响应
    void GetItemName(QListWidgetItem *item);  //获取图片名字
    void enlargeImage(QListWidgetItem *item); //放大显示图片
    void videoWidgetShowSlot(QListWidgetItem*item);

};

#endif // INDEXWIN_H
#include "indexwin.h"
#include//一次添加多个控件
#include     //测试信号槽功能是否实现
#include//弹窗功能使用
#include//多个按钮显示在某个窗口布局,点击按钮进入观看视频
#include       //查找路径
#include    //放置图片
#include  //设置图片的模式 上面图片下面名字
#include "widget.h"  //登录窗口
#include"imagewin.h" //显示放大图片的窗口
#include"testthread.h"
#include"videodecode.h"
#include"videoplaywin.h"

IndexWin::IndexWin(QWidget *parent) : QWidget(parent)
{
    this->resize(1500,800);//设置该窗口尺寸
    setWindowFlags(Qt::FramelessWindowHint);   //无边框
    setAttribute(Qt::WA_TranslucentBackground);//背景透明

    leftwin = new QWidget();
    topwin = new QWidget();
    //indexwin = new QWidget();
    rightwin = new QWidget();
    //QListWidget类 列表框控件用来加载并显示多个列表项
    videowinlist = new QListWidget();          //制作一个视频列表

    //设置背景颜色
    leftwin->setStyleSheet("background-color: rgb(252,157,154)");
    topwin->setStyleSheet("background-color: rgb(252,157,154)");
    //videowinlist->setStyleSheet("background-color: rgb(252,157,154)");//在下面setStyleSheet中实现
    //鼠标经过列表每一项有选中的动态效果
    videowinlist->setStyleSheet(                            //设置背景颜色
                             "QListWidget{background-color: rgb(252,157,154)}"
                             "QListWidget{border:none; color:black;outline:0px; }"
                             "QListWidget::Item{padding-left:0px;padding-top:20px; padding-bottom:4px;color:black}"
                             "QListWidget::Item:hover{background:lightgray; color:green;}"
                             "QListWidget::item:selected{background:lightgray; color:green; }"
                             );

    //设置显示模式--上面图片下面名称 设置显示模式为图标显示
    videowinlist->setViewMode(QListView::IconMode);
    //设置图片模式--上面图片下面名称 设置显示模式为列表显示
    //videowinlist->setViewMode(QListView::ListMode);
    //设置列表每一项不可移动
    videowinlist->setMovement(QListView::Static);
    //设置大小自动适应
    videowinlist->setResizeMode(QListView::Adjust);
    //设置单个Icon大小
    videowinlist->setIconSize(QSize(125,125));
    //从左到右
    videowinlist->setFlow(QListView::LeftToRight);

    //最初进入视频播放器时候的显示 - image目录下的所有图片
    //获取视频图片路径 -                 从当前目录下往下找
    QString videopath = QDir::currentPath()+"/image";
    qDebug()<setIconSize(QSize(100,100));
        //设置列表项整体的尺寸
        newitem->setSizeHint(QSize(250,190));
        //设置列表项中的文字对齐方式:水平居中
        newitem->setTextAlignment(Qt::AlignHCenter);
        //在列表框中添加列表项
        videowinlist->addItem(newitem);
    }

    connect(videowinlist,SIGNAL(itemClicked(QListWidgetItem*)),this,SLOT(videoWidgetShowSlot(QListWidgetItem*)));

    //控件布局
    //方法一:自定义所有控件
    //控件水平布局  顶部窗口放置水平布局
    QHBoxLayout *hboxlayout = new QHBoxLayout(topwin);
    logo1Lab = new QLabel();
    searchEdit = new QLineEdit();
    searchBtn = new QPushButton();
    return1Btn = new QPushButton();
    userBtn = new QPushButton();
    vipBtn = new QPushButton();
    recordBtn = new QPushButton();
    downloadBtn = new QPushButton();
    likeBtn = new QPushButton();
    shrinkBtn = new QPushButton();
    enlargeBtn = new QPushButton();
    cutBtn = new QPushButton();
    closeBtn = new QPushButton();

    logo1Lab->setFixedSize(170,110);
    logo1Lab->setPixmap(QPixmap(":/image/0510.png").scaled(logo1Lab->size()));
    //编辑框的大小位置设置
    searchEdit->setFixedSize(600,35);

    //按钮控件大小设置
    searchBtn->setFixedSize(40,40);
    searchBtn->setIcon(QIcon(QPixmap(":/image/050909.png")));
    searchBtn->setIconSize(QSize(40,40));
    return1Btn->setFixedSize(40,40);
    return1Btn->setIcon(QIcon(QPixmap(":/image/051001.png")));
    return1Btn->setIconSize(QSize(40,40));
    userBtn->setFixedSize(40,40);
    userBtn->setIcon(QIcon(QPixmap(":/image/050901.png")));
    userBtn->setIconSize(QSize(40,40));
    vipBtn->setFixedSize(40,40);
    vipBtn->setIcon(QIcon(QPixmap(":/image/050902.png")));
    vipBtn->setIconSize(QSize(40,40));
    recordBtn->setFixedSize(40,40);
    recordBtn->setIcon(QIcon(QPixmap(":/image/050903.png")));
    recordBtn->setIconSize(QSize(40,40));
    downloadBtn->setFixedSize(40,40);
    downloadBtn->setIcon(QIcon(QPixmap(":/image/050904.png")));
    downloadBtn->setIconSize(QSize(40,40));
    likeBtn->setFixedSize(40,40);
    likeBtn->setIcon(QIcon(QPixmap(":/image/050905.png")));
    likeBtn->setIconSize(QSize(40,40));
    shrinkBtn->setFixedSize(40,40);
    shrinkBtn->setIcon(QIcon(QPixmap(":/image/050906.png")));
    shrinkBtn->setIconSize(QSize(40,40));
    enlargeBtn->setFixedSize(40,40);
    enlargeBtn->setIcon(QIcon(QPixmap(":/image/050907.png")));
    enlargeBtn->setIconSize(QSize(40,40));
    cutBtn->setFixedSize(40,40);
    cutBtn->setIcon(QIcon(QPixmap(":/image/051101.png")));
    cutBtn->setIconSize(QSize(40,40));
    closeBtn->setFixedSize(40,40);
    closeBtn->setIcon(QIcon(QPixmap(":/image/050908.png")));
    closeBtn->setIconSize(QSize(40,40));

    //加空白 - 将控件右置 - 界面整体美观
    hboxlayout->addStretch();
    hboxlayout->addWidget(logo1Lab);
    hboxlayout->addWidget(searchEdit);
    hboxlayout->addWidget(searchBtn);
    hboxlayout->addWidget(return1Btn);
    hboxlayout->addWidget(userBtn);
    hboxlayout->addWidget(vipBtn);
    hboxlayout->addWidget(recordBtn);
    hboxlayout->addWidget(downloadBtn);
    hboxlayout->addWidget(likeBtn);
    hboxlayout->addWidget(shrinkBtn);
    hboxlayout->addWidget(enlargeBtn);
    hboxlayout->addWidget(cutBtn);
    hboxlayout->addWidget(closeBtn);

    //方法二:动态创建所有控件
    //按钮控件垂直  左边窗口放置一个垂直布局
    QVBoxLayout *vboxlayout1 = new QVBoxLayout(leftwin);
    QStringList funBtnlist;
    funBtnlist<<"推荐"<<"电视剧"<<"电影"<<"综艺"<<"儿童"<<"动漫"<<"游戏"<<"纪录片"<<"体育";
    for(int i=0;iaddWidget(newBtn);
    }
    //加空白 -- 将按钮上置
    //vboxlayout1->addStretch();

    //窗口布局
    //右半部分的布局放置顶部窗口和(视频列表)主窗口  两个窗口垂直
    QVBoxLayout *rightboxlayout = new QVBoxLayout(rightwin);
    rightboxlayout->addWidget(topwin);       //顶部窗口
    //rightboxlayout->addWidget(indexwin);
    rightboxlayout->addWidget(videowinlist); //视频列表

    //左边窗口和前面两个窗口垂直的部分 进行水平的放置
    //this指代最大布局
    QHBoxLayout *mainlayout = new QHBoxLayout(this);
    mainlayout->addWidget(leftwin);           //放置窗口
    //mainlayout->addLayout(rightboxlayout);  //放置布局
    mainlayout->addWidget(rightwin);

    //返回按钮回到登录界面
    connect(return1Btn,SIGNAL(clicked(bool)),this,SLOT(returnLoginSlot()));
    //关闭按钮关闭窗口
    connect(closeBtn,SIGNAL(clicked(bool)),this,SLOT(close()));
    //还原按钮的实现
    connect(shrinkBtn,SIGNAL(clicked(bool)),this,SLOT(showNormal()));
    //最大化按钮的实现
    connect(enlargeBtn,SIGNAL(clicked(bool)),this,SLOT(showMaximized()));
    //最小化按钮的实现
    connect(cutBtn,SIGNAL(clicked(bool)),this,SLOT(showMinimized()));
    //列表项双击itemDoubleClicked信号 - 获取图片名字
    //connect(videowinlist,SIGNAL(itemDoubleClicked(QListWidgetItem*)),this,
    //      SLOT(GetItemName(QListWidgetItem *)));
    //列表项双击信号 - 放大显示图片
    connect(videowinlist,SIGNAL(itemDoubleClicked(QListWidgetItem*)),this,
            SLOT(enlargeImage(QListWidgetItem *)));
}

//回到登录界面
void IndexWin::returnLoginSlot()
{
    //Widget - 登录窗口
    Widget *re1 = new Widget;
    re1->show();
    this->hide();
}

//视频播放器界面中左边窗口的不同的按钮实现不同的响应
//更新视频列表 - 分文件管理
void IndexWin::goNewwinSlot()
{
    //获取点击按钮的文本信息 - sender -对QPushButton对象进行操作
    QPushButton *btn = (QPushButton *)sender();
    //按钮对象文本信息的获取和保存
    QString btnName = btn->text();
    //qDebug()<text();

    //根据获取到的文本信息进行判断 - 根据文本信息进行区分不同的按钮 - 不同的按钮做出不同的响应
    //更新列表    分文件管理
    if(btnName == "推荐")
    {
        //先清空之前列表 再进行更新
         videowinlist->clear();
        //更新推荐列表
            //获取推荐中图片的路径 -                     从当前目录下往下找
            QString videopath = QDir::currentPath()+"/image/推荐";
            qDebug()<setIconSize(QSize(100,100));
                //设置列表项的尺寸
                newitem->setSizeHint(QSize(250,190));
                //设置文字对齐方式:水平居中
                newitem->setTextAlignment(Qt::AlignHCenter);
                //在列表框中添加列表项
                videowinlist->addItem(newitem);
            }
            //   弹窗            对话框悬空在上面-NULL
            QMessageBox::information(NULL,"推荐",btn->text());
    }
    if(btnName == "电视剧")
    {
        videowinlist->clear();

        //更新电视剧列表
        //获取电视剧图片路径 -                    从当前目录下往下找
        QString videopath = QDir::currentPath()+"/image/电视剧";
        qDebug()<setIconSize(QSize(100,100));
            //设置列表项的尺寸
            newitem->setSizeHint(QSize(250,190));
            //设置文字对齐方式:水平居中
            newitem->setTextAlignment(Qt::AlignHCenter);
            //在列表框中添加列表项
            videowinlist->addItem(newitem);
        }
            QMessageBox::information(NULL,"电视剧",btn->text());
    }
    if(btnName == "电影")
    {
        videowinlist->clear();

        //更新电影列表
        //获取电影图片路径 -                     从当前目录下往下找
        QString videopath = QDir::currentPath()+"/image/电影";
        qDebug()<setIconSize(QSize(100,100));
            //设置列表项的尺寸
            newitem->setSizeHint(QSize(250,190));
            //设置文字对齐方式:水平居中
            newitem->setTextAlignment(Qt::AlignHCenter);
            //在列表框中添加列表项
            videowinlist->addItem(newitem);
        }
        QMessageBox::information(NULL,"电影",btn->text());
    }
    if(btnName == "综艺")
    {
        videowinlist->clear();

        //更新综艺列表
        //获取综艺图片路径 -                     从当前目录下往下找
        QString videopath = QDir::currentPath()+"/image/综艺";
        qDebug()<setIconSize(QSize(100,100));
            //设置列表项的尺寸
            newitem->setSizeHint(QSize(250,190));
            //设置文字对齐方式:水平居中
            newitem->setTextAlignment(Qt::AlignHCenter);
            //在列表框中添加列表项
            videowinlist->addItem(newitem);
        }
        QMessageBox::critical(NULL,"综艺",btn->text());
    }
    if(btnName == "儿童")
    {
        QMessageBox::question(NULL,"儿童",btn->text());

        QString cinputFilePath = "test.avi";
//        videoDecodeThread *p = new videoDecodeThread(cinputFilePath);
//        p->start();

//        //线程的创建
//        testthread *t = new testthread;
//        //线程的启动
//        t->start();

    }
    if(btnName == "动漫")
    {
        QMessageBox::warning(NULL,"动漫",btn->text());
    }
    if(btnName == "游戏")
    {
        QMessageBox::about(NULL,"游戏",btn->text());
    }
    if(btnName == "纪录片")
    {
        QMessageBox::information(NULL,"纪录片",btn->text());
    }
    if(btnName == "体育")
    {
        QMessageBox::information(NULL,"体育",btn->text());
    }
}

//QListWidget获取Item信号槽 - 点击图片获取图片名称
void IndexWin::GetItemName(QListWidgetItem *item)
{
    QMessageBox::about(NULL,"视频列表",item->text());
    qDebug()<text();
}

//通过自定义的窗口显示图片
void IndexWin::enlargeImage(QListWidgetItem *item)
{
    QRect rect = this->geometry();//获取当前窗口坐标及大小 x、y、w、h

    //通过自定义的窗口显示图片
    imageWin *showImageWidget = new imageWin(item , QRect(rect.x(), rect.y()+rect.y()*1/4, rect.width(), rect.height()*2/3));
    showImageWidget->show();
}

void IndexWin::videoWidgetShowSlot(QListWidgetItem *item)
{
    qDebug()<text()<<"界面创建";

    QStringList list = item->text().split(".");//QString字符串分割函数
    qDebug()<show();

//    videoDecodeThread *p = new videoDecodeThread(cinputFilePath);
    //新建视频播放界面
//    p->start();


}

你可能感兴趣的:(FFmpeg音视频开发,音视频,qt,FFMPEG)