FFmpeg解碼H264及swscale縮放詳解

本文概要:

        本文介紹著名開源音視頻編解碼庫ffmpeg如何解碼h264碼流,比較詳細闡述了其h264碼流輸入過程,解碼原理,解碼過程。同時,大部分應用環境下,以原始碼流視頻大小展現並非最佳方式,所以,開發者不只僅須要對視頻流解碼,而且須要縮放圖像以展現於不一樣窗體下。算法

        綜上,本文除介紹ffmpeg解碼h264,同時闡述如何使用swscale縮放視頻流。       ubuntu

        文章使用的開發環境Ubuntu12.04.。交流郵箱:leoluopy@gmail.com。 轉載請註明出處 CSDN--固本培元。api

ffmpeg介紹:

          FFmpeg是一個開源免費跨平臺的視頻和 音頻流 方案,屬於自由 軟件 ,採用LGPL或GPL許可證(依據你選擇的組件)。它提供了錄製、轉換以及流化音視頻的完整解決方案。它包含了很是先進的音頻/視頻編解碼庫libavcodec,爲了保證高可移植性和編解碼質量,libavcodec裏不少codec都是從頭開發的。數組

開始解碼

         好了,很少說了。直接上工程和代碼吧。(注意在連接工程時,引用庫有鏈接順序,由於他們有相互依賴關係,若是缺乏將不能經過編譯。)bash

        

        須要鏈接的庫: 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")

          須要的頭文件:app

#include "libavcodec\\avcodec.h"
#include "libswscale/swscale.h"

          環境初始化代碼:(參考了api-example.c)ubuntu上使用的ffmpeg版本是0.6ide

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。ui

          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

相關文章
相關標籤/搜索