h.264 視頻解碼的一點小經驗

來源:互聯網
上載者:User

最近做視頻檔案264解碼,由於對這個領域不是很熟悉,感覺困難重重。不過經過不懈的努力,已經取得一些進展,心裡感覺特別慶幸。 剛開始做這個的時候,由於不熟悉,就在網上搜尋資料,網路上的資料雖然多,但是卻很雜亂,因此一開始走了不少彎路,現在把我的一點小小心得寫出來,後來的兄弟們可以參考一下,沒準能夠少走些彎路。當然啦,我在視頻處理方面仍然是個非常菜的菜鳥,如果是高手路過,看到我這所謂的“心得”,也請不要見笑,看到不對的地方請批評指正,呵呵。

剛開始做的時候,先是在網路上尋找資料,我覺得有一篇文章非常的有用,因為當時我最需要瞭解的就是世界上現存的各種轉碼器,每種都有什麼特性,比如說解碼速度是否能夠滿足即時播放的需求、對h.264標準的支援程度等等。這篇文章就是《H.264開源解碼器評測》,這篇文章詳細的評測了當今流行的幾種h.264解碼器,包括JM Decoder,T264 Decoder,X264 Decoder,ffmpeg libavcodec和Intel的IPP庫,經過作者的評測,發現速度最快的就是intel IPP了,但是intel IPP屬於商品化軟體,而其他的各種解碼器都屬於開源項目,所以最適合選擇的就是解碼速度第二的ffmpeg了,而且其速度完全可以滿足即時播放的要求;

選擇好瞭解碼器,第一步算是完成了,第二步就是研究ffmpeg的用法了。經過摸索,我的選擇是:到中華視頻網下在ffmpeg SDK 2.0,這恐怕是目前最適合在VC++6下使用的基於ffmpeg的SDK了,其易用性比較好。

第三步就是編寫播放器外殼了,外殼代碼採用VC++6編寫,我會在文張末尾給出外殼的所有代碼;注意:外科代碼擷取的lpdata是windows記憶體位元影像,具有dword對齊的特性,另外,解碼出的映像是倒立的,因此我專門寫了一個把映像倒轉的函數,運行速度還是挺快的,完全不妨礙即時播放;

上一階段的工作完成得還算滿意,下一階段的工作就是h.264 的 RTP payload協議了。

附錄:

h.264播放的外殼代碼-------------------------------------------------------------------------------------------------

// Decode264.cpp : Defines the initialization routines for the DLL.

#include "stdafx.h"
#include "Decode264.h"

//以下代碼為自己添加////////////////////////////
#include <stdlib.h>
#include <time.h>
#include "avformat.h"
#include "avcodec.h"
#include <windows.h>

//定義目標格式
#define DEST_FORMAT PIX_FMT_BGR24
//PIX_FMT_YUV420P

//定義全域變數
AVFormatContext *pFormatCtx;   //
int             i, videoStream;
AVCodecContext  *pCodecCtx;   
AVCodec         *pCodec;       //轉碼器
AVFrame         *pFrame;       //幀
AVFrame         *pFrameYUV;    //YUV幀

clock_t         t;
double          fps;
int                y_size, i_frame=0;
int                numBytes;
uint8_t         *buffer;       
////////////////////////////////////////////////

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//
// Note!
//
//  If this DLL is dynamically linked against the MFC
//  DLLs, any functions exported from this DLL which
//  call into MFC must have the AFX_MANAGE_STATE macro
//  added at the very beginning of the function.
//
//  For example:
//
//  extern "C" BOOL PASCAL EXPORT ExportedFunction()
//  {
//   AFX_MANAGE_STATE(AfxGetStaticModuleState());
//   // normal function body here
//  }
//
//  It is very important that this macro appear in each
//  function, prior to any calls into MFC.  This means that
//  it must appear as the first statement within the
//  function, even before any object variable declarations
//  as their constructors may generate calls into the MFC
//  DLL.
//
//  Please see MFC Technical Notes 33 and 58 for additional
//  details.
//

/////////////////////////////////////////////////////////////////////////////
// CDecode264App

BEGIN_MESSAGE_MAP(CDecode264App, CWinApp)
 //{{AFX_MSG_MAP(CDecode264App)
  // NOTE - the ClassWizard will add and remove mapping macros here.
  //    DO NOT EDIT what you see in these blocks of generated code!
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDecode264App construction

