一. 前言

一些涉及的基本概念:

  • 转码:一般指多媒体文件格式的转换,比如分辨率、码率、封装格式等;
  • 解复用(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格式。AnnexBAVCC/HVCC的区别在于参数集与帧格式,AnnexB的参数集spsppsNAL的形式存在码流中(带内传输),以startcode分割NAL

AVCC/HVCC 的参数集存储在extradata中(带外传输),使用NALU长度(固定字节,通常为4字节,从extradata中解析)分隔NAL,通常MP4MKV使用AVCC格式来存储。

Android的硬解只接受Annex-B格式的码流,所以在解码MP4 Demux出的视频流时,需要解析extradata,取出sps、pps,通过CSD(Codec-Specific Data)来初始化解码器;并且将AVCC码流转换为Annex-B,在ffmpeg中使用h264_mp4toannexb_filterhevc_mp4toannexb做转换。

  • 硬解时间戳不准确的问题

硬解码器解码视频到Surface,此时通过SurfaceTexture.getTimestamp()获得时间戳并不准确,某些机型会出现异常。所以还是要使用解码输入的时间戳,可将解码过程由异步转为同步,或者将pts存储到队列中来实现。

  • 音频硬编硬解解的速度

MediaCodec的音频编解码具体实现和机型有关,许多机型的MediaCodec音频编解码工作仍然是软件方案。经过测试MediaCodec音频硬编码较软编码有6%左右的提速,但MediaCodec音频硬解反而比软解的的速度慢,具体原因有待进一步调查。不过这只是部分机型的测试结果,更多机型的比较大家可以使用我们demo的转码/合成功能进行测试。

4.5 转码提速对比

下面以三星S8为例,短视频SDK在转码速度上的进步,更多机型的对比数据,请移步github wiki查看。

将1分钟1080p 18Mbps视频,转码成540p 1.2Mbps,不同版本时间开销大致如下:

机型 版本 编码方式 第一次合成时长 第二次合成时长 第三次合成时长 平均值
三星S8 V1.0.4 软编 52s 54s 58s 54.7s
  V1.1.2 软编 49s 50s 50s 49.7s
  V1.1.2 硬编 35s 36s 38s 36.3s
  V1.4.7 硬编 21.5s 21.9s 22.5s 22.0s

可以看到,使用了硬编、硬解等提速手段后,合成速度由54秒优化到22秒。

五. 模块化的思考

金山云短视频SDK的基础模块是基于直播SDK,整体来说,是一套push模式的流水线。

流水线中的每个模块都很好地实现了解耦,单独模块完成单一的功能,模块的复用也非常方便。前置模块在产生新的音视频帧后,会立即push给后续模块,后续模块需要尽快把前置模块产生的音视频帧消化掉,最大程度上保证实时性。为了保证音视频同步等逻辑,引入了大量同步锁。在短视频的开发中,遇到了不少的死锁和不方便。对于短视频这种非实时的场景,更多的时候,需要由后续模块(而非前置模块)来控制整个流程的进度。

当前处理过程中需要实现暂停,需要在前置模块加锁来实现。为了能方便以后的开发,我们会在接下来重新梳理这种push流水线的方式, 实现模块化的同时,尽量减少同步锁的使用。

六. 总结

转码对于普通用户来说不可见的,但却是短视频SDK的一个重要过程。怎么样让转码过程耗时更短,转码图像质量更高,特效添加更灵活,减少我们团队自身的开发和维护成本,同时也为开发者提供最方便易用的API,一直是金山云多媒体SDK团队的目标。

团队在很用心的开发短视频SDK,欢迎试用!

Android短视频SDK转码实践的更多相关文章

  1. 短视频 SDK 架构设计实践

    作者简介 孔维乐,七牛云客户端团队 Android 平台高级开发工程师,专注音视频,图形图像领域.OpenGL 专家,先后参与直播推流及连麦 SDK 的开发,主导短视频 SDK 的架构设计与实现, 对 ...

  2. 如何设计一款优秀的短视频 SDK

    2017 年,短视频成为了创业的新风口,各种短视频 App 如雨后春笋般先后上线,视频越来越像文字.图片一样,成为每一个 App 不可或缺的一部分. 1. 包体一定要尽可能小 如何做到尽可能的减小 S ...

  3. 短视频sdk:选择一个靠谱的短视频SDK 你需要了解这些

    2017 年,短视频成为了内容创业的新风口,各种短视频 App 如雨后春笋般先后上线.随着互联网内容消费升级,视频越来越像文字.图片一样,成为每一个 App 不可或缺的一部分. 为了能够更好地聚焦于业 ...

  4. 国内最简单的短视频SDK

    最近阿里百川和趣拍一起合作推出了一个短视频SDK.之前很多厂商可能都是用的Vitamio的短视频SDK.之后我考察过,也做过一些调查,发现Vitamio真的奇贵无比,屌丝公司根本用不起,阿里和趣拍这下 ...

  5. 短视频图像处理 OpenGL ES 实践

    2017年,短视频正以其丰富的内容表现力和时间碎片化的特点,快速崛起,而短视频最具可玩性之处就在支持人脸识别的动态贴图和各种不同效果的美颜.滤镜等.那短视频动态贴纸.滤镜.美颜等功能究竟是如何实现的呢 ...

  6. 【mob】Android短信验证+源码

    在很多的应用当中,都涉及到了短信验证的功能,比如在注册或者找回密码的时候,那么我们如何通过第三方的平台来完成这个功能呢? 本面博文就实现短信验证,来做一个小的栗子. 第一步-下载开发包 第二步-将SD ...

  7. 开发者选择短视频SDK,为何青睐七牛云?

    从文字到图片再到视频的互联网内容媒介发展途径,随着 5g 技术的逐渐落地愈发清晰.短视频市场中的角力也随着诸多资本和创业者的涌入,进入到白热化阶段.这样的情况下,选择合适的短视频SDK产品就显得尤为重 ...

  8. 短视频SDK用于旅游行业

    超级简单易用的短视频SDK来自RDSDK.COM.锐动天地为开发者提供短视频编辑.视频直播.特效.录屏.编解码.视频转换,等多种解决方案,涵盖PC.iOS.Android多平台.以市场为导向,不断打磨 ...

  9. 短视频SDK超级简单易用

    超级简单易用的短视频SDK来自RDSDK.COM.锐动天地为开发者提供短视频编辑.视频直播.特效.录屏.编解码.视频转换,等多种解决方案,涵盖PC.iOS.Android多平台.以市场为导向,不断打磨 ...

随机推荐

  1. 案例学习总结:原生JS实现表格排序

    最近在学习js的表格排序,没想到看不起眼的表格排序实际上却暗含了众多JS知识点.在这里记录一下此次学习过程.希望对大家也有所帮助. 完整的表格排序涉及了下列这些知识点: call方法使用 sort方法 ...

  2. Model中设置表单验证方法

    Model类里面定义$_validate属性支持的验证因子格式: 格式:array(验证字段,验证规则,错误提示,验证条件,附加规则,验证时间). 验证条件: (1)Model::EXISTS_TO_ ...

  3. ChatterBot之linux下安装mongodb 02

    当前环境 :centos 6.9 mongodb版本 mongodb-linux-x86_64-3.4.4.tgz 使用链接工具:studio-3t-x64.msi.zip 首先我们先来安装mongo ...

  4. macbookPro 搭建maven环境下载jar包

    今天要用itext的jar包,去官网看发现好像只能用maven下载,而我之前又换了电话,没办法咯,重新搭一次maven环境吧,在此记录,已便分享或自己将来查找 首选确定自己环境上jdk装好了,如果没有 ...

  5. 解决zabbix中文显示乱码问题

    中文显示问题,图表乱码 解决办法: [root@zabbix ~]# cd /usr/share/zabbix/include/ [root@zabbix include]# vim locales. ...

  6. Js中for循环的阻塞机制

    Js阻塞机制,跟Js引擎的单线程处理方式有关,每个window一个JS线程.所谓单线程,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码. 由于浏览器是事件驱动的(Event driven) ...

  7. java学习笔记之集合家族2

    集合体系 一.数据结构 List集合储存数据结构 <1>堆栈结构 特点:先进后出 <2>队列结构 特点:先进先出 <3>数组结构 特点:查询快,增删慢 <4& ...

  8. php两个问号??表示什么意思,PHP两个问号运算符,双问号表达式

    有同学给子恒老师留言, 说在php源代码中看到有两个问号?? 不知道是什么意思. 其实两个问题??是php7新推出的表达式, c = a ?? b; 表示如果a非空,则c = a, 如果a为空,则 c ...

  9. 用maven搭建java ee项目

    一.开发环境 jdk1.7  tomcat7 eclipse-jee-luna-R-win32 maven2.2.1 二搭建步骤 1.点击File->New->Other,选择maven ...

  10. mapreduce作业reduce被大量kill掉

    之前有一段时间.我们的hadoop2.4集群压力非常大.导致提交的job出现大量的reduce被kill掉.同样的job执行时间比在hadoop0.20.203上面长了非常多.这个问题事实上是redu ...