FFmpeg之視頻解碼
第一次寫CSDN,先熟悉熟悉FFmpeg
常用結(jié)構(gòu)體
1. AVFormatContext; //為封裝上下文;
2. AVCodecContext; //為解碼器上下文;
3. AVStream; //為存放的是各種流,如:音頻流,視頻流,字母等;
4. SwsContext; //為轉(zhuǎn)換上下文,主要用于將原始數(shù)據(jù)轉(zhuǎn)換成目標(biāo)格式的數(shù)據(jù);如:YUV或RGB;
5. AVCodec; //為解碼器;
6. AVpacket; //為數(shù)據(jù)包,用于將編碼數(shù)據(jù)發(fā)送給解碼器的;
7. AVFrame; //存放一般存放解碼后的數(shù)據(jù);
常用方法函數(shù)
1. av_register_all();
2. avformat_open_input();
3. av_find_best_stream( ); //找到最你指定的那個流的序號;
4. avformat_find_stream_info( ); //該函數(shù)主要用于給每個媒體流(音頻/視頻)的AVStream結(jié)構(gòu)體賦值
5. avcodec_find_decoder(); //找到音頻或視頻流對應(yīng)的編碼格式的解碼器;
6. avcodec_parameters_to_context(); //將解碼器參數(shù)輔導(dǎo)解碼器上下文中
7. avcodec_open2(); //打開解碼器;
8. sws_getContext(); //設(shè)置原始數(shù)據(jù)(YUV)轉(zhuǎn)換成其他格式( RGB )的轉(zhuǎn)換上下文,并返回?fù)Q上下文。
9. av_read_frame(); //視頻是一幀的,音頻可以是多幀。
10. avcodec_decode_video2(); //對視頻解碼
11. sws_scale(); //將一幀原始數(shù)據(jù)(YUV)轉(zhuǎn)換成我們指定格式 (RGB )。
視頻解碼的一些基礎(chǔ)知識:
-
視頻流是按一定的順序排列 I 幀,P 幀 和 B 幀的。
1. I 幀:為關(guān)鍵幀,它為幀內(nèi)壓縮,解碼后為一個完整的畫面,這是解碼的關(guān)鍵; 2. P 幀:為前向預(yù)測幀,它自己解碼是無法完成一幀畫面,它的解碼需要依賴上一次解碼的 I 幀(或P幀); 3. B 幀:為雙向預(yù)測內(nèi)插編碼幀,它以前面的I或P幀和后面的P幀為參考幀。
? 因此,重要性:I 幀 > P 幀 > B 幀。由于不同類型的幀的重要性不同,這意味著我們要按播放連貫的視頻,就必須按照一定規(guī)定來顯示這些幀,這就引來了兩個時間概念:解碼時間戳(DTS)和顯示時間戳(PTS)。
? 音頻同步的時候,也是以顯示時間戳為標(biāo)準(zhǔn)的。
-
視頻格式(AVPixelFormat)
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA, AV_PIX_FMT_NONE = -1, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, AV_PIX_FMT_YUV422P, …… //格式還有很多種,這里不在敘述。
代碼示例,Qt Creator中運行
主要思想:用主線程顯示視頻(即widget.h和.cpp),用MyFFmpeg線程來實現(xiàn)文件的解碼;
widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void init_MyFFmpeg();
void paintEvent(QPaintEvent* e);
private:
MyFFmpeg *myFFmpeg;
QPixmap pix;
private:
Ui::Widget *ui;
};
widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
myFFmpeg=new MyFFmpeg();
//收到myFFmpeg線程對象發(fā)來的圖片,就繪到widget上
connect(myFFmpeg,&MyFFmpeg::emitImage,[=](QImage image,int time){
pix = QPixmap::fromImage(image,Qt::AutoColor);
qDebug()<<"time:"<<time;
update(); //更新paintEvent時間
});
//啟動線程
myFFmpeg->start();
}
Widget::~Widget(){
delete ui;
}
void Widget::paintEvent(QPaintEvent* e){
QPainter painter(this);
pix.scaled(this->size(),Qt::IgnoreAspectRatio);
int x = this->width() - pix.width();
int y = this->height() - pix.height();
painter.drawPixmap(QPoint(x,y),pix);
}
MyFFmpeg.h文件
class MyFFmpeg: public QThread
{
Q_OBJECT
public:
MyFFmpeg();
~MyFFmpeg();
void run() override;
void init_MyFFmpeg();
signals:
void emitImage(QImage image);
private:
//ffmpeg相關(guān)變量預(yù)先定義與分配
AVFormatContext *m_pAVFormatCtx; //解封裝上下文
SwsContext *m_pSwsCtx; //視頻轉(zhuǎn)碼上下文
AVCodecContext *avCodecCtx; //解碼上下文
AVFrame *m_pAVFrame = 0; // 解碼后的原始數(shù)據(jù)Frame
AVFrame *m_pAVFrameRGB32 = 0; // 轉(zhuǎn)換后的目標(biāo)格式Frame
AVPacket *m_pAVPacket = 0; // ffmpag單幀數(shù)據(jù)包
int gotPicture = 0; // 解碼時數(shù)據(jù)是否解碼成功
int outBuffer_size = 0; // 解碼后的數(shù)據(jù)長度
uchar *outBuffer = 0; // 解碼后的數(shù)據(jù)存放緩存區(qū)
char m_errorBuff[1024]; //打開時發(fā)生的錯誤信息
int m_totalMs; //總時長
int m_videoStreamId; //視頻流
int m_fps; //每秒的視頻幀數(shù)
int m_pts; //獲得當(dāng)前解碼幀的時間
};
MyFFmpeg.cpp文件文章來源:http://www.zghlxwxcb.cn/news/detail-405302.html
MyFFmpeg::MyFFmpeg()
{
init_MyFFmpeg();
av_register_all();
}
void MyFFmpeg::run(){
const char* pathUrl="mmm.mkv";
//打開文件,解封裝
int ret = avformat_open_input(&m_pAVFormatCtx,pathUrl,NULL,NULL);
if(ret!=0){
av_strerror(ret,m_errorBuff,sizeof(m_errorBuff));
qDebug()<<"Open failed: "<<m_errorBuff;
exit(0);
}
m_totalMs = m_pAVFormatCtx->duration / (AV_TIME_BASE); //獲取視頻的總時間
//尋找視頻流序號
// m_videoStreamId = av_find_best_stream(m_pAVFormatCtx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
m_videoStreamId = -1;
for(int i=0;i<m_pAVFormatCtx->nb_streams;i++)
{
if(m_pAVFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)
{
m_videoStreamId=i;
break;
}
}
if(m_videoStreamId ==-1){
qDebug()<<"can not find videoStream";
exit(0);
}
//尋找解碼器
AVCodec *avCodec = avcodec_find_decoder( m_pAVFormatCtx->streams[m_videoStreamId]->codecpar->codec_id);
//avCodecCtx = avcodec_alloc_context3(avCodec);
avCodecCtx=m_pAVFormatCtx->streams[m_videoStreamId]->codec;
//打開編碼器
if(avcodec_open2(avCodecCtx,avCodec,NULL)<0){
qDebug()<<"can not open codec";
exit(0);
}
//進行格式轉(zhuǎn)換上下文設(shè)置,一般的pix_fmt都是AV_PIX_FMT_YUV420P
avCodecCtx->pix_fmt=AV_PIX_FMT_YUV420P; //這里我們手動給pix_fmt賦值A(chǔ)V_PIX_FMT_YUV420P,
// 如果不賦值,下面sws_getContext就會崩掉,因為沒有這句話 pix_fmt=-1;
m_pSwsCtx = sws_getContext(avCodecCtx->width, avCodecCtx->height, avCodecCtx->pix_fmt,
avCodecCtx->width, avCodecCtx->height, AV_PIX_FMT_RGBA, SWS_FAST_BILINEAR,
NULL, NULL, NULL);
outBuffer_size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, avCodecCtx->width, avCodecCtx->height,1);
outBuffer=(unsigned char*)malloc(outBuffer_size);
//將m_pAVFrameRGB32賦值, 并將m_pAVFrameRGB32的data變量指向outBuffer
av_image_fill_arrays(m_pAVFrameRGB32->data,m_pAVFrameRGB32->linesize, outBuffer,
AV_PIX_FMT_RGBA, avCodecCtx->width, avCodecCtx->height, 1);
//開始逐幀解碼視頻和音頻,這里我們之解碼視頻
while(av_read_frame(m_pAVFormatCtx, m_pAVPacket)>=0)
{
//找到視頻流
if(m_pAVPacket->stream_index==m_videoStreamId){
if(avcodec_decode_video2(avCodecCtx, m_pAVFrame, &gotPicture, m_pAVPacket)<0){
qDebug()<<"解碼視頻失敗";
exit(0);
}
if(gotPicture){
//對原始數(shù)據(jù)進行格式轉(zhuǎn)化
sws_scale(m_pSwsCtx, (const uint8_t* const *)m_pAVFrame->data, m_pAVFrame->linesize,0,
avCodecCtx->height, m_pAVFrameRGB32->data, m_pAVFrameRGB32->linesize);
//接下來就是將m_pAVFrameRGB32上的數(shù)據(jù)在設(shè)備上顯示出來。
QImage imageshow(*m_pAVFrameRGB32->data, avCodecCtx->width, avCodecCtx->height,
QImage::Format_RGB32);
//顯示當(dāng)前幀的時間,并轉(zhuǎn)化成秒
m_dts=m_pAVFrame->pts * av_q2d(m_pAVFormatCtx->streams[m_videoStreamId]->time_base);
//將線程中解碼好的圖片和顯示時間發(fā)送到主線程中
emitImage(imageshow, m_dts);
QThread::msleep(30);//延時使得畫面播放流暢
}
}
av_packet_unref(m_pAVPacket); //引用計數(shù)減1;
}
//正常來講這里是要執(zhí)行下面著幾句話的,但是釋放內(nèi)存的,這個線程就會出現(xiàn)沖突,所以我們將這些話放在了析構(gòu)函數(shù)中
//avformat_free_context(m_pAVFormatCtx);
//avcodec_free_context(&avCodecCtx);
//av_frame_free(&m_pAVFrame);
//av_frame_free(&m_pAVFrameRGB32);
//av_packet_free(&m_pAVPacket);
//qDebug()<<"音頻解碼結(jié)束";
}
MyFFmpeg::~MyFFmpeg()
{ //釋放內(nèi)存
avformat_free_context(m_pAVFormatCtx);
avcodec_free_context(&avCodecCtx);
av_frame_free(&m_pAVFrame);
av_frame_free(&m_pAVFrameRGB32);
av_packet_free(&m_pAVPacket);
qDebug()<<"音頻解碼結(jié)束";
}
void MyFFmpeg::init_MyFFmpeg()
{
m_pAVFormatCtx = avformat_alloc_context();
m_pAVFrame = av_frame_alloc();
m_pAVPacket = av_packet_alloc();
m_pAVFrameRGB32 = av_frame_alloc();
}
//
總結(jié)一下,這里也才了一個坑,就是
問題就是sws_getContext()中, avCodecCtx->pix_fmt=-1,導(dǎo)致系統(tǒng)崩潰,文章來源地址http://www.zghlxwxcb.cn/news/detail-405302.html
avCodecCtx->pix_fmt=AV_PIX_FMT_YUV420P; //必需在這里可以賦值 AV_PIX_FMT_YUV420P
m_pSwsCtx = sws_getContext(avCodecCtx->width, avCodecCtx->height, avCodecCtx->pix_fmt,
avCodecCtx->width, avCodecCtx->height, AV_PIX_FMT_RGBA, SWS_FAST_BILINEAR,
NULL, NULL, NULL);
第一次寫博客,參考了別人的一些經(jīng)驗,然后自己整理加自己的理解,主要是給自己記筆記,當(dāng)然也供小伙伴們參考,如果有錯誤,請諒解!??!
到了這里,關(guān)于FFmpeg之視頻解碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!