播放器技术分享(2):缓冲区管理

搞音视频开发好些年,分享过许多博客文章,比如:前几年发布的《FFmpeg技巧》系列,《Android音频开发》系列,《直播疑难杂症排查》系列等等。最近想把多年来开发和优化播放器的经验也分享出来,同时也考虑把自己业余时间开发的基于FFmpeg的播放器内核开源出来,希望能帮助到音视频领域的初学者。第一期文章要推出的内容主要涉及到播放器比较核心的几个技术点,大概的目录如下:

1。播放器技术分享(1):架构设计

2。播放器技术分享(2):缓冲区管理

3。播放器技术分享(3):音画同步

4。播放器技术分享(4):首开时间

5。播放器技术分享(5):延时优化

本篇是系列文章的第二篇,主要聊一聊播放器的缓冲区管理。

在上一篇文章中,我们有提到利用缓冲区把单线程模型的数据流改造为多线程模型,从而可以有效抵抗网络和解码的抖动,防止频繁卡顿,同时也能充分利用多核CPU的计算能力,如下图所示:播放器技术分享(2):缓冲区管理

播放器的读线程,将IO和解析器模块输出的“未解码”的音视频数据包放到“帧缓冲区”队列中,将解码后的数据,存放到“显示缓冲区”队列中。

我们深挖一下,这个“帧缓冲区”和“显示缓冲区”究竟起到了一个什么作用?

帧缓冲区,作为“读线程”和“解码线程”之间的缓冲池,它主要起到了三个作用:

<李>

抵抗网络抖动

<李>

抵抗解码抖动

<李>

避免被动丢帧导致花屏

假设没有“帧缓冲区”,即:IO→解析器→译码器整个流程是串行的,那么会有如下潜在问题:

<李>

IO网络抖动的时候(比如:短暂拥塞,无法读到数据),那么整个数据链条都会被卡住,译码器只能干等着IO恢复

<李>

解码器同样会出现“抖”动,因为解码某些复杂的视频帧,会耗时比较久,如果译码器卡住,同样IO模块也只能干等着

<李>

因为整个流程是串行的,每一帧都必须IO→解析器→解码器走完才会读取和处理下一帧,那么,当网络抖动的时候,会出现服务端TCP的协议栈缓存了较多的数据,在网络恢复的时候,下发到客户端的时候,因为接收不及时,导致TCP发送队列爆满而产生被动丢帧,从而使得后续因为数据不完整导致解码花屏

显示缓冲区,作为“解码线程”和“显示线程”之间的缓冲池,它主要起到了三个作用:

<李>

实现“音画同步”的必要条件

<李>

抵抗渲染抖动

假设没有“显示缓冲区”,即:解码器→渲染器整个流程是串行的,那么会有如下潜在问题:

<李>

无论是视频帧还是音频数据,都是解码完了就立马送入了渲染模块,无法添加音画同步的逻辑处理

<李>

如果渲染模块出现“抖”动,会直接阻塞×××,无法异步去解码帧缓冲区中的数据,降低了效率

:是指播放器主动暂停缓冲区的数据消费,等待数据生产者逐渐填充数据,直到达到某种条件再恢复

:是指数据的消费速度赶不上生产速度,从而被动滞留了数据在缓冲区中

,多用于点播场景,为了降低频繁卡顿,在开始播放视频之前,会主动缓冲一段时间(比如:10年代)的数据,再开始播放。当缓冲区内的数据因为网络抖动等原因消耗完了,会再次启动缓冲,如此循环。

播放器技术分享(2):缓冲区管理

如图所示,假设播放器缓冲区内的数据低于低这个水位点后,会主动暂停播放,启动缓冲过程直到缓冲区中的数据达到米水位值。

,多出现在直播场景,可能有2种原因:

<李>

手机等设备的解码性能不足,比如软解1080 p的高清视频,导致视频的解码和渲染的速度赶不上视频的读取速度,导致数据堆积在“帧缓冲区”

<李>

网络的频繁抖动,导致客户端无法及时拿到数据进行解码渲染,当网络恢复后,数据会迅速下发下来,但播放器已没有办法再快速消费掉(因为播放的速率是固定的,除非添加追帧的逻辑,后续文章会详细介绍)

播放器技术分享(2):缓冲区管理