rtp协议打包拆包h264数据 àì夳堔傛蜴生んèń 2021-12-16 23:43 577阅读 0赞 # rtp协议打包拆包h264数据 # ### 文章目录 ### * rtp协议打包拆包h264数据 * * nalu头 * rtp包头 * rtp打拆包h264 * * 打包 * 拆包 -------------------- 最近也在玩直播了,会写一点流媒体和ffmpeg滤波器和编解码器API方面的文章,本文简述一下rtp打包h264和拆包h264数据的方法。 ## nalu头 ## H264在网络上传输的是NALU,NALU的结构是:NAL头+RBSP ![\[外链图片转存失败(img-8Rn0pkFe-1562399870183)(./1562392303928.png)\]][img-8Rn0pkFe-1562399870183_._1562392303928.png] NAL头只有一个字节: ±--------------+ |0|1|2|3|4|5|6|7| ±±±±±±±±+ |F|NRI| Type | ±--------------+ F:禁止为,0表示正常,1表示错误,一般都是0 NRI:表示重要级别,11表示非常重要。 TYPE:表示该NALU的类型是什么 0:未规定 1:非IDR图像中不采用数据划分的片段 2:非IDR图像中A类数据划分片段 3:非IDR图像中B类数据划分片段 4:非IDR图像中C类数据划分片段 5:IDR图像的片段 6:补充增强信息 (SEI) 7:序列参数集/SPS 8:图像参数集/PPS 9:分割符 10:序列结束符 11:流结束符 12:填充数据 13 – 23:保留 24 – 31:未规定 ## rtp包头 ## ![\[外链图片转存失败(img-xu8lxonK-1562399870187)(./1562395728375.png)\]][img-xu8lxonK-1562399870187_._1562395728375.png] V(version):2 bits,RTP的版本,这里统一为2 P(padding):1 bit,如果置1,在packet的末尾被填充,填充有时是方便一些针对固定长度的算法的封装 X(extension):1 bit,如果置1,在RTP Header会跟着一个header extension CC(CSRC count): 4 bits,表示头部后contributing sources的个数 M(marker): 1 bit,具体这位的定义会在一个profile里 PT(playload type): 7 bits,表示所传输的多媒体的类型, sequence number: 16 bits,每个RTP packet的sequence number会自动加一,以便接收端检测丢包情况 timestamp: 32 bits,时间戳 SSRC: 32 bits,同步源的id,每两个同步源的id不能相同 CSRC: CC指定,范围是0-15 ## rtp打拆包h264 ## 由于rtp包在网络上传输大小的限制如果大小大于一定字节(一般1500)会需要拆分开来传输,将单个NALU单元拆分到多个RTP包中。 H264有两种分片方式 : `FU-A`、`FU-B` 因为常用的打包方式就是单个NAL包和FU-A方式,本文只介绍FU-A分片的打包方法。 FU-A的头固定是两个字节 , Fu指示器和Fu头 ![\[外链图片转存失败(img-Ygb3Q7j5-1562399870187)(./1562398126254.png)\]][img-Ygb3Q7j5-1562399870187_._1562398126254.png] // eg. : 7c 85 //0 H264的 F位 //11 H264的 NRI //11100 FU Type,28,即FU-A //1 S,Start,说明是分片的第一包 //0 E,End,如果是分片的最后一包,设置为1,这里不是 //0 R,Remain,保留位,总是0 //00101 NAl Type type shardUnitA struct { fuIndicator byte fuHeader byte } ### 打包 ### Nalu单元打包成分片的FU-A形式RTP载荷 var MaxRtpPayloadSize int = 1500 - 64 func (p *Packet) ParserNaluToRtpPayload(nalu []byte) [][]byte { var ret [][]byte var n []byte if nalu[0] == 0 && nalu[1] == 0 && nalu[2] == 1 { n = nalu[3:] } else { n = nalu[4:] } if len(n) < MaxRtpPayloadSize { ret = append(ret, n) } else { // eg. : 7c 85 //0 H264的 F位 //11 H264的 NRI //11100 FU Type,28,即FU-A //1 S,Start,说明是分片的第一包 //0 E,End,如果是分片的最后一包,设置为1,这里不是 //0 R,Remain,保留位,总是0 //00101 NAl Type saStart := shardUnitA{ 0, 0} saStart.fuIndicator = saStart.fuIndicator | n[0] saStart.fuIndicator = (saStart.fuIndicator & 0xe0) | 0x1c saStart.fuHeader = (saStart.fuHeader | 0x80) | (n[0] & 0x1f) saSlice := shardUnitA{ 0, 0} saSlice.fuIndicator = saSlice.fuIndicator | n[0] saSlice.fuIndicator = (saSlice.fuIndicator & 0xe0) | 0x1c saSlice.fuHeader = saSlice.fuHeader | (n[0] & 0x1f) saEnd := shardUnitA{ 0, 0} saEnd.fuIndicator = saEnd.fuIndicator | n[0] saEnd.fuIndicator = (saEnd.fuIndicator & 0xe0) | 0x1c saEnd.fuHeader = (saEnd.fuHeader | 0x40) | (n[0] & 0x1f) offset := 1 //n offset start := append(make([]byte, 0), saStart.fuIndicator, saStart.fuHeader) start = append(start, n[offset:MaxRtpPayloadSize + offset - 2]...) ret = append(ret, start) offset = offset + MaxRtpPayloadSize - 2 remain := len(n) - MaxRtpPayloadSize - 1 for remain > MaxRtpPayloadSize - 2 { slice := append(make([]byte, 0), saSlice.fuIndicator, saSlice.fuHeader) slice = append(slice, n[offset:MaxRtpPayloadSize + offset - 2]...) ret = append(ret, slice) offset = offset + MaxRtpPayloadSize - 2 remain = remain - MaxRtpPayloadSize - 2 } end := append(make([]byte, 0), saEnd.fuIndicator, saEnd.fuHeader) end = append(end, n[offset:]...) ret = append(ret, end) } return ret } ### 拆包 ### FU-A形式、RTP载荷拆包成单个nalu单元 package h264 import ( ) const ( i_frame byte = 0 p_frame byte = 1 b_frame byte = 2 ) const ( NalueTypeNotDefined byte = 0 NalueTypeSlice byte = 1 // slice_layer_without_partioning_rbsp() sliceheader NalueTypeDpa byte = 2 // slice_data_partition_a_layer_rbsp( ), slice_header NalueTypeDpb byte = 3 // slice_data_partition_b_layer_rbsp( ) NalueTypeDpc byte = 4 // slice_data_partition_c_layer_rbsp( ) NalueTypeIdr byte = 5 // slice_layer_without_partitioning_rbsp( ),sliceheader NalueTypeSei byte = 6 //sei_rbsp( ) NalueTypeSps byte = 7 //seq_parameter_set_rbsp( ) NalueTypePps byte = 8 //pic_parameter_set_rbsp( ) NalueTypeAud byte = 9 // access_unit_delimiter_rbsp( ) NalueTypeEoesq byte = 10 //end_of_seq_rbsp( ) NalueTypeEostream byte = 11 //end_of_stream_rbsp( ) NalueTypeFiller byte = 12 //filler_data_rbsp( ) NalueTypeFuA byte = 28 //Shard unitA NalueTypeFuB byte = 29 //Shard unitB ) var ParameterSetStartCode = []byte{ 0x00, 0x00, 0x00, 0x01} var StartCode = []byte{ 0x00, 0x00, 0x01} type Parser struct { naluByte byte shardA *shardUnitA // deprecated / only use to test internalBuffer []byte // use ParserToInternalSlice() to get a complete nalu packet } type shardUnitA struct { fuIndicator byte fuHeader byte } func NewParser() *Parser { return &Parser{ naluByte : 0, shardA : &shardUnitA{ 0, 0}, } } func (p *Parser) FillNaluHead(h byte) { p.naluByte = h p.shardA.fuIndicator = 0 p.shardA.fuHeader = 0 } func (p *Parser) NaluType() byte { return p.naluByte & 0x1f } func (p *Parser) ShardA() *shardUnitA { return p.shardA } func (p *Parser) FillShadUnitA(s [2]byte) { p.shardA.fuIndicator = s[0] p.shardA.fuHeader = s[1] } func (s *shardUnitA) IsStart() bool { return (s.fuHeader & 0x80) == 0x80 } func (s *shardUnitA) IsEnd() bool { return (s.fuHeader & 0x40) == 0x40 } func (s *shardUnitA) NaluType() byte { return s.fuHeader & 0x1f } func (s *shardUnitA) NaluHeader() byte { s1 := s.fuIndicator & 0xe0 s2 := s.fuHeader & 0x1f return (s1 | s2) } // deprecated only use test // must clear with ClearInternalBuffer() func (p *Parser) ParserToInternalSlice(pkt []byte) bool { p.FillNaluHead(pkt[0]) if p.NaluType() == NalueTypeFuA { p.FillShadUnitA([2]byte{ pkt[0], pkt[1]}) if p.ShardA().IsStart() { //fmt.Println("-----is fuA start------") p.internalBuffer = append(p.internalBuffer, StartCode[0:]...) p.internalBuffer = append(p.internalBuffer, p.ShardA().NaluHeader()) p.internalBuffer = append(p.internalBuffer, pkt[2:]...) return false } else if p.ShardA().IsEnd() { //fmt.Println("-----is fuA end--------") p.internalBuffer = append(p.internalBuffer, pkt[2:]...) //fmt.Println("len: ", len(p.internalBuffer), ":\n", hex.EncodeToString(p.internalBuffer)) return true } else { //fmt.Println("-----is fuA slice------") if len(p.internalBuffer) > 1920*1080*3 { panic("internalBuffer to Large, fix me") } p.internalBuffer = append(p.internalBuffer, pkt[2:]...) return false } } else { //fmt.Println("nalu : ", p.NaluType()) p.internalBuffer = append(p.internalBuffer, StartCode[0:]...) p.internalBuffer = append(p.internalBuffer, pkt[0:]...) //fmt.Println("len: ", len(p.internalBuffer), ":\n", hex.EncodeToString(p.internalBuffer)) return true } } func (p *Parser) GetInternalBuffer() []byte{ return p.internalBuffer } func (p *Parser) ClearInternalBuffer() { p.internalBuffer = p.internalBuffer[0:0] } [img-8Rn0pkFe-1562399870183_._1562392303928.png]: /images/20211214/bc3cfade126749938b193a8b3ed7dfed.png [img-xu8lxonK-1562399870187_._1562395728375.png]: /images/20211214/e9a86e6d6bad45a9a3cf240707ed3aff.png [img-Ygb3Q7j5-1562399870187_._1562398126254.png]: /images/20211214/893a47df26e94d4893c88fc2e3e23fd1.png
还没有评论,来说两句吧...