Discuz! Board

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 839|回复: 0

简单的视频播放例子(Qt)

[复制链接]

1214

主题

1975

帖子

7486

积分

认证用户组

Rank: 5Rank: 5

积分
7486
发表于 2022-10-8 13:50:55 | 显示全部楼层 |阅读模式
                           FFmpeg 简单的视频播放例子(Qt)
一、简述

      记--直接在FFmpeg官网下载已经编译好的库(也可以自己用源码编译),并在Qt程序中引用并使用,例子知识简单的播放,待完善。

       例子代码和FFmpeg相关库文件:https://lanzoux.com/b0c8c02qb 密码:3wdn        

二、下载FFmpeg库(也可以自己用源码编译)

        官网:https://ffmpeg.zeranoe.com/builds/





三、 在工程中引用FFmpeg

       3.1 使用QtCreator创建一个“Qt Widgts Application”



      3.2 将编译相关文件放到工程目录下

              3.2.1 在工程目录下创建ffmpeg目录,以统一存放ffmpeg相关文件



              3.2.2 解压ffmpeg-4.2.2-win32-dev.zip和ffmpeg-4.2.2-win32-shared.zip



           3.2.3 将相关文件拷贝到 3.2.1步骤创建的目录下

                   将ffmpeg-4.2.2-win32-dev目录下的include整个目录拷贝到ffmpeg目录下                       

                   将ffmpeg-4.2.2-win32-dev的lib目录下的所有.dll.a文件拷贝到ffmpeg/lib目录下



      3.3 在项目中添加引用

           3.3.1 在.pro文件添加头文件路径和lib路径



INCLUDEPATH += $$PWD/ffmpeg/include
LIBS += $$PWD/ffmpeg/lib/libavcodec.dll.a \
             $$PWD/ffmpeg/lib/libavfilter.dll.a \
             $$PWD/ffmpeg/lib/libavformat.dll.a \
             $$PWD/ffmpeg/lib/libavutil.dll.a \
             $$PWD/ffmpeg/lib/libswscale.dll.a
           3.3.2  在代码中添加头文件引用         

四、简单测试FFmpeg是否可用 (例子1)

          4.1 编写代码输出FFmpeg的配置信息和版本信息         

4.2 编译



4.3 运行

          因为是动态编译,在运行前需要将程序所需要的动态库拷贝到程序的同级目录,或者是将动态库路径设置到环境变量。

          例子是debug版本,就将动态库拷贝到debug目录





五、播放视频(例子2 )

      主要代码:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QTime>
#include <QIcon>
#include <QPixmap>
#include <QFileDialog>
#include <QMessageBox>
#include <QSlider>
#include <QComboBox>

//包含ffmpeg相关头文件,并告诉编译器以C语言方式处理
extern "C"
{
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libswscale/swscale.h>
    #include <libavdevice/avdevice.h>
    #include <libavformat/version.h>
    #include <libavutil/time.h>
    #include <libavutil/mathematics.h>
    #include <libavutil/imgutils.h>
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    //输出FFmpeg版本信息
    qDebug("ffmpeg versioin is: %u", avcodec_version());

    ui->setupUi(this);

    //初始化
    init();
}

MainWindow::~MainWindow()
{
    delete ui;
}

