FFmpeg代碼導讀系列(一,下半部)----HEVC在RTMP中的擴展

原文首發(fā)在LiveVideoStack,請從原文轉(zhuǎn)載,本文不接受再次轉(zhuǎn)載!

閱讀本文前,請先閱讀上半部分:FFmpeg代碼導讀系列(一)----基礎篇

金山云多媒體SDK團隊在移動直播、短視頻等項目中遇到了許多FFmpeg問題,特設立《FFmpeg從入門到出家》系列文稿,希望博君一笑的同時,能讓大家對FFmpeg有更深入的了解。

先在前面

FFmpeg社區(qū)曾經(jīng)有過一次針對RTMP擴展HEVC的討論,最終達成的結(jié)論是如果Adobe擴展了RTMP,那么FFmpeg才可以接受HEVC擴展?,F(xiàn)狀是Adobe更新RTMP協(xié)議遙遙無期。

隨著iOS 11HEVC的支持,還有Android芯片對H.265的支持。在移動平臺支持場景下,使用更高效率的視頻編碼,在相同帶寬負載情況下,能提供更優(yōu)質(zhì)的直播體驗??梢蚤喿x《基于iOS11的HEVC(H.265)硬編碼/硬解碼功能開發(fā)指南》了解iOS對HEVC的支持情況。

如果你也認可這個思路,那么你可以參考本文,在基于RTMP協(xié)議推流或者拉流中,體驗H.265/HEVC帶來的用戶體驗提升!

4. HEVC在RTMP中的擴展

為推進HEVC視頻編碼格式在直播方案中的落地,經(jīng)過CDN聯(lián)盟討論,并和主流云服務廠商達成一致,規(guī)范了HEVCRTMP/FLV中的擴展,具體修改內(nèi)容見下。

4.1 FLV規(guī)范擴展

HEVC為視頻編碼格式,因此對FLV規(guī)范的擴展,只集中在Video Tag,其它部分,無任何改動。

4.1.1 支持HEVC的VideoTagHeader

擴展后的VideoTagHeader如下圖所示(紅色字體為新增內(nèi)容):

支持HEVC的FLVTagHeader.png

修改點如下:

  1. CodecID - 定義HEVC格式的值為12;

  2. HEVCPacketType - 當CodecID == 12時,AVCPacketType為HEVCPacketType:

    • 如果HEVCPacketType為0,表示HEVCVIDEOPACKET中存放的是HEVC sequence header;
    • 如果HEVCPacketType為1,表示HEVCVIDEOPACKET中存放的是HEVC NALU;
    • 如果HEVCPacketType為2,表示HEVCVIDEPACKET中存放的是HEVC end of sequence,即HEVCDecoderConfigurationRecord;
  3. CompositionTime - 當CodecID == 12時,同樣需要CompositionTime

4.1.2 支持HEVC的VideoTagBody

當CodecID為12時,VideoTagBody中存放的就是HEVC視頻幀內(nèi)容。

擴展后的VideoTagBody如下圖所示(紅色字體為HEVC新增內(nèi)容):

支持HEVC的VideoTagBody.png

4.2 FFmpeg中的修改

我們已在FFmpeg的各個版本上提供相關的完整修改,具體參見:https://github.com/ksvc/FFmpeg, 完整patch獲取及相關說明見:https://github.com/ksvc/FFmpeg/wiki。

由第二章節(jié)的闡述可知,F(xiàn)LV的解復用和復用功能代碼分別在libavformt/flvdec.clibavformat/flvenc.c中,擴展后的修改也都集中在這兩個文件。本節(jié)將在FFmpeg3.3的基礎上,說明修改的關鍵點。

4.2.1 編碼類型定義

libavformat/flv.h中按照VideoTagHeader中的CodecID定義了一組視頻編碼格式的枚舉值,擴展后的枚舉定義如下:

