H264碼流分析

ffmpeg 中使用 H264 編碼

ffmpeg 已經(jīng)有實現(xiàn)好了編碼器渗磅,調(diào)用的時候指定AV_CODEC_ID_H264甫菠,會使用 x264 的軟編碼;如果需要硬編碼將查找編碼器改為avcodec_find_encoder_by_name("h264_qsv")解幼。

int size;
int in_w = mWidth;
int in_h = mHeight;
 
//x264軟編碼初始化
pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
if(!pCodec) {
  fprintf(stderr, "h264 codec not found");
  return false;
}

//qsv硬編碼
// pCodec = avcodec_find_encoder_by_name("h264_qsv");
// if(Q_NULLPTR == pCodec){
//     qDebug("avcodec_find_encoder failed!");
//     return false;
// }
 
pCodecCtx = avcodec_alloc_context3(pCodec);
pCodecCtx->codec_id = AV_CODEC_ID_H264;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->pix_fmt = PIX_FMT_YUV420P;
pCodecCtx->width = in_w;
pCodecCtx->height = in_h;
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 25;//幀率
pCodecCtx->bit_rate = mBitRate; //比特率
pCodecCtx->gop_size=25;

···

//讀取編碼后數(shù)據(jù)外永,在AVPacket中
if(0 != avcodec_send_frame(m_pEncodeContext, m_pSwsFrame)) {
    qDebug("avcodec_send_frame failed");
    return false;
}
while(0 == avcodec_receive_packet(m_pEncodeContext, m_pDstPacket)) {
    if(m_pCallback) {
        if (m_pDstPacket->flags & AV_PKT_FLAG_KEY) {
            qDebug("encode a key frame");
        }

        m_pCallback->onVideoEncodeFinished(m_pDstPacket->data, m_pDstPacket->size);

    }

    av_packet_unref(m_pDstPacket);
}

H264 是屬于視頻的編碼層的標準格式,平時用 ffmpeg 只是指定下參數(shù)就完事了匙铡,并沒有仔細了解图甜,今天來分析下 H264 碼流具體的內(nèi)容。

H264 原始碼流結(jié)構(gòu)

在 H.264/AVC 視頻編碼標準中鳖眼,整個系統(tǒng)框架被分為兩層黑毅,VCL(視頻編碼層 video coding layer) 和 NAL(網(wǎng)絡(luò)提取層 network abstraction layer):

  1. VCL:核心算法引擎,塊钦讳,宏塊及片的語法級別的定義矿瘦,負責高效的視頻內(nèi)容表示,通俗的講就是編碼器直接編碼之后的數(shù)據(jù)愿卒,這部分數(shù)據(jù)還不能直接用于保存和網(wǎng)絡(luò)傳輸缚去,否則在解析上存在困難。
  2. NAL:負責格式化數(shù)據(jù)并提供頭信息琼开,以保證數(shù)據(jù)適合各種信道和存儲介質(zhì)上的傳輸易结,通俗的講 NAL 就是 VCL 加了一些頭部信息封裝了一下。

H264 碼流數(shù)據(jù)由 NALU 序列組成,相鄰的 NALU 由起始碼 StartCode 隔開搞动,起始碼 StartCode 的兩種形式:3 字節(jié)的 0x000001 和 4 字節(jié)的 0x00000001躏精。3 字節(jié)的 起始碼只有在一個完整的幀被編為多個 slice(片)的時候,從第二個 slice 開始滋尉,使用 3 字節(jié)起始碼玉控。
NALU 是一個 NAL 單元,NALU = 字節(jié)的頭信息(NALU Header) + 原始字節(jié)序列載荷(RBSP)(實際為擴展字節(jié)序列載荷(EBSP)的字節(jié)流)

H264-Analyze-2020-06-07-11-19-46

NALU Header

NALU Header 由 3 部分組成狮惜,NALU Header = forbidden_bit(1bit) + nal_reference_bit(2bit) + nal_unit_type(5bit)高诺,共占用 1 個字節(jié)

  1. forbidden_bit 禁止位:編碼中默認值為 0,當網(wǎng)絡(luò)識別此單元中存在比特錯誤時碾篡,可將其設(shè)為 1虱而,以便接收方丟掉該單元,主要用于適應(yīng)不同種類的網(wǎng)絡(luò)環(huán)境
  2. nal_reference_bit 重要性指示位:用于在重構(gòu)過程中標記一個 NAL 單元的重要性开泽,值越大牡拇,越重要。尤其是當前 NALU 為圖像參數(shù)集穆律、序列參數(shù)集或 IDR 圖像時惠呼,或者為參考圖像條帶(片/Slice),或者為參考圖像的條帶數(shù)據(jù)分割時峦耘,nal_ref_idc 值肯定不為 0剔蹋。
  3. nal_unit_type:NALU 類型位: 可以表示 NALU 的 32 種不同類型特征,類型 1 ~ 12 是 H.264 定義的辅髓,類型 24 ~ 31 是用于 H.264 以外的泣崩,RTP 負荷規(guī)范使用這其中的一些值來定義包聚合和分裂,其他值為 H.264 保留洛口。
