RTMP协议封装H264和H265协议详解(转)
作者:壹零仓
网站:微信公众号
1 RTMP和FLV
有关RTMP和FLV格式详细介绍可查看如下文章:https://www.cnblogs.com/ssyfj/p/14684500.html
前文所述,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,获取规范文档
本文主要介绍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 | 描述 | 备注 |
---|---|---|
1 | array_completeness | 默认0 |
1 | reserved | 默认0 |
6 | NAL_unit_type | 帧类型 |
16 | numNalus | 此种类型的帧个数,一般为1,如果大于1,下面进入循环 |
16 | nalUnitLength | 2字节表示附加帧(VPS/SPS/PPS)的长度 |
N | NALU 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,其他组合方式一样,这里不做介绍