设为首页 加入收藏

TOP

Android短视频SDK转码实践(一)
2019-09-17 19:06:11 】 浏览:59
Tags:Android 视频 SDK 实践

一. 前言

一些涉及的基本概念:

  • 转码:一般指多媒体文件格式的转换,比如分辨率、码率、封装格式等;
  • 解复用(demux):从某种封装中分离出视频track和音频track,然后交给后续模块进行处理;
  • 复用(mux):将视频压缩数据(例如H.264)和音频压缩数据(例如AAC)合并到某种封装格式的文件中去。常提到的MP4即是一种封装;
  • 编码(encode):通过专门的算法(例如H.264AAC)来对原始音视频数据进行压缩;
  • 解码(decode):对压缩后的数据进行解压缩。

短视频APP中录制完成后,为什么要做转码:

  1. 原始视频文件码率较大,上传下载都需要很长时间,不利于传播;
  2. 编辑时增加特效、转场效果后,只是在预览中有效,原始文件并未改变,需要进行一次转码来把这些效果合成进最终的文件;
  3. 多段视频进行编辑前转码拼接为一个文件,方便后续的编辑;
  4. 目标格式和源文件格式不一致,比如需要从mp4转成gif

为什么不在服务端做转码呢?

  1. 短视频需要加入滤镜等效果,在移动端转码可以充分利用手机的GPU等资源,实现实时添加滤镜实时看到效果;
  2. 原始视频码率较大,上传下载都需要很长时间。

转码的主要流程如下:


 
图1. 转码基本流程

其中Audio FilterVideo Filter分别是指音频和视频的预处理。

  • 短视频转码的时机:
  1. 多段视频的导入;
  2. 转场完的合成;
  3. 编辑完的合成。

二. Demuxer方案的选择

Demuxer模块的实现,主要有以下三种方案:

  1. 方案一,使用播放器
    播放器的主要功能是播放,也就是从原始文件/流中提取出音视频,按照pts完成音视频的渲染。转码并不需要渲染,要求在保持音视频同步的情况下,尽快把解码数据重新按要求编码成新的音视频包,重新复用成文件。我们也曾经为了实现尽快这个要求,把播放器强行改造成快速播放的模式,但后来遇到了很多问题:

    • 音视频同步时机的问题,视频的解码是慢于音频的解码,必然需要实现同步逻辑。player中如果改成快速播放模式,player内部加上音视频同步的逻辑,改动非常大。如果player不管同步,解码数据直接上抛给调用层,则需要在短视频上层做音视频同步,引入了额外的工作量;
    • 使用硬解码时,从SurfaceTexture中获取的timestamp不准。因此最后放弃了这个方案。
  2. 方案二,使用MediaExtractor
    MediaExtractorAndroid系统封装好的用来分离容器中的视频track和音频trackJava类。优点是使用简单,缺点是支持的格式有限。

  3. 方案三,使用FFmpeg
    使用FFmpegav_read_frame API来做解复用,即实现简易版的播放器逻辑。

    • 优点:FFmpeg中对视频格式有大量兼容的逻辑,相比MediaExtractor兼容性好,增加新的输入格式的支持会更容易,同时音视频同步逻辑的控制更简单;
    • 缺点: 需要引用FFmpeg,相对来说SDK体积较大。

方案二的兼容性不如方案三。相比方案一,方案三把音视频的解复用和解码都放到了同一个线程,av_read_frame能输出同步交织的音视频packet,上层逻辑调用更清晰。
同时短视频其他功能模块已经引入了FFmpeg,转码模块引入FFmpeg并不增加包大小,所以选择了FFmpeg方案。

三. 转码的数据传递

金山云多媒体SDK实践中,Demuxer实际上是在C层做的,但是接口的封装是在Java层。解码结构也是一样。DemuxerDecoder之间如何高效地在JavaC层之间传递待解码的音视频包?

3.1 AVPacket的传递

FFmpegdemuxer模块解复用出来的为音频或视频的AVPacket。最开始的时候我们并没有在Java层对整个AVPacket的地址指针进行封装,而是把数据封装在ByteBuffer和其他的参数中。这样遇到了很多因为AVPacket中的参数没有传递到解码模块导致的问题。

最终我们通过intptr_tC层保存AVPacket的指针,同时在Java层以long类型来保存和传递这个指针,解决了这个问题。

3.2 AVFormatContext/AVCodecParams的传递

为了实现模块的复用,我们把DemuxerDecoder分成了两个模块。使用FFmpeg来实现时,Decoder模块可以和Demuxer模块共用AVFormatContext,通过AVFormatContext来创建AVCodecContext

但是这样会有一个问题,Demuxer的工作速度会快于Decoder,此时AVFormatContext是由Demuxer来创建的,Demuxer停止的时候会释放AVFormatContext。如果交给Decoder模块来释放,不利于模块的复用和解耦。最终我们发现在FFmpeg 3.3的版本中,AVCodecParams结构图中有Decoder所需要的全部信息,可以通过传递AVCodecParams来构造AVCodecContext

四. 转码提速

转码的速度是客户非常关心的一个点,转码时间太长,用户体验会非常差。我们花了非常多的精力来对短视频的转码时间进行提速。经验主要有以下这些点:

4.1 调整视频软编编码参数

转码的时间大部分都被视频的编码占用了,我们把x264编码做了调整,在保证画质影响较小的前提下,节省了30%以上的编码时间。

4.2 优化GPU数据读取

使用视频软编时,如何从GPU中把数据“下载”到CPU上,我们尝试了很多中方案,具体的我们会在另一篇文章中详细解释。之前的方案是使用ImageReader读取RGBA数据。优化为用OpenGL ESRGBA转换为YUVA。读取数据后从YUVA再转为I420,下载和格式转化总耗时,提速了大约40%。

4.3 开启硬编

硬编的缺点: 在Android平台上,硬编的兼容性较差,同时视频硬编的压缩比差于软编。
硬编的优点是显而易见的,编码器速度快,占用的资源也相对较少。

4.4 开启硬解

经过大量的测试,硬解的兼容性相较于硬编会好很多,使用硬解码,直接使用MediaCodec渲染到texture上,省去手动上传YUV的步骤,也节省了软解码的时间开销。

4.4.1 硬编解遇到的坑

关于Android的硬编解网上已经有很多例子,官方文档也比较完善。不过在实现过程中还是会遇到一些意想不到的问题。

  • 图像质量的问题

在硬编上线后,我们对比画质发现转码图像质量较差。原因是使用MediaCodec API时,选择的是MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBRCBR的好处是码率比较稳定,但是会牺牲画质,移动直播中选用CBR更合理。短视频转码场景硬编时推荐使用MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBRVBR会获得更好的图像质量。对于软编时,我们也尝试过ABR(也就是VBR),但实际测试下来效果并不能保证。

  • 硬解不兼容AVCC/HVCC 码流格式

H.264码流主要分Annex-BAVCC两种格式,H.265码流主要分为Annex-BHVCC

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Python 环境搭建 下一篇使用REST风格架构您需要知道的一..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目