WebRTC 源码分析(四):VideoCRE 与内存抖动优化
WebRTC 是个宝,初窥这部分代码时就被它的 Capturer 类的设计惊艳到了,仔细品鉴后越发佩服起来,里面简直填了太多坑了,如此宝贝,如不能为我所用,岂非一大憾事!而前三篇的解读,正是为了今天能将其剥离出来所做的铺垫,现在就有请我们今天的主角——VideoCRE, Video Capture, Render and Encode——闪亮登场。
VideoCRE 结构
我们当然可以直接使用 Capturer/Renderer/Encoder,但如果能将它们进行一定的封装,让基本的需求实现起来更加简单,岂不妙哉。
下面介绍一下 VideoCRE 的结构:
- 视频数据由 VideoCapturer 采集,例如 Camera1Capturer;
- VideoCapturer、SurfaceTextureHelper 等由 VideoSource 类管理;
- VideoCapturer 采集到的数据会回调给 VideoCapturer.CapturerObserver,VideoSink 实现了该接口;
- VideoSink 会把数据发送给多个 VideoRenderer.Callbacks,例如 SurfaceViewRenderer 负责预览,HwAvcEncoder 负责视频编码;
- HwAvcEncoder 则会把编码后的数据发送给多个 MediaCodecCallback,例如由 Streamer 进行网络传输实现直播功能,Mp4Recorder 负责本地录制;
同一路视频数据可以被多路消费,例如预览、低码率编码、高码率编码,而同一路编码数据,也可以被多路消费,例如推流、存文件。
VideoCRE 使用
demo 工程里实现了高低码率两路本地 MP4 录制功能,下面我们看看如何一步步实现这个功能。
首先是配置参数,标清和高清:
VideoConfigconfig=VideoConfig.builder().previewWidth(1280).previewHeight(720).outputWidth(448).outputHeight(800).fps(30).outputBitrate(800).build();VideoConfighdConfig=VideoConfig.builder().previewWidth(1280).previewHeight(720).outputWidth(720).outputHeight(1280).fps(30).outputBitrate(2000).build();
接下来是创建 VideoCapturer:
VideoCapturercapturer=createVideoCapturer();privateVideoCapturercreateVideoCapturer(){switch(MainActivity.sVideoSource){caseVideoSource.SOURCE_CAMERA1:returnVideoCapturers.createCamera1Capturer(true);caseVideoSource.SOURCE_CAMERA2:returnVideoCapturers.createCamera2Capturer(this);default:returnnull;}}
准备 Renderer 和 Encoder:
mVideoView=(SurfaceViewRenderer)findViewById(R.id.mVideoView1);try{Stringfilename="video_source_record_"+System.currentTimeMillis();mMp4Recorder=newMp4Recorder(newFile(Environment.getExternalStorageDirectory(),filename+".mp4"));mHdMp4Recorder=newMp4Recorder(newFile(Environment.getExternalStorageDirectory(),filename+"-hd.mp4"));}catch(IOExceptione){e.printStackTrace();Toast.makeText(this,"start Mp4Recorder fail!",Toast.LENGTH_SHORT).show();finish();return;}mHwAvcEncoder=newHwAvcEncoder(config,mMp4Recorder);mHdHwAvcEncoder=newHwAvcEncoder(hdConfig,mHdMp4Recorder);
创建 VideoSource和VideoSink ,VideoSource 也需要视频配置,但只需要使用预览尺寸、帧率,所以用 config 或者 hdConfig 都可以:
mVideoSink=newVideoSink(mVideoView,mHwAvcEncoder,mHdHwAvcEncoder);mVideoSource=newVideoSource(getApplicationContext(),config,capturer,mVideoSink);
初始化:
mVideoView.init(mVideoSource.getRootEglBase().getEglBaseContext(),null);mHwAvcEncoder.start(mVideoSource.getRootEglBase());mHdHwAvcEncoder.start(mVideoSource.getRootEglBase());
开始采集、录制:
@OverrideprotectedvoidonStart(){super.onStart();mVideoSource.start();}
内存抖动优化
完成了 VideoCRE 的剥离后,我发现内存抖动非常严重,CPU 占用也很高:
排查内存抖动,当然首选 Allocation Tracker 了,结果如下:
这里我们可以看到,60% 的内存分配都发生在 BufferInfo 对象上,但这个对象非常小,只有几个 primitive 数据成员,怎么会出现这么多分配呢?我们看次数,15s 内发生了 2.6 万次,每毫秒分配了 1.7 次。看代码发现,是我在单独的线程调用 dequeueOutputBuffer 时传入的 timeout 为 0,所以在疯狂的创建 BufferInfo 对象。
单独的线程设置 timeout 为 0 显然不合理,除了这里的内存分配,CPU 占用也会更高,所以我们可以设置一个合适的值,这里我换成 3000,也就是 3ms,结果如下:
我们可以看到,内存抖动减缓了很多,但仍比较明显。CPU 占用率倒是下降很多了。
这时 Allocation Tracker 的结果如下:
优化性能切忌盲目,要找准瓶颈,并且测量对比成效。
我们发现最大的分配竟是由一条日志代码引起的!所以不要以为在日志工具函数内通过变量控制是否打日志就够了,即便日志最终没有打印出来,但拼接日志字符串就可能已经成为瓶颈。
除了日志,还存在两处很高的分配:allocateDirect 和 BufferInfo。
其实 BufferInfo 没必要每次创建,我们消费 MediaCodec 输出是单线程行为,只需要分配一次即可。同理,容纳输出数据的 buffer 也没必要每次分配,只有需要扩容时创建即可。
经过上述优化,内存抖动再次减弱:
分析 Allocation Tracker,较高的内存分配分别为:
- Display#getRotation:19.58%;
- 取到 MediaCodec 输出后,构造 ByteBuffer 对象的拷贝:17.31%;
- 帧数据传递过程中 matrix 数组分配:16.32%;
上面这几点都改了之后,再剩下的就是 I420Frame 的创建、日志字符串拼接、Runnable 对象创建了。此外还发现了一个注意点:使用 ExecutorService 时,每次 submit 任务,还会创建一个链表节点对象,而 Handler 会复用 Message 对象,所以我把 ExecutorService 换成了 HandlerThread + Handler 的组合。当然,for each 遍历每次都会创建一个 Iterator 对象,虽然没有成为瓶颈,但也确实可观,何况可以一行代码进行优化,顺手就给做了。
I420Frame 也许可以用对象池来优化,Runnable 则可以把局部变量成员化,但现在其实已经优化了很多(对比测试一分钟的内存分配从 2613976 优化到 240352,优化为了 9.2%),而这些做法需要比较复杂的处理才能确保不会发生消费者-生产者的竞争问题,所以就先告一段落啦!另外,这里并没有贴出具体优化代码,想看代码的朋友,可以查看 GitHub 仓库的这个 commit。
最后在 Nexus 5X 安卓 7.1.2 上测试发现,Camera2 采集时,存在大量的 Binder 通信,内存抖动严重得多,而其中 48.86% 都是由 Binder 通信导致的。
- Camera1 采集:一分钟内存增长 0.32MB;
- Camera2 采集:一分钟内存增长 3.13MB;
对此我就真是黔驴技穷了,只能寄希望于大谷歌了 :(
总结
至此,WebRTC(安卓流媒体)视频的前段就已经差不多了,我们了解了采集、预览、编码的大体实现思路,也详细分析了这些步骤里面可能遇到的坑,最后将这三块相关的代码剥离成为了一个可以单独使用的模块:VideoCRE,并对其运行过程中的内存抖动进行了极大的优化,一分钟内存分配优化为了 9.2%。
当然,流媒体前段还有不少内容没有涵盖:美颜、特效(结合人脸识别、场景分割)、更复杂的渲染……这些内容我还需要更深入的学习和理解,才敢分享,而流媒体的中段(传输)、后段(解码播放)则还有更多的内容等着我们
https://blog.piasy.com/2017/08/11/VideoCRE-and-Memory-Churn-Opt/
WebRTC 源码分析(四):VideoCRE 与内存抖动优化的更多相关文章
- WebRTC源码分析四:视频模块结构
转自:http://blog.csdn.net/neustar1/article/details/19492113 本文在上篇的基础上介绍WebRTC视频部分的模块结构,以进一步了解其实现框架,只有了 ...
- JVM源码分析之警惕存在内存泄漏风险的FinalReference(增强版)
概述 JAVA对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了四种其他引用:SoftReference.WeakReference.PhantomReference.FinalR ...
- JVM源码分析之堆外内存完全解读
JVM源码分析之堆外内存完全解读 寒泉子 2016-01-15 17:26:16 浏览6837 评论0 阿里技术协会 摘要: 概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们 ...
- 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入
使用react全家桶制作博客后台管理系统 前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...
- WebRTC 源码分析(三):安卓视频硬编码
数据怎么送进编码器? 怎么从编码器取数据? 如何做流控? 在开始之前,我们先了解一下 MediaCodec 的基本知识. MediaCodec 基础 Developer 官网 上的描述已经很清楚了,下 ...
- Spark源码分析之九:内存管理模型
Spark是现在很流行的一个基于内存的分布式计算框架,既然是基于内存,那么自然而然的,内存的管理就是Spark存储管理的重中之重了.那么,Spark究竟采用什么样的内存管理模型呢?本文就为大家揭开Sp ...
- ABP源码分析四:Configuration
核心模块的配置 Configuration是ABP中设计比较巧妙的地方.其通过AbpStartupConfiguration,Castle的依赖注入,Dictionary对象和扩展方法很巧妙的实现了配 ...
- ABP源码分析四十七:ABP中的异常处理
ABP 中异常处理的思路是很清晰的.一共五种类型的异常类. AbpInitializationException用于封装ABP初始化过程中出现的异常,只要抛出AbpInitializationExce ...
- WebRTC源码分析(一):安卓相机采集实现分析
WebRTC 的代码量不小,一次性看明白不太现实,在本系列中,我将试图搞清楚三个问题: 客户端之间如何建立连接? 客户端之间如何实现数据传输? 音视频数据的采集.预览.编码.传输.解码.渲染完整流程. ...
随机推荐
- java、oracle对CLOB处理
oracle CLOB字段转换位VARCHAR 1.实际上处理CLOB字段的时候,直接TO_CHAR,当长度超过4000的时候,会报错,提示列被截取: CLOB转varchar2:select to_ ...
- Java操作XML的JAXB工具
在java中操作XML的工作中中,比较方便的工具是JAXB(Java Architecture for XML Binding). 利用这个工具很方便生成XML的tag和Java类的对应关系.参照网上 ...
- IOS 设备备份文件详解 (二)
这篇主要讲解如何解析Manifest.mbdb文件. 使用二进制工具打开这个文件,文件的头6个字节是固定的,相当于是文件的一种标识 后面的内容是一个一个的项,可以使用一个循环来读取文件,一个一个解析. ...
- 60.自己定义View练习(五)高仿小米时钟 - 使用Camera和Matrix实现3D效果
*本篇文章已授权微信公众号 guolin_blog (郭霖)独家公布 本文出自:猴菇先生的博客 http://blog.csdn.net/qq_31715429/article/details/546 ...
- 银联在线支付B2C UnionPay.NET
新春即将来临,首先给大家拜个早年,祝攻城狮们新年快乐.万事如意.合家欢乐.团团圆圆.幸福健康.来年更能大展宏图 实现各自的梦想! 同时预祝各大科技公司大佬们事业蒸蒸日上.公司转型突破创新.冲出突围带领 ...
- ISO27001适用性-导图
大图请戳这里
- WinEdt 6+SumatraPDF -- 实现TeX文档和PDF文件正反向搜索
这几天试用了新出不久的WinEdt 6,结合PDF文件的阅读器SumatraPDF,感觉实现TeX文档和PDF文件之间的正反向搜索十分便利,故此向大家特别推荐! CTeX v2.8版本的一项重要功能是 ...
- Anaconda的使用和包的更新;conda 创建虚拟环境
安装: 官网下好后,直接 sh * 会提示安装路径,选择No的话重新填入安装的路径就可以了: 添加到环境变量: echo 'export PATH="~/anaconda2/bin/$PAT ...
- (转)Using Python3.5 in Ubuntu - Trusty
转自:https://www.reddit.com/r/IPython/comments/3lf81w/using_python35_in_ubuntu_trusty/Note:照这个方案安装pyth ...
- 让浏览器兼容H5元素和媒体查询的代码
<!--[if lt IE 9]> <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.j ...