* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | V |P|X| CC |M| PT | sequence number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | synchronization source (SSRC) identifier |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | contributing source (CSRC) identifiers |
* | …… |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | header extension |
* | …… |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | vp8 payload descriptor (integer #octets) |
* : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | : vp8 payload header(3 octets) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | vp8 pyld her : |
* +-+-+-+-+-+-+-+-+ |
* : octets 4..N vp8 payload :
* | |
* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | : optional RTP padding |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
结构如下(左右两图结构一样,只有PictureID长度不同)
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
I: |X|R|N|S|R| PID |(REQUIRED) |X|R|N|S|R| PID |(REQUIRED)
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
X: |I|L|T|K| RSV |(OPTIONAL) X: |I|L|T|K| RSV |(OPTIONAL)
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
I: |M| PictureID |(OPTIONAL) I: |M| PictureID |(OPTIONAL)
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
L: | TLOPICIDX |(OPTIONAL) | PictureID |
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
T/K:|TID|Y| KEYIDX |(OPTIONAL) L: | TLOPICIDX |(OPTIONAL)
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
T/K: |TID|Y| KEYIDX |(OPTIONAL)
+-+-+-+-+-+-+-+-+
X为1时,有以下部分:
从ffmpeg的代码上看,TL0PICIDX,TID,Y,KEYIDX都被忽略了,不过其实大部分编码也都不适用这几位。
==更多详情可阅读:https://tools.ietf.org/html/draft-ietf-payload-vp8-12#section-4.2==
是VP8压缩数据的未压缩数据部分。前3个字节是I帧P帧通用的。I帧后7个字节被认为是payload的其他部分。
payload header只存在于S位为1,且PID为0的包(144)。
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|Size0|H| VER |P|
+-+-+-+-+-+-+-+-+
| Size1 |
+-+-+-+-+-+-+-+-+
| Size2 |
+-+-+-+-+-+-+-+-+
| Bytes 4..N of |
| VP8 payload |
: :
+-+-+-+-+-+-+-+-+
| OPTIONAL RTP |
| padding |
: :
+-+-+-+-+-+-+-+-+
+---------+-------------------------+-------------+
| Version | Reconstruction Filter | Loop Filter |
+---------+-------------------------+-------------+
| 0 | Bicubic | Normal |
| | | |
| 1 | Bilinear | Simple |
| | | |
| 2 | Bilinear | None |
| | | |
| 3 | None | None |
| | | |
| Other | Reserved for future use | |
+---------+-------------------------+-------------+
VP8编码数据
最后一个需要解析的是关于keyframe的了,只有是keyframe才带有keyframe header。这个头里面携带了图像的大小,以及起始的校验值0x9d012a(157 1 42)。rfc6386更是把代码直接贴出来了:头校验,不符合0x9d012a显然非VP8的keyframe了。
==更多详情可阅读:http://elkpi.com/topics/2014/12/vp8-rtp-payload.html==
RTP头的P为1才有这部分。
==参考资料:https://segmentfault.com/a/1190000023097934==
解释下面这段VP8数据的RTP包:
[144 96 42 244 223 91 236 167 131 214 222 250 190 222 0 1 64 49 0 0 144 128 245 116 16 191 0 157 1 42 128 2 224 ... 226 72 169 244 51 91 89 71 39]
//【首先是RTP Header】 144[10010000]: V=2;P=0说明没有填充;X=1说明多了一个字节的扩展位;CC=0,说明没有CSRC。96[01100000] M=0说明这一帧还没结束,payloadType=96;42 244是序列号;223 91 236 167是时间戳;131 214 222 250是SSRC;190 222 0 1 64 49 0 0是扩展位(0xBE开头,是One-Byte Header)。
//【然后是payload Describetor】144[10010000]:X=1说明有扩展位;R必须为0;N通常为0;S=1说明是一个新的VP8 partition的开始。因为X=1说明128[10000000]是X的值,I=1说明有PictureID的信息;L=0、T=0、K=0说明这几行信息都没有;RSV保留为默认为0;245 116 :M=1说明PictureID有15位,后面15位都是PictureID。
//【然后是payload header】16[00010000]前三位为size0,就不去计算了,H=1说明要显示,作用其实也不大;VER=0;重要的是!P=0说明是KeyFrame,191 0分别是size1和size2;再后面三位是157 1 42可以验证前面P=0。
[144 96 42 245 223 91 236 167 131 214 222 250 190 222 0 1 64 49 0 0 128 128 245 116 248 225 248 55 217 186 2 31 214 ... 43 155 167 49 69 104 111 73 208 212 104 222 4 243 189 41 80 28 141 54 134 233 231 82 223 33]
//【首先是RTP Header】 144[10010000]: V=2;P=0说明没有填充;X=1说明多了一个字节的扩展位;CC=0,说明没有CSRC。96[01100000] M=0说明这一帧还没结束,payloadType=96;42 245是序列号;223 91 236 167是时间戳(因为是同一帧,所以和上一个包是一样的);131 214 222 250是SSRC;190 222 0 1 64 49 0 0是扩展位。
//【然后是payload Describetor】128[10000000]:X=1说明有扩展位;R必须为0;N通常为0;S=0说明不是一个新的VP8 partition的开始,而是接着上一个包的。因为X=1说明128[10000000]是X的值,I=1说明有PictureID的信息;L=0、T=0、K=0说明这几行信息都没有;RSV保留为默认为0;245 116 :M=1说明PictureID有15位,后面15位都是PictureID(也和上一个包相同)。
//【然后是payload数据】因为这是接着前一个包的中间的帧数据,所以没有header了,而是直接接着上一个尾巴的,详情看后面的Payload函数代码。
[144 224 42 246 223 91 236 167 131 214 222 250 190 222 0 1 64 49 0 0 128 128 245 116 18 152 229 126 72 231 2 118 76 213 252 86 88 89 ... 186 62 12 89 67 104 195 134 95 83 59 40 226 187 37 90 18 0]
//【首先是RTP Header】 144[10010000]: V=2;P=0说明没有填充;X=1说明多了一个字节的扩展位;CC=0,说明没有CSRC。224[11100000] M=1说明这一帧到这个包就结束了,payloadType=96;42 246是序列号;(后面都和前两个包一样)223 91 236 167是时间戳;131 214 222 250是SSRC;190 222 0 1 64 49 0 0是扩展位。
//【然后是payload Describetor】128[10000000]:X=1说明有扩展位;R必须为0;N通常为0;S=0说明不是一个新的VP8 partition的开始,而是接着上一个包的。因为X=1说明128[10000000]是X的值,I=1说明有PictureID的信息;L=0、T=0、K=0说明这几行信息都没有;RSV保留为默认为0;245 116 :M=1说明PictureID有15位,后面15位都是PictureID(也和上一个包相同)。
//【然后是payload数据】因为这是接着前一个包的最后的帧数据,也是直接接着上一个尾巴的,没有header。
[144 224 42 247 223 91 249 79 131 214 222 250 190 222 0 1 64 49 0 0 144 128 245 117 49 51 0 228 224 175 188 243 87 167 71 167 216 133 23 242 73 96 55 216 63 252 111 125 121 180 94 67 58 11 ... 32 76 173 48 194 242 52 71 141 116 185 88 68 47 245 145 39 0]
//【首先是RTP Header】 144[10010000]: V=2;P=0说明没有填充;X=1说明多了一个字节的扩展位;CC=0,说明没有CSRC。224[01100000] M=1说明这一帧已经结束(一个包就包含了整个帧的数据,因为是P帧所以比较小,前面是I帧所以比较大),payloadType=96;42 247是序列号;223 91 249 79是时间戳;131 214 222 250是SSRC;190 222 0 1 64 49 0 0是扩展位。
//【然后是payload Describetor】144[10010000]:X=1说明有扩展位;R必须为0;N通常为0;S=1说明是一个新的VP8 partition的开始。因为X=1说明128[10000000]是X的值,I=1说明有PictureID的信息;L=0、T=0、K=0说明这几行信息都没有;RSV保留为默认为0;245 117 :M=1说明PictureID有15位,后面15位都是PictureID。
//【然后是payload header】49[00110001]前三位为size0,就不去计算了,H=1说明要显示;VER=0;重要的是!P=1说明是interframe,51 0分别是size1和size2;再后面三位不是157 1 42可以验证前面P=1,而是真实的VP8 payload了。
// Payload fragments a VP8 packet across one or more byte arrays
vp8HeaderSize := 1
//在这个封包方法中,只要了必选的VP8 payload Descriptor
func (p *VP8Payloader) Payload(mtu int, payload []byte) [][]byte {
maxFragmentSize := mtu - vp8HeaderSize//这里mtu=1200-12
payloadData := payload
payloadDataRemaining := len(payload)
payloadDataIndex := 0
var payloads [][]byte
// Make sure the fragment/payload size is correct这个帧封完了
if min(maxFragmentSize, payloadDataRemaining) <= 0 {
return payloads
}
for payloadDataRemaining > 0 {
currentFragmentSize := min(maxFragmentSize, payloadDataRemaining)
out := make([]byte, vp8HeaderSize+currentFragmentSize)
if payloadDataRemaining == len(payload) {
//如果是一个帧的第一个包,那么S位等于1,否则默认为0
out[0] = 0x10
}
copy(out[vp8HeaderSize:], payloadData[payloadDataIndex:payloadDataIndex+currentFragmentSize])
//数据是直接分割vp8 payload的,然后统一加上了一个vp8 payload Descriptor。所以只有每帧的第一个包里面的payload包含了payload header。
payloads = append(payloads, out)
payloadDataRemaining -= currentFragmentSize
payloadDataIndex += currentFragmentSize
}
return payloads
}