CDecode264App::CDecode264App()
{
 // TODO: add construction code here,
 // Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CDecode264App object

CDecode264App theApp;

//以下代碼為自己添加/////////////////////////////////////////////////////////

//把映像倒立過來;
long UpendBmp(unsigned char *lpdata,long width ,long height)
{

 long lBPL;//每行的位元組數,因為要考慮dword對齊
 long x,y,idx_src,idx_dest;
 unsigned char *tmpdata;

 if (0==((width*3)%4))  //nWidth * 3 是儲存每行像素需要的位元組數,如果是4的整數倍。
  lBPL = (width*3);   //那麼返回 nWidth * 3 ,就是每行的位元組數
 else  //如果不是4的整數倍,那麼就一定要加上一個數,達到4的整數倍,才是每行的位元組數。
  lBPL = (width*3+(4-((width*3)%4)));
 
 tmpdata= new unsigned char[lBPL * height];
 
 x =0;
 for (y=0 ; y<height ; y++)
 {
  idx_src =(height-1-y)*lBPL;//idx_src =(height-1-y)*lBPL+x*3;最佳化前
  idx_dest=y*lBPL;//idx_dest=y*lBPL+x*3;最佳化前
  memcpy(&tmpdata[idx_dest],&lpdata[idx_src],lBPL);//複製一行
 } 

 memcpy(lpdata,tmpdata,lBPL * height);
 delete[] tmpdata;

 return 0;
}

//建立一個bmp檔案。用於調試
static int av_create_bmp(char* filename,uint8_t *pRGBBuffer,
       int width,int height,int bpp)
{
   BITMAPFILEHEADER bmpheader;
   BITMAPINFO bmpinfo;
   FILE *fp;

   fp = fopen(filename,"wb");
   if(!fp)return -1;

   bmpheader.bfType = ('M'<<8)|'B';
   bmpheader.bfReserved1 = 0;
   bmpheader.bfReserved2 = 0;
   bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
   bmpheader.bfSize = bmpheader.bfOffBits + width*height*bpp/8;
       
   bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
   bmpinfo.bmiHeader.biWidth = width;
   bmpinfo.bmiHeader.biHeight = height;
   bmpinfo.bmiHeader.biPlanes = 1;
   bmpinfo.bmiHeader.biBitCount = bpp;
   bmpinfo.bmiHeader.biCompression = BI_RGB;
   bmpinfo.bmiHeader.biSizeImage = 0;
   bmpinfo.bmiHeader.biXPelsPerMeter = 100;
   bmpinfo.bmiHeader.biYPelsPerMeter = 100;
   bmpinfo.bmiHeader.biClrUsed = 0;
   bmpinfo.bmiHeader.biClrImportant = 0;

   fwrite(&bmpheader,sizeof(BITMAPFILEHEADER),1,fp);
   fwrite(&bmpinfo.bmiHeader,sizeof(BITMAPINFOHEADER),1,fp);
   fwrite(pRGBBuffer,width*height*bpp/8,1,fp);
   fclose(fp);

   return 0;
}

//擷取下一幀
static bool GetNextFrame(AVFormatContext *pFormatCtx,
       AVCodecContext *pCodecCtx,
                         int videoStream,
       AVFrame *pFrame)
{
    static AVPacket packet;                    //AV包。靜態變數。
    static int      bytesRemaining=0;          //位元組剩餘。靜態變數。
    static uint8_t  *rawData;                  //未經處理資料位元組數。靜態變數。
    static bool     fFirstTime=true;           //標誌,第一次;。靜態變數。
    int             bytesDecoded;              //解碼後獲得的位元組;
    int             frameFinished;             //幀解碼完畢標誌;

    // First time we're called, set packet.data to NULL to indicate it
    // doesn't have to be freed 當第一次被調用的時候,把packet.data設定為NULL,以表示
 //它沒有必要被釋放;
    if (fFirstTime){
        fFirstTime = false;
        packet.data = NULL;
    }

 //解碼那些包,直到我們解碼出一個完整的幀;
    // Decode packets until we have decoded a complete frame
    while (true)
    {
  //在當前包上工作,直到我們解碼出所有的。
        //Work on the current packet until we have decoded all of it
        while (bytesRemaining > 0)
        {
            // Decode the next chunk of data 解碼出下一個資料區塊
            bytesDecoded = avcodec_decode_video(pCodecCtx, pFrame,
                &frameFinished, rawData, bytesRemaining);

            // Was there an error?
            if (bytesDecoded < 0){
                fprintf(stderr, "Error while decoding frame/n");
                return false;
            }

            bytesRemaining -= bytesDecoded;
            rawData += bytesDecoded;

            // Did we finish the current frame? Then we can return
            if (frameFinished)           //如果我們完成了當前幀的解碼,就可以返回了
                return true;
        }

  //讀取下一個包,跳過所有的不是屬於這個流的包;
        // Read the next packet, skipping all packets that aren't for this
        // stream
        do{
            // Free old packet  釋放舊包
            if(packet.data != NULL)
                av_free_packet(&packet);

            // Read new packet  讀取新包
            if(av_read_frame(pFormatCtx, &packet) < 0)
                goto loop_exit;
        } while(packet.stream_index != videoStream);  //當不是要找的視頻流的時候,繼續迴圈,就是重新讀了;
  //直到找到要找的視頻流,退出迴圈;

        bytesRemaining = packet.size; //紀錄包的位元組數;
        rawData = packet.data;        //
    }

loop_exit:

    // Decode the rest of the last frame
    bytesDecoded = avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
                rawData, bytesRemaining);

    // Free last packet
    if(packet.data != NULL)
        av_free_packet(&packet);

    return frameFinished != 0;
}

