rtp协议打包拆包h264数据

rtp协议打包拆包h264数据

文章目录

  • rtp协议打包拆包h264数据
    • nalu头
    • rtp包头
    • rtp打拆包h264
      • 打包
      • 拆包


最近也在玩直播了,会写一点流媒体和ffmpeg滤波器和编解码器API方面的文章,本文简述一下rtp打包h264和拆包h264数据的方法。

nalu头

H264在网络上传输的是NALU,NALU的结构是:NAL头+RBSP
[外链图片转存失败(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:填充数据
1323:保留
2431:未规定

rtp包头

rtp协议打包拆包h264数据_第1张图片

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-AFU-B

因为常用的打包方式就是单个NAL包和FU-A方式,本文只介绍FU-A分片的打包方法。

FU-A的头固定是两个字节 , Fu指示器和Fu头
rtp协议打包拆包h264数据_第2张图片

// 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]
}

你可能感兴趣的:(音视频/流媒体/ffmpeg,Go从入门到入土,Go,rtp打包h264,rtp拆h264,流媒体,rtp,nalu)