ffmpeg編解碼詳細(xì)過程
注冊(cè)所有容器格式和CODEC:av_register_all()
打開文件:av_open_input_file()
從文件中提取流信息:av_find_stream_info()
窮舉所有的流辖所,查找其中種類為CODEC_TYPE_VIDEO
查找對(duì)應(yīng)的解碼器:avcodec_find_decoder()
打開編解碼器:avcodec_open()
為解碼幀分配內(nèi)存:avcodec_alloc_frame()
不停地從碼流中提取出幀數(shù)據(jù):av_read_frame()
判斷幀的類型镣衡,對(duì)于視頻幀調(diào)用:avcodec_decode_video()
解碼完后,釋放解碼器:avcodec_close()
關(guān)閉輸入文件:av_close_input_file()
首先第一件事情就是開一個(gè)視頻文件并從中得到流。我們要做的第一件事情就是使用av_register_all();來初始化libavformat/libavcodec:
這一步注冊(cè)庫中含有的所有可用的文件格式和編碼器,這樣當(dāng)打開一個(gè)文件時(shí),它們才能夠自動(dòng)選擇相應(yīng)的文件格式和編碼器。av_register_all()只需調(diào)用一次,所以技扼,要放在初始化代碼中。也可以僅僅注冊(cè)個(gè)人的文件格式和編碼嫩痰。
下一步剿吻,打開文件:
AVFormatContext *pFormatCtx;
const char *filename="myvideo.mpg";
av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL); // 打開視頻文件
最后三個(gè)參數(shù)描述了文件格式始赎,緩沖區(qū)大泻统取(size)和格式參數(shù);我們通過簡(jiǎn)單地指明NULL或0告訴 libavformat 去自動(dòng)探測(cè)文件格式并且使用默認(rèn)的緩沖區(qū)大小造垛。這里的格式參數(shù)指的是視頻輸出參數(shù)魔招,比如寬高的坐標(biāo)。
下一步五辽,我們需要取出包含在文件中的流信息:
av_find_stream_info(pFormatCtx)办斑; // 取出流信息
AVFormatContext 結(jié)構(gòu)體
dump_format(pFormatCtx, 0, filename, false);//我們可以使用這個(gè)函數(shù)把獲取到得參數(shù)全部輸出。
for(i=0; i<pFormatCtx->nb_streams; i++) //區(qū)分視頻流和音頻流
if(pFormatCtx->streams->codec.codec_type==CODEC_TYPE_VIDEO) //找到視頻流杆逗,這里也可以換成音頻
{
videoStream=i;
break;
}
接下來就需要尋找解碼器
AVCodec *pCodec;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
avcodec_open(pCodecCtx, pCodec)乡翅; // 打開解碼器
給視頻幀分配空間以便存儲(chǔ)解碼后的圖片:
AVFrame *pFrame;
pFrame=avcodec_alloc_frame();
/////////////////////////////////////////開始解碼///////////////////////////////////////////
第一步當(dāng)然是讀數(shù)據(jù):
我們將要做的是通過讀取包來讀取整個(gè)視頻流,然后把它解碼成幀罪郊,最后轉(zhuǎn)換格式并且保存蠕蚜。
while(av_read_frame(pFormatCtx, &packet)>=0) { //讀數(shù)據(jù)
if(packet.stream_index==videoStream){ //判斷是否視頻流
avcodec_decode_video(pCodecCtx,pFrame, &frameFinished,
packet.data, packet.size); //解碼
if(frameFinished) {
img_convert((AVPicture )pFrameRGB, PIX_FMT_RGB24,(AVPicture)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width,pCodecCtx->height);//轉(zhuǎn)換 }
SaveFrame(pFrameRGB, pCodecCtx->width,pCodecCtx->height, i); //保存數(shù)據(jù)
av_free_packet(&packet); //釋放
av_read_frame()讀取一個(gè)包并且把它保存到AVPacket結(jié)構(gòu)體中。這些數(shù)據(jù)可以在后面通過av_free_packet()來釋放悔橄。函數(shù)avcodec_decode_video()把包轉(zhuǎn)換為幀靶累。然而當(dāng)解碼一個(gè)包的時(shí)候,我們可能沒有得到我們需要的關(guān)于幀的信息癣疟。因此挣柬,當(dāng)我們得到下一幀的時(shí)候,avcodec_decode_video()為我們?cè)O(shè)置了幀結(jié)束標(biāo)志frameFinished睛挚。最后邪蛔,我們使用 img_convert()函數(shù)來把幀從原始格式(pCodecCtx->pix_fmt)轉(zhuǎn)換成為RGB格式。要記住扎狱,你可以把一個(gè) AVFrame結(jié)構(gòu)體的指針轉(zhuǎn)換為AVPicture結(jié)構(gòu)體的指針侧到。最后勃教,我們把幀和高度寬度信息傳遞給我們的SaveFrame函數(shù)。
到此解碼完畢床牧,顯示過程使用SDL完成考慮到我們以后會(huì)使用firmware進(jìn)行顯示操作荣回,SDL忽略不講。
音視頻同步
DTS(解碼時(shí)間戳)和PTS(顯示時(shí)間戳)
當(dāng)我們調(diào)用av_read_frame()得到一個(gè)包的時(shí)候戈咳,PTS和DTS的信息也會(huì)保存在包中。但是我們真正想要的PTS是我們剛剛解碼出來的原始幀的PTS壕吹,這樣我們才能知道什么時(shí)候來顯示它著蛙。然而,我們從avcodec_decode_video()函數(shù)中得到的幀只是一個(gè)AVFrame耳贬,其中并沒有包含有用的PTS值(注意:AVFrame并沒有包含時(shí)間戳信息踏堡,但當(dāng)我們等到幀的時(shí)候并不是我們想要的樣子)。咒劲。我們保存一幀的第一個(gè)包的PTS:這將作為整個(gè)這一幀的PTS顷蟆。我們可以通過函數(shù)avcodec_decode_video()來計(jì)算出哪個(gè)包是一幀的第一個(gè)包。怎樣實(shí)現(xiàn)呢腐魂?任何時(shí)候當(dāng)一個(gè)包開始一幀的時(shí)候帐偎,avcodec_decode_video()將調(diào)用一個(gè)函數(shù)來為一幀申請(qǐng)一個(gè)緩沖。當(dāng)然蛔屹,ffmpeg允許我們重新定義那個(gè)分配內(nèi)存的函數(shù)削樊。計(jì)算前一幀和現(xiàn)在這一幀的時(shí)間戳來預(yù)測(cè)出下一個(gè)時(shí)間戳的時(shí)間。同時(shí)兔毒,我們需要同步視頻到音頻漫贞。我們將設(shè)置一個(gè)音頻時(shí)間audioclock;一個(gè)內(nèi)部值記錄了我們正在播放的音頻的位置育叁。就像從任意的mp3播放器中讀出來的數(shù)字一樣迅脐。既然我們把視頻同步到音頻,視頻線程使用這個(gè)值來算出是否太快還是太慢豪嗽。