ffmpeg开发指南(使用 libavformat 和 libavcodec)
生活随笔
收集整理的這篇文章主要介紹了
ffmpeg开发指南(使用 libavformat 和 libavcodec)
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
ffmpeg開發(fā)指南(使用 libavformat 和 libavcodec)
Ffmpeg?中的Libavformat?和?libavcodec庫是訪問大多數(shù)視頻文件格式的一個很好的方法。不幸的是,在開發(fā)您自己的程序時,這套庫基本上沒有提供什么實際的文檔可以用來作為參考(至少我沒有找到任何文檔),并且它的例程也并沒有太多的幫助。
這種情況意味著,當我在最近某個項目中需要用到 libavformat/libavcodec 庫時,需要作很多試驗來搞清楚怎樣使用它們。這里是我所學習的--希望我做的這些能夠幫助一些人,以免他們重蹈我的覆轍,作同樣的試驗,遇到同樣的錯誤。你還可以從這里下載一個demo程序。我將要公開的這部分代碼需要0.4.8 版本的ffmpeg庫中的 libavformat/libavcodec 的支持(我正在寫最新版本)。如果您發(fā)現(xiàn)以后的版本與我寫的程序不能兼容,請告知我。
在這個文檔里,我僅僅涉及到如何從文件中讀入視頻流;音頻流使用幾乎同樣的方法可以工作的很好,不過,我并沒有實際使用過它們,所以,我沒于辦法提供任何示例代碼。
或許您會覺得奇怪,為什么需要兩個庫文件 libavformat 和 libavcodec :許多視頻文件格式(AVI就是一個最好的例子)實際上并沒有明確指出應該使用哪種編碼來解析音頻和視頻數(shù)據(jù);它們只是定義了音頻流和視頻流(或者,有可能是多個音頻視頻流)如何被綁定在一個文件里面。這就是為什么有時候,當你打開了一個AVI文件時,你只能聽到聲音,卻不能看到圖象--因為你的系統(tǒng)沒有安裝合適的視頻解碼器。所以, libavformat 用來處理解析視頻文件并將包含在其中的流分離出來, 而libavcodec 則處理原始音頻和視頻流的解碼。
打開視頻文件:
首先第一件事情--讓我們來看看怎樣打開一個視頻文件并從中得到流。我們要做的第一件事情就是初始化libavformat/libavcodec:
av_register_all();
這一步注冊庫中含有的所有可用的文件格式和編碼器,這樣當打開一個文件時,它們才能夠自動選擇相應的文件格式和編碼器。要注意你只需調(diào)用一次 av_register_all(),所以,盡可能的在你的初始代碼中使用它。如果你愿意,你可以僅僅注冊個人的文件格式和編碼,不過,通常你不得不這么做卻沒有什么原因。
下一步,打開文件:
AVFormatContext *pFormatCtx;
const char??*filename="myvideo.mpg";
// 打開視頻文件
if(av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)!=0)
handle_error(); // 不能打開此文件
最后三個參數(shù)描述了文件格式,緩沖區(qū)大小(size)和格式參數(shù);我們通過簡單地指明NULL或0告訴 libavformat 去自動探測文件格式并且使用默認的緩沖區(qū)大小。請在你的程序中用合適的出錯處理函數(shù)替換掉handle_error()。
下一步,我們需要取出包含在文件中的流信息:
// 取出流信息
if(av_find_stream_info(pFormatCtx)<0)
handle_error(); // 不能夠找到流信息
這一步會用有效的信息把 AVFormatContext 的流域(streams field)填滿。作為一個可調(diào)試的診斷,我們會將這些信息全盤輸出到標準錯誤輸出中,不過你在一個應用程序的產(chǎn)品中并不用這么做:
dump_format(pFormatCtx, 0, filename, false);
就像在引言中提到的那樣,我們僅僅處理視頻流,而不是音頻流。為了讓這件事情更容易理解,我們只簡單使用我們發(fā)現(xiàn)的第一種視頻流:
int i, videoStream;
AVCodecContext *pCodecCtx;
//?尋找第一個視頻流
videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams->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;
好了,我們已經(jīng)得到了一個指向視頻流的稱之為上下文的指針。但是我們?nèi)匀恍枰业秸嬲木幋a器打開它。
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;
// 打開解碼器
if(avcodec_open(pCodecCtx, pCodec)<0)
handle_error(); // 打不開解碼器
(那么什么是“截斷bit流”?好的,就像一會我們看到的,視頻流中的數(shù)據(jù)是被分割放入包中的。因為每個視頻幀的數(shù)據(jù)的大小是可變的,那么兩幀之間的邊界就不一定剛好是包的邊界。這里,我們告知解碼器我們可以處理bit流。)
存儲在 AVCodecContext結(jié)構(gòu)中的一個重要的信息就是視頻幀速率。為了允許非整數(shù)的幀速率(比如 NTSC的?29.97幀),速率以分數(shù)的形式存儲,分子在 pCodecCtx->frame_rate,分母在 pCodecCtx->frame_rate_base 中。在用不同的視頻文件測試庫時,我注意到一些編碼器(很顯然ASF)似乎并不能正確的給予賦值( frame_rate_base 用1代替1000)。下面給出修復補丁:
//?加入這句話來糾正某些編碼器產(chǎn)生的幀速錯誤
if(pCodecCtx->frame_rate>1000 && pCodecCtx->frame_rate_base==1)
pCodecCtx->frame_rate_base=1000;
注意即使將來這個bug解決了,留下這幾句話也并沒有什么壞處。視頻不可能擁有超過1000fps的幀速。
只剩下一件事情要做了:給視頻幀分配空間以便存儲解碼后的圖片:
AVFrame *pFrame;
pFrame=avcodec_alloc_frame();
就這樣,現(xiàn)在我們開始解碼這些視頻。
解碼視頻幀
就像我前面提到過的,視頻文件包含數(shù)個音頻和視頻流,并且他們各個獨自被分開存儲在固定大小的包里。我們要做的就是使用libavformat依次讀取這些包,過濾掉所有那些視頻流中我們不感興趣的部分,并把它們交給 libavcodec 進行解碼處理。在做這件事情時,我們要注意這樣一個事實,兩幀之間的邊界也可以在包的中間部分。
聽起來很復雜?幸運的是,我們在一個例程中封裝了整個過程,它僅僅返回下一幀:
bool?GetNextFrame(AVFormatContext *pFormatCtx, AVCodecContext *pCodecCtx,
int videoStream, AVFrame *pFrame)
{
static AVPacket packet;
static int??bytesRemaining=0;
static uint8_t?*rawData;
static bool??fFirstTime=true;
Int bytesDecoded;
Int frameFinished;
//?我們第一次調(diào)用時,將 packet.data 設置為NULL指明它不用釋放了
if(fFirstTime)
{
??fFirstTime=false;
??packet.data=NULL;
}
// 解碼直到成功解碼完整的一幀
while(true)
{
//?除非解碼完畢,否則一直在當前包中工作
??while(bytesRemaining > 0)
??{
//?解碼下一塊數(shù)據(jù)
????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;
}
現(xiàn)在,我們要做的就是在一個循環(huán)中,調(diào)用 GetNextFrame () 直到它返回false。還有一處需要注意:大多數(shù)編碼器返回 YUV 420 格式的圖片(一個亮度和兩個色度通道,色度通道只占亮度通道空間分辨率的一半(譯者注:此句原句為the chrominance channels samples at half the spatial resolution of the luminance channel))。看你打算如何對視頻數(shù)據(jù)處理,或許你打算將它轉(zhuǎn)換至RGB格式。(注意,盡管,如果你只是打算顯示視頻數(shù)據(jù),那大可不必要這么做;查看一下 X11 的 Xvideo 擴展,它可以在硬件層進行 YUV到RGB 轉(zhuǎn)換。)幸運的是, libavcodec 提供給我們了一個轉(zhuǎn)換例程 img_convert ,它可以像轉(zhuǎn)換其他圖象進行 YUV 和 RGB之間的轉(zhuǎn)換。這樣解碼視頻的循環(huán)就變成這樣:
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;
int??numBytes;
uint8_t *buffer;
// 分配一個AVFrame 結(jié)構(gòu)的空間
pFrameRGB=avcodec_alloc_frame();
if(pFrameRGB==NULL)
handle_error();
// 確認所需緩沖區(qū)大小并且分配緩沖區(qū)空間
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
pCodecCtx->height);
buffer=new uint8_t[numBytes];
// 在pFrameRGB中給圖象位面賦予合適的緩沖區(qū)
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
清除
好了,我們已經(jīng)處理了我們的視頻,現(xiàn)在需要做的就是清除我們自己的東西:
// 釋放 RGB 圖象
delete [] buffer;
av_free(pFrameRGB);
// 釋放YUV 幀
av_free(pFrame);
// 關(guān)閉解碼器(codec)
avcodec_close(pCodecCtx);
// 關(guān)閉視頻文件
av_close_input_file(pFormatCtx);
完成!
更新(2005年4月26號):有個讀者提出:在 Kanotix (一個 Debian 的發(fā)行版)上面編譯本例程,或者直接在 Debian 上面編譯,頭文件中avcodec.h 和avformat.h 需要加上前綴“ffmpeg”,就像這樣:
#include <ffmpeg/avcodec.h>
#include <ffmpeg/avformat.h>
同樣的, libdts 庫在編譯程序時也要像下面這樣加入進來:
g++ -o avcodec_sample.0.4.9 avcodec_sample.0.4.9.cpp \
-lavformat -lavcodec -ldts -lz
幾個月前,我寫了一篇有關(guān)使用ffmpeg下libavformat 和?libavcodec庫的文章。從那以來,我收到過一些評論,并且新的ffmpeg預發(fā)行版(0.4.9-pre1) 最近也要出來了,增加了對在視頻文件中定位的支持,新的文件格式,和簡單的讀取視頻幀的接口。這些改變不久就會應用到CVS中,不過這次是我第一次在發(fā)行版中看到它們。(順便感謝 Silviu Minut 共享長時間學習CVS版的ffmpeg的成果--他的有關(guān)ffmpeg的信息和demo程序在這里。)
在這篇文章里,我僅僅會描述一下以前的版本(0.4.8)和最新版本之間的區(qū)別,所以,如果你是采用新的 libavformat / libavcodec ,我建議你讀前面的文章。
首先,說說有關(guān)編譯新發(fā)行版吧。用我的編譯器( SuSE 上的 gcc 3.3.1 ),在編譯源文件 ffv1.c 時會報一個編譯器內(nèi)部的錯誤。我懷疑這是個精簡版的gcc--我在編譯 OpenCV 時也遇到了同樣的事情--但是不論如何,一個快速的解決方法就是在編譯此文件時不要加優(yōu)化參數(shù)。最簡單的方法就是作一個make,當編譯時遇到編譯器錯誤,進入 libavcodec 子目錄(因為這也是 ffv1.c 所在之處),在你的終端中使用gcc命令去編譯ffv1.c,粘貼,編輯刪除編譯器開關(guān)(譯者注:就是參數(shù))"-O3",然后使用那個命令運行g(shù)cc。然后,你可以變回ffmpeg主目錄并且重新運行make,這次應該可以編譯了。
都有哪些更新?
有那些更新呢?從一個程序員的角度來看,最大的變化就是盡可能的簡化了從視頻文件中讀取個人的視頻幀的操作。在ffmpeg 0.4.8 和其早期版本中,在從一個視頻文件中的包中用例程av_read_packet()來讀取數(shù)據(jù)時,一個視頻幀的信息通常可以包含在幾個包里,而另情況更為復雜的是,實際上兩幀之間的邊界還可以存在于兩個包之間。幸虧ffmpeg 0.4.9 引入了新的叫做av_read_frame()的例程,它可以從一個簡單的包里返回一個視頻幀包含的所有數(shù)據(jù)。使用av_read_packet()讀取視頻數(shù)據(jù)的老辦法仍然支持,但是不贊成使用--我說:擺脫它是可喜的。
這里讓我們來看看如何使用新的API來讀取視頻數(shù)據(jù)。在我原來的文章中(與 0.4.8 API相關(guān)),主要的解碼循環(huán)就像下面這樣:
while(GetNextFrame(pFormatCtx, pCodecCtx, videoStream, pFrame))
{
img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame,
??pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
// 處理視頻幀(存盤等等)
DoSomethingWithTheImage(pFrameRGB);
}
GetNextFrame() 是個有幫助的例程,它可以處理這樣一個過程,這個過程匯編一個完整的視頻幀所需要的所有的包。新的API簡化了我們在主循環(huán)中實際直接讀取和解碼數(shù)據(jù)的操作:
while(av_read_frame(pFormatCtx, &packet)>=0)
{
// 這是視頻流中的一個包嗎?
if(packet.stream_index==videoStream)
{
??// 解碼視頻流
??avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
????packet.data, packet.size);
??// 我們得到一幀了嗎?
??if(frameFinished)
??{
????// 把原始圖像轉(zhuǎn)換成 RGB
????img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,
??????(AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width,
??????pCodecCtx->height);
????// 處理視頻幀(存盤等等)
????DoSomethingWithTheImage(pFrameRGB);
??}
}
// 釋放用av_read_frame分配空間的包
av_free_packet(&packet);
}
看第一眼,似乎看上去變得更為復雜了。但那僅僅是因為這塊代碼做的都是要隱藏在GetNextFrame()例程中實現(xiàn)的(檢查包是否屬于視頻流,解碼幀并釋放包)。總的說來,因為我們能夠完全排除 GetNextFrame (),事情變得更簡單了。
我已經(jīng)更新了demo程序使用最新的API。簡單比較一下行數(shù)(老版本222行 Vs新版本169行)顯示出新的API大大的簡化了這件事情。
0.4.9的另一個重要的更新是能夠在視頻文件中定位一個時間戳。它通過函數(shù)av_seek_frame() 來實現(xiàn),此函數(shù)有三個參數(shù):一個指向 AVFormatContext 的指針,一個流索引和定位時間戳。此函數(shù)在給定時間戳以前會去定位第一個關(guān)鍵幀。所有這些都來自于文檔。我并沒有對av_seek_frame()進行測試,所以這里我并不能夠給出任何示例代碼。如果你成功的使用av_seek_frame()?,我很高興聽到這個消息。
捕獲視頻(Video4Linux and IEEE1394)
Toru Tamaki 發(fā)給我了一些使用 libavformat / libavcodec 庫從 Video4Linux 或者 IEEE1394 視頻設備源中抓捕視頻幀的樣例代碼。對 Video4Linux,調(diào)用av_open_input_file() 函數(shù)應該修改如下:
AVFormatParameters formatParams;
AVInputFormat *iformat;
formatParams.device = "/dev/video0";
formatParams.channel = 0;
formatParams.standard = "ntsc";
formatParams.width = 640;
formatParams.height = 480;
formatParams.frame_rate = 29;
formatParams.frame_rate_base = 1;
filename = "";
iformat = av_find_input_format("video4linux");
av_open_input_file(&ffmpegFormatContext,
????????filename, iformat, 0, &formatParams);
For IEEE1394, call av_open_input_file() like this:
AVFormatParameters formatParams;
AVInputFormat *iformat;
formatParams.device = "/dev/dv1394";
filename = "";
iformat = av_find_input_format("dv1394");
av_open_input_file(&ffmpegFormatContext,filename, iformat, 0, &formatParams);
頂3 踩
Ffmpeg?中的Libavformat?和?libavcodec庫是訪問大多數(shù)視頻文件格式的一個很好的方法。不幸的是,在開發(fā)您自己的程序時,這套庫基本上沒有提供什么實際的文檔可以用來作為參考(至少我沒有找到任何文檔),并且它的例程也并沒有太多的幫助。
這種情況意味著,當我在最近某個項目中需要用到 libavformat/libavcodec 庫時,需要作很多試驗來搞清楚怎樣使用它們。這里是我所學習的--希望我做的這些能夠幫助一些人,以免他們重蹈我的覆轍,作同樣的試驗,遇到同樣的錯誤。你還可以從這里下載一個demo程序。我將要公開的這部分代碼需要0.4.8 版本的ffmpeg庫中的 libavformat/libavcodec 的支持(我正在寫最新版本)。如果您發(fā)現(xiàn)以后的版本與我寫的程序不能兼容,請告知我。
在這個文檔里,我僅僅涉及到如何從文件中讀入視頻流;音頻流使用幾乎同樣的方法可以工作的很好,不過,我并沒有實際使用過它們,所以,我沒于辦法提供任何示例代碼。
或許您會覺得奇怪,為什么需要兩個庫文件 libavformat 和 libavcodec :許多視頻文件格式(AVI就是一個最好的例子)實際上并沒有明確指出應該使用哪種編碼來解析音頻和視頻數(shù)據(jù);它們只是定義了音頻流和視頻流(或者,有可能是多個音頻視頻流)如何被綁定在一個文件里面。這就是為什么有時候,當你打開了一個AVI文件時,你只能聽到聲音,卻不能看到圖象--因為你的系統(tǒng)沒有安裝合適的視頻解碼器。所以, libavformat 用來處理解析視頻文件并將包含在其中的流分離出來, 而libavcodec 則處理原始音頻和視頻流的解碼。
打開視頻文件:
首先第一件事情--讓我們來看看怎樣打開一個視頻文件并從中得到流。我們要做的第一件事情就是初始化libavformat/libavcodec:
av_register_all();
這一步注冊庫中含有的所有可用的文件格式和編碼器,這樣當打開一個文件時,它們才能夠自動選擇相應的文件格式和編碼器。要注意你只需調(diào)用一次 av_register_all(),所以,盡可能的在你的初始代碼中使用它。如果你愿意,你可以僅僅注冊個人的文件格式和編碼,不過,通常你不得不這么做卻沒有什么原因。
下一步,打開文件:
AVFormatContext *pFormatCtx;
const char??*filename="myvideo.mpg";
// 打開視頻文件
if(av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)!=0)
handle_error(); // 不能打開此文件
最后三個參數(shù)描述了文件格式,緩沖區(qū)大小(size)和格式參數(shù);我們通過簡單地指明NULL或0告訴 libavformat 去自動探測文件格式并且使用默認的緩沖區(qū)大小。請在你的程序中用合適的出錯處理函數(shù)替換掉handle_error()。
下一步,我們需要取出包含在文件中的流信息:
// 取出流信息
if(av_find_stream_info(pFormatCtx)<0)
handle_error(); // 不能夠找到流信息
這一步會用有效的信息把 AVFormatContext 的流域(streams field)填滿。作為一個可調(diào)試的診斷,我們會將這些信息全盤輸出到標準錯誤輸出中,不過你在一個應用程序的產(chǎn)品中并不用這么做:
dump_format(pFormatCtx, 0, filename, false);
就像在引言中提到的那樣,我們僅僅處理視頻流,而不是音頻流。為了讓這件事情更容易理解,我們只簡單使用我們發(fā)現(xiàn)的第一種視頻流:
int i, videoStream;
AVCodecContext *pCodecCtx;
//?尋找第一個視頻流
videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams->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;
好了,我們已經(jīng)得到了一個指向視頻流的稱之為上下文的指針。但是我們?nèi)匀恍枰业秸嬲木幋a器打開它。
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;
// 打開解碼器
if(avcodec_open(pCodecCtx, pCodec)<0)
handle_error(); // 打不開解碼器
(那么什么是“截斷bit流”?好的,就像一會我們看到的,視頻流中的數(shù)據(jù)是被分割放入包中的。因為每個視頻幀的數(shù)據(jù)的大小是可變的,那么兩幀之間的邊界就不一定剛好是包的邊界。這里,我們告知解碼器我們可以處理bit流。)
存儲在 AVCodecContext結(jié)構(gòu)中的一個重要的信息就是視頻幀速率。為了允許非整數(shù)的幀速率(比如 NTSC的?29.97幀),速率以分數(shù)的形式存儲,分子在 pCodecCtx->frame_rate,分母在 pCodecCtx->frame_rate_base 中。在用不同的視頻文件測試庫時,我注意到一些編碼器(很顯然ASF)似乎并不能正確的給予賦值( frame_rate_base 用1代替1000)。下面給出修復補丁:
//?加入這句話來糾正某些編碼器產(chǎn)生的幀速錯誤
if(pCodecCtx->frame_rate>1000 && pCodecCtx->frame_rate_base==1)
pCodecCtx->frame_rate_base=1000;
注意即使將來這個bug解決了,留下這幾句話也并沒有什么壞處。視頻不可能擁有超過1000fps的幀速。
只剩下一件事情要做了:給視頻幀分配空間以便存儲解碼后的圖片:
AVFrame *pFrame;
pFrame=avcodec_alloc_frame();
就這樣,現(xiàn)在我們開始解碼這些視頻。
解碼視頻幀
就像我前面提到過的,視頻文件包含數(shù)個音頻和視頻流,并且他們各個獨自被分開存儲在固定大小的包里。我們要做的就是使用libavformat依次讀取這些包,過濾掉所有那些視頻流中我們不感興趣的部分,并把它們交給 libavcodec 進行解碼處理。在做這件事情時,我們要注意這樣一個事實,兩幀之間的邊界也可以在包的中間部分。
聽起來很復雜?幸運的是,我們在一個例程中封裝了整個過程,它僅僅返回下一幀:
bool?GetNextFrame(AVFormatContext *pFormatCtx, AVCodecContext *pCodecCtx,
int videoStream, AVFrame *pFrame)
{
static AVPacket packet;
static int??bytesRemaining=0;
static uint8_t?*rawData;
static bool??fFirstTime=true;
Int bytesDecoded;
Int frameFinished;
//?我們第一次調(diào)用時,將 packet.data 設置為NULL指明它不用釋放了
if(fFirstTime)
{
??fFirstTime=false;
??packet.data=NULL;
}
// 解碼直到成功解碼完整的一幀
while(true)
{
//?除非解碼完畢,否則一直在當前包中工作
??while(bytesRemaining > 0)
??{
//?解碼下一塊數(shù)據(jù)
????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;
}
現(xiàn)在,我們要做的就是在一個循環(huán)中,調(diào)用 GetNextFrame () 直到它返回false。還有一處需要注意:大多數(shù)編碼器返回 YUV 420 格式的圖片(一個亮度和兩個色度通道,色度通道只占亮度通道空間分辨率的一半(譯者注:此句原句為the chrominance channels samples at half the spatial resolution of the luminance channel))。看你打算如何對視頻數(shù)據(jù)處理,或許你打算將它轉(zhuǎn)換至RGB格式。(注意,盡管,如果你只是打算顯示視頻數(shù)據(jù),那大可不必要這么做;查看一下 X11 的 Xvideo 擴展,它可以在硬件層進行 YUV到RGB 轉(zhuǎn)換。)幸運的是, libavcodec 提供給我們了一個轉(zhuǎn)換例程 img_convert ,它可以像轉(zhuǎn)換其他圖象進行 YUV 和 RGB之間的轉(zhuǎn)換。這樣解碼視頻的循環(huán)就變成這樣:
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;
int??numBytes;
uint8_t *buffer;
// 分配一個AVFrame 結(jié)構(gòu)的空間
pFrameRGB=avcodec_alloc_frame();
if(pFrameRGB==NULL)
handle_error();
// 確認所需緩沖區(qū)大小并且分配緩沖區(qū)空間
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
pCodecCtx->height);
buffer=new uint8_t[numBytes];
// 在pFrameRGB中給圖象位面賦予合適的緩沖區(qū)
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
清除
好了,我們已經(jīng)處理了我們的視頻,現(xiàn)在需要做的就是清除我們自己的東西:
// 釋放 RGB 圖象
delete [] buffer;
av_free(pFrameRGB);
// 釋放YUV 幀
av_free(pFrame);
// 關(guān)閉解碼器(codec)
avcodec_close(pCodecCtx);
// 關(guān)閉視頻文件
av_close_input_file(pFormatCtx);
完成!
更新(2005年4月26號):有個讀者提出:在 Kanotix (一個 Debian 的發(fā)行版)上面編譯本例程,或者直接在 Debian 上面編譯,頭文件中avcodec.h 和avformat.h 需要加上前綴“ffmpeg”,就像這樣:
#include <ffmpeg/avcodec.h>
#include <ffmpeg/avformat.h>
同樣的, libdts 庫在編譯程序時也要像下面這樣加入進來:
g++ -o avcodec_sample.0.4.9 avcodec_sample.0.4.9.cpp \
-lavformat -lavcodec -ldts -lz
幾個月前,我寫了一篇有關(guān)使用ffmpeg下libavformat 和?libavcodec庫的文章。從那以來,我收到過一些評論,并且新的ffmpeg預發(fā)行版(0.4.9-pre1) 最近也要出來了,增加了對在視頻文件中定位的支持,新的文件格式,和簡單的讀取視頻幀的接口。這些改變不久就會應用到CVS中,不過這次是我第一次在發(fā)行版中看到它們。(順便感謝 Silviu Minut 共享長時間學習CVS版的ffmpeg的成果--他的有關(guān)ffmpeg的信息和demo程序在這里。)
在這篇文章里,我僅僅會描述一下以前的版本(0.4.8)和最新版本之間的區(qū)別,所以,如果你是采用新的 libavformat / libavcodec ,我建議你讀前面的文章。
首先,說說有關(guān)編譯新發(fā)行版吧。用我的編譯器( SuSE 上的 gcc 3.3.1 ),在編譯源文件 ffv1.c 時會報一個編譯器內(nèi)部的錯誤。我懷疑這是個精簡版的gcc--我在編譯 OpenCV 時也遇到了同樣的事情--但是不論如何,一個快速的解決方法就是在編譯此文件時不要加優(yōu)化參數(shù)。最簡單的方法就是作一個make,當編譯時遇到編譯器錯誤,進入 libavcodec 子目錄(因為這也是 ffv1.c 所在之處),在你的終端中使用gcc命令去編譯ffv1.c,粘貼,編輯刪除編譯器開關(guān)(譯者注:就是參數(shù))"-O3",然后使用那個命令運行g(shù)cc。然后,你可以變回ffmpeg主目錄并且重新運行make,這次應該可以編譯了。
都有哪些更新?
有那些更新呢?從一個程序員的角度來看,最大的變化就是盡可能的簡化了從視頻文件中讀取個人的視頻幀的操作。在ffmpeg 0.4.8 和其早期版本中,在從一個視頻文件中的包中用例程av_read_packet()來讀取數(shù)據(jù)時,一個視頻幀的信息通常可以包含在幾個包里,而另情況更為復雜的是,實際上兩幀之間的邊界還可以存在于兩個包之間。幸虧ffmpeg 0.4.9 引入了新的叫做av_read_frame()的例程,它可以從一個簡單的包里返回一個視頻幀包含的所有數(shù)據(jù)。使用av_read_packet()讀取視頻數(shù)據(jù)的老辦法仍然支持,但是不贊成使用--我說:擺脫它是可喜的。
這里讓我們來看看如何使用新的API來讀取視頻數(shù)據(jù)。在我原來的文章中(與 0.4.8 API相關(guān)),主要的解碼循環(huán)就像下面這樣:
while(GetNextFrame(pFormatCtx, pCodecCtx, videoStream, pFrame))
{
img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame,
??pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
// 處理視頻幀(存盤等等)
DoSomethingWithTheImage(pFrameRGB);
}
GetNextFrame() 是個有幫助的例程,它可以處理這樣一個過程,這個過程匯編一個完整的視頻幀所需要的所有的包。新的API簡化了我們在主循環(huán)中實際直接讀取和解碼數(shù)據(jù)的操作:
while(av_read_frame(pFormatCtx, &packet)>=0)
{
// 這是視頻流中的一個包嗎?
if(packet.stream_index==videoStream)
{
??// 解碼視頻流
??avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
????packet.data, packet.size);
??// 我們得到一幀了嗎?
??if(frameFinished)
??{
????// 把原始圖像轉(zhuǎn)換成 RGB
????img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,
??????(AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width,
??????pCodecCtx->height);
????// 處理視頻幀(存盤等等)
????DoSomethingWithTheImage(pFrameRGB);
??}
}
// 釋放用av_read_frame分配空間的包
av_free_packet(&packet);
}
看第一眼,似乎看上去變得更為復雜了。但那僅僅是因為這塊代碼做的都是要隱藏在GetNextFrame()例程中實現(xiàn)的(檢查包是否屬于視頻流,解碼幀并釋放包)。總的說來,因為我們能夠完全排除 GetNextFrame (),事情變得更簡單了。
我已經(jīng)更新了demo程序使用最新的API。簡單比較一下行數(shù)(老版本222行 Vs新版本169行)顯示出新的API大大的簡化了這件事情。
0.4.9的另一個重要的更新是能夠在視頻文件中定位一個時間戳。它通過函數(shù)av_seek_frame() 來實現(xiàn),此函數(shù)有三個參數(shù):一個指向 AVFormatContext 的指針,一個流索引和定位時間戳。此函數(shù)在給定時間戳以前會去定位第一個關(guān)鍵幀。所有這些都來自于文檔。我并沒有對av_seek_frame()進行測試,所以這里我并不能夠給出任何示例代碼。如果你成功的使用av_seek_frame()?,我很高興聽到這個消息。
捕獲視頻(Video4Linux and IEEE1394)
Toru Tamaki 發(fā)給我了一些使用 libavformat / libavcodec 庫從 Video4Linux 或者 IEEE1394 視頻設備源中抓捕視頻幀的樣例代碼。對 Video4Linux,調(diào)用av_open_input_file() 函數(shù)應該修改如下:
AVFormatParameters formatParams;
AVInputFormat *iformat;
formatParams.device = "/dev/video0";
formatParams.channel = 0;
formatParams.standard = "ntsc";
formatParams.width = 640;
formatParams.height = 480;
formatParams.frame_rate = 29;
formatParams.frame_rate_base = 1;
filename = "";
iformat = av_find_input_format("video4linux");
av_open_input_file(&ffmpegFormatContext,
????????filename, iformat, 0, &formatParams);
For IEEE1394, call av_open_input_file() like this:
AVFormatParameters formatParams;
AVInputFormat *iformat;
formatParams.device = "/dev/dv1394";
filename = "";
iformat = av_find_input_format("dv1394");
av_open_input_file(&ffmpegFormatContext,filename, iformat, 0, &formatParams);
頂
總結(jié)
以上是生活随笔為你收集整理的ffmpeg开发指南(使用 libavformat 和 libavcodec)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker 终失 C 位,开源少年缘何
- 下一篇: CSS 状态管理,玩出花样了!