rtmp传输h.264视频的必备知识(一)

rtmp传输h.264视频流媒体的起始帧传输

rtmp传输h.264视频流媒体是目前常见的功能,近日对其进行了一些研究及总结。

要想利用rtmp协议将h.264流媒体顺利推流到rtmp服务器,就需要将已经编码好的h.264视频流媒体按照rtmp协议中flv的格式的一些规则,进行头封装及相应的封装才可以。

我们知道,如果想要rtmp客户端连接服务端拉流,我们客户端是怎么知道数据源推流的视频大小,视频帧率等信息呢?原因就是推流端需要提供,那怎么提供的呢?答案就是推流端在初始连接到rtmp服务器的时候要先传送必要的SPS和PPS。

那SPS和PPS又是什么呢?

其实在h.264中定义帧的类型有如下定义:

//H264定义的类型 values for nal_unit_type

typedef enum {

 NALU_TYPE_SLICE    = 1,//非关键帧

 NALU_TYPE_DPA      = 2,

 NALU_TYPE_DPB      = 3,

 NALU_TYPE_DPC      = 4,

 NALU_TYPE_IDR      = 5,//关键帧

 NALU_TYPE_SEI      = 6,

 NALU_TYPE_SPS      = 7,//SPS

 NALU_TYPE_PPS      = 8,//PPS

 NALU_TYPE_AUD      = 9,

 NALU_TYPE_EOSEQ    = 10,

 NALU_TYPE_EOSTREAM = 11,

 NALU_TYPE_FILL     = 12,

#if (MVC_EXTENSION_ENABLE)

 NALU_TYPE_PREFIX   = 14,

 NALU_TYPE_SUB_SPS  = 15,

 NALU_TYPE_SLC_EXT  = 20,

 NALU_TYPE_VDRD     = 24  // View and Dependency Representation Delimiter NAL Unit

#endif

} NaluType;

SPS即Sequence Paramater Set,又称作序列参数集。SPS中保存了一组编码视频序列(Coded video sequence)的全局参数。所谓的编码视频序列即原始视频的一帧一帧的像素数据经过编码之后的结构组成的序列。而每一帧的编码后数据所依赖的参数保存于图像参数集中。一般情况SPS和PPS的NAL Unit通常位于整个码流的起始位置。但在某些特殊情况下,在码流中间也可能出现这两种结构,主要原因可能为:

  • 解码器需要在码流中间开始解码;
  • 编码器在编码的过程中改变了码流的参数(如图像分辨率等);

在做视频播放时,为了让后续的解码过程可以使用SPS中包含的参数,必须对其中的数据进行解析。

我们以一段H.264 数据进行解析说明SPS及PPS部分,但我们不详细分析h.264,如需详细查看h.264数据解析,请看我的另一篇

,后面的数据以十六进制查看,h.264每帧的界定符00 00 00 01或者00 00 01

比如下面的 h264 文件片断这就包含三帧数据:

00 00 00 01 67 42 c0 33 a6 81 e0 51 a1 00 00 03 
00 01 00 00 03 00 32 8f 18 32 a0 00 00 00 01 68
ce 1f 20 00 00 01 06 05 ff ff 4c dc 45 e9 bd e6
d9 48 b7 96 2c d8 20 d9 23 ee ef 78 32 36 34 20
2d 20 63 6f 72 65 20 31 34 38 20 2d 20 48 2e 32
36 34 2f 4d 50 45 47 2d 34 20 41 56 43 20 63 6f

第一帧是00 00 00 01 67 42 c0 33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18 32 a0

第二帧是00 00 00 01 68 ce 1f 20

第三帧是00 00 01 06 05 ff ff 4c dc 45 e9 bd e6 d9 48 b7 96 2c d8 20 d9 23 ee ef 78 32 36 34 20 2d 20 63 6f 72 65 20 31 34 38 20 2d 20 48 2e 32 36 34 2f 4d 50 45 47 2d 34 20 41 56 43 20 63 6f

我们接下来对其详细的进行分析:

我们对第一帧去掉界定符,就是剩下的67 42 c0 33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18 32 a0

这里对于SPS有用的部分就是第一字节67开始

帧类型的方式判断为界面符后首字节的低四位。

第一帧的帧类型为: 0x67 & 0x0F = 7,这是一个 SPS 帧

第二帧的帧类型为: 0x68 & 0x0F = 8,这是一个 PPS 帧

第三帧的帧类型为: 0x06 & 0x0F = 6,这是一个 SEI 帧

所以本文第一帧为SPS帧,而我们要将该SPS帧中的一些数据,按照刚刚说到的FLV的一些形式,拼接为相应的叫做Video Tag格式,如果是音频就拼接为相应的叫做Audio Tag格式

FLV、F4V格式标准文档

video_file_format_spec_v10.pdf

