RTMP协议封装H264和H265协议详解

1 RTMP和FLV

前文所述,RTMP传输音视频消息时,其RTMP负载采用的时FLV的封装格式,这个说法有点不太准确,RTMP音视频消息负载并不是完整的flv封装,其只是采用了FLV tag body的封装格式,这里为什么不采用完整的flv格式封装呢? 这是因为RTMP header中携带的信息及RTMP推拉流交互中的消息的传递,已经能够完全覆盖FLV头字段及tag头字段所携带的信息,本周最小数据的原则,这里只保留了flv的 video/audio tag body格式,有关Video tag和Audio tag封装格式,可查阅规范《video_file_format_spec_v10.pdf》。

本文主要介绍RTMP发送H264和H265视频时,其RTMP视频消息的格式介绍及抓包分析。

2 RTMP协议封装H264视频流

从《流媒体之RTMP详解》这篇文章中我们详细介绍了RTMP推流和拉流的流程,这里就不做介绍,这里只介绍RTMP视频消息的格式,其他类型消息及交互过程可查阅此篇文章。

RTMP 视频消息与其他格式的消息一样,由消息头和消息体组成,根据前文介绍的消息分块传输的方式,拆分消息时,只把消息体拆分为多个chunk data,消息头融合进chunk header中,由chunk header + chunk data组成,很多文章直接把其称为RTMP Header和RTMP Body,wireshark中就这样标注。 chunk header中Chunk stream ID 是用来区分消息信道的,因为 RTMP 协议,所有的通信都是通过同一个 TCP 来完成的,因此所有类型的通信信道需要由 Chunk stream ID 来进行区分,从而判断当前收到的消息所属的信道类型,此是由用户定义的,Adobe 建议采用如下的分类:

图片

其中obs推流、ffmpeg推流都有自己的定义方式,与这个略有不同,CSID再解析式可只作为一路流的通道,由Message Type来标识音视频,消息类型定义如下图所示:

图片

这里可以看出视频消息的消息类型为9,当消息类型为9时,表示其消息体为video tag body(Video Data)封装格式的视频数据,有关Video Data的封装格式如下图所示(video_file_format_spec_v10 flv/f4v规范):图片

  • 帧类型(frametype):占高4位,定义视频的帧类型
  • 编码类型(CodecId):占低四位,定义了视频的编码格式,H264为7,videodata采用AVCVIDEOPACKET格式
  • 视频数据(videoData):根据编码类型选择对应的视频数据封装格式,这里介绍H264,选择AVCVIDEOPACKET格式 AVCVIDEOPACKET格式如下图所示:图片
  • AVC包类型(AVCPacketType):0表示为AVCC的序列头,这里视频打包格式不是我们常见的Annex B,而是AVCC方式,H264采用AVCC封包时,解码需要AVCC序列头,客户端解码必须接收到此序列头包才会解码;1表示H264的nalu,此包为H264数据包,与Annex B相比去除了nalu起始码00000001,增加了四个字节的nalu长度
  • Comosition time offset:组合帧时间便宜,一般没啥用,直接赋值为0
  • 视频数据(Data):根据AVC包类型,封装相应的数据,AVCPacketType=0时按照AVCC序列头格式封装,为1时按照nalu封装。 我的文章《fmp4打包H264详解》中详细介绍了AVCC H264的封装格式,RTMP发送视频数据时,客户端接收到AVCC 序列头之后,才能初始化解码器,进行后续nalu帧的解码操作,这里为了防止第一包数据丢失,一般服务器会在每一个IDR帧之前,都发送一包AVC sequence header,保证客户端能够在极端情况下,具备持续解码播放的能力,这里从RTMP发送AVC sequence header、AVCC Nalu来详细介绍封装格式。

2.1 RTMP发送AVC sequence header

RTMP发送AVC sequence header,其Video data封包方式(设包为buf数组),第一个字节为buf[0]=0x17=0B00010111,其frametype为关键帧,所以高四位为1,编码类型为H264,所以低四位为7,其视频数据采用AVCVIDEOPACKET格式,AVCPacketType为AVC序列头,所以buf[1]=0,buf[2,3,4]={0,0,0},此时其Data的格式为AVCDecoderConfigurationRecord(就是AVC sequence header),AVC sequence header格式如下:

图片