//對外的API介面。開啟264檔案,並且擷取必要的資訊,比如寬度高度幀數等等
long  __stdcall  open264file(char *filename,long *out_width ,
        long *out_height,long *out_framenum,
        long *out_bufsize)
{

 
    // Register all formats and codecs  註冊所有的格式和轉碼器
    av_register_all();

    // Open video file//開啟視頻檔案
    if (av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL) != 0) 
        return -1; // Couldn't open file如果不能開啟,那麼返回-1

    // Retrieve stream information  取流資訊         
    if (av_find_stream_info(pFormatCtx) < 0)
        return -1; // Couldn't find stream information

    // Dump information about file onto standard error
    dump_format(pFormatCtx, 0, filename, false);

    t = clock();
       
    // Find the first video stream   尋找第一個視頻流
    videoStream = -1;
    for (i=0; i<pFormatCtx->nb_streams; i++)
        if(pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO){
            videoStream=i;
            break;
        }
    if (videoStream == -1)
        return -1; // Didn't find a video stream
 
 //擷取該視頻流的一個轉碼器內容相關的指標;
    // Get a pointer to the codec context for the video stream 
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;

    // Find the decoder for the video stream 擷取解碼器
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
 
    if (pCodec == NULL)
        return -1; // Codec not found解碼器沒有找到;

 //告知解碼器,我們能處理被刪節的位流
 // 也就是說,幀的分界處的位流可以落到包的中間;
    // Inform the codec that we can handle truncated bitstreams -- i.e.,   
    // bitstreams where frame boundaries can fall in the middle of packets 
    if ( pCodec->capabilities & CODEC_CAP_TRUNCATED )
        pCodecCtx->flags|=CODEC_FLAG_TRUNCATED;

    // Open codec //開啟解碼器
    if ( avcodec_open(pCodecCtx, pCodec) < 0 )
        return -1; // Could not open codec 不能開啟解碼器,返回-1;

    // Allocate video frame   分配視訊框架
    pFrame = avcodec_alloc_frame();

 // Allocate an AVFrame structure     分配一個AVFrame結構
    pFrameYUV=avcodec_alloc_frame();     //解碼後的幀
    if(pFrameYUV == NULL)
        return -1;       

 //決定需要多大的緩衝空間,並且分配空間;
    // Determine required buffer size and allocate buffer  
    numBytes=avpicture_get_size(DEST_FORMAT, pCodecCtx->width,
        pCodecCtx->height);
    buffer = (uint8_t*)malloc(numBytes);
 
 //向外界輸出寬高、幀數;
 *out_width = pCodecCtx->width;
 *out_height = pCodecCtx->height;
 *out_framenum = pCodecCtx->frame_number;
 *out_bufsize = numBytes;

    // Assign appropriate parts of buffer to image planes in pFrameRGB 
 //把緩衝區中合適的部分指派到pFrameRGB中的映像面板
    avpicture_fill((AVPicture *)pFrameYUV, buffer, DEST_FORMAT,
        pCodecCtx->width, pCodecCtx->height);

 return 0;
}

//對外的API介面。關閉264檔案,釋放相關資源
long __stdcall close264file()
{
    //calculate decode rate 計算解碼速率
    t = clock() - t;
    fps = (double)(t) / CLOCKS_PER_SEC;
    fps = i_frame / fps;
    printf("/n==>Decode rate %.4f fps!/n", fps);
  
    // Free the YUV image  釋放yuv映像
    free(buffer);
    av_free(pFrameYUV);

    // Free the YUV frame  釋放yuv幀
    av_free(pFrame);
    // Close the codec    關閉解碼器
    avcodec_close(pCodecCtx);
    // Close the video file  關閉視頻檔案
    av_close_input_file(pFormatCtx);

    return 0;
}

//對外的API介面。擷取一幀解碼後的資料
long __stdcall GetNextFrame(unsigned char *lpdata)
{
 
    // Read frames  讀取個個幀
 if (GetNextFrame(pFormatCtx, pCodecCtx, videoStream, pFrame))
 {
            img_convert((AVPicture *)pFrameYUV, DEST_FORMAT, (AVPicture*)pFrame,
                                pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

   //調試用,向C盤寫入一個bmp檔案;
   //av_create_bmp("c://1.bmp",(unsigned char *)pFrameYUV->data[0],pCodecCtx->width,pCodecCtx->height,24);

            i_frame++;
            y_size = pCodecCtx->width * pCodecCtx->height;                         

   //寫入檔案
            /*fwrite(pFrameYUV->data[0], 1, y_size, fp);
            fwrite(pFrameYUV->data[1], 1, (y_size/4), fp);
            fwrite(pFrameYUV->data[2], 1, (y_size/4), fp);*/
   memcpy(lpdata,pFrameYUV->data[0],y_size*3);
   UpendBmp(lpdata,pCodecCtx->width,pCodecCtx->height);
   return 0;
 }
 else
 {return -1;}
}
/////////////////////////////////////////////////////////////////////////////

------------------------------------------------------------------------------------------------------------------------------

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.