ffmpeg API 筆記:使用libavcodec/libavformat/libswscale

其實研究ffmpeg不用找什麼教程,第一步應該是下載ffmpeg的源碼包。下面提到的An FFmpeg and SDL Tutorial確實有講 解,但是教程總是跟不上代碼的變化的,所以直接看可工作代碼最好;ffmpeg的結構很分明,後臺是幾個庫:libxxx,前臺是三個程序ffmpeg, ffplay, ffserver,那篇教程說的就是ffplay的實現。一個播放器,其實重點不是解碼,解碼的東西是lib去做的,主要是做聲音視頻的時鐘同步。 ffplay的代碼可以說是一個可用播放器最簡單的實現了,源碼裏面有個output_example.c,可以說是最基本的api示範吧。ffmpeg 是轉換編碼解碼轉換程序,因爲涉及重新採樣等等,所以代碼量也不少的。


這兩天"調研"了下ffmpeg的API,不得不承認被雷倒:ffmpeg又是一個很geek的項目,純社區開發,基於逆向,功能強大,但是文檔極 度有限,想了解API?看源碼去…… 網上關於ffmpeg API的資料,無非是ffmpeg文檔裏面的兩個鏈接,Using libavformat and libavcodec by
Martin Böhme(以及 其Update,介紹了新引入的API)跟An FFmpeg and SDL Tutorial
by Stephen Dranger;兩個tutorial基於ffmpeg 0.4.8,現在ffmpeg發佈的版本是0.5.0,好像數值相差不大,不過0.4.8是5年前的了(相比之下wine用了15年版本號纔到達1.0, 有過之餘無不及),兩個教程裏面的代碼在0.5.0下一編譯,哇,一堆錯誤,可不僅有些api函數變了,有些結構成員壓根就沒了,頭文件的位置更是不一樣 (各個庫分家了)……所以我調試了好幾個小時,終於把例子的代碼弄好(其實Martin Böhme那篇有一段09年加入的更新說明,鏈 接了有相關的解決辦法,我一開始沒注意,幾個小時自己解決,不過也有收穫)。

最後我調試好的代碼流程:打開一個視頻文件,抓取前5幀保存爲文件;
基於 Stephen Dranger的Tutorial1源 碼在此:GoogleCode

av_register_all();//初始化ffmpeg庫,如果系統裏面的ffmpeg沒配置好這裏會出錯
av_open_input_file();
av_find_stream_info();//查找文件的流信息
dump_format();//dump只是個調試函數,輸出文件的音、視頻流的基本信息了,幀率、分辨率、音頻採樣等等
for(...);//遍歷文件的各個流,找到第一個視頻流,並記錄該流的編碼信息
sws_getContext();//根據編碼信息設置渲染格式
avcodec_find_decoder();//在庫裏面查找支持該格式的解碼器
avcodec_open();//打開解碼器
pFrame=avcodec_alloc_frame();//分配一個幀指針,指向解碼後的原始幀
pFrameRGB=avcodec_alloc_frame();//分配一個幀指針,指向存放轉換成RGB後的幀
avpicture_fill(pFrameRGB);//給pFrameRGB幀加上分配的內存;
while true{
av_read_frame();//讀取一個幀(到最後幀則break)
avcodec_decode_video();//解碼該幀
sws_scale();//把該幀轉換(渲染)成RGB
SaveFrame();//對前5幀保存成ppm圖形文件(這個是自定義函數,非API)
av_free_packet();//釋放本次讀取的幀內存
}
avcodec_close();
av_close_input_file();

用到的API就這麼多,當然實際代碼稍複雜一點;ppm圖像是類似BMP的非壓縮格式,SaveFrame就是相當於把pFrameRGB的內存拷 貝進文件,寫文件並不複雜;

調試過程的問題,首先是頭文件,ffmpeg 0.5.0的API已經拆分成好幾個獨立的庫,用pacman -Ql ffmpeg看了下文件分佈,在include下好幾個目錄都是它的,看名字可以大概猜出他們的功能:

libavcodec:CODEC其實是Coder/Decoder的縮寫,也就是編碼解碼器;
libavdevice: 對輸出輸入設備的支持;
libavformat:對音頻視頻格式的解析
libavutil:集項工具;
libpostproc: 後期效果處理;
libswscale:視頻場景比例縮放、色彩映射轉換;

修改好頭文件包含,終於少了些not declared錯誤;

Martin Böhme那篇教程的代碼是使用g++編譯的,雖然代碼是C風格;在我修改了頭文件以及一些錯誤之後,居然鏈接出錯,av_register_all什麼 的函數統統undefined reference,想到ffmpeg是純C實現,以及以前用g++編譯GTK出現回呼函數找不到的經歷,相信又是g++的function mangling搞的,Google了一下,解 決方法是把幾個頭文件包含在exterc "C"裏面

代碼裏面的錯誤還涉及一些結構成員的變化,比如

pFormatCtx->streams[i]->codec.codec_type==CODEC_TYPE_VIDEO

就有個類型錯誤,因爲0.5.0裏面的codec已經是指針,而不是結構了,要把.換成->,而相應地獲得解碼器指針,不再需要&:

pCodecCtx=&pFormatCtx->streams[videoStream]->codec;

原教程提到一個視頻碼率的rate的Hack:

// Hack to correct wrong frame rates that seem to be generated by some codecs<br />    if(pCodecCtx->frame_rate>1000 && pCodecCtx->frame_rate_base==1)<br />        pCodecCtx->frame_rate_base=1000;

好吧現在frame_rate跟frame_rate_base壓根就沒了,去掉算了;

最麻煩的變化還是原代碼裏面的img_convert,就是解碼出一個幀的數據後,需要轉換成RGB格式才能寫入文件,然而這個函數在0.5.0裏 面徹底沒了。嘗試把img_convert完全註釋掉,把原始幀img_convert寫入文件,還算可喜的是能夠看到圖形,只是被分成三個畫面的通道圖 形罷了;

Google了一番,還是回到 Stephen Dranger的An FFmpeg and SDL Tutorial第八節,介紹了swscale的接口,雖然裏面的例子是轉換成SDL所用的YUV,而不是RGB;注意到其使用的參數 PIX_FMT_YUV420P,跟img_convert所用的PIX_FMT_RGB24有相同前序,就試試照樣花虎了;
給 sws_getContext傳入源格式的H/W,格式,輸出格式的H/W,PIX_FMT_RGB24格式,其中有個參數flags的解釋是 specify which algorithm and options to use for rescaling,是選擇在縮放過程中是使用線性還是雙立方等算法(參數文檔沒說,要找看源碼去),這裏照抄了例子裏面的SWS_BICUBIC。獲得 這個SwsContext,類似python的re.compile,再用這個轉換器去轉換每一個幀,所以後面每次解碼了幀後,調用sws_scale, 跟原來的img_convert倒是挺像;

也就是說,以後如果需要對視頻進行4:3跟16:9的轉換,就是在sws_getContext的參數裏面做設置了;

最後整個程序正常,會把視頻的前5幀抓成圖像了,只是會有個小警告:

[swscaler @ 0x1d8f670]No accelerated colorspace conversion found.

估計是轉換成RGB,swscale裏面沒有特別優化的算法?

目前發現程序運行的時候CPU佔用很高,是因爲程序還沒有控制幀速的時鐘,只會用盡CPU的性能不斷的讀取解碼;

PT修改過可編譯通過(ffmpeg 5.0/gcc 4.4.2/ubuntu 9.10)的前三個Tutorial代碼可在Google Code查看下載。(編譯方法請看各個文件頭部的註釋說明

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章