卡顿与花屏问题

在这里插入图片描述

卡顿问题

视频卡顿是在实时视频通话场景中非常重要的一个问题。卡顿率也是实时通话场景中一个非常重要的指标。一般来说,人眼在帧率达到 10fps 并且均匀播放时就不太能看出来卡顿了。如果两帧之间的播放时间间隔超过了 200ms,人眼就可以明显看出卡顿了。那一般什么情况下会导致两帧之间的播放时间间隔超过 200ms 呢?我们下面一个个来分析一下,并给出相应的解决方法。

在这里插入图片描述

帧率不够

如果实际采集到的帧率或者设置的帧率本身就只有 5fps,即便是均匀播放,两帧之间的间隔也会达到 200ms,那么这种情况下肯定会出现卡顿。画面看起来就像是快速播放的PPT。这种情况下相信你能明显地看出来卡顿的原因。如下图所示:

在这里插入图片描述
这个问题最好的解决方法当然就是提高帧率了,比如提高到 15fps 或者更高。当然有的时候采集帧率就是上不来,那我们就要定位一下,采集帧率不高的具体原因是什么。

机器性能不够,导致前处理或者编码耗时太长

在实时通话场景中,画面是需要实时地做前处理(美颜等操作)并编码之后发送到对端进行解码播放的。如果本身机器性能不够,而画面分辨率又很高,那么这可能会导致前处理一帧或者编码器编码一帧的耗时很高。如下面两张图所示:

在这里插入图片描述

在这里插入图片描述
这种情况下,即便是采集的帧率很高,但是前处理和编码操作机器处理不过来,从而最后导致两帧被发送出去的间隔也会很高,这时发送到对端,对端就可能会出现明显的卡顿。这种情况在比较老的手机上特别容易出现。

当出现这种情况的时候,我们可以在高分辨率的时候尽量使用 GPU 做前处理,并使用硬件编码或者将软件编码设置为快速档加快处理的速度。GPU 做前处理和硬件编码消耗 CPU比较小,并且速度更快。软件编码设置为快速档时很多费时间的编码工具都被关闭了,因此可以提高编码的速度。不过这里你需要注意一下,就是这样压缩率也会下降。

编码器输出码率超过实际网络带宽

这种情况是 RTC 实时通话场景中卡顿问题最常见的根因。当出现的时候往往会引起比较长时间的卡顿,有可能持续 1~2 秒钟时间。有的时候,网络突然变差,从而网络预估出来的带宽很小,但是实际播放的画面很复杂,且需要的编码码率又比较高,这样就比较容易出现发送码率大于实际带宽的问题。

我们在带宽预测的那节课中讲过,当发送码率大于实际带宽的时候,对于有缓冲区的网络设备,它一开始会将包放在缓冲区,且当缓冲区放不下了还是会丢包。
在这里插入图片描述
而对于没有缓冲区的网络设备,它是直接就丢包。
在这里插入图片描述
当包被丢弃了,对端就不能完整地恢复出一帧图像了。而我们知道,当一帧图像不能解码,那么之后所有参考它的图像就都不能解码了。并且,在 RTC 场景中,我们一般使用连续参考的参考帧结构,就是后面的 P 帧参考它的前一帧,这也就会导致在下一个 IDR 帧到来之前画面都会卡死。这样卡顿的时间就会很长。如果出现这种问题怎么办呢?

我们需要对发送码率做严格的限制,防止它超过预估带宽。这就需要编码器的输出码率要能够贴合预估带宽。也就是说,我给编码器设置多少编码码率,编码器最好就编码出多少码率。

那是不是我们应该选择 CBR 的码控算法呢?是的,在 RTC 视频通话场景下我们最好选择 CBR 的码控算法,从而保证输出码率能够比较好地贴合预估带宽。

如果使用 VBR 码控算法,编码器的输出码率会随着画面的复杂程度变化,那就会有很大的概率因为画面复杂而出现输出码率超过预估带宽的情况,从而导致对端出现严重的卡顿。而 CBR 码控算法是你设置多少目标码率,编码器的输出码率就会接近于目标码率。这样,超发的问题就会少很多,相应地对端出现卡顿的概率也会小很多。

复杂帧编码后过大或者 I 帧比较大

虽然,我们选择使用 CBR 的码控来编码可以使得一段时间内(比如说 500ms 或者 1 秒钟)的编码输出码率尽量地贴合预估带宽,但是有的时候编码画面变化很大的帧或者需要编码 IDR 帧的时候,还是会使得编码后这一帧的大小会比较大。如果一次性将这种大帧打包出来的所有包都直接发送到网络中,则会在一瞬间加剧网络的负担,从而容易引起网络丢包,继而引起卡顿的可能。如下面两张图所示:

