VP8 RTP负载格式

这几周挺忙的,都在搞webrtc的一些东西,webrtc用到的编码是VP8的。所以对VP8有了一些初步的了解,但是对其如何编解码还是不够深入了解,只是知道如果去解析RTP及构造RTP等一些粗浅的操作。这里顺便给记录一下。

首先,是需要有理论的知识,RTP Payload Format for VP8 Video
draft-ietf-payload-vp8-05,这份ietf的文档,先看一些,这里有介绍关于VP8的rtp payload实现方式。其实这份文档分为好几个版本,05版本的并不是最新的,最新的是13版本的。为什么用05版本的,因为我自己之前有下载了ffmpeg 2.2.4的代码,代码中关于vp8 rtp的实现是基于05版本的,所以就看了这份了。谷歌对于这份说明更新还是很勤快的,现在还只是草案。不过开发下来,觉得VP8还是没H264的RTP负载实现来的好吧,虽然就只分为keyframes及interframes。

看完文档,对于RTP这种流处理,肯定少不了一些抓包工具,这里要说的工具就是wireshark了。而且是12及其之后的版本。为何使用12的版本,因为只有12之后的版本,才有对H264及VP8做一些解析,不过VP8的解析还是相对简单,但是绝对是够用。先看几张图:

vp8-keyframe

 

上图中,是keyframe帧,在新版的wireshark可以使用vp8.hdr.frametype==keyframe或vp8.hdr.frametype==0来过滤。从keyframe header的解析上,可以看出视频流为cif分辨率。

continuation frame

接下来的这帧是continuation frame,这时候,除了VP8 payload descriptor就是payload了。还有就是interframe帧。如下:

vp8-interframe

interframe的话,比普通的continuation多了payload header。以上就是新版本wireshark提供给我们的一些信息了。通过这些信息可以初步的了解下vp8 rtp payload的一些实现方式。但是具体的话,还是要通过RFC文档及阅读ffmpeg代码来深入了解了。

VP8 rtp必带的就是payload descriptor。RFC文档中给出的格式如下:

前8位:

X:extended位,当该位为1时,后面这些 OPTIONAL(I,L,T,K)就需要进行解析了,如果为0的话,则直接忽略这些可选的项目。

R:reserved位。

N:Non-reference帧,当为1时,说明该帧可以被丢弃,就我目前抓到的报文,似乎还没有遇到被置为1的可丢弃的帧,大部分情况下,应该都是0的。当不知道该帧是否为参考时,必须被设置为0.

S:Start of VP8 partition,如果当前的帧为VP8 partition的起始,则该参数必须被置1,由于keyframe及interframe都是每个partition的起始,所以keyframe及interframe的话,S位肯定是1的,而continuation frame则不一定了。同一时间戳上的图片可能被分成多个的partition,这时候continuation frame中也就会出现S位为1的了。

PartID:partition index,如果S位为1,那么partid肯定是为1了。由于partition不可能会太大,所以这里只用了4位来表示,完全是够用的。

之后的ILTK都是需要X置1才有效。

I:picture id呈现标志位,置1时,必须在后面I所示行呈现picture id,一般从1开始依据图像顺序递增。目前大部分的软件都会把I置1.

L:TL0PICIDX呈现标志位,置1时,必须在后面L所示行呈现TL0PICIDX,目前看来ffmpeg并没有使用该位。当T被置1时,L必须被置0!

T:TID呈现标志位.被置1时,可选的TID/KEYIDX部分必须被呈现。TID|Y部分必须在其之后。如果K被置1但T为0,TID/KEYIDX必须呈现出来,但是TID|Y必须被忽略。T或K都不为1时,TID/KEYIDX都不必呈现!略为有点绕,不过好在ffmpeg直接把这几位都置0了。。

K:KEYIDX present,这个其实和T说明的差不多了。

RSV:预留位,必须全为0!

现在大部分的软件实现都会置将I置1,其余都置0,所以这里在之后最重要的就是解析picture id了。

PictureID:8位或16位的长度,其中首位为为1时,则为16位的长度,后15位为picture id,为0,则为8位的长度,后7位为picture id。

TL0PICIDX:8 bits temporal level zero index

TID:2 bits temporal layer index.

Y:1 layer sync bit.

KEYIDX:5 bits temporal key frame index.

从ffmpeg的代码上看,TL0PICIDX,TID,Y,KEYIDX都被忽略了,不过其实大部分编码也都不适用这几位。

以上就是VP8 payload descriptor的内容了,ffmpeg在函数 vp8_handle_packet (rtpdec_vp8.c)中有对这部分的解析代码。

代码上看,也是很是随意,哈哈,在取picture id的代码如下:

在descriptor之后,如果S位为1,则说明是起始的部分。这时候,还需要进一步的解析payload header,也就是VP8的头了,这部分长度为3字节。在libvpx中的结构体如下:

默认是不开启testing的,所以长度为24位,3字节。

P:类型,VP8只有两种,keyframe及interframe,分别为0和1.RFC6386中有定义。

VER:版本,内容如下:

H:显示位。0的时候,不显示,其实我觉得很奇怪,因为我这边抓到的报文,该位都是0,总不能都不显示吧,显然又是被解码器忽略了!

Size:首个partition的长度,19位,很奇怪的设定,计算方式是这样的1stPartitionSize =
Size0 + 8 * Size1 + 2048 * Size2

在之后,我构造VP8的报文的时候,一直很纠结于这个size的大小,因为我有修改了里面的一些长度,虽然失败了(对VP8编解码还是不够熟悉)。但是其实计算并不复杂,在libvpx代码中实现如下:

因为为24位,普通情况下int为32位,显然是足够存储的,所以通过移位及或之后,就可以得到所需要的值,在依次向右移位来赋值,就达到构造该payload header的目的了。

最后一个需要解析的是关于keyframe的了,只有是keyframe才带有keyframe header。这个头里面携带了图像的大小,以及起始的校验值0x9d012a。rfc6386更是把代码直接贴出来了:

头校验,不符合0x9d012a显然非VP8的keyframe了。

之后就是取图像的宽高了,以及一些分量信息。

这两个垂直及水平分量的内容如下:

这里还涉及了大小端的问题,目测开发这个libvpx的时候,使用x86的设备(现在大部分PC都小端)。默认直接小端传输了。。看下面这个实现,因为ppc是大端的,网络字节序也是大端,没想到大端的ppc反倒要做转换。

至此,VP8的RTP负载的内容大体就是这样了,之后的一些负载内容,涉及的是编解码还有图像信息,wireshark也没有给直接的解析出来,其实这里还有个keyframe的Color Space and Pixel Type,目前还没搞懂。有时间搞懂了,在补充吧!

 

 

转载请注明: 转载自elkPi.com

本文链接地址: VP8 RTP负载格式

2 Comments

  1. 自由马
    2017年1月17日

    你好,我现在用的最新的wireshark(2.2.3),但看不到keyframe信息,希望指点怎么能看到

    回复
    1. 米鹿π
      2017年1月20日

      vp8.hdr.frametype == 0 # 过滤keyframe
      vp8.hdr.frametype == 1 # 过滤interframe
      其中过滤出来的都是一帧的头RTP包,后续同一个时间戳的报文就是该帧的数据

      回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Scroll to top