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


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);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容