在这里插入图片描述
在这里插入图片描述
为了能够减小这种大帧带来的瞬时网络波动,我们可以在编码打包之后、发送之前,加一个平滑发送的模块来平滑地发送视频包。这个模块在 WebRTC 中叫做 PacedSender(节奏发送器)。那它的工作原理是怎么样的呢?

PacedSender 主要的工作原理就是编码输出的码流打包之后先放到它的缓冲区中,而不是直接发送。之后它再按照预估带宽大小对应的发送速度,将缓冲区中的数据发送到网络当中。如下图所示:

在这里插入图片描述
一般 PacedSender 每隔 5ms 左右发送一次包,并且它会在内部记录上一个 5ms 发送周期发送完之后剩余可发送的大小。同时,每隔 5ms 左右,它计算当前距离上一次发送包的时间差,乘上发送码率得到这段时间可以发送多少大小的数据。然后再加上上一次剩余可发送大小得到本次可发送大小。因为发送的时候是一个个 RTP 包发送的,而一般一个包差不多就是 1500 字节,所以上一个剩余可发送的大小可能为负数。

如果本次可发送大小大于 0,就从缓冲区中取包发送出去。并且,发送完包之后将剩余可发送大小减去发送包的大小。之后如果剩余可发送大小小于或等于 0,则停止发送,并等待下一个 5ms 发送周期再发送。

PacedSender 是通过控制实际发送码率来平滑发送的,这样能防止编码输出码率超过网络带宽太多,直接将包一次性发送到网络导致卡顿。但是我们要注意,如果编码器输出码率差网络带宽太多,也会导致 PacedSender 缓冲太多数据包,从而引起延时太长。

因此,编码器码控还是需要贴合网络预估带宽的。PacedSender 大多时候是用来防止一两帧编码后太大引起数据量突增造成丢包。因此,码控和 PacedSender 都很重要,它们是一起协作来减少卡顿的。

网络本身就有一定的丢包率

当然,我们选择 CBR 的码控同时使用了平滑发送方法,但有的时候网络变化太快了或者我们处在一个无线网络环境下,就是会有一定的丢包概率。那怎么办呢?

这就要使用我们前面多次讲到过的丢包重传策略了。因为对于视频来说,如果视频帧出现了丢包的话,帧就不完整了,那么当前帧也就不能拿去解码,就可能引起卡顿。如果强行解码,从这一帧开始到下一个 IDR 帧中间的帧,几乎都会出现解码花屏或者解码错误,而解码错误也会引起卡顿。

这个知识点你可以参考第 05~07 这三节课,里面详细讲述了为什么会这样。因此,如果真的出现丢包了,那么我们必须想办法将包恢复。其中,最常用的方法就是丢包重传。

丢包重传请求策略是在 Jitter Buffer 里面实现的。当接收端接收到视频 RTP 包之后,会检查 RTP 序列号。

如果序列号不连续,出现了跳变,也就是说,当前 RTP 包序列号减去收到的最大 RTP 包序列号大于 1,那么就认为中间的包有可能丢失了,Jitter Buffer 就将中间没有收到包的包序号都加入到丢包列表中。因为 UDP 经常会出现乱序到达的情况,如果中间的包后面到来了,也就是说 RTP 包序号小于收到的最大 RTP 包序号,Jitter Buffer 就将这个包序号从丢包列表中删除,防止重复传输。

接收端每隔一定时间将丢包列表组装成 RTCP 协议中的 NACK 报文发送给发送端,发送端会保存之前的发送历史数据。发送端收到 NACK 报文之后,就会解出 NACK 报文中携带的丢失包的序号,并且在发送的历史数据中找出这个包重新发送给接收端。接收端收到包就将丢包列表中的对应序列号删除。如下图所示:

在这里插入图片描述
但是这里有一个问题就是,有的包重传一次需要一段时间才能到接收端,因为 NACK 发送给发送端需要时间,重传包传输到接收端也需要时间,中间正好一来一往,差不多一个RTT(往返时间)时间。

因此,每个丢失包序号发送重传请求之后,下一次需要等一个 RTT 的时间。如果接收端等待一个 RTT 的时间后还没有收到对应序号的 RTP 包,则再次将该序号加入到重传请求中,不能每次 NACK 请求都把所有丢包列表中的序号加入到报文中,防止重传重复发送,加重网络负担。同时重传也是有次数限制的。如果一个包重传请求发送了好几次,比如说 10次,还没有收到,那就不再将该包加入到 NACK 报文中了。