H264-Analyze-2020-06-07-11-31-18

判斷起始碼后的第一個字節(jié)的后 5bit 就可以判斷出這個 NALU 的類型矫付,例如:00 00 00 01 67: 0x67&0x1f = 0x07 :SPS。另外說下重要的類型:

  • al_unit_type=5:表示當前 NAL 是 IDR 圖像的一個片第焰,在這種情況下买优,IDR 圖像中的每個片的 nal_unit_type 都應(yīng)該等于 5。注意挺举,IDR 圖像不能使用分區(qū)杀赢。
  • nal_unit_type=7:SPS,包括一個圖像序列的所有信息豹悬,即兩個IDR圖像間的所有圖像信息葵陵,如標識符 seq_parameter_set_id、幀數(shù)及 POC 的約束瞻佛、參考幀數(shù)目脱篙、解碼圖像尺寸和幀場編碼模式選擇標識等等娇钱。
  • nal_unit_type=8:PPS,PPS對應(yīng)的是一個序列中某一幅圖像或者某幾幅圖像绊困,包括一個圖像的所有slice的所有相關(guān)信息文搂,如圖像類型、序列號秤朗、標識符 pic_parameter_set_id煤蹭、可選的 seq_parameter_set_id、熵編碼模式選擇標識取视、片組數(shù)目硝皂、初始量化參數(shù)和去方塊濾波系數(shù)調(diào)整標識等等,解碼時某些序列號的丟失可用來校驗信息包的丟失與否作谭。

RBSP

NALU 的主體部分需要先介紹下面 3 個名詞稽物。

  1. SODB: String Of Data Bits 原始數(shù)據(jù)比特流,編碼后的原始數(shù)據(jù)折欠,即VCL數(shù)據(jù)贝或。
  2. RBSP: 原始字節(jié)序列載荷。由于 SODB 長度不一定是 8 的倍數(shù)锐秦,為了字節(jié)對齊咪奖,在 SODB 的后面填加了結(jié)尾比特(rbsp_trailing_bits)就得到了 RBSP。rbsp_trailing_bits = rbsp_stop_one_bit(值為 1酱床,1bit)+ rbsp_alignment_zero_bit(若干個 0羊赵,字節(jié)對齊填充)
  3. EBSP: 擴展字節(jié)序列載荷。NALU 之間是通過 StartCode 來隔開的斤葱,如果編碼后的原始數(shù)據(jù)含有 0x000001慷垮,就無法知道這個到底是不是 StartCode揖闸,因此為了使 NALU 主體中不包括與開始碼相沖突的數(shù)據(jù)揍堕,在 RBSP 數(shù)據(jù)中每遇到兩個字節(jié)連續(xù)為 0,就插入一個字節(jié)的 0x03汤纸,這樣就得到了 EBSP衩茸。

所以,SODB + 添加尾部字節(jié)對齊 ==> RBSP + 碰到 0x0000 插入 0x03 ==> EBSP贮泞。
VCL層是對核心算法引擎楞慈、塊、宏塊及片的語法級別的定義啃擦,最終輸出壓縮編碼后的數(shù)據(jù) SODB囊蓝。VCL數(shù)據(jù)在傳輸或存儲之前,先被映射或封裝進NAL單元中令蛉。NAL層將SODB打包成RBSP然后加上NALU Header聚霜,組成一個NALU狡恬。

H264-Analyze-2020-06-07-12-32-45

相應(yīng)的 RBSP 數(shù)據(jù)類型描述如下:

H264-Analyze-2020-06-07-12-57-26

視頻幀

從宏觀上來說,SPS蝎宇、PPS弟劲、IDR 幀(包含一個或多個 I-Slice)、P 幀(包含一個或多個 P-Slice )姥芥、B 幀(包含一個或多個 B-Slice )組成的視頻序列構(gòu)成了 H264 的碼流兔乞。
包含具體視頻數(shù)據(jù)的幀有:

類型 意義
I 幀 I 幀通常是每個 GOP(MPEG 所使用的一種視頻壓縮技術(shù))的第一個幀,經(jīng)過適度地壓縮凉唐,做為隨機訪問的參考點庸追,可以當成圖象。
P 幀 通過充分將低于圖像序列中前面已編碼幀的時間冗余信息來壓縮傳輸數(shù)據(jù)量的編碼圖像台囱,也叫預(yù)測幀
B 幀 既考慮與源圖像序列前面已編碼幀锚国,也顧及源圖像序列后面已編碼幀之間的時間冗余信息來壓縮傳輸數(shù)據(jù)量的編碼圖像,也叫雙向預(yù)測幀

