基于Qt和FFmpeg的簡易視頻播放器


1.前言

這里實(shí)現(xiàn)了簡易的視頻解碼钮惠,后面會陸續(xù)發(fā)布音視頻同步等相關(guān)文章

2.視頻解碼播放流程

FFmpeg解碼視頻與Qt顯示播放流程


3.結(jié)構(gòu)體概要介紹
  • AVFormatContext
    AVFormatContext 在FFmpeg中有很重要的作用觉啊,描述一個多媒體文件的構(gòu)成及其基本信息辛润,存放了視頻編解碼過程中的大部分信息维哈。通常該結(jié)構(gòu)體由avformat_open_input分配存儲空間,在最后調(diào)用avformat_input_close關(guān)閉

  • AVCodecContext
    AVCodecContext是一個描述編解碼器上下文的數(shù)據(jù)結(jié)構(gòu),包含了眾多編解碼器需要的參數(shù)信息

  • AVCodec
    AVCodec是存儲編解碼器信息的結(jié)構(gòu)體

  • AVFrame
    AVFrame 存放從AVPacket中解碼出來的原始數(shù)據(jù),其必須通過av_frame_alloc來創(chuàng)建脊串,通過av_frame_free來釋放。和AVPacket類似清钥,AVFrame中也有一塊數(shù)據(jù)緩存空間,在調(diào)用av_frame_alloc的時候并不會為這塊緩存區(qū)域分配空間放闺,需要使用其他的方法祟昭。在解碼的過程使用了兩個AVFrame,這兩個AVFrame分配緩存空間的方法也不相同

  • AVPacket
    AVpacket 用來存放解碼之前的數(shù)據(jù)怖侦,它只是一個容器篡悟,其data成員指向?qū)嶋H的數(shù)據(jù)緩沖區(qū),在解碼的過程中可有av_read_frame創(chuàng)建和填充AVPacket中的數(shù)據(jù)緩沖區(qū)匾寝,
    當(dāng)數(shù)據(jù)緩沖區(qū)不再使用的時候可以調(diào)用av_free_apcket釋放這塊緩沖區(qū)

  • SwsContext
    主要用于視頻圖像的轉(zhuǎn)換搬葬,比如格式轉(zhuǎn)換

4.函數(shù)概要功能
  • av_register_all()
    該函數(shù)在所有基于ffmpeg的應(yīng)用程序中幾乎都是第一個被調(diào)用的。只有調(diào)用了該函數(shù)艳悔,才能使用復(fù)用器急凰,編碼器等(新版本FFmpeg該函數(shù)為非必要調(diào)用)

  • avformat_open_input()
    該函數(shù)用于打開多媒體數(shù)據(jù),主要是探測碼流的格式

  • avformat_find_stream_info()
    讀取一部分視音頻數(shù)據(jù)并且獲得一些相關(guān)的信息,內(nèi)部實(shí)現(xiàn)了解碼器的查找猜年,解碼器的打開抡锈,視音頻幀的讀取,視音頻幀的解碼等工作

  • avcodec_find_decoder()
    獲取解碼器

  • avcodec_open2()
    該函數(shù)用于初始化一個視音頻編解碼器的AVCodecContext

  • av_read_frame()
    該函數(shù)作用是讀取碼流中的音頻若干幀或者視頻一幀

  • avcodec_decode_video2()
    該函數(shù)作用是解碼一幀視頻數(shù)據(jù)乔外,輸入一個壓縮編碼的結(jié)構(gòu)體AVPacket床三,輸出一個解碼后的結(jié)構(gòu)體AVFrame

  • sws_getContext()
    初始化一個SwsContext,將傳入的源圖像杨幼,目標(biāo)圖像的寬高撇簿,像素格式聂渊,以及標(biāo)志位分別賦值給該SwsContext相應(yīng)的字段

  • avpicture_fill()
    為已經(jīng)分配的空間的結(jié)構(gòu)體AVPicture掛上一段用于保存數(shù)據(jù)的空間

  • sws_scale()
    該函數(shù)用于轉(zhuǎn)換像素

5.簡易播放器的實(shí)現(xiàn)
項(xiàng)目構(gòu)成
  • 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();

    //編解碼器的注冊,最新版本不需要調(diào)用
    av_register_all();

    //描述多媒體文件的構(gòu)成及其基本信息
    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;
    }

    //讀取一部分視音頻數(shù)據(jù)并且獲得一些相關(guān)的信息
    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;
    }

    //獲取視頻流的編解碼器上下文的數(shù)據(jù)結(jié)構(gòu)
    AVCodecContext *pAVCodecCtx = pAVFormatCtx->streams[iVideoIndex]->codec;

    if (pAVCodecCtx == NULL)
    {
        std::cout<<"get codec fail"<<std::endl;
        avformat_close_input(&pAVFormatCtx);
        return;
    }

    //編解碼器信息的結(jié)構(gòu)體
    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中解碼出來的原始數(shù)據(jù)
    AVFrame *pAVFrame = av_frame_alloc();
    AVFrame *pAVFrameRGB = av_frame_alloc();

    //用于視頻圖像的轉(zhuǎn)換,將源數(shù)據(jù)轉(zhuǎn)換為RGB32的目標(biāo)數(shù)據(jù)
    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)));
    //為已經(jīng)分配的空間的結(jié)構(gòu)體AVPicture掛上一段用于保存數(shù)據(jù)的空間
    avpicture_fill((AVPicture *)pAVFrameRGB, pRgbBuffer, PIX_FMT_BGR32, pAVCodecCtx->width, pAVCodecCtx->height);

    //AVpacket 用來存放解碼之前的數(shù)據(jù)
    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的內(nèi)容四瘫,并沒有把packet本身
            av_free_packet(&packet);
            continue;
        }

        int iGotPic = -1;
        //解碼一幀視頻數(shù)據(jù)
        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)
        {
            //轉(zhuǎn)換像素
            sws_scale(pSwsCtx, (uint8_t const * const *)pAVFrame->data, pAVFrame->linesize, 0, pAVCodecCtx->height, pAVFrameRGB->data, pAVFrameRGB->linesize);

            //構(gòu)造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);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末汉嗽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子莲组,更是在濱河造成了極大的恐慌诊胞,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锹杈,死亡現(xiàn)場離奇詭異撵孤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)竭望,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門邪码,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咬清,你說我怎么就攤上這事闭专。” “怎么了旧烧?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵影钉,是天一觀的道長。 經(jīng)常有香客問我掘剪,道長平委,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任夺谁,我火速辦了婚禮廉赔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘匾鸥。我一直安慰自己蜡塌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布勿负。 她就那樣靜靜地躺著馏艾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奴愉。 梳的紋絲不亂的頭發(fā)上攒至,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音躁劣,去河邊找鬼迫吐。 笑死,一個胖子當(dāng)著我的面吹牛账忘,可吹牛的內(nèi)容都是我干的志膀。 我是一名探鬼主播熙宇,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼溉浙!你這毒婦竟也來了烫止?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤戳稽,失蹤者是張志新(化名)和其女友劉穎馆蠕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惊奇,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡互躬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了颂郎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吼渡。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖乓序,靈堂內(nèi)的尸體忽然破棺而出寺酪,到底是詐尸還是另有隱情,我是刑警寧澤替劈,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布寄雀,位于F島的核電站,受9級特大地震影響陨献,放射性物質(zhì)發(fā)生泄漏咙俩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一湿故、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膜蛔,春花似錦坛猪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至呜呐,卻和暖如春就斤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蘑辑。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工洋机, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人洋魂。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓绷旗,卻偏偏與公主長得像喜鼓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衔肢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容