重传也没有收到包

一般来说,前面的策略用上了之后,卡顿会小很多。但是,有的时候就是会有极端情况出现。毕竟,网络是千变万化的。如果实在是前面策略都用上了,还是出现了有包没有收到,导致帧不完整,继而导致没有帧可以解码成功的话,那么我们就需要使出最后的大招了,那就是关键帧请求,也叫 I 帧请求。I 帧请求使用 RTCP 协议中的 FIR 报文。这个策略也是工作在 Jitter Buffer 中的。具体如下图所示:

在这里插入图片描述
如果有一个帧解码失败,那之后的帧几乎都将解码失败,直到下一
个 IDR 帧到来。因此,如果有一帧出现了丢包的情况,导致后面的帧都无法解码了,那么接收端这个时候就需要发起一个关键帧请求报文给发送端,发送端收到关键帧请求之后应该立即编码一个 IDR 帧。这样接收端收到 IDR 帧之后就可以解码了,而前面不能解码的帧就全部删除掉。同时,将丢包列表清空掉。

花屏问题

帧不完整

如果帧出现了丢包就送去解码的话,若能解码成功,那肯定会出现解码花
屏的问题。尤其是 ffmpeg 作为解码器的时候,帧不完整也有很大的概率成功解码,但是得到解码后的图像却是花屏的。如下图所示:
在这里插入图片描述
我们在解码一帧数据之前一定要保证帧是完整的.

在 RTP 包里面,RTP 头有一个标志位 M,表示是一帧的结尾。因此只要收到这个标志位为 1 的包就代表收到了这一帧的最后一个包。那么如何判断一帧的第一个包有没有收到呢?如果收到了一帧的第一个包,也收到了这一帧最后一个包,那我们就有了一帧的第一个和最后一个包的 RTP 序列号了。只要中间的序列号对应的 RTP 包都收到了,那么当前帧就完整了,是不是?

现在的重点是怎么确定一帧第一个包有没有收到.

在单包模式打包方式的时候,一帧只打一个包,最后一个包就是第一个包。那只要收到最后一个包就等于收到了第一个包,很容易判断,是不是?

在组合打包方式的时候,一个包里面有好几个帧,那么,只要按照协议将几帧分离开来就可以。这时帧肯定是完整的。因为包丢了的话,这几帧就都丢了,不存在丢掉帧中的一部分,是不是?

问题在于在分片打包的时候,分片打包一帧会分成好几个包打包,而丢掉了一个包,帧就不完整了。我们知道分片打包时 FU Header 里面有一个 S 标志位。这个标志位表示的是第一个包,那我们是不是就可以使用这个标志位来判断是不是收到了第一个包了呢?

答案是:不是!不是!不是!如果使用这种方法来判断完整性的话,那大概率会出现花屏问题。

这个地方千万要注意,我们在 RTP 打包的时候是以 Slice 为单位打包的,而不是以帧为单位打包的。因此,前面几种方式都是只能表示一个 Slice 完整了,而不能表示一帧完整了。因为一帧是有可能有多个 Slice 的。

再一次强调前面关于单包、组合打包、分片打包的帧完整性判断都是错误的。那正确的帧完整性判断应该怎么做呢?

我们也是在 Jitter Buffer 中来对帧进行完整性判断的。首先,我们使用前面的方式判断Slice 的完整性,保证一个个 Slice 是完整的。我们讲到过使用
slice_header 中的 first_mb_in_slice 字段,来判断当前 Slice 是不是第一个 Slice。如果这个 first_mb_in_slice 字段为 0,就代表是帧的第一个 Slice 了。

我们找到帧的第一个 Slice,而 Slice 也判断了是完整的,再通过 RTP 头的 M 标志位判断了帧的最后一个包。如果第一个 Slice 的第一个包到帧的最后一个包之间的 RTP 包都收到了,那就代表帧完整了。这是一种方法。如下图所示:

在这里插入图片描述
从上面的图中我们可以看到两个帧都是完整的。而下面图中的帧 1 是不完整的。

在这里插入图片描述
还有另外一种方法,是 WebRTC 中在使用的方法,就是将每一个收到的包都排好序放在队列里面。Jitter Buffer 收到了当前帧的最后一个包(RTP 头的标志位 M 为 1)之后呢,从这个包往前遍历,要求 RTP 序列号一直连续,直到 RTP 时间戳出现一个跳变,代表已经到了前面帧的包了。

