共计 6629 个字符,预计需要花费 17 分钟才能阅读完成。
导读:WebRTC 中的 Android VDM(Video Device Manager)技术模块,是指 WebRTC 基于 Android 零碎,对视频数据采集、编码、解码和渲染的治理。当你拿到一部 Android 手机,通过网易云信 SDK 进行 RTC 通信时,你是否好奇,Android 零碎的 VDM 是如何实现的?WebRTC 又是如何应用 Android VDM 的?本文对 WebRTC 中 Android VDM 的实现进行了合成和梳理。
文|Iven
网易云信资深音视频客户端开发工程师
01 Android 图形系统介绍
视频是由一幅幅图像组成的序列,在 Android 零碎中,图像的载体是 Surface。Surface 能够了解为 Android 零碎内存中的一段绘图缓冲区。无论开发者应用什么渲染 API,所有内容都会渲染到 Surface 上。Android 的采集、编解码、渲染,都是基于对 Surface 的解决。Surface 示意缓冲区队列中的生产方,而缓冲区队列通常会被 SurfaceFlinger 或显示 OpenGL ES 耗费。在 Android 平台上创立的每个窗口都由 Surface 提供反对。所有被渲染的可见 Surface 都被 SurfaceFlinger 合成到屏幕,最初显示到手机屏幕上。
Android 图形系统中的生产者和消费者模型
后面提到,Surface 示意缓冲区队列中的生产方,对应下图的 Producer。BufferQueues 是 Android 图形组件之间的粘合剂,它是一个队列,将可生成图形数据缓冲区的组件(生产方)连贯到接收数据以便进行显示或进一步解决的组件(生产方)。一旦生产方移交其缓冲区,生产不便会负责将生产进去的内容进行解决。
图像流生产方能够是生成图形缓冲区以供耗费的任何内容。例如 Canvas 2D 和 mediaserver 视频解码器。图像流的最常见耗费方是 SurfaceFlinger,该零碎服务会耗费以后可见的 Surface,并应用窗口管理器中提供的信息将它们合成到屏幕。
整个 Android 图像的数据流管线很长,所以生产者和消费者的角色其实是绝对的,同一个模块对应的可能既是生产者,也是消费者。OpenGL ES 既能够作为生产方提供相机采集图像流,也能够作为生产方耗费视频解码器解码进去的图像流。
Android 图形显示 pipeline
SurfaceFlinger 是整个 Android 屏幕显示的外围。SurfaceFlinger 过程由 init 过程创立,把零碎中所有应用程序的最终绘图后果合并成一张,而后对立显示到物理屏幕上。
一个应用程序在送给 SurfaceFlinger 之前,是多个生产者在同时工作的,也就是有多个 Surface 通过 BufferQueue 向 SurfaceFlinger 输送数据。如下图的 Status Bar、System Bar、Icons/Widgets 等;但无论有多少个生产者,最终都在 SurfaceFlinger 这个消费者这里,合并成一个 Surface。
值得一提的是 SurfaceFlinger 的硬件加速性能,如果所有的合成都交给 SurfaceFlinger 进行解决,对 GPU 的累赘会减轻,所以当初大部分手机都反对硬件加速进行合成,也就是 HWComposer。HWComposer 通过专门的硬件加速加重 GPU 累赘,帮忙 Surfaceflinger 高效疾速地进行 Surface 的合成。
如下图,以关上零碎 Camera 为例,能够看到下图屏幕截图的显示其实对应了 6 个 Surface,图中标注了 4 个 Surface,另外的 2 个在图中不可见,所以没有标注进去。Camera 数据的显示作为其中一个 Surface(SurfaceView Layer),占用大部分的 SurfaceFlinger 合成计算,因为 Camera 数据内容会一直的变动,所以 GPU 须要从新绘制。
那么另外两个不可见的 Surface 是哪两个呢?其实就是下图对应的 NavigationBar,因为暗藏了,所以图中看不到。
另外一个是 Dim Layer,因为“USB 用于”这个窗口置顶了,使他前面的窗口产生了一个变暗的通明成果,这也是一个独自的 Surface。
02 WebRTC 中 Android 端 VDM
讲完 Android 零碎的 VDM 模块,那么 WebRTC 是如何治理应用 Capturer、Encoder、Decoder、Render 这四个模块的呢?还是依照生产者消费者模型,咱们分为:
- 生产者(绿色):Capturer、Decoder;
Capturer 采集到数据、Decoder 解码到数据后,一直往 SurfaceTexture 的 Surface 中送数据。SurfaceTexture 通过 OnFrameAvailableListener 告诉消费者进行解决。WebRTC 中 Capturer 应用 Camera1/Camera2 实现,Decoder 应用 MediaCodec 实现。 - 消费者(蓝色):Render、Encoder;
Render 和 Encoder 各自的 Surface 通过 eglCreateWindowSurface() 跟 EGLSurface 进行关联,而 EGLSurface 跟 SurfaceTexture 又是共用同一个 EGLContext。这样 EGLSurface 就买通了 SurfaceTexture 跟 render/Encoder 的数据通道。EGLSurface 通过读取 SurfaceTexture 的 Surface 数据,进行 shader 语言的图形绘制。最终通过 eglSwapBuffers() 来提交以后帧,数据最终绘制到 Render 和 Encoder 的 Surface 上。
WebRTC 中 Render 应用 SurfaceView,不过 TextureView 在开源的 WebRTC 代码中并没有实现,感兴趣的小伙伴能够自行实现。Encoder 应用 MediaCodec 实现。
采集
Android 零碎的采集在 WebRTC 中次要应用的是 Camera 采集和屏幕采集。WebRTC 中还有内部采集,比方从内部输出纹理数据和 buffer 数据,然而内部采集不依赖 Android 原生零碎性能,所以不在本文探讨范畴内。
- Camera 采集
经验了多个零碎相机架构迭代。目前提供 Camera1/Camera2/CameraX 三种应用形式。
Camera1 在 5.0 以前零碎上应用,应用办法比较简单。开发者能够设置的参数无限。能够通过 SurfaceTexure 获取纹理数据。如果视频前解决或者软件编码须要获取 buffer 数据,可通过设置摄像头采集视频流格局和 Nv21 数据。
Camera2 是 5.0 时,谷歌针对摄像头新推出的一套 API。开发应用上比 Camera1 简单,有更多的 Camera 控制参数,能够通过 SurfaceTexure 获取纹理数据。如果视频前解决或者软件编码须要获取 buffer 数据,可通过 ImageReader 设置监听,拿到 i420/rgba 等数据。
CameraX 是 jetpack 的一个反对库提供的办法。应用办法比 Camera2 更简略,从源码看次要是封装了 Camera1/Camera2 的实现,让用户不用去思考什么时候应用 Camera1,什么时候应用 Camera2。CameraX 让使用者更关注采集数据自身,而不是繁冗的调用形式和头疼的兼容性 / 稳定性问题。CameraX 在 WebRTC 源码中没有实现,感兴趣同学能够自行钻研。
- 屏幕采集
5.0 当前,Google 凋谢了屏幕共享 API:MediaProjection,然而会弹出录屏权限申请框,用户批准后能力开始录屏。在 targetSdkVersion 大于等于 29 时,零碎增强了对屏幕采集的限度,必须先启动相应的前台 Service,能力失常调用 getMediaProjection 办法。对于数据的采集,跟 Camera2 的数据采集形式相似,也是通过 SurfaceTexure 获取纹理数据,或者通过 ImageReader 获取 i420/rgba 数据。笔者尝试在屏幕共享时获取 i420,没有胜利,看起来大部分手机是不反对在屏幕共享时输入 i420 数据的。屏幕共享的采集帧率没法管制,次要法则是在屏幕静止时,采集帧率升高。如果静止画面,采集帧率能够达到最高的 60fps。屏幕共享 Surface 的长宽设置如果跟屏幕比例不统一,在局部手机上可能存在黑边问题。
编解码
说起 Android MediaCodec,这张图肯定会被重复提及。MediaCodec 的作用是解决输出的数据生成输入数据。首先生成一个输出数据缓冲区,将数据填入缓冲区提供给 Codec,Codec 会采纳异步的形式解决这些输出的数据,而后将填满输入缓冲区提供给消费者,消费者生产完后将缓冲区返还给 Codec。
在编码的时候,如果输出的数据是 texture,须要从 MediaCodec 获取一个 Surface,通过 EGLSurface 将 Texture 数据绘制到这个 Surface 上。这种形式全程基于 Android 零碎 Surface 绘制管线,认为是最高效的。
在解码的时候,如果想要输入到 texture,须要将 SurfaceTexture 的 Surface 设置给 MediaCodec,MediaCodec 作为生产者源源不断地将解码后的数据传递给 SurfaceTexture。这种形式全程基于 Android 零碎 Surface 绘制管线,认为是最高效的。
除了高效的基于 texture 的操作,MediaCodec 能够对压缩编码后的视频数据进行解码失去 NV12 数据,也反对对 i420/NV12 数据进行编码。
WebRTC 源码除了基于 MediaCodec 的硬件编解码,还实现了软件编解码。通过软硬件的切换策略,很好的思考了性能和稳定性的均衡。
MediaCodec 其实也有软件编解码的实现。MediaCodec 的底层实现是基于开源 OpenMax 框架,集成了多个软硬件编解码器。不过个别在理论应用过程中,并没有应用 Android 零碎自带的软件编解码,咱们更多的是应用硬件编解码。
在应用 MediaCodec 硬件编解码时,能够获取 Codec 相干信息。如下以“OMX.MTK.VIDEO.ENCODER.AVC”编码器为例,可通过 MediaCodecInfo 提供编码器名字、反对的色彩格局、编码 profile/level、可创立的最大实例个数等。
渲染
SurfaceView:从 Android 1.0(API level 1) 时就有。与一般 View 不同,SurfaceView 有本人的 Surface,通过 SurfaceHolder 进行治理。视频内容能够独自在这个 Surface 上进行独自线程渲染,不会影响主线程对事件的响应,然而不能进行挪动、旋转、缩放、动画等变动。
TextureView:从 Android 4.0 中引入,能够跟一般 View 一样进行挪动、旋转、缩放、动画等变动。TextureView 必须在硬件加速的窗口中,当有其余 View 在 TextureView 顶部时,更新 TextureView 内容时,会触发顶部 View 进行重绘,这无疑会减少性能方面耗费。在 RTC 场景中,大部分时候都会在视频播放窗口下面减少一些管制按钮,这时候应用 SurfaceView 无疑性能上更有劣势。
03 VDM 的跨平台工程实现
说到 WebRTC,不得不说它的跨平台特点。那么 Android VDM 是如何通过跨平台这个框架进行工作的呢?
依据笔者的了解,将 Android VDM 在 WebRTC 中的实现分为 4 层。从上到下分为:Android Java Application、Java API、C++ Wrapper、All In One API。
- All In One API :
理解 WebRTC 的同学都晓得,跨平台的代码都是 C/C++ 实现的,因为 C/C++ 语言在各平台具备良好的通用性。WebRTC 通过对各平台,包含 Android/IOS/Windows/MAC、Encoder/Decoder/Capturer/Render 模块的形象,造成了 All In One API。各平台基于这些 API,各自基于不同操作系统去实现对应性能。这里不得不赞叹 C++ 的多态性的厉害之处。通过 All In One API,WebRTC 在 PeerConnection 建设后的媒体数据传输、编解码器的策略管制、大小流、主辅流的切换等性能,能力顺利搭建,All In One API 是整个音视频通信建设的根底。
- C++ Wrapper:
这一层,是 Android 对应 Java 模块在 native 层的封装,并且继承自 All In One API 层的对应模块,由 C++ 实现。通过 Wrapper 使 C ++ 层能够无感知的拜访 Android 的 Java 对象。技术上,通过 Android 的 JNI 来实现。以 VideoEncoderWrapper 为例,VideoEncoderWrapper 封装了 Java 的 VideoEncoder 对象,VideoEncoderWrapper 又继承自 All In One API 的 VideoEncoder。这样通过调用 All In One API 的 VideoEncoder,实际上也就是执行到了 Android Java 的具体实现。除了 Android 平台,其余平台也能够通过同样的办法在这一层进行封装,这一层能够说是一个大熔炉,Android/IOS/Windows/MAC 的平台属性都能够失去封装。C++ Wrapper 这一层,真是 WebRTC 跨平台层和各平台具体实现的完满桥梁。
- Java API
这一层提供了 WebRTC 在 Java 实现的 API 接口,通过继承这些 API,使得 Android SDK Application 的实现具备更好的扩展性。比方 CameraVideoCapturer 和 ScreenCapturerAndroid 通过继承 VideoCapturer,实现了 Camera 和 Screen 的采集。当后续开发维护者想要增加其余视频采集形式时,通过继承 VideoCapturer,能够实现良好扩展性。再比方图中的 SurfaceViewRender 继承自 VideoSink,如果开发者想要实现基于 TextureView 的 Render,同样的通过继承 VideoSink,即可疾速实现。
- Android SDK Application
这一层是真正的 Android VDM 实现的中央,是基于 Android SDK API 对 Encode/Decode/Capture/Render 性能的具体实现。这是离 Android 零碎最近的一层。在这一层的实现中值得注意的是:Capturer/Encode/Decode 是由跨平台层触发对象的创立和销毁,而 Render 是从 Java 创建对象,而后被动传递到跨平台层的。所以对于 Render 的创立 / 销毁,须要分外留神,避免野指针的呈现。
04 RTC 场景中的 VDM 参数适配优化
上一章提到了 Android Java Application 层是具体性能实现的中央,而对于 All In One API 这一层,是对所有平台的形象。所以在调用的时候,并不关怀平台相干的一些兼容性问题。而对于 Android 零碎,绕不开的也是兼容性的问题。所以如果想要 Android VDM 性能在基于 All In One API 层的简单调用下,保持稳定运行,兼容性适配问题的解决是不可漠视的,须要有个比较完善的兼容适配框架,通过线上下发、本地配置读取、代码层面的逻辑解决等伎俩,对不同的设施机型、不同 CPU 型号、不同 Android 零碎版本、不同业务场景等进行全方位的是适配优化。下图是对兼容性问题的下发配置形式框架图。通过保护一份兼容配置参数,通过 Compat 设置到 VDM 各模块,以解决兼容性问题。
05 总结
本文通过对 Android 显示零碎的介绍,进而引出 WebRTC 在 Android 平台上的 VDM 实现,并且深刻 WebRTC 源码,将 Android VDM 在 WebRTC 中的实现剖解为 4 层,从上到下分为:Android SDK Application、Java API、C++ Wrapper、All In One API。
同时对于 Android 不能漠视的兼容性问题的工程实现,做了简略介绍。通过剖析 WebRTC 在 Android VDM 上的实现,咱们能够更加深刻理解 WebRTC 的视频零碎的实现架构,以及跨平台实现的架构思维。
作者介绍
Iven,网易云信资深音视频客户端开发工程师,次要负责视频工程,Android VDM 相干工作。