所以按照文档所说,就需要将要发往rtmp服务器的SPS中的数据buffer[0]位置设置为

0x17        也就是图中的高4位为1意味着关键帧,低4位为7意味着AVC格式(也就是h.264,AVC 实际上是 H.264 协议的别名。但自从H.264协议中增加了SVC的部分之后,人们习惯将不包含SVC的H.264协议那一部分称为 AVC,而将SVC这一部分单独称为SVC.)

而根据如下文档说明,我们要将AVCPacketType设置为AVC sequence header ,也就是0,其占用buffer[1]=0x00,其后为图中的CompositionTime,如果为AVCPacketType=0的话,这三个字节也是0,buffer[2]=0x00,buffer[3]=0x00,buffer[4]=0x00

然后后面的是AVCDecoderConfigurationRecord(其其实就是AVC sequence header),AVCDecoderConfigurationRecord.包含着是H.264解码相关比较重要的sps和pps信息,再给AVC解码器送数据流之前一定要把sps和pps信息送出,否则的话解码器不能正常解码。而且在解码器stop之后再次start之前,如seek、快进快退状态切换等,都需要重新送一遍sps和pps的信息.AVCDecoderConfigurationRecord在FLV文件中一般情况也是出现1次,也就是第一个video tag.

H.264 标准文档

H.264-AVC-ISO_IEC_14496-15.pdf

 

从图中可知,接下来的buffer[5]=0x01为配置版本号固定为1,而接下来的一些数据定义在另外一个文档,

H.264 标准文档

H.264-AVC-ISO_IEC_14496-10

由图中可以知道AVCProfileIndication被定义为13种配置,其中在附录A中显示

Conformance of a bitstream to the Baseline profile is indicated by profile_idc being equal to 66.

我们刚刚h.264中的对十六进制第一帧去掉界定符,就是剩下的67 42 c0 33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18 32 a0,然后我们又提出了67是SPS的标志,那么剩下的42为AVCProfileIndication(配置特征),也就是66,所以这里是Baseline(基线配置)。所以我们这里buffer[6]=0x42(十进制66).

对于基线配置的一些分析,我们放到后续的文章中,进行分析。

接线来剩下c0 33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18

c0是什么呢?在H.264-AVC-ISO_IEC_14496-10中规定,对每一种profile的支持级别,如果全部支持就将相应的位bit设置为1,否则就是0,我们看c0=二进制(1100 0000) ,对应的下图的8个bit,也就是前两个constraint_set0_flag=1,constraint_set1_flag=1,而前一个constraint_set0_flag代表对Baseline profile的支持程度,后一个constraint_set1_flag代表对Main profile的支持程度,也就是它们都为1,就是都是全部支持的,所以这里需要将buffer[7]=0xc0.

接下来的Level-IDC,我没有找到相关文档,引用另外一个人查找到的资料,

Profile_IDC:

image LevelIDC: 

 

image   
image

由图中我们也可知,我们剩下的33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18,buffer[8]=0x33(十进制的51),接下来是一个关键点,很多人都搞混了,先做一些解释。

引用别人的文章进行一下描述:

h.264码流格式

H.264标准中指定了视频如何编码成独立的包,但如何存储和传输这些包却未作规范,虽然标准中包含了一个Annex附件,里面描述了一种可能的格式Annex B,但这并不是一个必须要求的格式。
为了针对不同的存储传输需求,出现了两种打包方法。一种即Annex B格式,另一种称为AVCC格式。

  • Annex B

从上文可知,一个NALU中的数据并未包含他的大小(长度)信息,因此我们并不能简单的将一个个NALU连接起来生成一个流,因为数据流的接收端并不知道一个NALU从哪里结束,另一个NALU从哪里开始。
Annex B格式用起始码(Start Code)来解决这个问题,它在每个NALU的开始处添加三字节或四字节的起始码0x000001或0x00000001。通过定位起始码,解码器就可以很容易的识别NALU的边界。
当然,用起始码定位NALU边界存在一个问题,即NALU中可能存在与起始码相同的数据。为了防止这个问题,在构建NALU时,需要将数据中的0x000000,0x000001,0x000002,0x000003中插入防竞争字节(Emulation Prevention Bytes)0x03,使其变为:

0x000000 = 0x0000 03 00
0x000001 = 0x0000 03 01
0x000002 = 0x0000 03 02
0x000003 = 0x0000 03 03
解码器在检测到0x000003时,将0x03抛弃,恢复原始数据。

由于Annex B格式每个NALU都包含起始码,所以解码器可以从视频流随机点开始进行解码,常用于实时的流格式。在这种格式中通常会周期性的重复SPS和PPS,并且经常时在每一个关键帧之前。

  • AVCC(也就是我们这里的AVC)

