一. 前言

一些涉及的基本概念:

  • 转码:一般指多媒体文件格式的转换,比如分辨率、码率、封装格式等;
  • 解复用(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. em单位使用小结

    em是一个css的单位. em是一个相对的单位.相对于当前对象内文本的字体尺寸.如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸.(引自CSS2.0手册) 一般在DOM元素中,当前 ...

  2. 使用Navicat导入.csv文件(过程和注意点)

    1.创建一个数据库,右键点击表,选择导入向导. 2.在跳出的弹窗中选择.CSV文件,点击下一步 3.选择文件来源和编码规格,点击下一步 如果发现上传后中文出现乱码请使用10008这个编码规则 4.选择 ...

  3. 《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(上)

    目录 前言 第1章 安装 第2章 程序的基本结构 第3章 模板 第4章 Web表单 第5章 数据库 第6章 电子邮件 第7章 大型程序的结构   前言 学习Python也有一个半月时间了,学到现在感觉 ...

  4. POJ3624--01背包

    Charm Bracelet Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 34013   Accepted: 15087 ...

  5. 为什么要初始化CSS?

    因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异. 当然,初始化样式会对SEO有一定的影响,但鱼和熊掌不可兼得,但力求影响最小的情况下 ...

  6. nanomsg 如何写数据到PipelineDB

    nanomsg:https://github.com/nanomsg/nanomsg PipelineDB:https://github.com/pipelinedb/pipelinedb nanom ...

  7. 分享一些JAVA相关资源

    前言 以前在学习JAVA时,因为搜索相关资源过于不便,所以在搜集了一些好用的资源之后,将此分享. 文档主要包括面试文档, JAVA的技术文档(如JAVA并发实战.设计模式之类),LINUX的相关文档以 ...

  8. calc() ---一个会计算的css属性

    最近这个月一直在赶项目开发,遇到的问题和学到的前端知识没有更新到博客园,现在闲了下来,就整理一下前端知识. 在项目开发中,在样式这方面花费的时间较多,因为针对于数字的变化特别多,本人不爱记数字,在看设 ...

  9. Xamarin 使用极光推送 详细教程

    源码下载地址:http://download.csdn.net/download/kendocross/8677263  有兴趣的看以去看看 一.首先新建一个Xamarin.Android 项目,过程 ...

  10. Java爬虫——网易云热评爬取

    爬取目标网址 :   http://music.163.com/#/song?id=409649818 需要爬取信息 :   网易云top13热评 使用之前的 HttpURLConnection 获取 ...