1. rtmp/flv封裝視頻方式
準(zhǔn)確的說(shuō),RTMP是傳輸協(xié)議,傳輸協(xié)議內(nèi)部的封裝是flv格式,其實(shí)我們所說(shuō)的支持H.265,是在flv封裝格式里面支持H.265編碼數(shù)據(jù)。
flv對(duì)視頻的封裝格式, 原有VideoTagHeader定義如下:
------------------------------------------------------------------------------------ | FrameType(4bits) | CodecID(4bits) | AVCPacketType(8bits)| CompositionTime(24bits)| ------------------------------------------------------------------------------------
其中:
FrameType: 4個(gè)bits, 1: keyframe, 也就是I幀; 2: inter frame, 非I幀,B幀或P幀;
CodecID: 4個(gè)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個(gè)bits,也就是一個(gè)字節(jié),0: AVC sequence header; 1: AVC NALU
CompositionTime: 3個(gè)字節(jié)(24bits),表示pts與dts的差值;
舉例:
如果視頻數(shù)據(jù)是H.264的sequence header(也就是包含sps/pps的Avcc Header),就應(yīng)該是0x17 00;
如果視頻數(shù)據(jù)是H.264的Iframe,就應(yīng)該是0x17 01;
如果視頻數(shù)據(jù)是H.264的非Iframe,就應(yīng)該是0x27 01
flv的標(biāo)準(zhǔn)中,只設(shè)定了H.264的codecId為7,之后的flv標(biāo)準(zhǔn)就沒(méi)在針對(duì)video的codecId進(jìn)行增加,這也就是導(dǎo)致后面rtmp/flv沒(méi)有支持H.265的標(biāo)準(zhǔn)。
1.1. 國(guó)內(nèi)rtmp/flv對(duì)H.265的支持
隨著國(guó)內(nèi)前10年移動(dòng)互聯(lián)網(wǎng)對(duì)直播需求的增加,對(duì)高清畫(huà)質(zhì)的需求與日俱增,支持H.265直播的需求很早就在各家CDN和云廠商成為top需求。
因此,國(guó)內(nèi)云廠商和CDN廠商對(duì)H.265很早就支持,支持的方式比較簡(jiǎn)單,就是自定義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(國(guó)內(nèi)自定義H265的CodecID);
這樣的自定義的好處:迅速解決了國(guó)內(nèi)統(tǒng)一rtmp/flv支持H.265的格式標(biāo)準(zhǔn);國(guó)內(nèi)的CDN廠家的服務(wù)都遵循CodecID=12來(lái)實(shí)現(xiàn)rtmp/flv直播服務(wù)。
舉例:
如果視頻數(shù)據(jù)是H.265的sequence header,就應(yīng)該是0x1c 00;
如果視頻數(shù)據(jù)是H.265的Iframe,就應(yīng)該是0x1c 01;
如果視頻數(shù)據(jù)是H.265的非Iframe,就應(yīng)該是0x2c 01
國(guó)內(nèi)的多個(gè)開(kāi)源也都遵循國(guó)內(nèi)的H.265標(biāo)準(zhǔn):
SRS
media-server(git@github.com:ireader/media-server.git)
ffmpeg_rtmp_h265 自定義補(bǔ)丁
但是,同樣有其局限性:CodecID是自定義的,并且對(duì)CodecID只有4個(gè)bits的局限性沒(méi)能解決,后面對(duì)新增的編碼方式無(wú)法適用,如新增VP8,VP9, AV1,或未來(lái)的H.266,擴(kuò)展會(huì)很難。
國(guó)外為準(zhǔn)的流媒體開(kāi)源,并未支持CodecId=12為H.265,如:
ffmpeg: 未支持CodecId=12為H.265;
obs: 未支持CodecId=12為H.265;
2. Enhanced-RTMP
Enhanced-Rtmp公布支持H.265的標(biāo)準(zhǔn),徹底解決在rtmp/flv支持H.265的編碼。
2.1 Enhanced-Rtmp規(guī)范
Enhanced-Rtmp規(guī)范
flv對(duì)視頻的封裝格式, 原有VideoTagHeader定義如下:
------------------------------------------------------------------------------------ | FrameType(4bits) | CodecID(4bits) | AVCPacketType(8bits)| CompositionTime(24bits)| ------------------------------------------------------------------------------------
而Enhanced-RTMP對(duì)上面的格式進(jìn)行修改:
首先FrameType第一個(gè)bit變?yōu)镮sExHeader,如下
------------------------------------- | IsExHeader(1bit)FrameType(3bits) | -------------------------------------
也就是在原FrameType的最高位加了1bit的IsExHeader標(biāo)志位,如果IsExHeader使能,表示Enhanced-RTMP格式使能,后面的定義是Enhanced-Rtmp格式;否則還遵循之前的rtmp/flv傳統(tǒng)規(guī)范。
格式的具體邏輯如下,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傳統(tǒng)規(guī)范
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]; //后續(xù)數(shù)據(jù)為常規(guī)的視頻數(shù)據(jù)
}
else // IsExHeader使能,表示Enhanced-Rtmp格式使能
{
PacketType = UB[4];// 0 = PacketTypeSequenceStart
// 1 = PacketTypeCodedFrames
// 2 = PacketTypeSequenceEnd
// 3 = PacketTypeCodedFramesX
// 4 = PacketTypeMetadata
// 5 = PacketTypeMPEG2TSSequenceStart
FourCC = UB[32];// 4字節(jié)的FourCC,如下字符表示對(duì)應(yīng)的視頻CodecId
// AV1 = { 'a', 'v', '0', '1' }
// VP9 = { 'v', 'p', '0', '9' }
// HEVC = { 'h', 'v', 'c', '1' }, 也就是h265
// 如果類(lèi)型是HEVC, 也就是H265,后續(xù)規(guī)范如下
if (FourCC == HEVC)
{
if (PacketType == PacketTypeSequenceStart)
{
//如果PacketType是PacketTypeSequenceStart,表示后續(xù)H265的數(shù)據(jù)內(nèi)容是DecoderConfigurationRecord,也就是常說(shuō)的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
{
//無(wú)CompositionTime,節(jié)省3字節(jié)的空間
}
//隨后是正常的H265數(shù)據(jù)
DATA = [HEVC NALU]
}
}
}
2.2 ffmpeg6.1實(shí)現(xiàn)Enhance RTMP
ffmpeg在version6.1中正式支持Enhance RTMP,以此支持H.265 in rtmp/flv。這里介紹其對(duì)應(yīng)的實(shí)現(xiàn)。
首先下載對(duì)應(yīng)的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封裝的實(shí)現(xiàn),對(duì)應(yīng)文件: libavformat/flvenc.c
static int flv_write_packet(AVFormatContext *s, AVPacket *pkt) { //...... // 如果編碼格式是H265, 使用如下的實(shí)現(xiàn) if (par->codec_id == AV_CODEC_ID_HEVC) { // 如果報(bào)文pts和dts不一樣,packettype為PacketTypeCodedFrames; // 否則packettype為PacketTypeCodedFramesX int pkttype = (pkt->pts != pkt->dts) ? PacketTypeCodedFrames : PacketTypeCodedFramesX; // 第一個(gè)字節(jié),最高bit位寫(xiě)入FLV_IS_EX_HEADER; // 第一個(gè)字節(jié),最高的第3, 4bits寫(xiě)入FLV_FRAME_KEY或FLV_FRAME_INTER // 第一個(gè)字節(jié),最低2bits位,寫(xiě)入PacketTypeCodedFramesX或PacketTypeCodedFrames avio_w8(pb, FLV_IS_EX_HEADER | pkttype | frametype); // ExVideoTagHeader mode with PacketTypeCodedFrames(X) // 后4個(gè)字節(jié),寫(xiě)入FourCC,寫(xiě)入4個(gè)字符: "hvc1" avio_write(pb, "hvc1", 4); // pkttype為PacketTypeCodedFrames,寫(xiě)入3個(gè)字節(jié)的pts與dts的差值 if (pkttype == PacketTypeCodedFrames) avio_wb24(pb, pkt->pts - pkt->dts); } // 寫(xiě)入H.264的數(shù)據(jù) avio_write(pb, pkt->data, pkt->size); // ...... }
2.3 OBS實(shí)現(xiàn)Enhance RTMP
OBS在Release 29版本正式支持Enhance RTMP,實(shí)現(xiàn)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
具體實(shí)現(xiàn)在: plugins/obs-outputs/rtmp-stream.c
enum packet_type_t {
PACKETTYPE_SEQ_START = 0, //表示報(bào)文序列開(kāi)始
PACKETTYPE_FRAMES = 1, //表示該幀dts與pts有差值
PACKETTYPE_SEQ_END = 2, //flv文件最后一幀
#ifdef ENABLE_HEVC
PACKETTYPE_FRAMESX = 3, //表示該幀dts == pts
#endif
PACKETTYPE_METADATA = 4
};
//函數(shù)flv_packet_ex的最后一個(gè)參數(shù)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
// 第一個(gè)字節(jié),最高bit位寫(xiě)入1, FRAME_HEADER_EX = 8 << 4;
// 第一個(gè)字節(jié),最高的第3, 4bits寫(xiě)入FLV_FRAME_KEY或FLV_FRAME_INTER
// 第一個(gè)字節(jié),最低2bits位,寫(xiě)入PacketTypeCodedFramesX或PacketTypeCodedFrames, dts與pts相等或不等;
// 或?qū)懭隤ACKETTYPE_SEQ_START,或PACKETTYPE_SEQ_END
s_w8(&s,
FRAME_HEADER_EX | type | (packet->keyframe ? FT_KEY : FT_INTER));
// 后4個(gè)字節(jié),寫(xiě)入FourCC,寫(xiě)入4個(gè)字符: "hvc1"
s_w4cc(&s, codec_id);
#ifdef ENABLE_HEVC
// hevc composition time offset
if (codec_id == CODEC_HEVC && type == PACKETTYPE_FRAMES) {
// PacketType為PACKETTYPE_FRAMES,寫(xiě)入3個(gè)字節(jié)的pts與dts的差值
s_wb24(&s, get_ms_time(packet, packet->pts - packet->dts));
}
#endif
// 寫(xiě)入h265的幀數(shù)據(jù)
s_write(&s, packet->data, packet->size);
}
2.4 SRS實(shí)現(xiàn)Enhance RTMP
SRS是國(guó)內(nèi)最流行的流媒體服務(wù)器,支持多種直播協(xié)議RTMP, httpflv, HLS,支持WebRTC,也支持安放協(xié)議28181。SRS也率先支持Enhance RTMP,服務(wù)端能夠接受Enhance RTMP的推流,同時(shí)也兼容國(guó)內(nèi)CodecId=12的H.265的rtmp方案。
但是在rtmp或httpflv拉流方向,是應(yīng)用國(guó)內(nèi)CodecId=12的H.265的rtmp方案。
SRS的github地址: https://github.com/ossrs/srs.git, 當(dāng)前支持分支develop。
實(shí)現(xiàn)主要在srs_kernel_codec.cpp這個(gè)文件中。
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是否支持,從而獲取到編碼類(lèi)型
if (!is_ext_header) {
// 如果不是Rtmp Enhance,用傳統(tǒng)的方式判斷
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個(gè)字節(jié)的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) {
// 如果是傳統(tǒng)的Rtmp,讀取1個(gè)字節(jié)的packet_type,和3個(gè)字節(jié)的composition_time
packet_type = (SrsVideoAvcFrameTrait)stream->read_1bytes();
composition_time = stream->read_3bytes();
} else {
// 如果使能Rtmp Enhance,當(dāng)packet_type==1的時(shí)候,dts才與pts不一致,才讀取3個(gè)字節(jié)的composition_time,
// 否則沒(méi)有composition_time字段;
if (packet_type == 1) {
composition_time = stream->read_3bytes();
}
}
}
2.5 media-server實(shí)現(xiàn)Enhance RTMP
media-server是國(guó)內(nèi)開(kāi)源庫(kù)中實(shí)現(xiàn)音視頻格式和流媒體類(lèi)型最全的開(kāi)源之一,支持flv, mp4, mkv, hls, mpeg, rtmp, rtp, rtsp, sip等音視頻格式和流媒體協(xié)議,并且在各種封裝中支持的codec非常豐富,如H.264, H.265, AV1, H.266都有支持。其采用C語(yǔ)言開(kāi)發(fā),兼容性好,同時(shí)適合服務(wù)器和嵌入式的開(kāi)發(fā)。
media-server的Flv模塊同時(shí)支持demuxer和muxer,對(duì)Enhance RTMP支持比較全,同時(shí)支持AV1, H.265, H.266。(居然還支持H.266,并且有H.266 annexb to mp4和H.266 mp4 to annexb的代碼)
Enhance RTMP的實(shí)現(xiàn)主要在flv-header.c這個(gè)文件中, 關(guān)鍵的對(duì)應(yīng)代碼如下:
// demuxer
int flv_video_tag_header_read(struct flv_video_tag_header_t* video, const uint8_t* buf, size_t len) {
// 如果第一個(gè)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;
}
}
}
//否則走傳統(tǒng)flv的解析流程
//....
}
// muxer: 通過(guò)編譯宏控制
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
//否則走傳統(tǒng)flv的muxer流程
}
3. 國(guó)內(nèi)RTMP支持H.265與Enhance RTMP的兼容性問(wèn)題
因?yàn)閲?guó)內(nèi)之前支持H.265的方案是CodecID=12,現(xiàn)在存在的兼容性其實(shí)有兩個(gè)方向:
推流方向(上行)
拉流方向(下行)
3.1 推流方向
推流方向的兼容性,主要取決于服務(wù)端的兼容性,也就是說(shuō)服務(wù)端必須同時(shí)能支持:
國(guó)內(nèi)CodecID=12的H.265方案
Enhance Rtmp的H.265方案
先說(shuō)結(jié)論,上行推流方向的兼容性是沒(méi)有問(wèn)題,只需要在服務(wù)端做好兼容性,同時(shí)支持國(guó)內(nèi)外的兩種方案。
具體我們以SRS服務(wù)為例,只需要判斷第一bit位是否使能,就能得知后面應(yīng)該走RTMP Enhance流程,還是走傳統(tǒng)的RTMP流程
bool is_ext_header = frame_type & 0x80;
//判斷Rtmp Enhance是否支持,從而獲取到編碼類(lèi)型
if (!is_ext_header) {
// 如果不是Rtmp Enhance,用傳統(tǒng)的方式判斷
} else {
// 如果使能Rtmp Enhance,獲取packet_type和frame_type
// 如果packet_type == 1,獲取composition_time(3字節(jié),dts與pts的差值)
}
因?yàn)榈谝粋€(gè)bit位就能判斷后續(xù)的代碼處理流程,所以推流上行做兼容性是比較容易的;
上行解包后,形成數(shù)據(jù)結(jié)構(gòu)的對(duì)象,將其傳到下行拉流處,因?yàn)橐曨l幀數(shù)據(jù)部分已經(jīng)去掉flvTagHeader頭,所以該對(duì)象傳遞到下行處理。
以下為偽碼:
class SrsVideoFrame
{
public:
SrsVideoAvcFrameType frame_type; // 幀類(lèi)型: I/非I
SrsVideoAvcFrameTrait avc_packet_type;// 是否sequenche header等類(lèi)型
public:
int nb_samples; // 幀個(gè)數(shù),
SrsSample samples[SrsMaxNbSamples];// 幀數(shù)組,每個(gè)SrsSample是一個(gè)視頻幀數(shù)據(jù)
};
SrsVideoFrame中的SrsSample數(shù)據(jù),是去掉flvTagHeader的視頻幀數(shù)據(jù),這樣傳遞到下行后,可以根據(jù)需要再次打包flvTagHeader,下行可以再次打包成傳統(tǒng)的CodecId=12的H.265格式,也可以打包成Enhance RTMP格式(因?yàn)橄滦械募嫒菪詥?wèn)題,不推薦下行打包成Enhance RTMP格式,下一節(jié)會(huì)說(shuō)明原因)
3.2 拉流方向
拉流方向的兼容性,主要取決于客戶(hù)端的兼容性,也就是rtmp或http-flv拉流播放器端的兼容性。
因?yàn)橹皣?guó)內(nèi)的大多數(shù)已有的播放器,都支持CodecId=12的H.265方案,現(xiàn)存市場(chǎng)很多播放器是沒(méi)能支持Enhanche RTMP,也就是說(shuō)即使服務(wù)端下行方向支持Enhance RTMP,把Enhance RTMP流推給客戶(hù)端,客戶(hù)端可能存在不能識(shí)別的情況。對(duì)于大量存量的rtmp/http-flv播放器(僅僅支持CodecId=12的H.265方案),向下的流推送只能繼續(xù)采用CodecId=12的H.265方案。
所以,當(dāng)前國(guó)內(nèi)大多數(shù)云服務(wù)商和CDN在下行方向,都僅僅支持CodecID=12的國(guó)內(nèi)H.265方案。
總結(jié)
簡(jiǎn)單一句話總結(jié)兼容性:上行推流國(guó)內(nèi)外兩種推流都能兼容,但是下行僅僅提供CodecID=12的國(guó)內(nèi)H.265方案支持。
審核編輯:劉清
-
AVC
+關(guān)注
關(guān)注
0文章
23瀏覽量
11337 -
rtmp協(xié)議
+關(guān)注
關(guān)注
0文章
2瀏覽量
1820
原文標(biāo)題:詳解Enhanced-RTMP支持H.265
文章出處:【微信號(hào):livevideostack,微信公眾號(hào):LiveVideoStack】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
H.265技術(shù)興起,高清視頻商機(jī)凸顯
教您在79元ARM平臺(tái)實(shí)現(xiàn)H.265視頻解碼
H.265:網(wǎng)絡(luò)視頻的4K超高清時(shí)代,網(wǎng)絡(luò)高清智能播放器的春...
H.265與H.264對(duì)比分析
Hi3521DV200 H.265編解碼
H.265:網(wǎng)絡(luò)視頻的高清時(shí)代
華為近日宣布即將在安防領(lǐng)域采用H.265技術(shù)
H.264與H.265視頻編解碼器的區(qū)別
H.264編碼和H.265編碼的區(qū)別
4K H.265解碼的簡(jiǎn)單描述和作用
【轉(zhuǎn)】為什么H.265編碼優(yōu)于H.264編碼?
千視新品D260:H.265多接口高清解碼器
H.265與VP9編碼質(zhì)量的對(duì)比
教您在79元ARM平臺(tái)實(shí)現(xiàn)H.265視頻解碼
Enhanced-RTMP協(xié)議如何支持H.265呢?
評(píng)論