如果包序号一直是连续的,那么代表当前帧就是完整的了。因为两帧的时间戳是不可能一样的。这也是一种方法。但是这种方法需要所有包都放在一个队列里面,并且排好序。它没有前一种方法灵活。如下图所示:

在这里插入图片描述

参考帧不完整

当一帧完整了之后,我们是不是就可以将帧送去解码,就不会出现花屏了呢?答案是:不是这样的。

因为,我们前面强调过很多遍,需要参考帧也是完整的才能送解码,并且参考帧的参考帧也要是完整的才行。如果参考帧不完整或者丢失,会出现如下图所示的花屏。

在这里插入图片描述
那也就是说,如果是连续参考的话,或者说你不知道编码器使用的参考结构的话,你就需要保证从 IDR 帧开始到当前帧为止所有的帧都是完整的,并且前面的帧都已经解码了,那当前帧才能送去解码。因为只要有一帧没有解码就会出现花屏。具体如下图所示。这部分功能一般也是实现在 Jitter Buffer 中。

在这里插入图片描述
当然,有的时候我们并不一定使用连续参考,比如,我们下一节课会讲到的 SVC 编码,就不是连续参考的。那就不要求前面的帧都完整才可以解码。或者,你自己设计了参考结构,并不是使用了连续参考的方法做编码的,也不需要要求所有的帧都完整。

这个时候你需要设计自己的协议告诉接收端什么时候是可解码的,防止出现花屏。如果没有的话,那就当作连续参考处理,防止花屏的出现。因为这种情况花屏一旦出现,到下一个 IDR 帧到来都会一直花屏,这是不能接受的。

YUV 格式问题

另外一种常见的花屏问题,就是渲染的时候 YUV 格式弄错了。这种问题经常会出现,我们声网的客户就出现了好几次没有处理好这个问题导致的花屏。这种情况有一个特点,就是图像的大体轮廓是对的,但是颜色是有问题的。如下图所示,左图 YUV 格式是正确的,而右图 YUV 格式是错误的。

在这里插入图片描述
根因是 YUV 中的 Y 分量是对的,但是 UV 是错误的。这种时候你就应该想到,很有可能就是 NV12 当作 NV21 处理了,或者 I420 当作 NV21 处理了,类似这种 YUV 格式弄错了的问题。其处理方式也很简单,就是使用正确的 YUV 格式就对了。渲染或者读取 YUV的时候一定不要弄错了 YUV 的类型。

Stride 问题

最后一种花屏问题,是老生常谈的问题啦。那就是 Stride 问题。解码后渲染前一定要处理好 YUV 的 Stride 问题,不要和宽度弄混了。如果出现类似下图的现象的话,去看看你的Stride 是不是弄错了吧。

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
DMA2D是一种专为图形显示优化的技术,它可以通过硬件加速来提高图像处理的效率。然而,当使用DMA2D时,有时会出现花屏问题,即显示屏上出现不正常的图案或颜色。 导致DMA2D花屏的主要原因是使用不当或配置错误。DMA2D的配置参数需要正确地设置,包括源图像地址、目标图像地址、宽度、高度等。如果配置错误,可能会导致DMA2D无法正确处理图像数据,从而导致花屏问题。 为了解决DMA2D花屏问题,我们可以采取以下措施: 1. 配置正确:确保DMA2D的配置参数正确设置,包括地址、宽度、高度等。 2. 初始化正确:在使用DMA2D之前,需要进行正确的初始化操作,包括时钟配置、中断配置等。 3. 数据准备:在使用DMA2D之前,确保源图像和目标图像的数据正确准备,包括数据类型、像素格式等。数据类型和像素格式需要与DMA2D的配置参数匹配。 4. 确保资源可用:DMA2D可能与其他硬件资源共享,如存储器或显示控制器。在使用DMA2D之前,需要确保这些资源可用,且没有冲突。 5. 错误处理:DMA2D在处理图像数据时可能会发生错误,如溢出、超时等。需要正确处理这些错误,以防止花屏问题的发生。 而如果不使用DMA2D,可能不会出现花屏问题。因为DMA2D是通过硬件加速来处理图像数据的,相比软件处理更高效。但是在图像处理的过程中,可能会由于软件处理的效率不够高而导致显示延迟或卡顿问题。 综上所述,DMA2D花屏问题通常是由于配置错误或使用不当导致的。通过正确的配置和使用,可以避免花屏问题的发生。而不使用DMA2D的情况下,可能不会出现花屏问题,但可能会面临软件处理效率低下的挑战。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Learning together

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值