这个公众号会路线图式的遍历分享音视频技术:音视频基础(完成) → 音视频工具(完成) → 音视频工程示例(完成) → 音视频工业实战(进行中)。关注一下成本不高,错过干货损失不小 ↓↓↓
这篇文章是音视频工业实战主题专栏中关于视频发布流程的优化。视频发布流程是指视频录制和编辑完成后,对视频进行转码、上传的过程。在这个过程中,我们关注的指标包括发布成功率、发布耗时等。
发布的过程可能包括转码过程和上传过程,这两个阶段的成功率都会影响整体的发布成功率。
在 iOS/Android 设备上进行编解码,有多种方案可以选择:
根据机型的情况,我们通常会优先选择一种作为对应机型的首选编码方式,如果我们选择的某一种编码方式在编码的过程中遇到一些不可恢复的错误时,可以切到另外一种编码方式作为兜底来尝试提升转码成功率。比如,硬编码失败切软编码。
同理,如果在解码过程中遇到了错误,也可以使用相同的策略作为兜底。
在解码过程中,可以将最近一个 GOP 暂存下来,在下一个 I 帧数据到来时清理。存储这个 GOP 序列主要是为了在解码器遇到解码错误的时候可以尝试先将解码器刷新(flush),然后将当前缓存的 GOP 序列塞给解码器做再次解码。这样可以把解码出错的重试控制在 GOP 的粒度。
如果重塞 GOP 后解码器还是失败,那就可以尝试上文中讲到的切到另外一种兜底解码方式上去做重试。
当然,对于转码报错,可以重试转码流程。
另外,需要上报转码错误的细分错误码,这样就能更好的定位转码失败的原因进行针对性的优化。
视频文件较大时,通常对应的上传耗时就较长,在上传的过程中如果出现网络抖动造成连接出现问题,就会直接导致此次上传失败,即使之前已经上传了很多数据也只能前功尽弃了。
将视频大文件进行分片上传,每个分片由于体积较小上传成功的概率会更高,即使出现某一个分片因为网络抖动而上传失败,也可以对该分片进行重试,不影响已经成功上传的分片,因此可以提高整体的上传成功率。
就近上传是指能够根据上传者所处的地理位置,分配离上传者最近的存储中心进行上传,这样的上传链路会更优。
就近上传的能力带来最大的收益是减少了上传者到服务器的传输距离,这样一方面可以减少传输距离,提升上传速度;一方面可以提高稳定性,保证成功率。
预探测上传主要是一种针对各类网络错误场景进行优化的手段,例如网络连接失败、超时、DNS 劫持等。预探测上传的措施包括:
同样的,对于上传报错,可以重试上传流程。
另外,需要上报上传错误的细分错误码,这样就能更好的定位上传失败的原因进行针对性的优化。
如果在发布视频的时候会做转码,这个发布时长就包括转码时长和上传时长。
转码可能会包括解封装、解码、特效模块渲染、编码、封装等流程。
在下列场景中,可以减少不必要的转码:
减少了不必要的转码,可以大大降低转码耗时,同时也可以降低因转码引发的错误,从而提高转码成功率。
转码过程中解码、编码、解封装、封装环节都可以拆分出来支持异步多线程处理。
如果更进一步,可以在 GOP 的粒度支持多线程并发转码。这样可以提升转码速度,但是这种方案会容易出现多线程问题,需要仔细衡量。
我们一般会同时提供硬件编码和软件编码的能力,我们可以根据设备类型以及在设备上提前做 Benchmark 的数据情况来决策在不同的机器上使用软编还是硬编,以及设置何种编码参数。这样我们可以根据当前的机型智能适配更优的转码方式来提升转码速度。
在整个视频编辑的工作流中,抽帧模块、预览播放和转码模块都有可能需要使用解码器,由于操作对象大多情况下是同一个视频,所以解码器的参数几乎都是一致的。为了能够更快的获取解码器,可以实现一个解码器复用池来优化解码器的使用性能。
当外界请求解码器池的时候,解码器池会在池中寻找属性匹配(宽、高、H.264/H.265、硬解/软解等)并且处于空闲状态的解码器。如果当前没有符合条件的解码器实例,解码器池会创建解码器并设置解码器为非空闲状态。解码器池也会定时清理空闲的解码器实例,优化内存。
libyuv 是 Google 开源的 YUV 图像处理库,实现对各种 YUV 和 RGB 数据之间的转换,包括数据转换、裁剪、缩放、旋转。它是跨平台的,支持在多种操作系统和 CPU 架构上进行编译运行,支持 SSE、AVX、NEON 等 SIMD 指令加速。
在视频发布过程中,某些模块中会需要用到各种 YUV 和 RGB 格式之间的转换,使用指令加速的 libyuv 替换手写的内存拷贝移动方法能缩短转换时间。
在预览播放和导出模块中,用户应用了特效,因为特效渲染模块一般只接受纹理数据,所以我们需要将解码后数据(PixelBuffer)转换成纹理数据,传给特效模块进行渲染,在渲染流程后拿到的纹理数据又需要转回 PixelBuffer。
一开始我们使用的是 OpenGL 中的 glReadPixels,在研究过方案后在 Android 上我们选择了使用转换速度更快的 ImageReader 进行加速。
根据渲染后续流程中的需要的解码格式选择 RGBA 或者 YUV,可以在 ImageReader 构造时就指定渲染返回的 Image 对象的数据格式(ImageFormat 或是 PixelFormat 的一种),避免在 ImageReader 返回数据后再进行一次多余的转换。
ImageReader 创建后返回 Surface 对象,我们将经过特效模块后的纹理数据传给 Surface 进行渲染。ImageReader 就会通过同步(acquireLatestImage)和异步方式(监听 onImageAvailable 回调)获取 Image 对象。
在客户端优化视频上传等待时长,一方面可以将传统的视频文件整体上传的方式优化为视频分段并发上传或者流水线上传的方式,一方面可以优化上传的传输协议。
在服务端也可以相应的做一些视频状态的优化来降低用户上传视频的等待时长。
当原始视频上传成功后即标记视频为可用,不必等其他版本转码完成,从而降低上传等待时长。观看端拉取视频时若对应码率视频转码完成就拉对应码率视频,未完成转码就拉原始视频。
下图是最简单的视频上传处理流程:
上传并转码后再分发的流程
下图是优化为只依赖一个版本的视频而非全部视频版本转码完成后的处理流程:
上传不依赖转码就分发的流程
这样的优化可以降低上传等待时长,但是也会造成一些弱网用户由于拉取高码率的视频导致更多的卡顿。在实际中绝大多数情况下,其余视频版本是能够迅速完成转码并达到可用来满足不同的用户。
在客户端录制完视频后,对视频进行分段然后并行上传到服务端可以提升上传速度。当然,这里的分段需要是可独立转码的切片,可以选择使用 FMP4、TS 等切片格式。
客户端将视频分段上传时,需要对分段标记好 index;服务端收到分段后并行的对各个分段进行转码来降低整体转码时长;当所有分段都转码完成后,服务端再按照 index 顺序将其组装起来,即可下发给消费端播放。
客户端分段并发上传流程
在服务端,将每个视频处理流水线构建为一个有向无循环图。每个节点都是执行单元,每个边则代表两个节点间的依赖关系。每个节点在所有依赖项完成后执行运行。下图表示以非分段的方式处理视频的大致流程:
非分段方式处理视频流程
上图中大部分工作都发生在转码节点中。如果能并行化这部分,我们就可以显著减少上传延时。分段通道旨在通过为每个片段添加转码任务来并行化该部分。然后,添加一个拼接任务,将每个片段的视频帧连接起来,放在一个新的容器中。此拼接任务取决于通道每个片段任务是否完成。如下图所示:
转码节点并行化流程
将视频分段上传和转码可以降低上传等待时长,但是也会带来一些其他消耗。比如:
对于分段长度,如果我们设置分段长度越小,则并发度就可以越高,但是随之而来的上述消耗也就更高,会带来资源的浪费。所以可以将分段长度保持在某个阈值以上,一般设置为几秒即可。
对视频进行分段上传并不适用于所有的场景。当原始视频时长越小,分段上传的收益也就越小;反之原始视频时长越大,分段上传的收益才会越大。下图展示了短视频和长视频的非分段视频处理和分段视频处理的耗时比较:
非分段与分段视频处理耗时对比
总的来讲,决定要不要做分段上传要取决于产品对视频时长的限制,以及在处理具体视频时的策略选择。
在某些情况下,上传的媒体已经可以在大多数设备上播放了。如果是这样,我们可以完全跳过视频转码并将视频直接标记为可分发。这样不仅减少了延迟,而且在这种情况下我们不需要对视频进行转码。
达标视频尽早分发流程
要实现尽快分发,最重要的是要对视频进行检测。可以根据码率、编码格式、封装格式等参数对视频进行筛选,因为码率过高会造成播放端卡顿过多,编码格式、封装格式不兼容会造成播放端无法播放。
当码率、编码格式、封装格式通过了筛选,可以进一步对视频质量进行检测,防止较差质量的视频被发放给用户。
视频质量检测流程
由此产生的透传通道比转码通道完成得更迅速,这极大地改善了视频处理时长并提高了视频质量,因为转码是一个有损的过程。
透传的优化也需要在其他方面做一些权衡,这里的权衡取决于我们设定的码率上限,其原因有几种:
所以我们需要通过控制码率的上限来控制这些因素。
在上文提到的 1.2.2、就近上传
,除了能提升上传成功率,也能提升上传速度。
在上文提到的 1.2.3、预探测上传
,除了能提升上传成功率,也能提升上传速度。
我们还可以使用 QUIC、KCP 等传输协议进行定制开发和优化,对传统的传输协议进行替换来优化上传速度。
当然这要涉及到整个传输链路的协议改造,是个较大的工程。
对于较大的视频平台,通常会遇到一些视频发布者搬运他人视频的问题,这时候就会出现同一个视频在平台上被重复上传的问题。对于这种情况,我们可以实现视频秒传来避免平台已有的视频被重复上传。
视频秒传的大致逻辑是在客户端上传视频前使用 MD5、CRC 等算法计算视频数据的校验值,然后发送到服务端来检查服务端是否已经存在该视频,如果已经存在,则服务端只用拷贝一份视频即可,客户端也不用上传该视频,从而大大降低视频上传耗时。
视频预上传是指在用户选择视频后,但还未点击『发布/上传』按钮前,就预先在后台将视频上传,当用户后面确实点击『发布/上传』按钮时,视频上传任务已经部分或者全部完成了,从而降低用户感知的上传耗时。
当然,这种做法有一些投机因素,因为如果用户选择了视频后还对视频进行了编辑,或者用户最终没有点击『发布/上传』按钮,之前提早在后台上传视频就白做了。这时候如果视频还未上传完成,则需要取消上传任务;如果视频已经上传完成,则需要再通知服务端删除视频。
上面我们从视频发布成功率和发布耗时两方面介绍了一些优化思路,视频发布是一个细致而漫长的过程,我们将在后续继续补充和完善相关的优化方案,大家可以关注我们的公众号,以便及时收到更新。