FFmpeg從入門到出家(HEVC在RTMP中的擴展)

由金山雲視頻雲技術團隊提供:FFmpeg從入門到出家第三季;

爲推進HEVC視頻編碼格式在直播方案中的落地,經過CDN聯盟討論,並和主流雲服務廠商達成一致,規範了HEVC在RTMP/FLV中的擴展,具體修改內容見下。

4.1 FLV規範擴展

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

4.1.1 支持HEVC的VideoTagHeader

擴展後的VideoTagHeader如下圖所示(紅色字體爲新增內容):
FFmpeg從入門到出家(HEVC在RTMP中的擴展)
圖9. 支持HEVC的FLVTagHeader

修改點如下:

  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;

  1. CompositionTime - 當CodecID == 12時,同樣需要CompositionTime。

4.1.2 支持HEVC的VideoTagBody

當CodecID爲12時,VideoTagBody中存放的就是HEVC視頻幀內容。

擴展後的VideoTagBody如下圖所示(紅色字體爲HEVC新增內容):
FFmpeg從入門到出家(HEVC在RTMP中的擴展)
圖10. 支持HEVC的VideoTagBody

4.2 FFmpeg中的修改

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

由第二章節的闡述可知,FLV的解複用和複用功能代碼分別在libavformt/flvdec.c和libavformat/flvenc.c中,擴展後的修改也都集中在這兩個文件。本節將在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方能正確解碼。

HEVC與AVC視頻幀在FLV中的存放格式相同,所以只需在讀取Video Tag的地方增加AV_CODEC_ID_HEVC的判斷條件即可,調整後的代碼如下:

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;

   ……

}

}

AVCDecoderConfigurationRecord和HEVCDecoderConfigurationRecord都是存放在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轉爲HEVCDecoderConfigurationRecord結構並寫入。

4.2.3.2 write packet

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

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視頻幀轉爲HVCC格式。

AnnexB與AVCC/HVCC(ISO/IEC14496-15中所定義,通常也稱爲MPEG-4格式)的區別在於參數集與幀格式,AnnexB的參數集sps、pps以NAL的形式存在碼流中(帶內傳輸),以startcode分割NAL。而HVCC 的參數集存儲在extradata中(帶外傳輸),使用NALU長度(固定字節,通常爲4字節,從extradata中解析)分隔NAL。

4.2.3.3 write trailer

結束時需要寫入HEVC end of sequence,其格式與AVC end of sequence相同,直接複用即可,flv_write_trailer的修改內容如下:

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);

  1. 結束語

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

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

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