enum {
    FLV_CODECID_H263    = 2,
    FLV_CODECID_SCREEN  = 3,
    FLV_CODECID_VP6     = 4,
    FLV_CODECID_VP6A    = 5,
    FLV_CODECID_SCREEN2 = 6,
    FLV_CODECID_H264    = 7,
    FLV_CODECID_REALH263= 8,
    FLV_CODECID_MPEG4   = 9,
    FLV_CODECID_HEVC   = 12,
};

4.2.2 FLV demux

在解復用過程中,flv_read_packet方法是整個過程的核心,它里面完成了對每個Tag的讀取和解析。

4.1.1中提到,當如果HEVCPacketType為0時,表示HEVCVIDEOPACKET中存放的是HEVC sequence header,也就是HEVCDecoderConfigurationRecord,解碼時需設置HEVCDecoderConfigurationRecord方能正確解碼。

HEVCAVC視頻幀在FLV中的存放格式相同,所以只需在讀取Video Tag的地方增加AV_CODEC_ID_HEVC的判斷條件即可,調(diào)整后的代碼如下

if (st->codecpar->codec_id == AV_CODEC_ID_AAC ||
    st->codecpar->codec_id == AV_CODEC_ID_H264 ||
    st->codecpar->codec_id == AV_CODEC_ID_HEVC ||
    st->codecpar->codec_id == AV_CODEC_ID_MPEG4) {
    int type = avio_r8(s->pb);
    size--;
    if (st->codecpar->codec_id == AV_CODEC_ID_H264 ||
         st->codecpar->codec_id == AV_CODEC_ID_HEVC ||
         st->codecpar->codec_id == AV_CODEC_ID_MPEG4) {
         // sign extension
         int32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000;
         pts = dts + cts;
         if (cts < 0) { // dts might be wrong
             if (!flv->wrong_dts)
                av_log(s, AV_LOG_WARNING,
                        "Negative cts, previous timestamps might be wrong.\n");
            flv->wrong_dts = 1;
        } else if (FFABS(dts - pts) > 1000*60*15) {
           av_log(s, AV_LOG_WARNING,
                       "invalid timestamps %"PRId64" %"PRId64"\n", dts, pts);
           dts = pts = AV_NOPTS_VALUE;
        }
   }

   if (type == 0 && (!st->codecpar->extradata ||
       st->codecpar->codec_id == AV_CODEC_ID_AAC ||
       st->codecpar->codec_id == AV_CODEC_ID_HEVC ||
       st->codecpar->codec_id == AV_CODEC_ID_H264)) {
       AVDictionaryEntry *t;
       if (st->codecpar->extradata) {
           if ((ret = flv_queue_extradata(flv, s->pb, stream_type, size)) < 0)
               return ret;
           ret = FFERROR_REDO;
           goto leave;
       }
       if ((ret = flv_get_extradata(s, st, size)) < 0)
           return ret;
       ……
   }
}

AVCDecoderConfigurationRecordHEVCDecoderConfigurationRecord都是存放在AVStream->AVCodecParameter->extradata中。

4.2.3 FLV mux

FLV mux的修改相對較多、header、packet、trailer中均有涉及。

4.2.3.1 write header

flv_write_header中主要完成了以下工作:

  1. 寫入FLV Header;
  2. 寫入Metadata;
  3. 如果音頻編碼格式為AAC,則寫入第一個Audio Tag,其AudioTagBody中存放的是AAC sequence header;
  4. 如果視頻編碼格式為AVC,則寫入第一個Video Tag,其中VideoTagBody中存放的是AVC sequence header。

同樣,當視頻編碼格式HEVC時,也要寫入第一個VideoTag,其中VideoTagBody中存放的是HEVCDecoderConfigurationRecord,修改點如下

avio_w8(pb, par->codec_tag | FLV_FRAME_KEY); // flags
avio_w8(pb, 0); // AVC sequence header
avio_wb24(pb, 0); // composition time

