一、概述
本文會講到的內(nèi)容:
1、FFmpeg結構
2、FFmpeg解碼
3、FFmpeg的時間timebase
4、FFmpeg編碼
5、FFmpeg封裝mp4
二、FFmpeg結構

AVFormatContext:文件信息上下文,其中就包含了流信息結構AVStream
AVStream:流信息,其中就包含具體的音視頻格式信息AVCodecContext
AVCodecContext:具體的音視頻格式信息。要解碼音視頻,需要從這里拿到相關信息,比如AVCodecID
AVPacket:未解碼的音視頻內(nèi)容。比如視頻的每一幀數(shù)據(jù),就體現(xiàn)在一個packet中
AVFrame:解碼后的音視頻內(nèi)容。
通過FFmpeg解析mp4的過程來理解:

思考
1、如何得到音頻的編碼格式?
AVFormatContext->AVStream->AVCodecContext->AVCodecID
2、如何得到這個mp4的總時間?
AVFormatContext->duration
3、如何得到視頻的分辨率?
AVCodecContext->width、height
三、FFmpeg解碼
1、解碼H264(Annex-B)
Annex-B:H264其中一種表示格式。一般運用在網(wǎng)絡傳輸中。
一般是以0x00000001或者0x000001開頭,視頻幀分為I、P、B幀。
I幀還會帶上sps,pps信息,有這兩個信息才可以正常解碼。
0000000167+(SPS的內(nèi)容)+0000000168+(PPS的內(nèi)容)+0000000165+(IDR主幀的內(nèi)容):
標志位計算公式:code&0x1F,通過公式計算,得到:
SPS:0x07
PPS:0x08
IDR:0x05
0x67 & 0x1F = 0x07,所以0x67是sps的標志位。

解碼的部分代碼:

思考
這里的解碼器AVCodecContext不需要配置參數(shù)就可以解碼成功,為什么?
1、可以得到具體的NAL
2、可以得到解碼需要的信息
因為前面提到的sps,pps已經(jīng)攜帶了解碼的一些信息,比如分辨率,格式等等。FFmpeg會自動解析,然后進行解碼。
p2p流通過Annex-B這種格式傳H264。好處是每一個主幀都是獨立的,就算前面的主幀攜帶的sps、pps有錯,導致解碼失敗。后面的主幀也不受影響,還是可以解碼成功。這樣的容錯性就更好。
2、解碼H264(AVCC)
AVCC:H264其中一種表示格式。一般運用在mp4封裝格式中。
與Annex-B區(qū)別有兩點:一個是參數(shù)集(SPS, PPS)組織格式,一個是分隔。
Annex-B:使用start code分隔NAL(start code為三字節(jié)或四字節(jié),0x000001或0x00000001,一般是四字節(jié));SPS和PPS按流的方式寫在頭部。

組成:
extradata+NAL長度+NAL+NAL長度+NAL。。。
NAL長度所占字節(jié)、SPS與PPS等包含在extradata中
extradata格式:

舉個例子:


思考
FFmpeg如何解碼AVCC格式的H264?
解碼的兩大條件:
1、可以得到具體的NAL
2、可以得到解碼需要的信息
前面提到了。必現(xiàn)要有extradata。才可以得到拆分出具體的NAL,并且extradata也包含了sps,pps等的解碼需要的信息。
解碼AVCC的H264:

3、如何得到extradata
a、從mp4文件中解析得到
AVFormatContext->AVStream->AVCodecContext->extradata
b、自己構造
(1)、直接構造extradata

(2)、先創(chuàng)建Annex-B格式的extradata,再通過ffmpeg自帶函數(shù)轉(zhuǎn)成AVCC的extradata
-先構造Annex-B方式的extradata:

-extradata(Annex-B) ----> extradata(AVCC)

四、timebase
timebase:視頻處理中的一種時間單位
基本的時間單位:時、分、秒、毫秒
視頻中最小單位為幀,幀都是毫秒級別
幀率為15,那么每幀時間為:1000/15=66.6666毫秒
1、不用浮點型
2、精度更高
只能擴大倍速了,比如擴大100倍,那么每一幀就是6666,這時候就得定義一種新的單位,可以轉(zhuǎn)換為我們認知的時間:秒。
最終的數(shù)值都可以轉(zhuǎn)化為秒。
比如,100毫秒,就是0.1秒
100*(1/1000) = 0.1秒
這里的timebase = 1/1000,表示 1秒=1000 毫秒。所以timebase包含了與秒的對應關系。
思考
1、剛才的6666,它的timebase是多少呢?
6666*timebase = 0.066秒
timebase = 1/100000

2、上面的pkt_pts_time是怎么得出來的?
timebase = 1/15360
pts = 1024
pts_time = 1024 * (1/15360) = 0.066667
五、FFmpeg編碼
YUV 編碼為 H264

解碼代碼:

是否這樣就可以編碼成功了呢?像解碼一樣,不需要參數(shù)?
引出的編碼相關的問題:
1、每一幀壓縮百分比多少,即編碼大小未知
2、每一幀的分辨率未知
3、每一幀的原圖片格式未知。YUV420? YUV422
所以這些東西都需要配置。
AVCodecContext的編碼參數(shù)配置:

思考
1、如果編碼的分辨率不按圖片的分辨率設置會怎樣?
設置多少,編碼多少。如果圖片分辨率1600X1200,我設置成800X600。結果如下:

2、如何控制編碼后幀的大???
a、通過修改碼流參數(shù),bit_rate
有時候你會發(fā)現(xiàn),碼流改小了很多,但編碼后的大小變化不大
b、設置正確的timebase值
只有設置了正確的timebase,在改變bit_rate后,編碼后的大小才能正確改變。
可以根據(jù)AVFrame中的timebase,具體你可以看它的pts值,推斷大概的timebase。
比如:
你以幀率為15來算,那么每一幀的時間為1/15秒
1、比如第二幀 pts為1,那么1xtimebase = 1/15,所以timebase為(AVRational){1, 15}
2、比如第二幀pts為1024,同理1024xtimebase = 1/15,所以timebase為1/15除以1024,所以為(AVRational){1, 15360}
六、FFmpeg封裝mp4

重點1:AVCodecContext的配置
需要配置正確的AVCodecContext->extradata
1、Annex-B的H264,配置對應的extradata(解碼時候有提到)
2、AVCC的H264,配置對應的extradata
重點2:配置正確的timebase
如果合成的mp4播放速度不對,很大概率都是timebase設置錯誤了。
控制倍速播放等等,也通過修改timebase值,達到修改了每一幀的展示時間。
思考
timebase由1/15 改成 1/30,會怎樣?
原本某幀的pts=30,30(1/15) = 2,就是這幀在第2秒才展示
30(1/30) = 1,這幀變?yōu)榈?秒展示,所以播放速度快了一倍