AVCC格式不使用起始码作为NALU的分界,这种格式在每个NALU前都加上一个指定NALU长度的大端格式表示的前缀。这个前缀可以是1、2或4个字节,所以在解析AVCC格式的时候需要将指定的前缀字节数的值保存在一个头部对象中,这个都通常称为extradata或者sequence header。同时,SPS和PPS数据也需要保存在extradata中。
H.264 extradata语法如下:

bitsline by byteremark
8versionalways0x01
8avc profilesps[0][1]
8avc compatibilitysps[0][2]
8avc levelsps[0][3]
6reservedall bits on
2NALULengthSizeMinusOne
3reservedall bits on
5number of SPS NALUs usually1
16SPS size
Nvariable SPS NALU data
8number of PPS NALUs usually1
16PPS size
Nvariable PPS NALU data

其中第5字节的后2位表示的就是NAL size的字节数。需要注意的是,这个NALULengthSizeMinusOne是NALU前缀长度减一,即,假设前缀长度为4,那么这个值应该为3。
这里还需要注意的一点是,虽然AVCC格式不使用起始码,但防竞争字节还是有的。

AVCC格式的一个优点在于解码器配置参数在一开始就配置好了,系统可以很容易的识别NALU的边界,不需要额外的起始码,减少了资源的浪费,同时可以在播放时调到视频的中间位置。这种格式通常被用于可以被随机访问的多媒体数据,如存储在硬盘的文件。

大家明白了吗,我们的buffer[9]=0xFF,为什么是0xFF呢? 原因就是我们在文档中看到的,

前6位为111111,后两位代表前面头的长度,也就是将h.264转换成可以进行网络rtmp传输的flv的相应的格式的媒体流的时候,不用原来的nalu 起始位00 00 00 01 来代表一个nalu了,而是用一个固定的4个字节来代表一个nalu,而这4个字节,在NALULengthSizeMinusOne这个位置表示的时候不能写0x04,而是0x04-1=0x03,而0x03 的二进制是11,所以该处NALULengthSizeMinusOne的完整表示是二进制(1111 1111),十六进制是0xFF,这个是规则,所以用它来代表这里的nalu的起始位置,所以buffer[9]=(0xFF).

接下来的就是,numOfSequenceParameterSets,也就是SPS的个数,我们常常只获得一个SPS,所以个数为1,而高位前三位的是保留的111,所以就是1110 0001,也就是0xE1,所以buffer[10]=(0xE1).

接下来的就是两个字节的是(SPS的长度),因为我们的前面数据可知,我们原来的第一帧不包含h.264起始位的是23字节,所以小于255,则buffer[11]=0x00,buffer[12]=0x17(十进制23)

然后是将23个字节的SPS数据添加到这个数据的末尾,所以现在buffer的长度就是13+23=36个字节,

也就是buffer[13] ~ buffer[35] = 67 42 c0 33 a6 81 e0 51 a1 00 00 03 00 01 00 00 03 00 32 8f 18 32 a0

下一个数据的下标是36。

然后是numOfPictureParameterSets,也就是PPS的个数,这里就是buffer[36]=0x01

然后是两个字节的是(PPS的长度),因为我们的前面数据可知,我们原来的第二帧不包含h.264起始位的是4字节,所以小于255,则buffer[37]=0x00,buffer[38]=0x04。

然后是将4个字节的PPS数据添加到这个数据的末尾,所以现在buffer的长度就是39+4=43个字节,

也就是buffer[39] ~ buffer[42] = 68 ce 1f 20

最后总结一下,所传输的rtmp头数据为

buffer[0]=0x17
buffer[1]=0x00
buffer[2]=0x00
buffer[3]=0x00
buffer[4]=0x00
buffer[5]=0x01
buffer[6]=0x42
buffer[7]=0xc0
buffer[8]=0x33
buffer[9]=0xff
buffer[10]=0xe1
buffer[11]=0x00
buffer[12]=0x17
buffer[13]=0x67
buffer[14]=0x42
buffer[15]=0xc0
buffer[16]=0x33
buffer[17]=0xa6
buffer[18]=0x81
buffer[19]=0xe0
buffer[20]=0x51
buffer[21]=0xa1
buffer[22]=0x00
buffer[23]=0x00
buffer[24]=0x03
buffer[25]=0x00
buffer[26]=0x01
buffer[27]=0x00
buffer[28]=0x00
buffer[29]=0x03
buffer[30]=0x00
buffer[31]=0x32
buffer[32]=0x8f
buffer[33]=0x18
buffer[34]=0x32
buffer[35]=0xa0
buffer[36]=0x01
buffer[37]=0x00
buffer[38]=0x04
buffer[39]=0x68
buffer[40]=0xce
buffer[41]=0x1f
buffer[42]=0x20

至此,我们的rtmp传输h.264起始帧部分介绍完毕,后面就是进行调用发送了。

  • 14
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值