攻克视频技术

Posted 江河(Krisen)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了攻克视频技术相关的知识,希望对你有一定的参考价值。

文章目录

从参数的角度看视频图像

像素
1.像素是图像的基本单元,一个个像素就组成了图像
分辨率
1.图像(或视频)的分辨率是指图像的大小或尺寸
2.一张 1920x1080 的图像,前者 1920 指的是该图像的宽度方向上有 1920 个像素点,而后者 1080 指的是图像的高度方向上有 1080 个像素点
3.常见的分辨率有QCIF(176x144)、CIF(352x288)、360P(640x360)、720P(1280x720)、1080P(1920x1080)、4K(3840x2160)

同样一张图像用不同的分辨率表示

  • 像素就只是一个带有颜色的小块。
  • 图像的分辨率越高,图像就越清晰。

第 2 句话不够严谨。原始图像的话,分辨率越高确实会越清
晰,但是我们看到的图像往往是经过后期处理的,比如放大缩小,或者磨皮美颜。经过处理过后的图像,尤其是放大之后的图像,分辨率很高,但是它并没有很清晰,放大的图像是通过“插值”处理得到的,而插值的像素是使用邻近像素经过插值算法计算得到的,跟实际相机拍摄的像素是不一样的,相当于“脑补”出来的像素值
位深
1.像素是由 R、G、B 三个值组成的(有的时候还会有 Alpha
值,代表透明度
2.通常 R、G、B 各占 8 个位,也就是一个字节。8 个位能表示 256 种颜色值,那 3 个通道的话就是 256 的 3 次方个颜色值,总共是 1677 万种颜色。我们称这种图像是 8bit 图像,而这个 8bit 就是位深。
3.,位深越大,我们能够表示的颜色值就越多,位深越大,我们能够表示的颜色值就越多
Stride(跨距)
1.图像存储时内存中每行像素所占用的空间

一张 RGB 图像,分辨率是 1278x720。我们将它存储在内存当中, 一行像素需要 1278x3=3834 个字节,3834 除以 16
无法整除。因此,没有 16 字节对齐。所以如果需要对齐的话,我们需要在 3834 个字节后面填充 6 个字节,也就是 3840个字节做 16
字节对齐,这样这幅图像的 Stride 就是 3840 了 每读取一行数据的时候需要跳过这多余的 6 个字节。如果没有跳 过的话,这 6
个字节的像素就会被我们误认为是下一行开始的 2 个像素(每个像素 R、G、B 各占 1 个字节,2 个像素共 6
个字节)。那这样得到的图像就完全错了,显示出来的就是“花屏”现象,屏幕会出现一条条的斜线

2.不同的视频解码器内部实现的不同,会导致输出的图像的 Stride 不一样。
帧率
1.1 秒钟内图像的数量就是帧率
2.帧率高,代表着每秒钟处理的图像数量会很高,从而需要的设备性能就比较高
码率
1.视频在单位时间内的数据量的大小,一般是 1 秒钟内的数据量,其单位一般是 Kb/s 或者 Mb/s
2.用压缩工具压缩同一个原始视频的时候,码率越高,图像的失真就会越小,视频画面就会越清晰。但同时,码率越高,存储时占用的内存空间就会越大,传输时使用的流量就会越多

并不是码率越高,清晰度就会越高

视频压缩之后的清晰度还跟压缩时选用的压缩算法,以
及压缩时使用的压缩速度有关。压缩算法越先进,压缩率就会越高,码率自然就会越小。压缩速度越慢,压缩的时候压缩算法就会越精细,最后压缩率也会有提高,相同的清晰度,码率也会更小
小结

码率可以是固定的,也可以是变化的。
如果是固定码率,

  1. 编码后的码率小于固定码率,填充数据
  2. 编码后的码率大于固定码率,丢弃细节数据,降低码率

固定码率:
  当然不同的码率其视频效果,文件大小等都是不一样的。在流式播放方案中使用固定码率最为有效。使用固定码率时,比特率在流的进行过程中基本保持恒定并且接近目标比特率,始终处于由缓冲区大小确定的时间窗内。固定码率的缺点在于编码内容的质量不稳定。因为内容的某些片段要比其他片段更难压缩,所以图像的某些部分质量就比其他部分差。此外,固定码率会导致相邻流的质量不同。通常在较低比特率下,质量的变化会更加明显。
  
动态码率:
  动态码率近年来在视频编码处理中应用是比较多的。当编码内容中混有简单数据和复杂数据(例如,在快动作和慢动作间切换的视频)时,动态码率是很有优势的。使用动态码率时,系统将自动为内容的简单部分分配较少的比特,从而留出足量的比特用于生成高质量的复杂部分。这意味着复杂性恒定的内容(例如新闻播音)不会受益于动态码率。对混合内容使用动态码率时,在文件大小相同的条件下,动态码率的输出结果要比固定码率的输出结果质量好得多。在某些情况下,与固定码率文件质量相同的动态码率文件,其大小可能只有前者的一半
  
  一般来说除非要求绝对固定,不然不会填充数据。毕竟浪费带宽。
你的回答考虑地很全面。

原来图像是这么丰富多彩的

RGB

YUV
1.YUV 图像将亮度信息Y 与色彩信息 U、V 分离开来。Y 表示亮度,是图像的总体轮廓,称之为 Y 分量。U、V表示色度,主要描绘图像的色彩等信息,分别称为 U 分量和 V 分量
2.YUV 主要分为 YUV 4:4:4、YUV 4:2:2、YUV 4:2:0 这几种常用的类型

3.YUV 存储方式主要分为两大类:Planar 和 Packed 两种。Planar 格式的 YUV 是先连续存储所有像素点的 Y,然后接着存储所有像素点的 U,之后再存储所有像素点的 V,也可以是先连续存储所有像素点的 Y,然后接着存储所有像素点的 V,之后再存储所有像素点的 U。Packed 格式的 YUV 是先存储完所有像素的 Y,然后 U、V 连续的交错存储


Color Range 这个东西。对于一个 8bit 的 RGB 图像,它的每
一个 R、G、B 分量的取值按理说就是 0~255 的。但是真的是这样的吗?其实不是的。这里就涉及到 Color Range 这个概念。Color Range 分为两种,一种是 Full Range,一种是 Limited Range。Full Range 的 R、G、B 取值范围都是 0~255。而 Limited Range的 R、G、B 取值范围是 16~235。

在做 RGB往 YUV 转换的时候我们需要知道是使用的哪个标准的哪种 Range 做的转换
系统采集出来给到用户的图像就是 YUV 的话,你也需要获取这个 YUV 的存储格式、转换标准和 Color Range。这样才能保证正确地处理 YUV 和 RGB 之间的转换

如何高质量地缩放图像

情形 1:播放窗口与原始图像分辨率不匹配的时候需要缩放。
情形 2:我们在线观看视频时会有多种分辨率可以选择,即需要在一个图像分辨率的基础上缩放出多种不同尺寸的图像出来做编码,并保存多个不同分辨率的视频文件。
情形 3:RTC 场景,有的时候我们需要根据网络状况实时调节视频通话的分辨率。这个也是需要缩放算法来完成的
缩放的基本原理
1.先将目标图像的像素位置映射到原图像的对应位置上,然后把通过插值计算得到的原图像对应位置的像素值作为目标图像相应位置的像素值
2.我们只需要将目标图像中的像素位置(x,y)映射到原图像的(x * w0 / w1,y * h0 / h1),再插值得到这个像素值就可以了,这个插值得到的像素值就是目标图像像素点(x,y)的像素值


三种插值算法
1.最近邻插算法
首先,将目标图像中的目标像素位置,映射到原图像的映射位置。
然后,找到原图像中映射位置周围的 4 个像素。
最后,取离映射位置最近的像素点的像素值作为目标像素。

2.双线性插值算法
取待插值像素周围的 4 个像素,将这 4 个像素值通过一定的运算得到最后的插值像素

线性插值是在两个点中间的某一个位置插值得到一个新的值。线性插值认为,这个需要插值得到的点跟这两个已知点都有一定的关系,并且,待插值点与离它近的那个点更相似。
因此,线性插值是一种以距离作为权重的插值方式,距离越近权重越大,距离越远权重越小。

双线性插值本质上就是在两个方向上做线性插值
双线性插值其实就是三次线性插值的过程

假设我们要插值求的点是 p 点,其坐标为 (x,y)。已知周围 4 个像素分别是 a、b、c、d。我们先通过 a 和 b 水平线性插值求得 m,再通过 c、d 水平插值求得 n。有了 m 和 n之后,再通过 m、n 垂直插值求得 p 点的像素值


举例
720P 放大到 1080P 为例,那么 1080P 图像中的目标像素点(2,2)的双线性插值过程
首先,将目标像素点(2,2)映射到原图像的(1.33,1.33)位置,对应下面图中的点p。找到(1.33,1.33)周围的 4 个像素(1,1)、(2,1)、(1,2)和(2,2),分别对应图中的点a、b、c和d。

先通过这 4 个像素插值得到中间像素 m 和 n 的像素值。m 和 n 的坐标分别为(1.33,1)和(1.33,2)。通过上面的公式可以求得点 p(1.33,1.33)的像素值是:

插值求得(1.33,1.33)的值之后,将其赋值给 1080P 目标图像的(2,2)位置的像素点就可以了。这就是双线性插值的过程

双三次插值算法
基本原理同前两种插值算法差不多,不同的是:
第一,双三次插值选取的是周围的 16 个像素,比前两种插值算法多了 3 倍。
第二,双三次插值算法的周围像素的权重计算是使用一个特殊的 BiCubic 基函数来计算
的。

总结

双三次插值需要周围 16 个像素,对于左上角的点,比如(0.5,0.5),它周围不够 16 个点怎么办呢?

一般将第一行和第一列复制填充一下。

视频究竟是怎么编码压缩的

视频编码的原理
通过帧内预测或者帧间预测去除空间冗余和时间冗余,从而得
到一个像素值相比编码块小很多的残差块。之后我们再通过 DCT 变换将低频和高频信息分离开来得到变换块,然后再对变换块的系数做量化。由于高频系数通常比较小,很容易量化为 0,同时人眼对高频信息不太敏感,这样我们就得到了一串含有很多个 0,大多数情况下是一串含有连续 0 的“像素串”,并且人的观感还不会太明显。这样,最后熵编码就能
把图像压缩成比较小的数据,以此达到视频压缩的目的
宏块
每一帧图像,划分成一个个块来进行编码的,这一个个块在 H264 中叫做宏块,宏块大小一般是16x16(H264、VP8),32x32(H265、VP9),64x64(H265、VP9、AV1),128x128(AV1)这几种。

空间冗余。
比如说将一帧图像划分成一个个 16x16 的块之后,相邻的块很多时候都有比 较明显的相似性,这种就叫空间冗余。
时间冗余。
一个帧率为 25fps 的视频中前后两帧图像相差只有 40ms,两张图像的变化 是比较小的,相似性很高,这种叫做时间冗余。
视觉冗余。
我们的眼睛是有视觉灵敏度这个东西的。人的眼睛对于图像中高频信息的敏感度是小于低频信息的。有的时候去除图像中的一些高频信息,人眼看起来跟不去除高 频信息差别不大,这种叫做视觉冗余。
信息熵冗余。
我们一般会使用Zip 等压缩工具去压缩文件,将文件大小减小,这个对于 图像来说也是可以做的,这种冗余叫做信息熵冗余。

对于一个 YUV 图像,我们把它划分成一个个 16x16 的宏块(以 H264 为例),Y、U、V分量的大小分别是 16x16、8x8、8x8。这里我们只对 Y 分量进行分析(U、V 分量同理)。假设 Y 分量这 16x16 个像素就是一个个数字,我们从左上角开始之字形扫描每一个像素值,则可以得到一个“像素串”。

行程编码
“aaaabbbccccc” 压缩成 “4a3b5c”,字符串由 13 个字节压缩到 7 个字节,这个叫做行程编码 对图像宏块扫描出来的这个“像素串”做同样的行程编码操作
如果想要达到压缩的目的,我们必须要使得编码前的字符串中出现比较多连续相同的字符。这对于图像块也是一样的。我们需要使得扫描出来的“像素串”,也尽量出现连续相同的像素值,最好是一连串数字很小(比如 0)的“像素串”,因为 0 在二进制中只占 1 个位就可以了(有的编码算法是可以做到的,比如指数哥伦布编码,它就可以做到 0 只占用一个位。事实上,算术编码可以做到一个符号只占用 0 点几个位)
帧内预测
是在当前编码图像内部已经编码完成的块中找到与将要编码的块相邻的块。一般就是即将编码块的左边块、上边块、左上角块和右上角块,通过将这些块与编码块相邻的像素经过多种不同的算法得到多个不同的预测块。然后我们再用编码块减去每一个预测块得到一个个残差块。最后,我们取这些算法得到的残差块中像素的绝对值加起来最小的块为预测块。而得到这个预测块的算法为帧内预测模式。

帧间预测
在前面已经编码完成的图像中,循环遍历每一个块,将它作为预测块,用当前的编码块与这个块做差值,得到残差块,取残差块中像素值的绝对
值加起来最小的块为预测块,预测块所在的已经编码的图像称为参考帧。预测块在参考帧中的坐标值 (x0, y0) 与编码块在编码帧中的坐标值 (x1, y1) 的差值 (x0 - x1, y0 - y1) 称之为运动矢量。而在参考帧中去寻找预测块的过程称之为运动搜索。事实上编码过程中真正的运动搜索不是一个个块去遍历寻找的,而是有快速的运动搜索算法的

如何做到将这串像素值变成有很多 0 的“像素串”
人眼对高频信息不太敏感,因为人眼看到的效果可能差别不大,所以我们可以去除一些高频信息
DCT 变换和量化
为了分离图像块的高频和低频信息,我们需要将图像块变换到频域。常用的变换是 DCT 变换

低频信息在左上角,其余的都是高频信息。那么如果我们对变换块的像素值进行“之字形”扫描,这样得到的像素串,前面的就是数值比较大的低频系数,后面就是数值比较小的高频部分
量化
我们让变换块的系数都同时除以一个值,这个值我们称之为量化步长,也就是QStep(QStep 是编码器内部的概念,用户一般使用量化参数 QP 这个值,QP 和 QStep一一对应)QStep越大,得到量化后的系数就会越小。同时,相同的 QStep 值,高频系数值相比低频系数值更小,量化后就更容易变成 0

QStep越大,得到量化后的系数就会越小。同时,相同的 QStep 值,高频系数值相比低频系数值更小,量化后就更容易变成 0。这样一来,我们就可以将大部分高频系数变成 0
有损编码
QStep 越大,损失就越大。QStep 跟 QP 一一对应,也就是说确定
了一个 QP 值,就确定了一个 QStep。所以从编码器应用角度来看,QP 值越大,损失就越大,从而画面的清晰度就会越低。同时,QP 值越大系数被量化成 0 的概率就越大,这样编码之后码流大小就会越小,压缩就会越高
编码器

从上面表格中可以看到,标准越新,最大编码块就越大,块划分的方式也越多,编码模式也就越多。因此压缩效率也会越高,但是带来的编码耗时也越大。所以在选择编码器的时候需要根据自己的实际应用场景来选择,同时还需要考虑专利费的问题。还有一个就是考虑有没有硬件支持的问题。目前 H264 和 H265 的硬件支持已经很好了,AV1 才刚开始,
硬件支持较少,之后会有更多硬编硬件支持。

如果是在性能比较差的机器上编码,最好使用 H264 和 VP8 等速度快的编码器。如果是在比较新的机器上,可以考虑 H265 编码。中等机器如果支持 H265 硬编也是可以考虑的。但有一个问题就是 H265 需要考虑专利费的问题,同时浏览器原生不支持 H265 编码,所以有这方面需求的,最好不要使用 H265,可以考虑使用 VP9,甚至可以考虑AV1。另外,由于 AV1 原生标准就支持屏幕编码的优化,所以屏幕编码场景下可以考虑使
用 AV1 编码

小结
视频编码主要分为熵编码、预测、DCT 变换和量化这几个步骤。
这里你需要注意的是,视频编码实际的步骤是预测、DCT 变换和量化,最后是熵编码。

1.熵编码(以行程编码为例):视频编码中真正实现“压缩”的步骤,主要去除信息熵冗余。在出现连续多个 0 像素的时候压缩率会更高。

2.帧内预测:为了提高熵编码的压缩率,先将当前编码块的相邻块像素经过帧内预测算法得到帧内预测块,再用当前编码块减去帧内预测块得到残差块,从而去掉空间冗余。

3.帧间预测:类似于帧内预测,在已经编码完成的帧中,先通过运动搜索得到帧间预测块,再与编码块相减得到残差块,从而去除时间冗余。

4.DCT 变换和量化:将残差块变换到频域,分离高频和低频信息。由于高频信息数量多但大小相对较小,又人眼对高频信息相对不敏感,我们利用这个特点,使用 QStep 对DCT 系数进行量化,将大部分高频信息量化为 0,达到去除视觉冗余的目的

视频编码过程中,一帧图像能同时进行帧内预测和帧间预测吗?

1.一帧图像即存在空间冗余又存在时间冗余,所以是帧间预测和帧内预测都是可以同时可以在一帧上应用的。这样一个编码的宏块,都会即由本帧内的前面的宏块又由相关帧的预测块影响。
但是I帧是不能进行帧间预测的。因为帧间预测是需要依赖于
参考帧的,这样肯定需要一开始有一个帧是可以独立的编解码的。不然大家都相互依赖了。
2.帧内预测和帧间预测都是以块为基本单元的,而一帧包含多个块,所以,可以将帧间预测与帧内预测施加到同一帧的不同块上
3.I帧只能进行帧内预测,因为I帧需要能够自己独立编解码,如果使用帧间预测就有依赖了。P帧既可以帧间预测又可以帧内预测。

原来你是这样的H264

帧类型


1.IDR 帧之后的帧不能再参考 IDR 帧之前的帧,,如果某一帧编码错误,之后的帧参考了这个错误帧,则也会出错。此时编码一个 IDR 帧,由于它不参考其它帧,所以只要它自己编码是正确的就不会有问题。之前有错误的帧也不会再被用作参考帧,这样就截断了编码错误的传递,且之后的帧就可以正常编 / 解码了

2.有 IDR 这种特殊的 I 帧,也就有普通的 I 帧。普通的 I 帧就是指当前帧只使用帧内预测编码,但是后面的 P 帧和 B 帧还是可以参考普通 I 帧之前的帧。但是这里我要说明一下,一般来说我们不太会使用这种普通 I 帧,大多数情况下还是直接使用 IDR 帧,尤其是在流媒体场景,比如 RTC 场景。只是说如果你非要用这种普通 I 帧,标准也是支持的

GOP
1.从一个 IDR 帧开始到下一个 IDR 帧的前一帧为止,这里面包含的 IDR 帧、普通 I 帧、P 帧和 B 帧,我们称为一个 GOP(图像组)


2.GOP 越大,编码的 I 帧就会越少。相比而言,P 帧、B 帧的压缩率更高,因此整个视频的编码效率就会越高。但是 GOP 太大,也会导致 IDR 帧距离太大,点播场景时进行视频的seek 操作就会不方便

3.在 RTC 和直播场景中,可能会因为网络原因导致丢包而引起接收端的丢帧,大的GOP 最终可能导致参考帧丢失而出现解码错误,从而引起长时间花屏和卡顿

4.GOP 不是越大越好,也不是越小越好,需要根据实际的场景来选择

slice

1.图像内的层次结构就是一帧图像可以划分成一个或多个 Slice,而一个 Slice 包含多个宏块,且一个宏块又可以划分成多个不同尺寸的子块

码流格式
H264 码流有两种格式:一种是 Annexb 格式;一种是 MP4 格式

1.Annexb 格式使用起始码来表示一个编码数据的开始。起始码本身不是图像编码的内容,只是用来分隔用的。起始码有两种,一种是 4 字节的“00 00 00 01”,一种是 3字节的“00 00 01”
(1)“00 00 00”修改为“00 00 03 00”;
(2)“00 00 01”修改为“00 00 03 01”;
(3)“00 00 02”修改为“00 00 03 02”;
(4)“00 00 03”修改为“00 00 03 03”。
同样地在解码端,我们在去掉起始码之后,也需要将对应的字节串转换回来

2.MP4 格式没有起始码,而是在图像编码数据的开始使用了 4 个字节作为长度标识,用来表示编码数据的长度,这样我们每次读取 4 个字节,计算出编码数据长度,然后取出编码数据,再继续读取 4 个字节得到长度,一直继续下去就可以取出所有的编码数据了。

NALU
1.为了能够将一些通用的编码参数提取出来,不在图像编码数据中重复,H264 设计了两个重要的参数集:一个是 SPS(序列参数集);一个是 PPS(图像参数集)
2.SPS 主要包含的是图像的宽、高、YUV 格式和位深等基本信息;
3.PPS 则主要包含熵编码类型、基础 QP 和最大参考帧数量等基本编码信息。
4.如果没有 SPS、PPS 里面的基础信息,之后的 I 帧、P 帧、B 帧就都没办法进行解码。因此 SPS 和 PPS 是至关重要的
** NALU(网络抽象层单元)**
SPS 是一个 NALU、PPS是一个 NALU、每一个 Slice 也是一个 NALU。每一个 NALU 又都是由一个 1 字节的NALU Header 和若干字节的 NALU Data 组成的。而对于每一个 Slice NALU,其 NALU Data 又是由 Slice Header 和 Slice Data 组成

NALU Header
占一个字节 8位

F:forbidden_zero_bit,占 1bit,禁止位,H264 码流必须为 0;
NRI: nal_ref_idc,占 2bits,可以取 00~11,表示当前 NALU 的重要性。参考帧、SPS 和 PPS 对应的 NALU 必须要大于 0;
Type: nal_unit_type,占 5bits,表示 NALU 类型。其取值如下表所示

注意:NALU 类型只区分了 IDR Slice 和非 IDR Slice,至于非 IDR Slice 是普通 I Slice、P Slice 还是 B Slice,则需要继续解析 Slice Header 中的 Slice Type 字段得到

用二进制查看工具打开实际编码后的码流数据

多 Slice 时如何判断哪几个 Slice 是同一帧的?
在 H264 码流中,帧是以 Slice 的方式呈现的,或者可以说在 H264 码流
里是没有“帧“这种数据的,只有 Slice。但是有个问题是,一帧有几个 Slice 是不会告诉你的。也就是说码流中没有字段表示一帧包含几个 Slice。

Slice NALU 由 NALU Header 和 NALU Data 组成,其中 NALU Data 里面就是Slice 数据,而 Slice 数据又是由 Slice Header 和 Slice Data 组成。在 Slice Header 开始的地方有一个 first_mb_in_slice 的字段,表示当前 Slice 的第一个宏块 MB 在当前编码图像中的序号。我们只要解析出这个宏块的序号出来
如果 first_mb_in_slice 的值等于 0,就代表了当前 Slice 的第一个宏块是一帧的第一个宏块,也就是说当前 Slice 就是一帧的第一个 Slice
如果 first_mb_in_slice 的值不等于 0,就代表了当前 Slice 不是一帧的第一个 Slice。
直到找到下一个 first_mb_in_slice 为 0 的 Slice,就代表新的一帧的开始,那么其前一个 Slice 就是前一帧的最后一个 Slice 了


其中,first_mb_in_slice 是以无符号指数哥伦布编码的,需要使用对应的解码方式才能解码出来。但是有一个小技巧,如果只是需要判断 first_mb_in_slice 是不是等于 0,不需要计算出实际值的话,只需要通过下面的方式计算就可以了。


如何从 SPS 中获取图像的宽高?
在编码端编码一个视频的时候,我们是需要设置分辨率告诉编码器图像的实际宽高的。但是解码器是不需要设置分辨率的,那我们在解码端或者说接收端如何知道视频的分辨率大小呢

在编码器编码的时候会将分辨率信息编码到 SPS 中。在 SPS 中有几个字段用来表示分辨率的大小。我们可以解码出这几个字段并通过一定的规则计算得到分辨率的大小。这几个字段分别是

这几个字段都是通过无符号指数哥伦布编码的,需要先解码出来。解码得到具体值之后,通过以下方法就可以得到分辨率了


如何计算得到 QP 值?
量化过程是引入失真最主要的环节。而量化最主要的参数就是 QP 值,并且 QP 值的大小严重影响到编码画面的清晰度。因此 QP 值非常重要

在 PPS 中有一个全局基础 QP,字段是 pic_init_qp_minus26。当前序列中所有依赖该PPS 的 Slice 共用这个基础 QP,且每一个 Slice 在这个基础 QP 的基础上做调整。在Slice Header 中有一个 slice_qp_delta 字段来描述这个调整偏移值。更进一步,H264 允许在宏块级别对 QP 做更进一步的精细化调节。这个字段在宏块数据里面,叫做mb_qp_delta。

如果需要得到 Slice 级别的 QP 则只需要考虑前两个 QP 相关字段。如果需要计算宏块
QP,则需要三个都考虑。但是宏块 QP 需要解析整个 Slice 数据,计算量大。一般我们直
接计算到 Slice QP 就可以了。计算方法如下:


小结
在一个视频图像序列中,我们将其划分成一个个 GOP。GOP 包含一个 IDR 帧到下一个 IDR 帧的前一帧中的所有帧。GOP 的大小选择需要根据实际应用场景来选择,一般 RTC 和直播场景可以稍微大一些,而点播场景一般小一些

在 H264 中,每一帧图像又可以分为 I 帧、P 帧和 B 帧,而 I 帧又包含了普通 I 帧和 IDR帧。帧可以划分为一个或者多个 Slice,并且最后帧都是以 Slice 的方式在码流中呈现。同时 H264 码流中除了 Slice 数据之外,还有 SPS 和 PPS 两个参数集,分别用来存放基础图像信息和基础编码参数。SPS 和 PPS 非常重要,如果丢失了,将无法进行解码。

每一个 Slice 和 SPS、PPS 都是通过 NALU 来封装的,且 NALU 含有一个 1 字节的NALU Header。我们可以通过 NALU Header 中的 NALU Type 来判断 NALU 的类型。同时,每一个 NALU 的分隔有两种方式:一种是 Annexb 格式,通过使用起始码分隔;一种是 MP4 格式,通过一个 4 字节的长度来表示 NALU 的大小,从而起到分隔的作用。

为什么有B帧的时候延迟会高

1.B帧需要双向参考,pts 和 dts 不一致。因此需要等待后面的 p 帧解码后才能继续,从而引入了延时。编码的时候也是一样的,需要先等后面的P帧先编码才能编码B帧

如何减少空间冗余

一幅图像中相邻像素的亮度和色度信息是比较接近的,并且亮度和色度信息也是逐渐变化的,不太会出现突变。也就是说,图像具有空间相关性

帧内预测就是利用这个特点,帧内预测通过利用已经编码的相邻像素的值来预测待编码的像素值,最后达到减少空间冗余的目的

我们是通过已经编码了的像素值去预测待编码的像素值。你可能会问,已经编码了的像素值变成码流了,不再是一个个像素了,怎么去预测待编码的像素呢?其实已经编码了的像素是会重建成重建像素,用来做之后待编码块的参考像素的。你可以认为是已经编码的块会解码成像素用来做参考像素

不同块大小的帧内预测模式
视频编码是以块为单位进行的。在 H264 标准里面,块分为宏块和子块。宏块的大小是 16 x 16(YUV 4:2:0 图像亮度块为 16 x 16,色
度块为 8 x 8)。在帧内预测中,亮度宏块可以继续划分成 16 个 4 x 4 的子块。因为图像中有的地方细节很多,我们需要划分成更小的块来做预测会更精细,所以会将宏块再划分成 4 x 4 的子块。

帧内预测是根据块的大小分为不同的预测模式的。还有一个点就是亮度块和色度块的预测是分开进行的

  1. 宏块大小是 16 x 16,其中亮度块为 16 x 16,色度块为 8 x 8;
  2. 帧内预测中亮度块和色度块是分开独立进行预测的,即亮度块参考已编码亮度块的像素,而色度块参考已编码色度块的像素;
  3. 16 x 16 的亮度块可以继续划分成 16 个 4 x 4 的子块。

所以,我们在实际帧内预测的时候就会分为:4 x 4 亮度块的预测、16 x 16 亮度块的预测、8 x 8 色度块的预测(注意亮度 8 x 8 模式和 I_PCM 模式很少使用,我们这里不做讨论)。

4 x 4 的块帧内预测模式,基本包含亮度 16 x 16 和色度 8 x 8的模式,4 x 4 亮度块的帧内预测模式总共有 9 个。其中有 8 种方向模式和一种 DC 模式,且方向模式指的是预测是有方向角度的。

1.Vertical 模式
当前编码亮度块的每一列的像素值,都是复制上边已经编码块的最
下面那一行的对应位置的像素值
Vertical 模式得到的预测块同一列中的像素值都是一样的。该模式得到的块就叫做Vertical 预测块。注意,该模式只有在上边块存在的时候才可用,如果不存在则该模式不可用。比如图像最上边的块就没有可参考的块存在。

2.Horizontal 模式
当前编码亮度块的每一行的像素值,都是复制左边已经编码块的
最右边那一列的对应位置的像素值Horizontal 模式得到的预测块同一行的像素值都是一样的,该模式得到的块就叫做 Horizontal 预测块。注意,该模式只有在左边块存在的时候才可用,如果不存在则该模式不可用。比如图像最左边的块就没有可参考的块存在

3.DC 模式
当前编码亮度块的每一个像素值,是上边已经编码块的最下面那一行和左
边已编码块右边最后一列的所有像素值的平均值。注意,DC 模式预测得到的块中每一个像素值都是一样的。DC 模式得到的块就叫做 DC 预测块。

4.Diagonal Down-Left 模式
Diagonal Down-Left 模式是上边块和右上块(上边块和右上块有可能是一个块,因为可能是一个 16 x 16 的亮度块,意思理解就可以)的像素通过插值得到。如果上边块和右上块不存在则该模式无效

5.Diagonal Down-Right 模式
Diagonal Down-Right 模式需要通过上边块、左边块和左上角对角的像素通过插值得到。如果这三个有一个不存在则该模式无效。

6.Vertical-Right 模式
Vertical-Right 模式是需要通过上边块、左边块以及左上角对角的像素插值得到的。必须要这三个都有效才能使用,否则该模式无效。

  1. Horizontal-Down 模式
    Horizontal-Down 模式需要通过上边块、左边块以及左上角对角的像素插值得到。必须要这三个都有效才能使用,否则该模式无效。

8.Vertical-Left 模式
Vertical-Left 模式是需要通过上边块和右上块(上边块和右上块有可能是一个块,因为可能是一个 16 x 16 的亮度块,意思理解就可以)最下面一行的像素通过插值得到

9.Horizontal-Up 模式
Horizontal-Up 模式是需要通过左边块的像素通过插值得到的。如果左边块不存在,则该模式不可用。

16 x 16 亮度块的帧内预测模式
16 x 16 亮度块总共有4种预测模式。它们分别是Vertical模式,Horizontal模式、DC 模式和Plane 模式

Plane 预测块的每一个像素值,都是将上边已编码块的最下面那一行,和左边已编码块右边最后一列的像素值经过下面公式计算得到的

8 x 8 色度块的帧内预测模式跟 16 x 16 亮度块的是一样的 与 16 x 16 亮度块不同的是,块大小不同,所以参考像素值数量会不同。

帧内预测模式的选择
学习了这么多的模式,而每一个块却只能有一种帧内预测模式。那我们怎么确定一个块到底使用哪种模式呢?编码基础弄明白了之后可以阅读一下 x264 的代码
学习了这么多的模式,而每一个块却只能有一种帧内预测模式。那我们怎么确定一个块到底使用哪种模式呢?我们这边先把思路讲一讲,具体细节不展开。等到你把编码基础弄明白了之后可以阅读一下 x264 的代码,里面有关于具体如何去选择模式的方法
对于每一个块或者子块,我们可以得到预测块,再用实际待编码的块减去预测块就可以得到残差块。主要有下面 3 种方案来得到最优预测模式:

第一种方案,先对每一种预测模式的残差块的像素值求绝对值再求和,称之为 cost,然后取其中残差块绝对值之和也就是 cost 最小的预测模式为最优预测模式。
第二种方案,对残差块先进行 Hadamard 变换(在 DCT 变换和量化那节课中会介绍),变换到频域之后再求绝对值求和,同样称为 cost,然后取 cost 最小的预测模式为最优预测模式。
第三种方案,也可以对残差块直接进行 DCT 变换量化熵编码,计算得到失真大小和编码后的码流大小,然后通过率失真优化(作为课外内容自行学习,这里不展开讨论)的方法来选择最优预测模式

率失真优化的思想
我们知道预测之后经过 DCT 变换再量化会丢失高频信息。一般来说 QP 越大,丢失的信息越多,失真就越大,但是码流大小也越小;反之,QP 越小,丢失的信息越少,但是码流大小就越大。这是一个跷跷板。我们一般会在失真和码流大小之间平衡,尽量找到在一定码率下,失真最小的模式作为最优的预测模式,这就是率失真优化的思想。

其实还有很多不同的方案,比如有的为了加速模式选择的过程,率失真计算的时候,只会进行 DCT 变换和量化,不会进行熵编码。码流大小直接通过 QP 值估算或者使用预测模式的大小来代替。这些方案都可以,具体看编码器的实现。一般来说,选择过程越精细效果越好,但是速度会越慢。

通过上面讲的这些方法我们找到了每一个 4 x 4 块的最优模式之后,将这 16 个 4 x 4 块的cost 加起来,与 16 x 16 块的最小 cost 对比,选择 cost 最小的块划分方式和预测模式作为帧内预测模式。

小结

我们还简单介绍了一下预测模式的选择方法,主要有计算残差块绝对值之和、将残差块做 Hadamard 变换之后再求和、率失真优化等几种方案来得到 cost,然后我们cost 最小的模式作为帧内预测模式

如何减少时间冗余

1.在帧内预测中,我们是在当前编码的图像内寻找已编码块的像素作为参考像素计算预测块。而帧间预测是在其他已经编码的图像中去寻找参考像素块的。这正是帧内预测和帧间预测的区别先过。。。。
2.帧间预测是可以在多个已经编码的图像里面去寻找参考像素块的,我们称之为多参考。多参考和单参考(只在一帧图像里面寻找参考像素块)其实底层的原理是一样的,只是多参考需要多搜索几个参考图像去寻找参考块而已,所以我们讲解的时候就使用单参考讲解
3.帧间预测既可以参考前面的图像也可以参考后面的图像(如果参考后面的图像,后面的图像需要提前先编码,然后再编码当前图像)。只参考前面图像的帧我们称为前向参考帧,也叫 P 帧;参考后面的图像或者前面后面图像都参考的帧,我们称之为双向参考帧,也叫做 B 帧。B 帧相比 P 帧主要是需要先编码后面的帧,并且 B 帧一个编码块可以有两个预测块,这两个预测块分别由两个参考帧预测得到,最后加权平均得到最终的预测
块。P 帧和 B 帧的底层逻辑基本是一样的
帧间编码
以 H264 标准为基础来聊聊 P 帧的帧间编码过程
块大小
帧内预测有亮度 16 x 16、亮度 4 x 4 和色度 8 x 8 这几种块。类似
地,在帧间预测也一样有不同的块和子块大小。相比帧内预测,帧间预测的块划分类型要多很多。宏块大小 16 x 16,可以划分为 16 x 8,8 x 16, 8 x 8 三种,其中 8 x 8 可以继续划分成 8 x 4,4 x 8 和 4 x 4,这是亮度块的划分。在 YUV 4:2:0 中,色度块宽高大小都是亮度块的一半。亮度宏块的划分方式如下图所示

参考帧和运动矢量
在已经编码的帧里面找到一个块来作为预测块,这个已经编码的帧
称之为参考帧。在 H264 标准中,P 帧最多支持从 16 个参考帧中选出一个作为编码块的参考帧,但是同一个帧中的不同块可以选择不同的参考帧,这就是多参考

通常在 RTC 场景中,比如 WebRTC 中,P 帧中的所有块都参考同一个参考帧。并且一般会选择当前编码帧的前一帧来作为参考帧

这是因为自然界的运动一般是连续的,同时在短时间之内的变化相对比较小,所以前面的帧通常是最接近当前编码帧的,并且两者的差距比较小。因此,我们比较容易从前一帧中找到一个跟当前编码块差距很小的块作为预测块,这样编码块减去预测块得到的残差块的像素值很多都是 0,压缩效率是不是就很高了

虽然运动变化比较小,但是还是有变化啊,比如说下图中的场景。

图中的小车在往前开,树是不动的。我们可以看到车相对于树的距离是变化的。那我们怎么来表示这个变化呢?
比如说上面两幅图像中,小车从前一幅图像中的(32,80)的坐标位置,变化到当前图像(80,80)的位置,向前行驶了 48 个像素。很明显,如果我们选用(32,80)这个块作为当前(80,80)这个编码块的预测块的话,是不是就可以得到全为 0 像素的残差块了?这是因为小车本身是没有变化的,变化的只是小车的位置。这个位置变化我们怎么表示呢?我们用运动矢量来表示。我们称(32 - 80, 80 - 80)也就是(-48, 0)为运动矢量。
我们先把运动矢量编码到码流当中,这样解码端只要解码出运动矢量,使用运动矢量就可以在参考帧中找到预测块了,我们再解码出残差(如果有的话),残差块加上预测块就可以恢复出图像块了用运动矢量来表示编码帧中编码块和参考帧中的预测块之间的位置的差值

运动搜索
运动搜索的目标就是在参考帧中找到一个块,称之为预测块,且这个预测块与编码块的差距最小。从计算机的角度来说就是,编码块跟这个预测块的差值,也就是残差块的像素绝对值之和(下面我们用 SAD 表示残差块的像素绝对值之和)最小。全搜索算法一定可以搜索到最相似的预测块,但费时

从参考帧中第一个像素开始,将一个个 16 x16 大小的块都遍历一遍。我们总是可以找到差距最小的块。这种方法我们称之为全搜索算法

搜索算法中每一个搜索的点都是搜索块的左上角像素点

  1. 钻石搜索算法
    以一个菱形的模式去寻找最优预测块

    第一步,以绿色点(起点)为中心点,搜索绿色点和旁边蓝色线连接的 4 个点,得到的最佳匹配点为橙色点,非中心点。
    第二步,再以橙色点为中心点,搜索橙色点和旁边黄色线连接的 4 个点,最佳匹配点是中心点橙色点,搜索完毕,橙色点为最佳匹配点。
    2.六边形搜索算法

    第一步,以绿色点(起点)为中心点,搜索中心点和旁边蓝色线连接的 6 个点,得到的最佳匹配点为橙色点,非中心点。
    第二步,再以橙色点为中心点,搜索橙色点和旁边黄色线连接的 6 个点,最佳匹配点是是中心点橙色点。
    第三步,再以橙色点为中心点,搜索橙色点和旁边蓝色线连接菱形的 4 个点,最佳匹配点为黑色点。
    第四步,还是以橙色点为中心点,搜索旁边红色线连接的正方形的 4 个点,并与菱形搜索得到的最佳匹配点黑色点比较,找到最后的最佳匹配点为红色点,搜索完毕。
    通过上面的快速搜索算法我们就能够得到编码块在参考帧中的最佳匹配点,以最佳匹配点为左上角像素的块就是预测块,并且预测块左上角像素在参考帧中的坐标 (x1, y1) 与编码块在当前编码帧中的坐标 (x0, y0) 的差值(x1 - x0, y1 - y0)就是运动矢量
    搜索的起始点怎么确定
    搜索的起始点可以使用当前编码块的左边块、右边块、左上角块和右上角块的运动矢量预测得到一般一个块最大也就 16 x 16 的大小,而运动的物体一般远大于这个大小,所以相邻块的运动方向大多数是很相似的。因此,我们一般会通过相邻已经编码块的运动矢量来预测当前块的运动矢量。这个预测的运动矢量也经常用做搜索的起点

小车的运动是连续的,如果小车向前
行驶了 48.5 个像素点呢?又或者是向前行驶了 48.25 个像素点呢?运动矢量选择(-48.5, 0)或者(-48.25,0)吗?可是 0.5 个像素点是什么样的,0.25 个像素点又是什么样的?图像上都没有这种像素点啊,怎么办呢?

其实没关系的,我们还是可以使用(-48,0)作为运动矢量,只是预测块中的小车位置与我们编码块中的小车位置会相差个 0.5 或者 0.25 个像素,得到的残差会大一些,压缩效率稍微低一些,问题也不大


为了能够解决这种半个像素或者 1/4 个像素的运动带来的压缩效率下降的问题,我们通过对参考帧进行半像素和 1/4 像素插值(统称为亚像素插值)的方式来解决用插值的方式将半像素和 1/4 像素算出来,也当作一个像素
插值得到的小车跟原始的小车的对应像素点的像素值并不是完全一样的,毕竟插值得到的像素点是利用滤波算法加权平均得到的
因此,半像素插值得到的预测块并不一定就比整像素预测块的残差小。只是我们多了很多个半像素预测块和 1/4 像素预测块的选择,所以我们可以在整像素预测块、半像素预测块和 1/4 像素预测块里面选择一个最好的

亚像素精度运动搜索
1.先通过快速搜索算法进行整像素运动搜索算法得到整像素的运动矢(就是我们在运动搜索小节中讲述的内容)。
2. 对参考帧进行半像素和 1/4 像素插值。
3.以整像素运动矢量指向的整像素为起点,进行钻石搜索算法,分别求得中心点以及上、下、左、右四个半像素点对应预测块的残差块,得到 SAD 值。取 SAD 值最小的点为最佳匹配点。
4.以半像素运动搜索的最佳匹配点为起点,分别求得中心点以及上、下、左、右四个 1/4像素点对应预测块的残差块,得到 SAD 值,并取最小的点为最佳匹配点。通过上面亚像素搜索算法得到的最佳匹配点就可以得到最后的运动矢量了。假设整像素运动矢量为 (a0, b0),半像素最佳匹配点相对于整像素最佳匹配点的运动矢量为 (a1,b1),1/4 像素最佳匹配点相对于半像素最佳匹配点的运动矢量为 (a2, b2),则最后运动矢量(a,b)的值的计算方法如下:

相当于原先的运动矢量乘以了 4,即原先 1/4 像素的 0.25 变成了 1,0.5 像素变成了 2,1个像素则变成了 4。这主要是因为我们不用小数形式来表示运动矢量。因为浮点型数据会有精度误差,所以我们通过乘以 4 把它变成整数。
通过上面的整像素运动搜索和亚像素精度运动搜索,我们就得到了最终的运动矢量了。有了运动矢量之后,我们需要将运动矢量的信息也编码到码流中,并且解码的时候直接取出来用就可以在参考帧中把预测块找出来了。那运动矢量是直接编码到码流中的吗?其实不是的
运动矢量预测
运动矢量跟我们的编码块一样不是直接编码进去的,而是先用周围相邻块的运动矢量预测一个预测运动矢量,称为 MVP。将当前运动矢量与 MVP 的残差称之为 MVD,然后编码到码流中去的。解码端使用同样的运动矢量预测算法得到 MVP,并从码流中解码出运动矢量残差 MVD,MVP+MVD 就是运动矢量了
以 16 x 16 宏块为例:

1.取当前编码宏块的左边块 A、上边块 B、右上块 C。如果右上块不存在或者参考帧与当前编码宏块不同(多参考的时候会存在),则使用左上块 D 替换 C,即 C = D。
2.求得 A、B、C 块的参考帧有多少个与当前编码块的参考帧相同,记为 count。
3.如果 count > 1,则取 A、B、C 块的运动矢量的中值(就是 A、B、C 块运动矢量的 3个 x 和 3 个 y 分别取中间值作为 MVP 的 x 和 y)
4.如果 count = 1,则直接将这个块的运动矢量作为 MVP。
5.如果 count = 0,并且 B、C 都不存在,A 存在的话,则直接将 A 的运动矢量作为MVP
6.如果上述条件都不满足,则取 A、B、C 块运动矢量的中值
SKIP 模式
如果运动矢量就是 MVP,也就是说 MVD 为 (0,0),同时,残差块经过变换量化后系数也都是等于 0,那么当前编码块的模式就是 SKIP

相比于 SKIP 模式,其它模式要不就是 MVD 不为 0,要不就是量化后的残差系数不为 0,或者两者都不为 0。所以说 SKIP 模式是一种特例,由于 MVD 和残差块都是等于 0,因此压缩效率特别高。

比如说 P 帧中的静止部分,前后两帧不会变化,运动矢量直接为 0,而且残差块像素值本身因为几乎没有变化基本为 0,只有少部分噪声引起的比较小的值,量化后更是全部变成了 0。这种图像中的静止部分或者是图像中的背景部分大多数时候都是 SKIP 模式。这种模式非常省码率,且压缩效率非常高。因为需要编码的信息非常少,所以单独在这里跟你讨论一下

帧间模式的选择
编码块帧间模式的选择其实就是参考帧的选择、运动矢量的确定,以及块大小(也就是块划分的方式)的选择,如果 SKIP 单独拿出来算的话就再加上一个判断是不是 SKIP 模式。我们主要是确定这 4 个东西

之前的讨论当中我们都是以当前编码帧的前一帧作为参考帧的,也就是说是单参考的,不涉及到参考帧的选择。其实,如果是多参考的话,编码块在选择参考帧的时候只需要遍历每一个参考帧进行块划分,然后再对每一个块进行运动搜索得到运动矢量就可以了。跟单参考相比就是多了一个参考帧遍历的操作。所以我们这里还是以单参考帧的方式来讲讲帧间模式的选择过程

注意,帧间模式的选择大多数是看编码器的实现的,并且不同编码器实现都会不一样,所以我们只是讲讲其中一种模式选择的思路,具体的细节各个编码器都各不相同。具体选择过程如下:
1.首先判断当前宏块是不是可以作为 SKIP 块(通过相邻已经编码的块是不是存在 SKIP块,和当前块使用 MVP 做运动矢量之后,残差块变换量化后是不是都为 0 等算法来判断),如果可以作为 SKIP 块则模式选择结束,不再进行下面的划分了。
2.宏块大小为 16 x 16。首先不划分宏块,直接使用 16 x 16 大小的块,在参考帧中进行运动搜索,得到运动矢量和预测块,通过 MVP 求得 MVD,通过预测块求得残差块,并求得残差块的 SATD 值(残差块经过 Hadamard 变换之后求绝对值再求和),估计MVD 的编码后占用的字节数,将两个值加起来作为 cost16x16。
3.将 16 x 16 块划分成 4 个 8 x 8 的子块,分别进行运动搜索,并求得每一个 8 x 8 子块的 MVD 和残差块,最后分别得到 4 个子块的 cost8x8
(1)如果 4 个 8 x 8 子块的 cost8x8 之和小于 16 x 16 块的 cost16x16 的话,我们再分别对每一个 8 x 8 子块划分成 4 个 4 x 4 子块,同样分别进行运动搜索,得到每一个 4 x 4子块的 cost4x4。
如果 4 个 cost4x4 之和小于 cost8x8,则将 8 x 8 块划分成 4 x 8 和 8 x 4 两种子块分别求得 cost4x8 和 cost8x4,再根据 4 个 cost4x4、2 个 cost4x8 和 2 个 cost8x4 的大小,选择最终的 8x8 划分的方式,并将对应的 cost 值更新到 cost8x8。否则不划分 8 x 8 子块。
(2)如果 4 个 8 x 8 子块的最新的 cost8x8 之和还是小于 cost16x16 的话,则再将 16 x16 划分成两个 8 x 16 和 16 x 8 子块,并分别求得 cost8x16 和 cost16x8,对比 8x8、16x8、8x16 的 cost 值,并决定最终 16 x 16 块的划分方式。
(3)否则的话,不划分 16 x 16 的块。
4.得到了编码宏块的帧间模式之后,我们还需要对编码宏块进行帧内模式的选择。是的,没错。在 P 帧和 B 帧中的宏块也是可以使用帧内模式的,所以我们需要看是帧间模式cost 更小还是帧内模式 cost 更小。这也回答了我们在第 4 节课里留的思考题。如果帧内模式更小则使用帧内模式;如果是帧间模式更小则使用帧间模式。但是一般来说 P 帧和 B 帧宏块决策出来绝大多数还是帧间模式的。
小结
1.宏块的划分。为了能够更准确的找到预测块,我们可以将 16 x 16 的宏块继续划分成更小的子块来做运动搜索。因为图像有的地方静止的背景画面或者平坦的区域可以直接选用最大的块来搜索预测块;而有的地方细节很多,图像中的物体运动方向也各不相同,可能就需要划分成更小的块来做运动搜索。这样每一个块都拥有自己独立的运动矢量,
并且得到的预测块更接近于编码块,从而有利于提高压缩效率。<

如何攻克 Android 调试难题?| 技术头条

如何攻克 Android 调试难题?| 技术头条

作者 | 小南瓜地瓜
责编 | 郭芮

最近在调试项目的亮屏速度,我们希望在按下power键后到亮屏这个时间能达到500MS以内,在Rockchip 3399和3288上面的时间都不能达到要求,因此引发了一系列的调试之路。


如何攻克 Android 调试难题?| 技术头条

计算按下power键到亮屏的时间



Android 唤醒时间统计


如何攻克 Android 调试难题?| 技术头条

刚开始的时候,我只在android阶段统计时间,也能看到时间的差异,但是不是最准确的,我统计的时间日志如下:


01-18 09:13:40.992 683 772 D SurfaceControlExcessive delay in setPowerMode(): 743ms
01-18 09:13:45.304 683 772 D SurfaceControlExcessive delay in setPowerMode(): 757ms
01-18 09:13:49.559 683 772 D SurfaceControlExcessive delay in setPowerMode(): 725ms
01-18 09:18:27.461 683 772 D SurfaceControlExcessive delay in setPowerMode(): 741ms
01-18 09:18:32.766 683 772 D SurfaceControlExcessive delay in setPowerMode(): 743ms
01-18 09:18:35.861 683 772 D SurfaceControlExcessive delay in setPowerMode(): 745ms
01-18 09:18:38.345 683 772 D SurfaceControlExcessive delay in setPowerMode(): 733ms



Kernel从Power到亮屏的时间统计


后来同事中的精英古总在他的代码上加入了从按下Power键到亮屏的时间,直接通过printk打印,代码如下:


diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
old mode 100644
new mode 100755
index 17c3b94..2b39662
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -504,6 +504,7 @@ static int panel_simple_enable(struct drm_panel *panel)
        }

        p->enabled = true;