一個視頻序列的第一幀又叫 IDR 幀。IDR 幀的作用是立刻刷新玄坦,使錯誤不致傳播血筑,從 IDR 幀開始,重新算一個新的序列開始編碼煎楣。
I 幀可以不依賴其他幀就解碼出一幅完整的圖像豺总,而 P 幀、B 幀不行择懂。P 幀需要依賴視頻流中排在它前面的幀才能解碼出圖像喻喳,B 幀則需要依賴視頻流中排在它前面或后面的幀才能解碼出圖像。在視頻流中困曙,先到來的 B 幀無法立即解碼表伦,需要等待它依賴的后面的 P 幀先解碼,播放時間與解碼時間不一致慷丽,這時就需要DTS和PTS了蹦哼。

  • DTS(Decoding Time Stamp):解碼時間戳,這個時間戳的意義在于告訴播放器該在什么時候解碼這一幀的數(shù)據(jù)要糊。
  • PTS(Presentation Time Stamp):顯示時間戳纲熏,這個時間戳用來告訴播放器該在什么時候顯示這一幀的數(shù)據(jù)。

在沒有B幀的時候锄俄,DTS局劲、PTS順序是一致的,比如直播等延遲要求小的使用場景奶赠。音頻也有PTS鱼填、DTS,但是音頻沒有類似視頻的 B 幀毅戈,不需要雙向預(yù)測苹丸,所以音頻幀的 DTS塑猖、PTS 順序是一致的。

H264-Analyze-2020-06-07-17-28-25

slice

幀圖像可編碼成一個或者多個片(slice)谈跛,分片的目的是為了限制誤碼的擴散和傳輸羊苟,使編碼片相互間保持獨立。
每一個 slice 總體來看都由兩部分組成感憾,一部分作為 slice header蜡励,用于保存 slice 的總體信息(如當前 slice 的類型等),另一部分為 slice body阻桅,通常是一組連續(xù)的宏塊結(jié)構(gòu)凉倚。

H264-Analyze-2020-06-07-17-07-27

slice 的類型如下:

類型 意義
I slice 只包含 I 宏塊
P slice 包含 P 和 I 宏塊
B slice 包含 B 和 I 宏塊
SP slice 包含 P 或 I 宏塊,用于不同碼流之間的切換
SI slice 一種特殊類型的編碼宏塊

宏塊

宏塊是視頻信息的主要承載者。一個編碼圖像通常劃分為多個宏塊組成.包含著每一個像素的亮度和色度信息嫂沉。視頻解碼最主要的工作則是提供高效的方式從碼流中獲得宏塊中像素陣列稽寒。
一個宏塊由一個 16×16 亮度像素和附加的一個 8×8 Cb 和一個 8×8 Cr 彩色像素塊組成。宏塊分類如下:

類型 意義
I 宏塊 利用從當前片中已解碼的像素作為參考進行幀內(nèi)預(yù)測
P 宏塊 利用前面已編碼圖像作為參考進行幀內(nèi)預(yù)測
B 宏塊 利用雙向的參考圖像(當前和未來的已編碼圖像幀)進行幀內(nèi)預(yù)測
H264-Analyze-2020-06-07-17-19-09

H264 解析流程

拿到 RBSP 或 SODB 之后趟章,根據(jù)對應(yīng)的 NALU Header 類型來解析編碼的數(shù)據(jù)杏糙,解析流程如下:

H264-Analyze-2020-06-07-16-24-28

使用 ffmpeg 的話一般是不會自己去解析的,因為數(shù)據(jù)都在對應(yīng)的結(jié)構(gòu)體變量中蚓土,比如判斷一幀是否是 I 幀:

if (m_pDstPacket->flags & AV_PKT_FLAG_KEY) {
    qDebug("encode a key frame");
}

參考鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谅河,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子确丢,更是在濱河造成了極大的恐慌绷耍,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲜侥,死亡現(xiàn)場離奇詭異褂始,居然都是意外死亡,警方通過查閱死者的電腦和手機剃毒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門病袄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搂赋,“玉大人赘阀,你說我怎么就攤上這事∧缘欤” “怎么了基公?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宋欺。 經(jīng)常有香客問我轰豆,道長胰伍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任酸休,我火速辦了婚禮骂租,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斑司。我一直安慰自己渗饮,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布宿刮。 她就那樣靜靜地躺著互站,像睡著了一般。 火紅的嫁衣襯著肌膚如雪僵缺。 梳的紋絲不亂的頭發(fā)上胡桃,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音磕潮,去河邊找鬼翠胰。 笑死,一個胖子當著我的面吹牛自脯,可吹牛的內(nèi)容都是我干的亡容。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼冤今,長吁一口氣:“原來是場噩夢啊……” “哼闺兢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起戏罢,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤屋谭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后龟糕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桐磁,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年讲岁,在試婚紗的時候發(fā)現(xiàn)自己被綠了我擂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡缓艳,死狀恐怖校摩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阶淘,我是刑警寧澤衙吩,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站溪窒,受9級特大地震影響坤塞,放射性物質(zhì)發(fā)生泄漏冯勉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一摹芙、第九天 我趴在偏房一處隱蔽的房頂上張望灼狰。 院中可真熱鬧,春花似錦浮禾、人聲如沸伏嗜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽承绸。三九已至,卻和暖如春挣轨,著一層夾襖步出監(jiān)牢的瞬間军熏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工卷扮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荡澎,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓晤锹,卻偏偏與公主長得像摩幔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鞭铆,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345