1. 程式人生 > >FFmepg基本使用流程

FFmepg基本使用流程

在原創的基礎上整理了下步驟,原文地址不好著了,但是原文標題叫《ffmpeg開發指南》。

基於0.4.8 版本的ffmpeg。


1初始化

首先第一件事情--讓我們來看看怎樣開啟一個視訊檔案並從中得到流。我們要做的第一件事情就是初始化libavformat/libavcodec:

 av_register_all();

這一步註冊庫中含有的所有可用的檔案格式和編碼器,這樣當開啟一個檔案時,它們才能夠自動選擇相應的檔案格式和編碼器。要注意你只需呼叫一次 av_register_all(),所以,儘可能的在你的初始程式碼中使用它。如果你願意,你可以僅僅註冊個人的檔案格式和編碼,不過,通常你不得不這麼做卻沒有什麼原因。

 

2開啟檔案

AVFormatContext*pFormatCtx;
const char    *filename="myvideo.mpg";
// 開啟視訊檔案
if(av_open_input_file(&pFormatCtx,filename, NULL, 0, NULL)!=0)
       handle_error(); // 不能開啟此檔案

最後三個引數描述了檔案格式,緩衝區大小(size)和格式引數;我們通過簡單地指明NULL或0告訴 libavformat 去自動探測檔案格式並且使用預設的緩衝區大小。請在你的程式中用合適的出錯處理函式替換掉handle_error()。

 

3尋找流資訊

// 取出流資訊
if(av_find_stream_info(pFormatCtx)<0)
    handle_error(); // 不能夠找到流資訊

這一步會用有效的資訊把AVFormatContext 的流域(streams field)填滿。作為一個可除錯的診斷,我們會將這些資訊全盤輸出到標準錯誤輸出中,不過你在一個應用程式的產品中並不用這麼做:

dump_format(pFormatCtx,0, filename, false);

 就像在引言中提到的那樣,我們僅僅處理視訊流,而不是音訊流。為了讓這件事情更容易理解,我們只簡單使用我們發現的第一種視訊流:


好了,我們已經得到了一個指向視訊流的稱之為上下文的指標。但是我們仍然需要找到真正的編碼器開啟它。

int i, videoStream;
AVCodecContext*pCodecCtx;
//   尋找第一個視訊流
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)
    handle_error(); //Didn't find a video stream 
// 得到視訊流編碼上下文的指標
pCodecCtx=pFormatCtx->streams[videoStream]->codec; 

4  尋找解碼器

AVCodec *pCodec;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL)
    handle_error(); // 找不到解碼器
// 通知解碼器我們能夠處理截斷的bit流--ie,
// bit流幀邊界可以在包中
if(pCodec->capabilities& CODEC_CAP_TRUNCATED)
    pCodecCtx->flags|=CODEC_FLAG_TRUNCATED;


 

5開啟解碼器

if(avcodec_open(pCodecCtx,pCodec)<0)
    handle_error(); // 打不開解碼器(那麼什麼是“截斷bit流”?好的,就像一會我們看到的,視訊流中的資料是被分割放入包中的。因為每個視訊幀的資料的大小是可變的,那麼兩幀之間的邊界就不一定剛好是包的邊界。這裡,我們告知解碼器我們可以處理bit流。)

儲存在AVCodecContext結構中的一個重要的資訊就是視訊幀速率。為了允許非整數的幀速率(比如 NTSC的   29.97幀),速率以分數的形式儲存,分子在 pCodecCtx->frame_rate,分母在 pCodecCtx->frame_rate_base 中。在用不同的視訊檔案測試庫時,我注意到一些編碼器(很顯然ASF)似乎並不能正確的給予賦值(frame_rate_base 用1代替1000)。下面給出修復補丁:

 // 加入這句話來糾正某些編碼器產生的幀速錯誤
if(pCodecCtx->frame_rate>1000&& pCodecCtx->frame_rate_base==1)
    pCodecCtx->frame_rate_base=1000;

 注意即使將來這個bug解決了,留下這幾句話也並沒有什麼壞處。視訊不可能擁有超過1000fps的幀速。


 6給視訊幀分配空間以便儲存解碼後的圖片

 AVFrame *pFrame;
pFrame=avcodec_alloc_frame();
就這樣,現在我們開始解碼這些視訊。解碼視訊幀就像我前面提到過的,視訊檔案包含數個音訊和視訊流,並且他們各個獨自被分開儲存在固定大小的包裡。我們要做的就是使用libavformat依次讀取這 些包,過濾掉所有那些視訊流中我們不感興趣的部分,並把它們交給 libavcodec 進行解碼處理。在做這件事情時,我們要注意這樣一個事實,兩幀之間的邊界也可以在包的中間部分。

