1. rtmp/flv封裝視頻方式
準確的說,RTMP是傳輸協議,傳輸協議內部的封裝是flv格式,其實我們所說的支持H.265,是在flv封裝格式里面支持H.265編碼數據。
flv對視頻的封裝格式, 原有VideoTagHeader定義如下:
------------------------------------------------------------------------------------ | FrameType(4bits) | CodecID(4bits) | AVCPacketType(8bits)| CompositionTime(24bits)| ------------------------------------------------------------------------------------
其中:
FrameType: 4個bits, 1: keyframe, 也就是I幀; 2: inter frame, 非I幀,B幀或P幀;
CodecID: 4個bits,
1:JPEG (currently unused);
2:Sorenson H.263;
3:Screen video;
4:On2 VP6;
5:On2 VP6 with alpha channel;
6:Screen video version 2;
7:AVC;
這里如果是H.264,就是7。
AVCPacketType: 8個bits,也就是一個字節,0: AVC sequence header; 1: AVC NALU
CompositionTime: 3個字節(24bits),表示pts與dts的差值;
舉例:
如果視頻數據是H.264的sequence header(也就是包含sps/pps的Avcc Header),就應該是0x17 00;
如果視頻數據是H.264的Iframe,就應該是0x17 01;
如果視頻數據是H.264的非Iframe,就應該是0x27 01
flv的標準中,只設定了H.264的codecId為7,之后的flv標準就沒在針對video的codecId進行增加,這也就是導致后面rtmp/flv沒有支持H.265的標準。
1.1. 國內rtmp/flv對H.265的支持
隨著國內前10年移動互聯網對直播需求的增加,對高清畫質的需求與日俱增,支持H.265直播的需求很早就在各家CDN和云廠商成為top需求。
因此,國內云廠商和CDN廠商對H.265很早就支持,支持的方式比較簡單,就是自定義H.265的CodecID=0xC,也就是CodecID值為12。
1: JPEG (currently unused); 2: Sorenson H.263; 3: Screen video; 4: On2 VP6; 5: On2 VP6 with alpha channel; 6: Screen video version 2; 7: AVC; 12: H.265(國內自定義H265的CodecID);
這樣的自定義的好處:迅速解決了國內統一rtmp/flv支持H.265的格式標準;國內的CDN廠家的服務都遵循CodecID=12來實現rtmp/flv直播服務。
舉例:
如果視頻數據是H.265的sequence header,就應該是0x1c 00;
如果視頻數據是H.265的Iframe,就應該是0x1c 01;
如果視頻數據是H.265的非Iframe,就應該是0x2c 01
國內的多個開源也都遵循國內的H.265標準:
SRS
media-server(git@github.com:ireader/media-server.git)
ffmpeg_rtmp_h265 自定義補丁
但是,同樣有其局限性:CodecID是自定義的,并且對CodecID只有4個bits的局限性沒能解決,后面對新增的編碼方式無法適用,如新增VP8,VP9, AV1,或未來的H.266,擴展會很難。
國外為準的流媒體開源,并未支持CodecId=12為H.265,如:
ffmpeg: 未支持CodecId=12為H.265;
obs: 未支持CodecId=12為H.265;
2. Enhanced-RTMP
Enhanced-Rtmp公布支持H.265的標準,徹底解決在rtmp/flv支持H.265的編碼。
2.1 Enhanced-Rtmp規范
Enhanced-Rtmp規范
flv對視頻的封裝格式, 原有VideoTagHeader定義如下:
------------------------------------------------------------------------------------ | FrameType(4bits) | CodecID(4bits) | AVCPacketType(8bits)| CompositionTime(24bits)| ------------------------------------------------------------------------------------
而Enhanced-RTMP對上面的格式進行修改:
首先FrameType第一個bit變為IsExHeader,如下
------------------------------------- | IsExHeader(1bit)FrameType(3bits) | -------------------------------------
也就是在原FrameType的最高位加了1bit的IsExHeader標志位,如果IsExHeader使能,表示Enhanced-RTMP格式使能,后面的定義是Enhanced-Rtmp格式;否則還遵循之前的rtmp/flv傳統規范。
格式的具體邏輯如下,UB代表bit的站位符(舉例: UB[4]表示站位4bits)
IsExHeader = (UB[4] & 0b1000 != 0) ? true : false; FrameType = UB[4] & 0b0111; //1 = key frame, 2 = inter frame if (IsExHeader == 0) { //如果IsExHeader未使能, 還遵循之前的rtmp/flv傳統規范 CodecId = UB[4];//4bits的codecId,H.264的值為7. AVCPacketType = UB[8];//8bits的AVCPacketType, 0: sequence header; 1: NALU CompositionTime = UB[24];//24bits的CompositionTime,表示pts與dts的差值 DATA = [H264 NALU]; //后續數據為常規的視頻數據 } else // IsExHeader使能,表示Enhanced-Rtmp格式使能 { PacketType = UB[4];// 0 = PacketTypeSequenceStart // 1 = PacketTypeCodedFrames // 2 = PacketTypeSequenceEnd // 3 = PacketTypeCodedFramesX // 4 = PacketTypeMetadata // 5 = PacketTypeMPEG2TSSequenceStart FourCC = UB[32];// 4字節的FourCC,如下字符表示對應的視頻CodecId // AV1 = { 'a', 'v', '0', '1' } // VP9 = { 'v', 'p', '0', '9' } // HEVC = { 'h', 'v', 'c', '1' }, 也就是h265 // 如果類型是HEVC, 也就是H265,后續規范如下 if (FourCC == HEVC) { if (PacketType == PacketTypeSequenceStart) { //如果PacketType是PacketTypeSequenceStart,表示后續H265的數據內容是DecoderConfigurationRecord,也就是常說的sequence header; DATA = [HEVCDecoderConfigurationRecord] } else if (PacketType == PacketTypeCodedFrames || PacketType == PacketTypeCodedFramesX) { if (PacketType == PacketTypeCodedFrames) { //如果PacketType是PacketTypeCodedFrames,就是pts與dts有差值 CompositionTime = UB[24];//24bits,表示pts與dts的差值 } else //如果PacketType是PacketTypeCodedFramesX { //無CompositionTime,節省3字節的空間 } //隨后是正常的H265數據 DATA = [HEVC NALU] } } }
2.2 ffmpeg6.1實現Enhance RTMP
ffmpeg在version6.1中正式支持Enhance RTMP,以此支持H.265 in rtmp/flv。這里介紹其對應的實現。
首先下載對應的ffmpeg6.1版本源碼:
git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg cd ffmpeg git fetch origin release/6.1 git checkout release/6.1
flv封裝的實現,對應文件: libavformat/flvenc.c
static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) { //...... // 如果編碼格式是H265, 使用如下的實現 if (par->codec_id == AV_CODEC_ID_HEVC) { // 如果報文pts和dts不一樣,packettype為PacketTypeCodedFrames; // 否則packettype為PacketTypeCodedFramesX int pkttype = (pkt->pts != pkt->dts) ? PacketTypeCodedFrames : PacketTypeCodedFramesX; // 第一個字節,最高bit位寫入FLV_IS_EX_HEADER; // 第一個字節,最高的第3, 4bits寫入FLV_FRAME_KEY或FLV_FRAME_INTER // 第一個字節,最低2bits位,寫入PacketTypeCodedFramesX或PacketTypeCodedFrames avio_w8(pb, FLV_IS_EX_HEADER | pkttype | frametype); // ExVideoTagHeader mode with PacketTypeCodedFrames(X) // 后4個字節,寫入FourCC,寫入4個字符: "hvc1" avio_write(pb, "hvc1", 4); // pkttype為PacketTypeCodedFrames,寫入3個字節的pts與dts的差值 if (pkttype == PacketTypeCodedFrames) avio_wb24(pb, pkt->pts - pkt->dts); } // 寫入H.264的數據 avio_write(pb, pkt->data, pkt->size); // ...... }
2.3 OBS實現Enhance RTMP
OBS在Release 29版本正式支持Enhance RTMP,實現H.265 in rtmp/flv的直播推流。
獲取版本:
git clone git@github.com:obsproject/obs-studio.git cd obs-studio git fetch origin release/29.1 git checkout release/29.1
具體實現在: plugins/obs-outputs/rtmp-stream.c
enum packet_type_t { PACKETTYPE_SEQ_START = 0, //表示報文序列開始 PACKETTYPE_FRAMES = 1, //表示該幀dts與pts有差值 PACKETTYPE_SEQ_END = 2, //flv文件最后一幀 #ifdef ENABLE_HEVC PACKETTYPE_FRAMESX = 3, //表示該幀dts == pts #endif PACKETTYPE_METADATA = 4 }; //函數flv_packet_ex的最后一個參數type為packet_type_t的取值范圍 void flv_packet_ex(struct encoder_packet *packet, enum video_id_t codec_id, int32_t dts_offset, uint8_t **output, size_t *size, int type) { // ........ // packet ext header // 第一個字節,最高bit位寫入1, FRAME_HEADER_EX = 8 << 4; // 第一個字節,最高的第3, 4bits寫入FLV_FRAME_KEY或FLV_FRAME_INTER // 第一個字節,最低2bits位,寫入PacketTypeCodedFramesX或PacketTypeCodedFrames, dts與pts相等或不等; // 或寫入PACKETTYPE_SEQ_START,或PACKETTYPE_SEQ_END s_w8(&s, FRAME_HEADER_EX | type | (packet->keyframe ? FT_KEY : FT_INTER)); // 后4個字節,寫入FourCC,寫入4個字符: "hvc1" s_w4cc(&s, codec_id); #ifdef ENABLE_HEVC // hevc composition time offset if (codec_id == CODEC_HEVC && type == PACKETTYPE_FRAMES) { // PacketType為PACKETTYPE_FRAMES,寫入3個字節的pts與dts的差值 s_wb24(&s, get_ms_time(packet, packet->pts - packet->dts)); } #endif // 寫入h265的幀數據 s_write(&s, packet->data, packet->size); }
2.4 SRS實現Enhance RTMP
SRS是國內最流行的流媒體服務器,支持多種直播協議RTMP, httpflv, HLS,支持WebRTC,也支持安放協議28181。SRS也率先支持Enhance RTMP,服務端能夠接受Enhance RTMP的推流,同時也兼容國內CodecId=12的H.265的rtmp方案。
但是在rtmp或httpflv拉流方向,是應用國內CodecId=12的H.265的rtmp方案。
SRS的github地址: https://github.com/ossrs/srs.git, 當前支持分支develop。
實現主要在srs_kernel_codec.cpp這個文件中。
srs_error_t SrsFormat::video_avc_demux(SrsBuffer* stream, int64_t timestamp) { uint8_t frame_type = stream->read_1bytes(); bool is_ext_header = frame_type & 0x80; //判斷Rtmp Enhance是否支持,從而獲取到編碼類型 if (!is_ext_header) { // 如果不是Rtmp Enhance,用傳統的方式判斷 codec_id = (SrsVideoCodecId)(frame_type & 0x0f); frame_type = (frame_type >> 4) & 0x0f; } else { // 如果使能Rtmp Enhance,獲取packet_type和frame_type packet_type = (SrsVideoAvcFrameTrait)(frame_type & 0x0f); frame_type = (frame_type >> 4) & 0x07; // 讀取4個字節的FourCC,判斷是否是HEVC. uint32_t four_cc = stream->read_4bytes(); if (four_cc == 0x68766331) { // 'hvc1'=0x68766331 codec_id = SrsVideoCodecIdHEVC; } } //判斷Rtmp Enhance是否支持,從而獲取到composition_time(dts與pts的差值) if (!is_ext_header) { // 如果是傳統的Rtmp,讀取1個字節的packet_type,和3個字節的composition_time packet_type = (SrsVideoAvcFrameTrait)stream->read_1bytes(); composition_time = stream->read_3bytes(); } else { // 如果使能Rtmp Enhance,當packet_type==1的時候,dts才與pts不一致,才讀取3個字節的composition_time, // 否則沒有composition_time字段; if (packet_type == 1) { composition_time = stream->read_3bytes(); } } }
2.5 media-server實現Enhance RTMP
media-server是國內開源庫中實現音視頻格式和流媒體類型最全的開源之一,支持flv, mp4, mkv, hls, mpeg, rtmp, rtp, rtsp, sip等音視頻格式和流媒體協議,并且在各種封裝中支持的codec非常豐富,如H.264, H.265, AV1, H.266都有支持。其采用C語言開發,兼容性好,同時適合服務器和嵌入式的開發。
media-server的Flv模塊同時支持demuxer和muxer,對Enhance RTMP支持比較全,同時支持AV1, H.265, H.266。(居然還支持H.266,并且有H.266 annexb to mp4和H.266 mp4 to annexb的代碼)
Enhance RTMP的實現主要在flv-header.c這個文件中, 關鍵的對應代碼如下:
// demuxer int flv_video_tag_header_read(struct flv_video_tag_header_t* video, const uint8_t* buf, size_t len) { // 如果第一個bit是1,則Enhance Rtmp使能 if (len >= 5 && 0 != (buf[0] & 0x80)) { video->keyframe = (buf[0] & 0x70) >> 4; //獲取到是否keyframe video->avpacket = (buf[0] & 0x0F); //獲取packettype video->cts = 0; // default switch(FLV_VIDEO_FOURCC(buf[1], buf[2], buf[3], buf[4])) { case FLV_VIDEO_FOURCC_AV1: video->codecid = FLV_VIDEO_AV1; return 5; case FLV_VIDEO_FOURCC_HEVC: case FLV_VIDEO_FOURCC_VVC: { if (video->avpacket == 1) //如果packettype==1,則dts與pts的差值存在 { video->cts = ((uint32_t)buf[5] << 16) | ((uint32_t)buf[6] << 8) | buf[7]; } return 5; } } } //否則走傳統flv的解析流程 //.... } // muxer: 通過編譯宏控制 int flv_video_tag_header_write(const struct flv_video_tag_header_t* video, uint8_t* buf, size_t len) { #ifdef FLV_ENHANCE_RTMP buf[0] = 0x80 | (video->keyframe << 4) /*FrameType*/; buf[0] |= (0 == video->cts && FLV_AVPACKET == video->avpacket) ? FLV_PACKET_TYPE_CODED_FRAMES_X : video->avpacket; switch (video->codecid) { case FLV_VIDEO_AV1: SetFourCC(&buf[1], FLV_VIDEO_FOURCC_AV1); return 5; case FLV_VIDEO_H265: SetFourCC(&buf[1], FLV_VIDEO_FOURCC_HEVC); if (len >= 8 && FLV_AVPACKET == video->avpacket && video->cts != 0) { SetCTS(&buf[5], video->cts); return 8; } return 5; case FLV_VIDEO_H266: SetFourCC(&buf[1], FLV_VIDEO_FOURCC_VVC); if (len >= 8 && FLV_AVPACKET == video->avpacket && video->cts != 0) { SetCTS(&buf[5], video->cts); return 8; } return 5; default: break; // fallthrough } #endif //否則走傳統flv的muxer流程 }
3. 國內RTMP支持H.265與Enhance RTMP的兼容性問題
因為國內之前支持H.265的方案是CodecID=12,現在存在的兼容性其實有兩個方向:
推流方向(上行)
拉流方向(下行)
3.1 推流方向
推流方向的兼容性,主要取決于服務端的兼容性,也就是說服務端必須同時能支持:
國內CodecID=12的H.265方案
Enhance Rtmp的H.265方案
先說結論,上行推流方向的兼容性是沒有問題,只需要在服務端做好兼容性,同時支持國內外的兩種方案。
具體我們以SRS服務為例,只需要判斷第一bit位是否使能,就能得知后面應該走RTMP Enhance流程,還是走傳統的RTMP流程
bool is_ext_header = frame_type & 0x80; //判斷Rtmp Enhance是否支持,從而獲取到編碼類型 if (!is_ext_header) { // 如果不是Rtmp Enhance,用傳統的方式判斷 } else { // 如果使能Rtmp Enhance,獲取packet_type和frame_type // 如果packet_type == 1,獲取composition_time(3字節,dts與pts的差值) }
因為第一個bit位就能判斷后續的代碼處理流程,所以推流上行做兼容性是比較容易的;
上行解包后,形成數據結構的對象,將其傳到下行拉流處,因為視頻幀數據部分已經去掉flvTagHeader頭,所以該對象傳遞到下行處理。
以下為偽碼:
class SrsVideoFrame { public: SrsVideoAvcFrameType frame_type; // 幀類型: I/非I SrsVideoAvcFrameTrait avc_packet_type;// 是否sequenche header等類型 public: int nb_samples; // 幀個數, SrsSample samples[SrsMaxNbSamples];// 幀數組,每個SrsSample是一個視頻幀數據 };
SrsVideoFrame中的SrsSample數據,是去掉flvTagHeader的視頻幀數據,這樣傳遞到下行后,可以根據需要再次打包flvTagHeader,下行可以再次打包成傳統的CodecId=12的H.265格式,也可以打包成Enhance RTMP格式(因為下行的兼容性問題,不推薦下行打包成Enhance RTMP格式,下一節會說明原因)
3.2 拉流方向
拉流方向的兼容性,主要取決于客戶端的兼容性,也就是rtmp或http-flv拉流播放器端的兼容性。
因為之前國內的大多數已有的播放器,都支持CodecId=12的H.265方案,現存市場很多播放器是沒能支持Enhanche RTMP,也就是說即使服務端下行方向支持Enhance RTMP,把Enhance RTMP流推給客戶端,客戶端可能存在不能識別的情況。對于大量存量的rtmp/http-flv播放器(僅僅支持CodecId=12的H.265方案),向下的流推送只能繼續采用CodecId=12的H.265方案。
所以,當前國內大多數云服務商和CDN在下行方向,都僅僅支持CodecID=12的國內H.265方案。
總結
簡單一句話總結兼容性:上行推流國內外兩種推流都能兼容,但是下行僅僅提供CodecID=12的國內H.265方案支持。
審核編輯:劉清
-
AVC
+關注
關注
0文章
21瀏覽量
10996 -
rtmp協議
+關注
關注
0文章
2瀏覽量
1642
原文標題:詳解Enhanced-RTMP支持H.265
文章出處:【微信號:livevideostack,微信公眾號:LiveVideoStack】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論