+       printk("%s exit\n", __func__);

        return 0;
 }
diff --git a/drivers/input/keyboard/rk_keys.c b/drivers/input/keyboard/rk_keys.c
old mode 100644
new mode 100755
index fed5ced..537b599
--- a/drivers/input/keyboard/rk_keys.c
+++ b/drivers/input/keyboard/rk_keys.c
@@ -134,6 +134,10 @@ static void keys_timer(unsigned long _data)
                key_dbg(pdata, "%skey[%s]: report event[%d] state[%d]\n",
                        button->type == TYPE_ADC ? "adc" : "gpio",
                        button->desc, button->code, button->state);
+               if(strcmp(button->desc, "power") == 0)
+               printk("%skey[%s]: report event[%d] state[%d]\n",
+                       button->type == TYPE_ADC ? "adc" : "gpio",
+                       button->desc, button->code, button->state);
                input_event(input, EV_KEY, button->code, button->state);
                input_sync(input);
        }



如何攻克 Android 调试难题?| 技术头条

统计每个驱动的resume函数调用时间



上面的时间对我们调试非常有用,然后就需要细分到每个驱动的resume函数执行的时间,用的方法是我之前写过的,大概统计了下TP,LCD,sensor的resume时间,发现TP和LCD占用的时间非常多,然后跟同事一起看了下,同事把TP resume里面的代码用工作队列实现后速度明显有了提升。