版本号固定为1,因此buf[5]=1,后面三个字节表示编码规格、编码兼容性和编码等级,从SPS中即可获取,所以buf[6]=SPS[1],buf[7=SPS[2],buf[8]=SPS[3],lengthSizeMinusOne表示用多少字节来表示nalu size,一般默认为3,其值+1,表示nalu要使用4个字节来表示长度,buf[9]= 0xFC|0x3=0xFF,下一个字节表示SPS个数,H264一般SPS只有一个,因此buf[10]=E0|01=E1,接下来就是SPS size和SPS data,SPS size站2个字节,buf[11,12,13,14]=SPS size(大端模式),后面直接为SPS的数据,这里注意要去掉起始码之后的数据,假设去掉起始码之后SPS为24,则buf[11-12]=0x0018,buf[13-26]= SPS data,下一个字节为PPS个数,一般为1,buf[27]=1,之后为PPS Size和PPS Data,这里假设PPS Size=4,则buf[28-29]=0x04,buf[30-33]=PPS Data,至此RTMP传输的起始帧AVC sequence header组装完毕,此帧可每次随IDR帧重复发送

2.2 RTMP发送AVCC视频帧数据‘

前文介绍了RTMP发送AVC序列帧方式,真正的视频帧是如何发送的呢?真正的视频帧发送很简单,如果为I帧,buf[0]=0x17,表示关键帧,H264编码;视频帧AVCPacketType类型为AVC Nalu,所以buf[1]=1,buf[2,3,4]={0,0,0},此时其Data的格式为AVCC Nalu封装方式,即是4字节nalu大小+nalu数据(去掉起始码),所以buf[5-8]=nalu size,buf[9-n]= nalu data,至此I帧的RTMP 负载就组合完了;P帧与I帧就第一个字节不同,P帧不是关键帧,因此frametype=2,buf[0]=0x27,其他组合方式一样,这里不做介绍。

3 RTMP协议封装H265视频流

RTMP协议中并没有介绍H265相关规范,可采用CDN联盟的HEVC扩展标准,将HEVC的VideoTagHeader定义为12,详见下图:图片

当HEVC编码时,CodecID=12,视频数据封装方式采用HEVC封装采用HVCC的封装格式,与AVCC类似,其无起始码,前面四个字节表示nalu的size大小,其客户端解码需要首先接收到HEVC的序列头,序列头格式如下:

图片

格式中定义相关字段的值,可以直接从HEVC的SPS序列帧中解析得到,按照此格式组合成HEVC的序列头,其buf[0]=0x1C,表示关键帧,编码格式为HEVC,buf[1]=0表示HEVC的序列帧,buf[2-4]=0x000000,buf[5]=1,之后的编码信息赋值按照从H265的SPS中解析出的值赋值即可,其中numOfArrays表示后面数组的个数,一般为0x03,表示包含VPS/SPS/PPS,后面数组的封装方式示意如下(《ISO-14496-15 AVC file format》中有详细说明):

// The CodecPrivate syntax shall follow the
// syntax of HEVCDecoderConfigurationRecord
// defined in ISO/IEC 14496-15.
//
// The number zero (0) shall be written to
// the configurationVersion variable until
// official finalization of 14496-15, 3rd ed.
//
// After its finalization, this field and the
// following CodecPrivate structure shall
// follow the definition of the
// HEVCDecoderConfigurationRecord in 14496-15.

unsigned int(8) configurationVersion;
unsigned int(2) general_profile_space;
unsigned int(1) general_tier_flag;
unsigned int(5) general_profile_idc;
unsigned int(32) general_profile_compatibility_flags;
unsigned int(48) general_constraint_indicator_flags;
unsigned int(8) general_level_idc;
bit(4) reserved = ‘1111’b;
unsigned int(12) min_spatial_segmentation_idc;
bit(6) reserved = ‘111111’b;
unsigned int(2) parallelismType;
bit(6) reserved = ‘111111’b;
unsigned int(2) chromaFormat;
bit(5) reserved = ‘11111’b;
unsigned int(3) bitDepthLumaMinus8;
bit(5) reserved = ‘11111’b;
unsigned int(3) bitDepthChromaMinus8;
bit(16) avgFrameRate;
bit(2) constantFrameRate;
bit(3) numTemporalLayers;
bit(1) temporalIdNested;
unsigned int(2) lengthSizeMinusOne;
unsigned int(8) numOfArrays;
for (j=0; j < numOfArrays; j++) {
bit(1) array_completeness;
unsigned int(1) reserved = 0;
unsigned int(6) NAL_unit_type;
unsigned int(16) numNalus;
for (i=0; i< numNalus; i++) {
unsigned int(16) nalUnitLength;
bit(8*nalUnitLength) nalUnit;
}
}

详细解释如下:

bits描述备注
1array_completeness默认0
1reserved默认0
6NAL_unit_type帧类型
16numNalus此种类型的帧个数,一般为1,如果大于1,下面进入循环
16nalUnitLength2字节表示附加帧(VPS/SPS/PPS)的长度
NNALU data附加帧(VPS/SPS/PPS)的数据

一般情况下numOfArrays=3,第一个数组为VPS相关数据,第二个数组为SPS,第三个数组为PPS

Hevc视频帧的封装式与H264类似,当为I帧时,buf[0]=0x1C,表示关键帧,H265编码;视频帧类型为HEVCNalu,所以buf[1]=1,buf[2,3,4]={0,0,0},此时其Data的格式为HEVC Nalu封装方式,即是4字节nalu大小+nalu数据(去掉起始码,需要去掉转移字节),所以buf[5-8]=nalu size,buf[9-n]= nalu data,至此I帧的RTMP 负载就组合完了;P帧与I帧就第一个字节不同,P帧不是关键帧,因此frametype=2,buf[0]=0x2C,其他组合方式一样,这里不做介绍。

作者:壹零仓,文中涉及到一些文档可关注公众号——壹零仓获取。

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论