//初始化
void MainWindow::init()
{
    mVideoWidth = this->width();
    mVideoHeight = this->height();

    //设置视频label的宽高为窗体宽高
    ui->labelVideo->setGeometry(0, 0, mVideoWidth, mVideoHeight);

    //初始化播放速度
    mPlaySpdVal = PLAY_SPD_10;

    //初播放状态wei
    mVideoPlaySta = Video_PlayFinish;

    /*****在状态栏添加一个用来显示视频路径的label************/
    //用于显示已经选择的视频文件路径
    mVideoFile = new QLabel(this);
    mVideoFile->setToolTip("视频文件");
    ui->statusBar->addWidget(mVideoFile);

    /*****在工具栏添加选择动作************/
    //创建一个选择视频文件动作
    QAction* pActionSelectVideoFile = new QAction(QIcon(QPixmap(":/selectVideo.png")), "选择");

    //设置鼠标悬停提示文本
    pActionSelectVideoFile->setToolTip("选择视频文件");

    //将动作添加到工具栏
    ui->mainToolBar->addAction(pActionSelectVideoFile);

    //连接动作的点击事件
    QObject::connect(pActionSelectVideoFile, SIGNAL(triggered(bool)), this, SLOT(on_selectVideoFile()));

    /*****在工具栏添加播放/暂停动作************/
    //创建一个播放/暂停视频动作
    mActionPlayCtrl = new QAction(QIcon(QPixmap(":/play.png")), "播放");

    //设置鼠标悬停提示文本
    mActionPlayCtrl->setToolTip("播放视频");

    //将动作添加到工具栏
    ui->mainToolBar->addAction(mActionPlayCtrl);

    //连接动作的点击事件
    QObject::connect(mActionPlayCtrl, SIGNAL(triggered(bool)), this, SLOT(on_videoPlayCtrl()));

    /*****在工具栏添加停止动作************/
    //创建一个停止播放视频动作
    QAction* pActionStopPlay = new QAction(QIcon(QPixmap(":/stop.png")), "停止");

    //设置鼠标悬停提示文本
    pActionStopPlay->setToolTip("停止播放");

    //将动作添加到工具栏
    ui->mainToolBar->addAction(pActionStopPlay);

    //连接动作的点击事件
    QObject::connect(pActionStopPlay, SIGNAL(triggered(bool)), this, SLOT(on_stopPlayVideo()));

    /*****在工具栏添加窗体大小适配视频宽高动作************/
    //创建一个停止播放视频动作
    QAction* pActionAutoSize = new QAction(QIcon(QPixmap(":/autoSize.png")), "自适应");

    //设置鼠标悬停提示文本
    pActionAutoSize->setToolTip("窗体自适应视频宽高");

    //将动作添加到工具栏
    ui->mainToolBar->addAction(pActionAutoSize);

    //连接动作的点击事件
    QObject::connect(pActionAutoSize, SIGNAL(triggered(bool)), this, SLOT(on_autoSize()));

    //添加分隔符
    ui->mainToolBar->addSeparator();

    //添加播放速度下拉选择
    QStringList plsySpdItems;
    plsySpdItems << "0.5";
    plsySpdItems << "0.8";
    plsySpdItems << "1";
    plsySpdItems << "1.2";
    plsySpdItems << "1.5";
    plsySpdItems << "2";
    plsySpdItems << "3";
    plsySpdItems << "4";
    QComboBox* comboBoxPlaySpd = new QComboBox();
    comboBoxPlaySpd->setToolTip("播放速度");
    comboBoxPlaySpd->addItems(plsySpdItems);//添加倍速选项
    comboBoxPlaySpd->setCurrentIndex(2);//设置默认速度1
    comboBoxPlaySpd->setEditable(false);//设置为不可编辑

    //设置样式
    //comboBoxPlaySpd->setStyleSheet("QComboBox { border:1px solid black; min-width: 30px;} QComboBox QAbstractItemView::item {min-height:20px; background-color: rgb(0, 0, 0);} QFrame { border: 1px solid black; }");
    ui->mainToolBar->addWidget(comboBoxPlaySpd);
    QObject::connect(comboBoxPlaySpd, SIGNAL(currentIndexChanged(int)), this, SLOT(on_playSpdChange(int)));

    //添加分隔符
    ui->mainToolBar->addSeparator();

    //添加播放声音滑动条到工具栏
    QString sliderStyle = "QSlider::groove:horizontal{ \
            height: 6px; \
            background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: , stop: 0 rgb(124, 124, 124), stop: 1.0 rgb(72, 71, 71)); \
   } \
    QSlider::handle:horizontal { \
            width: 5px; \
            background: rgb(0, 160, 230); \
            margin: -6px 0px -6px 0px; \
            border-radius: 9px; \
   }";
    QSlider* slider = new QSlider(Qt::Orientation::Horizontal, this);
    slider->setToolTip("音量");
    slider->setMaximumWidth(100);//设置最大值
    slider->setRange(0, 100);//设置滑动范围
    slider->setStyleSheet(sliderStyle);//设置样式
    ui->mainToolBar->addWidget(slider);
    //连接动作的滑动事件
    QObject::connect(slider, SIGNAL(sliderMoved(int)), this, SLOT(on_VolumeChange(int)));

    /*****在工具栏添加一个用来显示音量的label************/
    mCurVolume = new QLabel("音量60");
    mCurVolume->setToolTip("当前音量值");
    ui->mainToolBar->addWidget(mCurVolume);


}

//延时, 不能直接sleep延时,UI主线程不能直接被阻塞,不然会有问题的
void delay(int ms)
{
    QTime stopTime;
    stopTime.start();
    //qDebug()<<"start:"<<QTime::currentTime().toString("HH:mm:ss.zzz");
    while(stopTime.elapsed() < ms)//stopTime.elapsed()返回从start开始到现在的毫秒数
    {
        QCoreApplication::processEvents();
    }
    //qDebug()<<"stop :"<<QTime::currentTime().toString("HH:mm:ss.zzz");
}