然后有很长一段时间不知道干嘛,想打印其他每个驱动的resume时间,一直没找到方法,后面看到一个代码,非常有用。


kernel/drivers/base/power/main.c

static void dpm_show_time(ktime_t starttime, pm_message_t state, char *info)                                                       
{                
    ktime_t calltime;
    u64 usecs64;
    int usecs;

    calltime = ktime_get();
    usecs64 = ktime_to_ns(ktime_sub(calltime, starttime));
    do_div(usecs64, NSEC_PER_USEC);
    usecs = usecs64;
    if (usecs == 0)
        usecs = 1
    pr_info("PM: %s%s%s of devices complete after %ld.%03ld msecs\n",
        info ?: "", info ? " " : "", pm_verb(state.event),
        usecs / USEC_PER_MSEC, usecs % USEC_PER_MSEC);
}   


这个函数用来打印resume的函数消耗的时间,但是如何去触发打印这个函数呢?


  • 一定保证设备进入深度睡眠,串口也进入深度睡眠,没有任何打印后。

  • 执行以下命令:

echo N > /sys/module/printk/parameters/console_suspend
//使控制台在suspend最后才关闭,这样可以打印出休眠过程完整信息
echo 1 > /sys/power/pm_print_times
//使能调试变量



打印的LOG类似下面的:


[ 37.031413] bcmsdh_sdmmc_resume Exit
[ 37.082174] PMresume of devices complete after 78.589 msecs
[ 37.085277] [BT_RFKILL]: ** disable irq
[ 37.087645] Restarting tasks ... 



如何攻克 Android 调试难题?| 技术头条

修改Lcd配置减小resume时间



古总在调试过程中展现了非常厉害的功底,第一步就是修改了LCD的参数,让亮屏时间加快。修改如下:


--- a/arch/arm/boot/dts/rk3288-pad.dts
+++ b/arch/arm/boot/dts/rk3288-pad.dts
@@ -169,10 +169,10 @@
                dsi,lanes = <4>;

                prepare-delay-ms = <20>;
-               init-delay-ms = <20>;
-               enable-delay-ms = <100>;
-               disable-delay-ms = <20>;
-               unprepare-delay-ms = <20>;
+               //init-delay-ms = <20>;
+               enable-delay-ms = <1>;
+               disable-delay-ms = <1>;
+               unprepare-delay-ms = <1>;
                panel-init-sequence = [
                        15 32 02 8F A5
                        15 01 02 83 00




如何攻克 Android 调试难题?| 技术头条

修改DRM 超时时间减小唤醒时间



这是最关键的,DRM框架非常复杂,RK也是从开源的DRM移植过来使用,在DRM部分有个时间超时导致问题,最终跟RK拿到最新的patch让唤醒时间直接加速500MS。


我们在日志下发现问题,并给询问了RK,最终发现这部分代码没有更新到最新的部分。


hi rk:为什么亮屏的时候有时候会打印这句VOP等待超时?请问下这是什么意思。

[ 1211.293492] rockchip-vop ff930000.vop: wait win close timeout

[ 1211.293514] rockchip-vop ff930000.vop: [drm:vop_crtc_enable] Update mode to 1200*1920, close all win

有时候却不会打印。

[ 1216.423283] rockchip-vop ff930000.vop: [drm:vop_crtc_enable] Update mode to 12001920, close all win [ 1223.899741] rockchip-vop ff930000.vop: [drm:vop_crtc_enable] Update mode to 12001920, close all win

[ 1234.386252] rockchip-vop ff930000.vop: [drm:vop_crtc_enable] Update mode to 1200*1920, close all win


patch代码如下:


--- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
+++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c
@@ -139,6 +139,9 @@
 #define to_vop_win(x) container_of(x, struct vop_win, base)
 #define to_vop_plane_state(x) container_of(x, struct vop_plane_state, base)

+/*add by VENDOR_PATCH for seep up the drm vop driver at 2018/1/18 for RK Defect #191554, VENDOR_PATCH PAD100-193*/
+#define VENDOR_PATCH
+
 struct vop_zpos {
        int win_id;
        int zpos;
@@ -868,9 +871,15 @@ static void vop_disable_all_planes(struct vop *vop)

        vop_disable_allwin(vop)
;
        vop_cfg_done(vop);
+#ifdef VENDOR_PATCH
        ret = readx_poll_timeout_atomic(vop_is_allwin_disabled,
                                        vop, active, active,
+                                       0100 * 1000);
+#else
+       ret = readx_poll_timeout_atomic(vop_is_allwin_disabled,
+                               vop, active, active,
                                        0500 * 1000);
+#endif
        if (ret)
                dev_err(vop->dev, "wait win close timeout\n");
 }
@@ -2215,20 +2224,36 @@ static size_t vop_crtc_bandwidth(struct drm_crtc *crtc,
        u16 htotal = adjusted_mode->crtc_htotal;
        u16 vdisplay = adjusted_mode->crtc_vdisplay;
        int clock = adjusted_mode->crtc_clock;
+#ifndef VENDOR_PATCH
        struct vop *vop = to_vop(crtc
)
;
        const struct vop_data *vop_data = vop->data;
+#endif
        struct vop_plane_state *vop_plane_state;
        struct drm_plane_state *pstate;
        struct vop_bandwidth *pbandwidth;
        struct drm_plane *plane;
        u64 bandwidth;
        int i, cnt = 0;
+#ifdef VENDOR_PATCH
+       int plane_num = 0;
+#endif

        if (!htotal || !vdisplay)
                return 0;

+#ifndef VENDOR_PATCH
        pbandwidth = kmalloc_array(vop_data->win_size, sizeof(*pbandwidth),
                                   GFP_KERNEL);
+#else
+       for_each_plane_in_state(state, plane, pstate, i) {
+               if (pstate->crtc != crtc || !pstate->fb)
+                       continue;
+               plane_num++;
+       }
+       pbandwidth = kmalloc_array(plane_num, sizeof(*pbandwidth),
+                                  GFP_KERNEL);
+#endif
+
        if (!pbandwidth)
                return -ENOMEM;

@@ -2421,7 +2446,10 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
        rockchip_set_system_status(sys_status)
;
        mutex_lock(&vop->vop_lock);
        vop_initial(crtc);
-
+#ifdef VENDOR_PATCH
+       vop_disable_allwin(vop);
+       VOP_CTRL_SET(vop, standby, 0);
+#endif
        VOP_CTRL_SET(vop, dclk_pol, 1);
        val = (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) ?
                   0 : BIT(HSYNC_POSITIVE);
@@ -2549,8 +2577,9 @@ static void vop_crtc_enable(struct drm_crtc *crtc)
        /*
         * enable vop, all the register would take effect when vop exit standby
         */

+#ifndef VENDOR_PATCH
        VOP_CTRL_SET(vop, standby, 0)
;
-
+#endif
        enable_irq(vop->irq);
        drm_crtc_vblank_on(crtc);
        mutex_unlock(&vop->vop_lock);



如何攻克 Android 调试难题?| 技术头条

修改QOS相关代码



QOS为Quality Of Service(服务质量)的简称,对PM QoS而言,表示Linux kernel电源管理相关的服务质量。那到底什么是服务质量呢?


我们知道,Linux PM的主要功能,是节省功耗,但同时,会付出一定的性能代价,例如延迟(latency)增加、吞吐量(throughput)下降。可以把PM当作一种服务,把它对性能的影响,类比为服务的质量(QoS)。对性能的影响越大,QoS越低,反之越高。


我们可以这么认为,我们在某个时候需要增加代码的执行速度,就通过这个去控制CPU的运行策略,这样确保代码可以快速执行。


不过这个方法没有使用到,如果对某个resume时间不是十分满意,可以尝试这个方法。



如何攻克 Android 调试难题?| 技术头条

休眠唤醒流程图



从网上拷贝了个休眠唤醒的流程图,如果以后有问题需要分析的话,可以跟进这个流程去排查。


如何攻克 Android 调试难题?| 技术头条

作者简介:韦启发,从事十余年嵌入式领域学习与工作,曾就职于TCL、中兴,从0开始创业开发过产品。目前在500强企业从事嵌入式系统软件开发工作。

本文系作者投稿,版权归作者所有。



 热 文 推 荐 


 

 

如何攻克 Android 调试难题?| 技术头条

print_r('点个好看吧!');
var_dump('点个好看吧!');
NSLog(@"点个好看吧!");
System.out.println("点个好看吧!");
console.log("点个好看吧!");
print("点个好看吧!");
printf("点个好看吧!");
cout << "点个好看吧!" << endl;
Console.WriteLine("点个好看吧!");
fmt.Println("点个好看吧!");
Response.Write("点个好看吧!");
alert("点个好看吧!")
echo "点个好看吧!"

点击“阅读原文”,打开 CSDN App 阅读更贴心!

喜欢就点击“好看”吧

以上是关于攻克视频技术的主要内容,如果未能解决你的问题,请参考以下文章

图像识别已攻克 谷歌将发力视频识别和搜索

Hadoop开发 Hadoop视频教程 段海涛老师Hadoop完全攻克Hadoop视频教程 Hadoop开发

安防E周刊图像识别已攻克 谷歌将发力视频识别和搜索

软考上午题难点5分钟攻克系列

软考上午题难点5分钟攻克系列

软考上午题难点5分钟攻克系列