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ù)用于初始化一個視音頻編解碼器的AVCodecContextav_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)
-
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);
}