美文网首页FFmpeg
基于Qt和FFmpeg的简易视频播放器

基于Qt和FFmpeg的简易视频播放器

作者: saltcc | 来源:发表于2019-07-31 07:58 被阅读5次

    1.前言

    这里实现了简易的视频解码,后面会陆续发布音视频同步等相关文章

    2.视频解码播放流程

    FFmpeg解码视频与Qt显示播放流程


    3.结构体概要介绍
    • AVFormatContext
      AVFormatContext 在FFmpeg中有很重要的作用,描述一个多媒体文件的构成及其基本信息,存放了视频编解码过程中的大部分信息。通常该结构体由avformat_open_input分配存储空间,在最后调用avformat_input_close关闭

    • AVCodecContext
      AVCodecContext是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息

    • AVCodec
      AVCodec是存储编解码器信息的结构体

    • AVFrame
      AVFrame 存放从AVPacket中解码出来的原始数据,其必须通过av_frame_alloc来创建,通过av_frame_free来释放。和AVPacket类似,AVFrame中也有一块数据缓存空间,在调用av_frame_alloc的时候并不会为这块缓存区域分配空间,需要使用其他的方法。在解码的过程使用了两个AVFrame,这两个AVFrame分配缓存空间的方法也不相同

    • AVPacket
      AVpacket 用来存放解码之前的数据,它只是一个容器,其data成员指向实际的数据缓冲区,在解码的过程中可有av_read_frame创建和填充AVPacket中的数据缓冲区,
      当数据缓冲区不再使用的时候可以调用av_free_apcket释放这块缓冲区

    • SwsContext
      主要用于视频图像的转换,比如格式转换

    4.函数概要功能
    • av_register_all()
      该函数在所有基于ffmpeg的应用程序中几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器等(新版本FFmpeg该函数为非必要调用)

    • avformat_open_input()
      该函数用于打开多媒体数据,主要是探测码流的格式

    • avformat_find_stream_info()
      读取一部分视音频数据并且获得一些相关的信息,内部实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作

    • avcodec_find_decoder()
      获取解码器

    • avcodec_open2()
      该函数用于初始化一个视音频编解码器的AVCodecContext

    • av_read_frame()
      该函数作用是读取码流中的音频若干帧或者视频一帧

    • avcodec_decode_video2()
      该函数作用是解码一帧视频数据,输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame

    • sws_getContext()
      初始化一个SwsContext,将传入的源图像,目标图像的宽高,像素格式,以及标志位分别赋值给该SwsContext相应的字段

    • avpicture_fill()
      为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间

    • sws_scale()
      该函数用于转换像素

    5.简易播放器的实现
    项目构成
    • main.cpp
    #include "mainwindow.h"
    #include <QApplication>
    #include <iostream>
    #include <QDebug>
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
        return a.exec();
    }
    
    • mainwindow.h
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QPaintEvent>
    #include <QImage>
    
    #include "SimpleVideoPlayer.h"
    
    namespace Ui {
    class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
    protected:
        void paintEvent(QPaintEvent *event);
    
    private:
        Ui::MainWindow *ui;
    
        SimpleVideoPlayer _simpleVp;
        QImage _Qimg;
    
    private slots:
        void sigGetVideoFrame(QImage img);
    };
    
    #endif // MAINWINDOW_H
    
    • mainwindow.cpp
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    #include <QPainter>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        //信号槽
        connect(&_simpleVp,SIGNAL(sigCreateVideoFrame(QImage)),this,SLOT(sigGetVideoFrame(QImage)));
        //视频文件路径
        _simpleVp.setVideo("D:/Qt/testffmpeg/testffmpeg/in2.mp4");
        _simpleVp.start();
    
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::paintEvent(QPaintEvent *event)
    {
        QPainter painter(this);
        painter.setBrush(Qt::black);
        painter.drawRect(0, 0, this->width(), this->height());
    
        if (_Qimg.size().width() <= 0)
            return;
    
        //比例缩放
        QImage img = _Qimg.scaled(this->size(),Qt::KeepAspectRatio);
        int x = this->width() - img.width();
        int y = this->height() - img.height();
    
        x /= 2;
        y /= 2;
    
        //QPoint(x,y)为中心绘制图像
        painter.drawImage(QPoint(x,y),img);
    }
    
    void MainWindow::sigGetVideoFrame(QImage img)
    {
        _Qimg = img;
        //paintEvent
        update();
    }
    
    • SimpleVideoPlayer.h
    #ifndef SIMPLEVIDEOPLAYER_H
    #define SIMPLEVIDEOPLAYER_H
    
    #include <QThread>
    #include <QImage>
    
    class SimpleVideoPlayer : public QThread
    {
        Q_OBJECT
    
    public:
        explicit SimpleVideoPlayer();
        ~SimpleVideoPlayer();
    
        void setVideo(const QString &videoPath)
        {
            this->_videoPath = videoPath;
        }
    
        inline void play();
    
    signals:
        void sigCreateVideoFrame(QImage image);
    
    protected:
        void run();
    
    private:
        QString _videoPath;
    };
    
    #endif // SIMPLEVIDEOPLAYER_H
    
    
    • SimpleVideoPlayer.cpp
    #include "SimpleVideoPlayer.h"
    #include <iostream>
    
    #include <QDebug>
    
    extern "C"
    {
        #include "libavcodec/avcodec.h"
        #include "libavformat/avformat.h"
        #include "libavutil/pixfmt.h"
        #include "libswscale/swscale.h"
    }
    
    SimpleVideoPlayer::SimpleVideoPlayer()
    {
    
    }
    
    SimpleVideoPlayer::~SimpleVideoPlayer()
    {
    
    }
    
    void SimpleVideoPlayer::play()
    {
        start();
    }
    
    void SimpleVideoPlayer::run()
    {
        if (_videoPath.isNull())
        {
            return;
        }
    
        char *file = _videoPath.toUtf8().data();
    
        //编解码器的注册,最新版本不需要调用
        av_register_all();
    
        //描述多媒体文件的构成及其基本信息
        AVFormatContext *pAVFormatCtx = avformat_alloc_context();
    
        if (avformat_open_input(&pAVFormatCtx, file, NULL, NULL) != 0)
        {
            std::cout<<"open file fail"<<std::endl;
            avformat_free_context(pAVFormatCtx);
            return;
        }
    
        //读取一部分视音频数据并且获得一些相关的信息
        if (avformat_find_stream_info(pAVFormatCtx, NULL) < 0)
        {
            std::cout<<"avformat find stream fail"<<std::endl;
            avformat_close_input(&pAVFormatCtx);
            return;
        }
    
        int iVideoIndex = -1;
    
        for (uint32_t i = 0; i < pAVFormatCtx->nb_streams; ++i)
        {
            //视频流
            if (pAVFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
            {
                iVideoIndex = i;
                break;
            }
        }
    
        if (iVideoIndex == -1)
        {
            std::cout<<"video stream not find"<<std::endl;
            avformat_close_input(&pAVFormatCtx);
            return;
        }
    
        //获取视频流的编解码器上下文的数据结构
        AVCodecContext *pAVCodecCtx = pAVFormatCtx->streams[iVideoIndex]->codec;
    
        if (pAVCodecCtx == NULL)
        {
            std::cout<<"get codec fail"<<std::endl;
            avformat_close_input(&pAVFormatCtx);
            return;
        }
    
        //编解码器信息的结构体
        AVCodec *pAVCodec = avcodec_find_decoder(pAVCodecCtx->codec_id);
    
        if (pAVCodec == NULL)
        {
            std::cout<<"not find decoder"<<std::endl;
            return;
        }
    
        //初始化一个视音频编解码器
        if (avcodec_open2(pAVCodecCtx, pAVCodec, NULL) < 0)
        {
            std::cout<<"avcodec_open2 fail"<<std::endl;
            return;
        }
    
        //AVFrame 存放从AVPacket中解码出来的原始数据
        AVFrame *pAVFrame = av_frame_alloc();
        AVFrame *pAVFrameRGB = av_frame_alloc();
    
        //用于视频图像的转换,将源数据转换为RGB32的目标数据
        SwsContext *pSwsCtx = sws_getContext(pAVCodecCtx->width, pAVCodecCtx->height, pAVCodecCtx->pix_fmt,
                                             pAVCodecCtx->width, pAVCodecCtx->height, PIX_FMT_RGB32,
                                             SWS_BICUBIC, NULL, NULL, NULL);
        int iNumBytes = avpicture_get_size(PIX_FMT_RGB32, pAVCodecCtx->width, pAVCodecCtx->height);
    
        uint8_t *pRgbBuffer = (uint8_t *)(av_malloc(iNumBytes * sizeof(uint8_t)));
        //为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间
        avpicture_fill((AVPicture *)pAVFrameRGB, pRgbBuffer, PIX_FMT_BGR32, pAVCodecCtx->width, pAVCodecCtx->height);
    
        //AVpacket 用来存放解码之前的数据
        AVPacket packet;
        av_new_packet(&packet, pAVCodecCtx->width * pAVCodecCtx->height);
    
        //读取码流中视频帧
        while (av_read_frame(pAVFormatCtx, &packet) >= 0)
        {
            if (packet.stream_index != iVideoIndex)
            {
                //清空packet中data以及buf的内容,并没有把packet本身
                av_free_packet(&packet);
                continue;
            }
    
            int iGotPic = -1;
            //解码一帧视频数据
            if (avcodec_decode_video2(pAVCodecCtx, pAVFrame, &iGotPic, &packet) < 0)
            {
                av_free_packet(&packet);
                std::cout<<"avcodec_decode_video2 fail"<<std::endl;
                break;
            }
    
            if (iGotPic > 0)
            {
                //转换像素
                sws_scale(pSwsCtx, (uint8_t const * const *)pAVFrame->data, pAVFrame->linesize, 0, pAVCodecCtx->height, pAVFrameRGB->data, pAVFrameRGB->linesize);
    
                //构造QImage
                QImage img(pRgbBuffer, pAVCodecCtx->width, pAVCodecCtx->height, QImage::Format_RGB32);
    
                //绘制QImage
                emit sigCreateVideoFrame(img);
            }
    
            av_free_packet(&packet);
            msleep(15);
        }
    
        //资源回收
        av_free(pAVFrame);
        av_free(pAVFrameRGB);
        sws_freeContext(pSwsCtx);
        avcodec_close(pAVCodecCtx);
        avformat_close_input(&pAVFormatCtx);
    }
    

    相关文章

      网友评论

        本文标题:基于Qt和FFmpeg的简易视频播放器

        本文链接:https://www.haomeiwen.com/subject/gevarctx.html