FFmpeg代码导读系列(一,下半部)----HEVC在RTMP中的扩展
金山云多媒體SDK團隊在移動直播、短視頻等項目中遇到了許多FFmpeg問題,特設立《FFmpeg從入門到出家》系列文稿,希望博君一笑的同時,能讓大家對FFmpeg有更深入的了解。
先在前面
FFmpeg社區曾經有過一次針對RTMP擴展HEVC的討論,最終達成的結論是如果Adobe擴展了RTMP,那么FFmpeg才可以接受HEVC擴展。現狀是Adobe更新RTMP協議遙遙無期。
隨著iOS 11對HEVC的支持,還有Android芯片對H.265的支持。在移動平臺支持場景下,使用更高效率的視頻編碼,在相同帶寬負載情況下,能提供更優質的直播體驗。可以閱讀《基于iOS11的HEVC(H.265)硬編碼/硬解碼功能開發指南》了解iOS對HEVC的支持情況。
如果你也認可這個思路,那么你可以參考本文,在基于RTMP協議推流或者拉流中,體驗H.265/HEVC帶來的用戶體驗提升!
4. HEVC在RTMP中的擴展
為推進HEVC視頻編碼格式在直播方案中的落地,經過CDN聯盟討論,并和主流云服務廠商達成一致,規范了HEVC在RTMP/FLV中的擴展,具體修改內容見下。
4.1 FLV規范擴展
HEVC為視頻編碼格式,因此對FLV規范的擴展,只集中在Video Tag,其它部分,無任何改動。
4.1.1 支持HEVC的VideoTagHeader
擴展后的VideoTagHeader如下圖所示(紅色字體為新增內容):
支持HEVC的FLVTagHeader.png
修改點如下:
CodecID - 定義HEVC格式的值為12;
HEVCPacketType - 當CodecID == 12時,AVCPacketType為HEVCPacketType:
- 如果HEVCPacketType為0,表示HEVCVIDEOPACKET中存放的是HEVC sequence header;
- 如果HEVCPacketType為1,表示HEVCVIDEOPACKET中存放的是HEVC NALU;
- 如果HEVCPacketType為2,表示HEVCVIDEPACKET中存放的是HEVC end of sequence,即HEVCDecoderConfigurationRecord;
CompositionTime - 當CodecID == 12時,同樣需要CompositionTime
4.1.2 支持HEVC的VideoTagBody
當CodecID為12時,VideoTagBody中存放的就是HEVC視頻幀內容。
擴展后的VideoTagBody如下圖所示(紅色字體為HEVC新增內容):
支持HEVC的VideoTagBody.png
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 extensionint32_t cts = (avio_rb24(s->pb) + 0xff800000) ^ 0xff800000;pts = dts + cts;if (cts < 0) { // dts might be wrongif (!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中主要完成了以下工作:
同樣,當視頻編碼格式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 timeif (par->codec_id == AV_CODEC_ID_HEVC)ff_isom_write_hvcc(pb, par->extradata, par->extradata_size, 0); elseff_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的區別在于參數集與幀格式,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);
作者:金山視頻云
鏈接:https://www.jianshu.com/p/ac7d4037add7
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。
總結
以上是生活随笔為你收集整理的FFmpeg代码导读系列(一,下半部)----HEVC在RTMP中的扩展的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 去除git mergetool不产生*.
- 下一篇: HEVC概念缩写对照表