if (par->codec_id == AV_CODEC_ID_HEVC)
       ff_isom_write_hvcc(pb, par->extradata, par->extradata_size, 0);
else
       ff_isom_write_avcc(pb, par->extradata, par->extradata_size);

ff_isom_write_hvcc的作用是將extradata轉(zhuǎn)為HEVCDecoderConfigurationRecord結(jié)構(gòu)并寫入。

4.2.3.2 write packet

flv_write_packet的作用是寫入音視頻幀,其中有關寫入video數(shù)據(jù)的地方,都需要加上AV_CODEC_ID_HEVC的判斷條件,修改內(nèi)容如下:

    else if (par->codec_id == AV_CODEC_ID_HEVC ){
        if (par->extradata_size > 0 && *(uint8_t*)par->extradata != 1)
            if ((ret = ff_hevc_annexb2mp4_buf(pkt->data, &data, &size, 0, NULL)) < 0)
                return ret;
    }

ff_hevc_annexb2mp4_buf方法的作用是將Annex-B格式的HEVC視頻幀轉(zhuǎn)為HVCC格式。

AnnexB與AVCC/HVCC的區(qū)別在于參數(shù)集與幀格式,AnnexB的參數(shù)集sps、pps以NAL的形式存在碼流中(帶內(nèi)傳輸),以startcode分割NAL。而HVCC 的參數(shù)集存儲在extradata中(帶外傳輸),使用NALU長度(固定字節(jié),通常為4字節(jié),從extradata中解析)分隔NAL。

4.2.3.3 write trailer

結(jié)束時需要寫入HEVC end of sequence,其格式與AVC end of sequence相同,直接復用即可,flv_write_trailer的修改內(nèi)容如下:

 if (par->codec_type == AVMEDIA_TYPE_VIDEO &&
           (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_HEVC || par->codec_id == AV_CODEC_ID_MPEG4))
                put_avc_eos_tag(pb, sc->last_ts);

5. 結(jié)束語

本文只是簡單介紹了如何在FFmpeg中擴展rtmp協(xié)議對HEVC編碼格式的支持,而要將HEVC應用于直播整體方案,除推流端和播放端要提供相應能力外,源站、CDN、轉(zhuǎn)碼服務同樣都需要提供這種能力。金山云直播整體方案,已完全支持HEVC視頻編碼格式,歡迎大家使用。

客戶端的直播/短視頻SDK,在適配了系統(tǒng)的HEVC硬編/解碼能力外,還提供了高效的HEVC軟編/解碼方案,如果您感興趣,可聯(lián)系我們咨詢或討論。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 原文首發(fā)在LiveVideoStack,請從原文轉(zhuǎn)載,本文不接受再次轉(zhuǎn)載。 從事音視頻技術開發(fā)對FFmpeg都不會...
    金山視頻云閱讀 5,524評論 2 27
  • 轉(zhuǎn)發(fā)自 2016-10-27李智文騰訊Bugly 概要 分享內(nèi)容: 互聯(lián)網(wǎng)內(nèi)容載體變遷歷程,文字——圖片/聲音——...
    樹懶啊樹懶閱讀 12,363評論 3 122
  • 小M,今天外拍的模特。上個星期我們約好了今天去中大外拍。由于我是個攝影小白,我怕自己拍不好,擔心我的模特失望,我決...
    喵小千閱讀 504評論 3 6
  • 最羨慕嬰兒的睡眠。更佩服嬰兒的睡姿。只有我想不到的,沒有寶寶做不到的…… 可愛的寶寶,愿你一世都有如此酣暢的睡眠!
    蘆葦7閱讀 505評論 0 0
  • 愿我們都有一顆名導的心 《比利林恩的中場故事》--喬 啊爾文 《少年派》--蘇拉·沙瑪 《喜宴》趙文瑄 《色戒》湯...
    高碧碧閱讀 264評論 0 2

友情鏈接更多精彩內(nèi)容