聽起來很複雜?幸運的是,我們在一個例程中封裝了整個過程,它僅僅返回下一幀。

  

7 讀取檔案,解碼得到視訊幀

boolGetNextFrame(AVFormatContext *pFormatCtx, AVCodecContext *pCodecCtx, intvideoStream, AVFrame *pFrame)
{
static AVPacket packet;
static int    bytesRemaining=0;
static uint8_t   *rawData;
static bool     fFirstTime=true;
Int bytesDecoded;
Int frameFinished;
    
//   我們第一次呼叫時,將 packet.data 設定為NULL指明它不用釋放了
if(fFirstTime)
{
       fFirstTime=false;
       packet.data=NULL;
}
 
// 解碼直到成功解碼完整的一幀
while(true)
{
    //   除非解碼完畢,否則一直在當前包中工作
       while(bytesRemaining > 0)
       {
      //   解碼下一塊資料
        bytesDecoded=avcodec_decode_video(pCodecCtx, pFrame,
           &frameFinished, rawData, bytesRemaining); 
         if(bytesDecoded <0) // 出錯了?
         {
            fprintf(stderr,"Error while decoding frame\n");
            return false;
         }
 
        bytesRemaining-=bytesDecoded;
        rawData+=bytesDecoded;
 
         // 我們完成當前幀了嗎?接著我們返回
         if(frameFinished)
            return true;
       }
 
	// 讀取下一包,跳過所有不屬於這個流的包
       do
       {
         // 釋放舊的包
         if(packet.data!=NULL)
            av_free_packet(&packet);
 
         // 讀取新的包
         if(av_read_packet(pFormatCtx,&packet)<0)
            goto loop_exit;
       }while(packet.stream_index!=videoStream);
 
       bytesRemaining=packet.size;
       rawData=packet.data;
}
loop_exit:
 
// 解碼最後一幀的餘下部分
bytesDecoded=avcodec_decode_video(pCodecCtx,pFrame, &frameFinished,
       rawData, bytesRemaining);
 
// 釋放最後一個包
if(packet.data!=NULL)
       av_free_packet(&packet);
 
return frameFinished!=0;
}


 

現在,我們要做的就是在一個迴圈中,呼叫 GetNextFrame () 直到它返回false。還有一處需要注意:大多數編碼器返回 YUV 420 格式的圖片(一個亮度和兩個色度通道,色度通道只佔亮度通道空間解析度的一半(譯者注:此句原句為the chrominance channels samples at half the spatial resolution ofthe luminance channel))。看你打算如何對視訊資料處理,或許你打算將它轉換至RGB格式。(注意,儘管,如果你只是打算顯示視訊資料,那大可不必要這麼做;查 看一下 X11 的 Xvideo 擴充套件,它可以在硬體層進行YUV到RGB 轉換。)

8 圖片格式轉換

幸運的是,libavcodec 提供給我們了一個轉換例程img_convert :它可以像轉換其他圖象進行 YUV 和 RGB之間的轉換。這樣解碼視訊的迴圈就變成這樣: 

while(GetNextFrame(pFormatCtx,pCodecCtx, videoStream, pFrame))
{
img_convert((AVPicture *)pFrameRGB,PIX_FMT_RGB24, (AVPicture*)pFrame,
       pCodecCtx->pix_fmt,pCodecCtx->width, pCodecCtx->height);
 
// 處理視訊幀(比如顯示到螢幕上)
DoSomethingWithTheImage(pFrameRGB);
}


RGB圖象pFrameRGB (AVFrame *型別)的空間分配如下:

AVFrame *pFrameRGB;
pFrameRGB=avcodec_alloc_frame();
if(pFrameRGB==NULL)
handle_error();

 

9 DoSomethingWithTheImage

顯示這一幀,或者別的操作。

 

10播放結束進行清理工作

好了,我們已經處理了我們的視訊,現在需要做的就是清除我們自己的東西:

// 釋放 RGB 圖象
delete [] buffer;
av_free(pFrameRGB);
 
// 釋放YUV 幀
av_free(pFrame);
 
// 關閉解碼器(codec)
avcodec_close(pCodecCtx);
 
// 關閉視訊檔案
av_close_input_file(pFormatCtx);

 

11完成