1
头图

编者按:很长时间以来,国内CDN都是通过“CodecID=0xC”的设置来实现H.265支持,但这未能彻底解决CodecID只有4个bits的尴尬,未来RTMP无法支持更多的编码标准。Enhanced-RTMP会彻底解决这一问题。如果你有兴趣分享自己的经验和思考,可以通过contribute@livevideostack.com联系我们。

文 / 小新快跑

千呼万唤使出来,rtmp/flv算是有统一支持H.265的国际版本。本文将介绍:

  • 现存rtmp/flv支持H.265的方式;
  • Enhanced-RTMP协议如何支持H.265;
  • ffmpeg/obs/srs/media-server各个开源的实现;
  • 国内方案与国外方案的兼容性问题。

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的差值;

举例:

  1. 如果视频数据是H.264的sequence header(也就是包含sps/pps的Avcc Header),就应该是0x17 00;
  2. 如果视频数据是H.264的Iframe,就应该是0x17 01;
  3. 如果视频数据是H.264的非Iframe,就应该是0x27 01
  4. 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标准:

但是,同样有其局限性: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的标准(https://github.com/veovera/enhanced-rtmp),彻底解决在rtmp/flv支持H.265的编码。

2.1 Enhanced-Rtmp规范

Enhanced-Rtmp规范(https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp.pdf)

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的github地址: https://github.com/ireader/media-server

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方案支持。


LiveVideoStack
260 声望85 粉丝