视频码率/分辨率/卡顿的计算方法参考:
1、 码率问题:
你们提到的客户给出的码率字段定义是视频文件的大小/视频播放时长.这个我们也是这么计算的,
只是有些细节的问题,需要说明一下,以免你对我们现有的定义有误解:
首先,码率的单位,一般为 kbps,
1B=8b
B=byte(字节)
b=bit(位)
k=kilo
所以这里的 :kbps = kilo bits per second
其次,针对不同的视频格式又不同的码率算法,
不知道你有疑问的是哪一种,看描述,你应该是对 MP4 格式的码率计算有疑问,可以重点看一下
MP4 的码率算法:
1) MP4:
在数据流中通过 ftyp box 找到 moov box,进而在 moov box 中找到 mvhd box,mvhd box 包含
着该格式视频的一些基础信息,这里我们要用到的是 timescale 和 duration 这两个字段,timescale
字段表示该视频的时间尺度—即在其时间坐标系统中每秒传递的时间单位数,duration 字段表
示视频在时间尺度单元中的持续时间。
视频的时间长度由公式 duration / timescale 得到,单位为 s;
视频的大小 filesize 通过在 http response header 中解析 Content-Length 或者 Content-Range
(Content-Range 取'/'后的部分)得到,单位为 Byte;一般情况下从两者中分析出的的值应该
一致,但是当视频分段传输,Content-Length 小于 Content-Range 时,以 Content-Range 中取得
的值为准。
所以视频的码率为:
rate = 8 * filesize / (duration / timescale) ,码率的单位为 bits/s,一般输出的时候会进行单位转换
rate/1024,变成 kbps。
2) FLV:
FLV 格式的视频数据中可以通过解析 videodatarate 字段直接得到码率,filesize 字段得到文件大
小。
3)TS:
找到 PAT 表(节目关联表),进而找到 PMT 表(节目映射表)对应的 PID,然后通过 PID 找
到 PMT 表,根据第二步找到的视频内容(H.264:stream_type =0x1B; H.265:stream_type =0x24)
对应的 TS 的 PID,将视频内容对应的 TS 找到,将其中的 payload 部分的结构(PES)拿出来,
找到视频内容对应的 PTS,1PTS 表示 1/90000 秒,只找 payload_unit_start_indicator 为 1 的包。
在前面查到一个含 PCR 字段的 TS 包,取出 PCR 值和 PID,然后在后续流量中查找这个 PID 的
TS 包,取出 PCR 值,将这两个包的位置之间的 BYTE 大小除 PCR 的差值再乘 90000 再乘 8,
即得该 TS 流的码率。
3、分辨率问题:
这个也是不同的格式算法不同:
1) MP4:
分析完码率之后继续分析,找到 tkhd box,可以直接分析得到 width 和 heigth:
2) FLV:
FLV 格式的视频数据中可以通过解析 width 和 heigth 直接得到。
3)TS:
TS 格式的视频,解析宽高需要找到视频的序列参数集 SPS,SPS 的内容大致可以分为几个部
分:
1、自引 ID;
2、解码相关信息,如档次级别、分辨率、子层数等;
3、某档次中的功能开关标识及该功能的参数;
4、对结构和变换系数编码灵活性的限制信息;
5、时域可分级信息;
6、VUI。
这里我们主要解析分辨率,即宽高,用到的信息为 pic_width_in_luma_samples 和
pic_height_in_luma_samples :
得到 pic_width_in_luma_samples 和 pic_height_in_luma_samples 值后即可通过以下公式得到视频
宽高(H.264 和 H.265 的计算方法还有一些差别):
H.264 的宽高计算公式为:
width = (sps.pic_width_in_mbs_minus1+1)*16;
height= (sps.pic_height_in_map_units_minus1+1)*16;
H.265 计算宽高的公式为:
width = sps.pic_width_in_luma_samples;
height = sps.pic_height_in_luma_samples;
编辑此区域
你提到的从 getInfo 中取,只针对腾讯视频中的 http 请求中带有相关信息的时候,获取不到的
时候就是用上面的方法分析得到的;
就我们看,分辨率和码率也有你说的这种分辨率等级和分辨率像素区间的关系,
你提到的 144P 以下的分辨率,视频码率也存在 2MB 以上的值,这应该是分析的有问题,如果有
相关的数据可以提供给我们进行进一步分析。
4、卡顿次数占比问题
我们的对卡顿的认定方式是根据 缓存数据是否足够一定数量的播放量来认定的,即,当视频缓存
到一定数量(一般为 2 秒的播放量,2*码率)之后,认为视频播放成功,之后:
当视频缓存不足一定数量(一般为 3 秒的播放量,3*码率)之后,认为出现卡顿,记一次卡顿,并
统计卡顿时间,当缓存够这些数据之后重新开始判定是否出现卡顿。
3、关于 TS 的分析,
TS 是苹果系统里面用得非常多的一种格式,现在网络中也有 20%左右的量是 TS 格式的,和 Mp4、
Flv 合在一起占了绝大多数网络流量。
TS 的包是一个一个 188 字节的包组成,这 188 字节里面由一个 0x47 开头的包作为同步,也就是说,
如果你找到了 0x47,如果与它相隔 188 个字节的地方又是一个 0x47,基本上就是一个 TS 的包。另
外,对于我们实际上还有辅助判断办法,比如.TS 的文件一般 content_type 为 video/mp2t(但反
过来,video/mp2t 类型的有一小部分是 mp4);另外,访问的 url 里面一般都有".ts"或者".ts?"的
字段。
一般将“video/mp2t"和是否包含".ts"合并起来判断,基本上可以拿出绝大多数的 ts,如果再加上
0x47 这个判断,就更加准确了,基本上 100%
TS 包的表结构如下:
第一步:找到 PAT 表(节目关联表),为的目的是找到 PMT 表对应的 PID
PAT 表的 PID 为 0,一般它在整个 TS 的前几个(一般在 1,2 个)TS 结构中。我举的例子就是在第二
个 TS 结构中,如下:
0x47 和第一个 0x47 正好隔了 188 个,第一个 TS 包的 PID 不为 0( 0 0000 0001 0001),忽略,第二个
TS 包的 PID 为 0,为 PAT 表(注意 PID 后面还要跳过一个 8bit 的长度才是 PAT 表结构)。
PAT 表结构如下:
N loop 那里就是标识后面多个表结构信息的地方,所以找到 PAT 之后,直接从它的结构的第 9 个字节
开始(如果从 0x47 算起,就是第 14 个字节),就是 N 个 program 的结构了,从这里面找 Program number
为 0x00 01 的结构,他的 Network PID 即为 PMT 对应的 PID。
这里我们看到,除掉最后 32 个字节的 CRC 外,只有一个 program 结构(4 个字节),其中 program number
为 0x01,PID 为 0xF0 的后 13 个 bit,即 0x 10 00,
第二步:根据第一步找到的 PMT 表的 PID(0x 10 10),找到 PMT 表
挺幸运,第三个 TS 结构实际上就是 PMT 表,如下图,找 PID 的办法,我第一步里面已经说了
pmt 的结构如下:
1.
2.
typedef struct TS_PMT
{
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
: 8; //固定为 0x02, 表示 PMT 表
: 1; //固定为 0x01
unsigned table_id
unsigned section_syntax_indicator
unsigned zero
unsigned reserved_1
unsigned section_length
: 1; //0x01
: 2; //0x03
: 12;//首先两位 bit 置为 00,它指示段的 byte 数,由段长度域开始,包含
CRC。
unsigned program_number
unsigned reserved_2
unsigned version_number
unsigned current_next_indicator
: 16;// 指出该节目对应于可应用的 Program map PID
: 2; //0x03
: 5; //指出 TS 流中 Program map section 的版本号
: 1; //当该位置 1 时,当前传送的 Program map section 可用;
//当该位置 0 时,指示当前传送的 Program map section 不可用,下一个 TS 流的
Program map section 有效。
unsigned section_number
unsigned last_section_number
unsigned reserved_3
unsigned PCR_PID
: 8; //固定为 0x00
: 8; //固定为 0x00
: 3; //0x07
: 13; //指明 TS 包的 PID 值,该 TS 包含有 PCR 域,
//该 PCR 值对应于由节目号指定的对应节目。
//如果对于私有数据流的节目定义与 PCR 无关,这个域的值将为 0x1FFF。
unsigned reserved_4
unsigned program_info_length
数。
: 4; //预留为 0x0F
: 12; //前两位 bit 为 00。该域指出跟随其后对节目信息的描述的 byte
std::vector PMT_Stream; //每个元素包含 8 位, 指示特定 PID 的节目元素包的类型。该处
PID 由 elementary PID 指定
unsigned reserved_5
unsigned reserved_6
unsigned CRC_32
23.
24.
25.
26. } TS_PMT;
: 3; //0x07
: 4; //0x0F
: 32;
我们主要在乎的是 PMT_Stream(第 13 个字节开始,从 0x47 开始为第 18 个字节)里面的内容,
PMT_Stream 的结构如下(有多少个,由 program_info_length 决定)
1.
2.
3.
4.
5.
6.
7.
typedef struct TS_PMT_Stream
{
unsigned stream_type
unsigned elementary_PID
有相关的节目元素
unsigned ES_info_length
元素的 byte 数
unsigned descriptor;
}TS_PMT_Stream;
我们还是看这个例子,
: 8; //指示特定 PID 的节目元素包的类型。该处 PID 由 elementary PID 指定
:13; //3 个 bit reserved,后 13 个 bit 指示 TS 包的 PID 值。这些 TS 包含
: 12; //前 4 位 bit 为 reserved。后 12 个 bit 指示跟随其后的描述相关节目
//长度由 ES_info_length 决定
第一个 PMT_Stream
stream_type =0x1B,查下表,后面为 H.264 格式的 video 的描述,它的 PID 为 E1 的后 13 个 bit,即 0x0100;
即后面所有的 TS 结构,PID 为 0x01 00 的,都是 H.264 的视频(我们第三步的目标出来了!),这个 stream 的 descriptor
长度为 0( ES_info_length 这里为 0),所以总共就 5 byte 长;
第二个 PMT_Stream
stream_type =0x0F,查下表,为 audio,PID 为 0x0101,ES_info_length 为 6,所以总长度 11 个 byte(这
些都是后话,对于本次分析意义不大,说这些只是为了编程时好理解)
第三步:根据第二步找到的视频内容对应的 TS 的 PID,将视频内容对应的 TS 找到,将其中的 payload
部分的结构(PES)拿出来,找到视频内容对应的 PTS(相当重要啊,直接关系到视频播放时间)
还是那个包,很幸运,第五个 TS 的 PID 就是 0x10 00(把 0x47 41 00 的 41 00 拆开,取后面 13 个
bit),
我们只找 payload_unit_start_indicator 为 1 的包(标识这是 PES 的开头包)
但是要注意,TS 的 adaptation field control 字段为 11,表示后面接着有个 adaptation 结构,然
后才是 payload 部分,几个取值代表的意思见下:
00 reserved for future use by ISO/IEC
01 no adaptation_field, payload only
10 adaptation_field only, no payload
11 adaptation_field followed by payload
这个 adaptation 对于我们意义,不大,直接读取它的第一个字节(表示他的长度)然后跳过这个长
度的 byte,直接到 payload 部分即可