//重新设置窗体和视频Label的宽高
void MainWindow::resizeWindow(int width, int height)
{
    if(width<1)
    {
       width = this->width();
    }

    if(height<1)
    {
        height = this->height();
    }

    this->setGeometry(100, 100, width, height);
    ui->labelVideo->setGeometry(0, 0, width, height);
}

//窗体变化事件
void MainWindow::resizeEvent(QResizeEvent* event)
{
    Q_UNUSED(event);

    //让视频Label跟随窗体变化
    ui->labelVideo->resize(this->size());
}

//使用FFmpeg播放视频
int MainWindow::playVideo(char* videoPath)
{
    unsigned char* buf;
    int isVideo = -1;
    int ret, gotPicture;
    unsigned int i, streamIndex = 0;
    AVCodec *pCodec;
    AVPacket *pAVpkt;
    AVCodecContext *pAVctx;
    AVFrame *pAVframe, *pAVframeRGB;
    AVFormatContext* pFormatCtx;
    struct SwsContext* pSwsCtx;

    //创建AVFormatContext
    pFormatCtx = avformat_alloc_context();

    //初始化pFormatCtx
    if (avformat_open_input(&pFormatCtx, videoPath, NULL, NULL) != 0)
    {
        qDebug("avformat_open_input err.");
        return -1;
    }

    //获取音视频流数据信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
    {
        avformat_close_input(&pFormatCtx);
        qDebug("avformat_find_stream_info err.");
        return -2;
    }

    //找到视频流的索引
    for (i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            streamIndex = i;
            isVideo = 0;
            break;
        }
    }

    //没有视频流就退出
    if (isVideo == -1)
    {
        avformat_close_input(&pFormatCtx);
        qDebug("nb_streams err.");
        return -3;
    }

    //获取视频流编码
    pAVctx = avcodec_alloc_context3(NULL);;

    //查找解码器
    avcodec_parameters_to_context(pAVctx, pFormatCtx->streams[streamIndex]->codecpar);
    pCodec = avcodec_find_decoder(pAVctx->codec_id);
    if (pCodec == NULL)
    {
        avcodec_close(pAVctx);
        avformat_close_input(&pFormatCtx);
        qDebug("avcodec_find_decoder err.");
        return -4;
    }

    //初始化pAVctx
    if (avcodec_open2(pAVctx, pCodec, NULL) < 0)
    {
        avcodec_close(pAVctx);
        avformat_close_input(&pFormatCtx);
        qDebug("avcodec_open2 err.");
        return -5;
    }

    //初始化pAVpkt
    pAVpkt = (AVPacket *)av_malloc(sizeof(AVPacket));

    //初始化数据帧空间
    pAVframe = av_frame_alloc();
    pAVframeRGB = av_frame_alloc();

    //创建图像数据存储buf
    //av_image_get_buffer_size一帧大小
    buf = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGB32, pAVctx->width, pAVctx->height, 1));
    av_image_fill_arrays(pAVframeRGB->data, pAVframeRGB->linesize, buf, AV_PIX_FMT_RGB32, pAVctx->width, pAVctx->height, 1);

    //根据视频宽高重新调整窗口大小 视频宽高 pAVctx->width, pAVctx->height
    resizeWindow(pAVctx->width, pAVctx->height);

    //初始化pSwsCtx
    pSwsCtx = sws_getContext(pAVctx->width, pAVctx->height, pAVctx->pix_fmt, pAVctx->width, pAVctx->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);

    //循环读取视频数据
    for(;;)
    {
        if(mVideoPlaySta == Video_Playing)//正在播放
        {
            if(av_read_frame(pFormatCtx, pAVpkt) >= 0)//读取一帧未解码的数据
            {
                //如果是视频数据
                if (pAVpkt->stream_index == (int)streamIndex)
                {
                    //解码一帧视频数据
                    ret = avcodec_decode_video2(pAVctx, pAVframe, &gotPicture, pAVpkt);
                    if(ret < 0)
                    {
                        qDebug("Decode Error.\n");
                        continue;
                    }

                    if(gotPicture)
                    {
                        sws_scale(pSwsCtx, (const unsigned char* const*)pAVframe->data, pAVframe->linesize, 0, pAVctx->height, pAVframeRGB->data, pAVframeRGB->linesize);
                        QImage img((uchar*)pAVframeRGB->data[0], pAVctx->width, pAVctx->height, QImage::Format_RGB32);
                        ui->labelVideo->setPixmap(QPixmap::fromImage(img));
                        delay(mPlaySpdVal);//播放延时
                    }
                }
                av_packet_unref(pAVpkt);
            }
            else
            {
                break;
            }
        }
        else if(mVideoPlaySta == Video_PlayFinish)//播放结束
        {
            break;
        }
        else//暂停
        {
            delay(300);
        }
    }

    //释放资源
    sws_freeContext(pSwsCtx);
    av_frame_free(&pAVframeRGB);
    av_frame_free(&pAVframe);
    avcodec_close(pAVctx);
    avformat_close_input(&pFormatCtx);

    mVideoPlaySta = Video_PlayFinish;
    qDebug()<<"play finish!";

    return 0;
}

