FFmpeg解碼H264及swscale縮放詳解
本文概要:
本文介紹著名開源音視頻編解碼庫ffmpeg如何解碼h264碼流,比較詳細闡述了其h264碼流輸入過程,解碼原理,解碼過程。同時,大部分應用環境下,以原始碼流視頻大小展示并不是最佳方式,因此,開發者不僅僅需要對視頻流解碼,并且需要縮放圖像以展示于不同窗體下。
綜上,本文除介紹ffmpeg解碼h264,同時闡述如何使用swscale縮放視頻流。
文章使用的開發環境Ubuntu12.04.。交流郵箱:leoluopy@gmail.com。 轉載請注明出處 CSDN--固本培元。
ffmpeg介紹:
FFmpeg是一個開源免費跨平臺的視頻和 音頻流 方案,屬于自由 軟件 ,采用LGPL或GPL許可證(依據你選擇的組件)。它提供了錄制、轉換以及流化音視頻的完整解決方案。它包含了非常先進的音頻/視頻編解碼庫libavcodec,為了保證高可移植性和編解碼質量,libavcodec里很多codec都是從頭開發的。
開始解碼
好了,不多說了。直接上工程和代碼吧。(注意在鏈接工程時,引用庫有連接順序,因為他們有相互依賴關系,如果缺少將不能通過編譯。)
需要連接的庫: VS代碼如下
#pragma comment (lib,"..\\FFMPEG_lib\\avformat.lib")
#pragma comment (lib,"..\\FFMPEG_lib\\avutil.lib")
#pragma comment (lib,"..\\FFMPEG_lib\\swscale.lib")
#pragma comment (lib,"..\\FFMPEG_lib\\avcodec.lib")
#pragma comment (lib,"..\\FFMPEG_lib\\avdevice.lib")
#pragma comment (lib,"..\\FFMPEG_lib\\avfilter.lib")
需要的頭文件:
#include "libavcodec\\avcodec.h"
#include "libswscale/swscale.h"
環境初始化代碼:(參考了api-example.c)ubuntu上使用的ffmpeg版本是0.6
avcodec_init(); //首先,main函數中一開始會去調用avcodec_init()函數,該函數的作用是初始化libavcodec,而我們在使用avcodec編解碼庫時,該函數必須被調用。
avcodec_register_all();//注冊所有的編解碼器(codecs),解析器(parsers)以及碼流過濾器(bitstream filters)。當然我們也可以使用個別的注冊函數來注冊我們所要支持的格式。
AVCodec *codec;
AVCodecContext *c= NULL;
int frame, size, got_picture, len;
FILE *fin, *fout;
AVFrame *picture,*dst_picture;
uint8_t inbuf[INBUF_SIZE + FF_INPUT_BUFFER_PADDING_SIZE], *inbuf_ptr;
char buf[1024];
/* set end of buffer to 0 (this ensures that no overreading happens for damaged mpeg streams) */
memset(inbuf + INBUF_SIZE, 0, FF_INPUT_BUFFER_PADDING_SIZE);
printf("Video decoding\n");
/* find the mpeg1 video decoder */
codec = avcodec_find_decoder(CODEC_ID_H264);
if (!codec){
fprintf(stderr, "codec not found\n");
exit(1);
}
c= avcodec_alloc_context();
picture= avcodec_alloc_frame();
if(codec->capabilities&CODEC_CAP_TRUNCATED){
c->flags|= CODEC_FLAG_TRUNCATED; /* we dont send complete frames */
}
/* for some codecs, such as msmpeg4 and mpeg4, width and height
MUST be initialized there because these info are not available
in the bitstream */
/* open it */
if (avcodec_open(c, codec) < 0){
fprintf(stderr, "could not open codec\n");
exit(1);
}
avcodec_init和avcodec_register_all初始化了相關的解碼器,申請了解碼需要的空間等。
其他解碼需要具備的是AVcontext、AVCodec、以及AVFrame。
AVContext是解碼需要的環境,其中存儲了比如長寬,編碼器算法,位圖格式等信息。
AVCondec就是你所選擇的的編解碼器了,使用枚舉來索引,申請空間后與解碼函數配合使用。
AVFrame與AVPicture比較像,都存儲解碼后的位圖信息。
解碼:
avcodec_decode_video需要輸入參數,AVContext,AVFrame,數據首地址以及數據長度。同時傳入一個int指針用于記錄解碼返回的解碼成功幀數。
len記錄本次解碼消耗的字節數。
len = avcodec_decode_video(c, picture, &got_picture, inbuf_ptr, size);
注意:在解碼過程中不要清理contxt環境,以及解碼器,如果有必要字節流空間有保存意義,因為,264傳輸過程中,有PTS以及DTS之分,播放時間以及解碼時間如果不一致,可能導致,先到數據需要存儲后到達他解碼時間時解碼。
同時,h264碼流分IPB幀,只有I幀是比較全面的圖像信息。如果在解碼I幀完成后,清空解碼環境context,后續解碼將持續返回錯誤信息,直至下一個I幀出現。作者親測,望看到此文的朋友在做解碼時不會再走這條彎路。
自此,解碼部分闡述完畢。
縮放:
利用ffmpeg進行圖像數據格式的轉換以及圖片的縮放應用中,主要用到了swscale.h文件中的三個函數,分別是:
struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param);
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
const int srcStride[], int srcSliceY, int srcSliceH,
uint8_t *const dst[], const int dstStride[]);
void sws_freeContext(struct SwsContext *swsContext);
sws_getContext 函數可以看做是初始化函數,它的參數定義分別為:
int srcW,int srcH 為原始圖像數據的高和寬;
int dstW,int dstH 為輸出圖像數據的高和寬;
enum AVPixelFormat srcFormat 為輸入和輸出圖片數據的類型;eg:AV_PIX_FMT_YUV420、PAV_PIX_FMT_RGB24;
int flags 為scale算法種類;eg:SWS_BICUBIC、SWS_BICUBLIN、SWS_POINT、SWS_SINC;
SwsFilter *srcFilter ,SwsFilter *dstFilter,const double *param 可以不用管,全為NULL即可;
sws_scale 函數則為執行函數,它的參數定義分別為:
struct SwsContext *c 為sws_getContext函數返回的值;
const uint8_t *const srcSlice[],uint8_t *const dst[] 為輸入輸出圖像數據各顏色通道的buffer指針數組;
const int srcStride[],const int dstStride[] 為輸入輸出圖像數據各顏色通道每行存儲的字節數數組;
int srcSliceY 為從輸入圖像數據的第多少列開始逐行掃描,通常設為0;
int srcSliceH 為需要掃描多少行,通常為輸入圖像數據的高度;
sws_freeContext 函數為結束函數,它的參數即為sws_getContext函數返回的值;
做一個實際縮放YUV420函數打包實例如下:
int ScaleImg(AVCodecContext *pCodecCtx,AVFrame *src_picture,AVFrame *dst_picture,int nDstH ,int nDstW )
{
int i ;
int nSrcStride[3];
int nDstStride[3];
int nSrcH = pCodecCtx->height;
int nSrcW = pCodecCtx->width;
struct SwsContext* m_pSwsContext;
uint8_t *pSrcBuff[3] = {src_picture->data[0],src_picture->data[1], src_picture->data[2]};
nSrcStride[0] = nSrcW ;
nSrcStride[1] = nSrcW/2 ;
nSrcStride[2] = nSrcW/2 ;
dst_picture->linesize[0] = nDstW;
dst_picture->linesize[1] = nDstW / 2;
dst_picture->linesize[2] = nDstW / 2;
printf("nSrcW%d\n",nSrcW);
m_pSwsContext = sws_getContext(nSrcW, nSrcH, PIX_FMT_YUV420P,
nDstW, nDstH, PIX_FMT_YUV420P,
SWS_BICUBIC,
NULL, NULL, NULL);
if (NULL == m_pSwsContext)
{
printf("ffmpeg get context error!\n");
exit (-1);
}
sws_scale(m_pSwsContext, src_picture->data,src_picture->linesize, 0, pCodecCtx->height,dst_picture->data,dst_picture->linesize);
printf("line0:%d line1:%d line2:%d\n",dst_picture->linesize[0] ,dst_picture->linesize[1] ,dst_picture->linesize[2]);
sws_freeContext(m_pSwsContext);
return 1 ;
}
函數很簡單,申請環境初始指針,后縮放即可。讀到此文的朋友,這個函數可以直接拷貝使用喲。如果有疑問可以留言或者郵件:leoluopy@gmail.com
下面有一個縮放圖像的效果圖:
目的位圖的空間申請:
注意:上面的縮放函數如果直接使用而在沒有解碼成功或者沒有申請目的位圖空間時,將報段錯誤。
原因:沒有解碼成功,位圖源地址將是指向空的地址,目的位圖地址同樣。
申請目的位圖的方式:
dst_picture = avcodec_alloc_frame();
if (!dst_picture){
return ;
}
if(avpicture_alloc((AVPicture *)dst_picture, c->pix_fmt,c->width*2, c->height*2)<0){
printf("dst_picture allocate failed\n");
exit(1);
}
初始化后即可以用于縮放了。
圖像的內存Dump方法:
上文已經比較詳細的闡述了ffmpeg的解碼以及縮放原理及流程,然而在實際運用環境中,無論是從實時播放或者是從歷史回放來看,我們需要的是像素位圖,而不是ffmpeg的結構體。那么怎么轉換呢?下文介紹了相關的內容。
作為實際運用環境中,像素位圖格式,筆者使用的是比較常用的YUV格式。
編解碼圖像格式
承接上文繼續,在大部分現場環境下,為了節省傳送帶寬以及提高系統工作效率,視頻原始位圖格式以及解碼后展示格式一般使用YUV420。
其中YV12以及IYUV是最常見的格式。
簡單的說,YUV格式又分為平面格式以及打包格式。他們各有利弊。本文使用的是YUV平面格式即三通道分別使用不同入口地址存儲,這樣的好處是,與ffmpeg解碼接口方面,AVFrame中數據結構可以便捷拷貝圖像信息。
YV12:

