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完成