//选择视频文件
void MainWindow:n_selectVideoFile()
{
    QString path = QFileDialog::getOpenFileName(this,"选择视频文件","D:\\Test\\Video\\","WMV视频(*.wmv);;MP4视频(*.mp4);;AVI视频(*.avi)");
    if(!path.isEmpty())
    {
        mVideoFile->setText(path);
        on_videoPlayCtrl();
    }
}

//视频播放/暂停控制
void MainWindow:n_videoPlayCtrl()
{
    QString videoFilePath = mVideoFile->text();
    if(videoFilePath.isEmpty())
    {
        return;
    }

    //判断文件是都存在(以防被删除了)
//    QFileInfo fileInfo(videoFilePath);
//    if(fileInfo.isFile())
//    {
//        QMessageBox::information(this, "提示", "找不到文件:"+videoFilePath);
//        return;
//    }

    //播放和暂停切换
    if(mActionPlayCtrl->text() == "播放")
    {
        mActionPlayCtrl->setText("暂停");
        mActionPlayCtrl->setToolTip("暂停视频");
        mActionPlayCtrl->setIcon(QIcon(QPixmap(":/pause.png")));

        if(mVideoPlaySta == Video_PlayFinish)
        {
            mVideoPlaySta = Video_Playing;
            playVideo(videoFilePath.toLocal8Bit().data());
            on_stopPlayVideo();
        }
        else
        {
            mVideoPlaySta = Video_Playing;
        }
    }
    else
    {
        mActionPlayCtrl->setText("播放");
        mActionPlayCtrl->setToolTip("播放视频");
        mActionPlayCtrl->setIcon(QIcon(QPixmap(":/play.png")));

        if(mVideoPlaySta == Video_Playing)
        {
            mVideoPlaySta = Video_PlayPause;
        }
    }
}

//停止播放视频
void MainWindow:n_stopPlayVideo()
{
    mVideoPlaySta = Video_PlayFinish;
    mActionPlayCtrl->setText("播放");
    mActionPlayCtrl->setToolTip("播放视频");
    mActionPlayCtrl->setIcon(QIcon(QPixmap(":/play.png")));
}

//窗体自适应视频宽高
void MainWindow:n_autoSize()
{
    resizeWindow(mVideoWidth, mVideoHeight);
}

//窗体关闭事件
void MainWindow::closeEvent(QCloseEvent* event)//窗体关闭事件
{
    //没有在播放视频时直接退出
    if(mVideoPlaySta != Video_PlayFinish)
    {
        if(QMessageBox::Yes == QMessageBox::information(this, "提示", "确认关闭?", QMessageBox::Yes, QMessageBox::No))
        {
            mVideoPlaySta = Video_PlayFinish;
            //event->accept();
        }
        else
        {
            event->ignore();//忽略,不关闭
        }
    }
}

//播放音量变化
void MainWindow:n_VolumeChange(int volume)
{
    mCurVolume->setText("音量"+QString::number(volume));
}

//播放速度变化
void MainWindow:n_playSpdChange(int spdVal)
{
    switch(spdVal)
    {
        case 0://0.5
            mPlaySpdVal = PLAY_SPD_05;
            break;
        case 1://0.8
            mPlaySpdVal = PLAY_SPD_08;
            break;
        case 2://1
            mPlaySpdVal = PLAY_SPD_10;
            break;
        case 3://1.2
            mPlaySpdVal = PLAY_SPD_12;
            break;
        case 4://1.5
            mPlaySpdVal = PLAY_SPD_15;
            break;
        case 5://2
            mPlaySpdVal = PLAY_SPD_20;
            break;
        case 6://3
            mPlaySpdVal = PLAY_SPD_30;
            break;
        case 7://4
            mPlaySpdVal = PLAY_SPD_40;
            break;
        default:
            break;
    }
}
       5.1 播放.wmv视频、.mp4视频、播放avi视频下效果。



       其它视频格式尚未测试。

六、查看接口变更信息

        请下载ffmpeg源码或者是在github上查看。在doc/APIchanges


————————————————
版权声明:本文为CSDN博主「Genven_Liang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/nanfeibuyi/article/details/105915235

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|firemail ( 粤ICP备15085507号-1 )

GMT+8, 2024-3-29 10:19 , Processed in 0.058820 second(s), 20 queries .

Powered by Discuz! X3

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表