最近也在玩直播了,会写一点流媒体和ffmpeg滤波器和编解码器API方面的文章,本文简述一下rtp打包h264和拆包h264数据的方法。
H264在网络上传输的是NALU,NALU的结构是:NAL头+RBSP
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:未规定
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包在网络上传输大小的限制如果大小大于一定字节(一般1500)会需要拆分开来传输,将单个NALU单元拆分到多个RTP包中。
H264有两种分片方式 : FU-A
、FU-B
因为常用的打包方式就是单个NAL包和FU-A方式,本文只介绍FU-A分片的打包方法。
// 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]
}