IYUV

這里做簡單的敘述:詳細可以參考:
http://blog.csdn.net/searchsun/article/details/2443867
如何Dump
好了,對YUV格式又初步了解后,下面介紹如果導出這像素位圖數據。ffmpeg將圖像的亮度以及色差信息保存在AVFrame中的data[0]、data[1]、data[2]中。
詳細參考:
AVFrame和AVPicture對比:
http://yul100887.blog.163.com/blog/static/200336135201211143525930/
所有操作均是針對這三個指針展開的,如下:
pgm_save(picture->data[0], picture->linesize[0], //Y
c->width, c->height, outfilename);
pgm_save(picture->data[1], picture->linesize[1],
c->width/2, c->height/2, outfilename); //U
pgm_save(picture->data[2], picture->linesize[2],
c->width/2, c->height/2, outfilename); //V
void pgm_save(unsigned char *buf,int wrap, int xsize,int ysize,char *filename)
{
FILE *f;
int i;
f=fopen(filename,"ab+");
for(i=0;i<ysize;i++)
{
fwrite(buf + i * wrap, 1, xsize, f );
}
fclose(f);
}
代碼對YUV三個像素通道分開dump,讀者可以根據自己的需要對函數進行包裝。data[0] 是亮度信息入口指針,同時傳入圖像長寬,以及存儲內存區域行寬。 data[1] data[2] 類似。
最后需要注意的是:linesize總是容易被遺忘,livesize[0]=height ,livesize[1]=height/2 ,livesize[2]=height /2,
此文到此結束,感謝閱讀。
---------------------leoluopy
參考文章:
使用ffmpeg進行圖像格式轉換及縮放
http://blog.csdn.net/skys_broyal/article/details/10337147
AVFrame和AVPicture對比:
http://yul100887.blog.163.com/blog/static/200336135201211143525930/
from:http://blog.csdn.net/gubenpeiyuan/article/details/19548019
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成