修改chromium内核,实现小程序的camera组件同层渲染(Android端)

4,180 阅读6分钟

一、原理分析及准备

(一)同层渲染原理

首先,从微信的这篇小程序同层渲染原理剖析文档入手,看看同层渲染的原理及效果。

技术方向关键点

Android 端小程序在 Android 端采用 chromium 作为 WebView 渲染层,与 iOS 不同的是,Android 端的 WebView 是单独进行渲染而不会在客户端生成类似 iOS 那样的 Compositing View (合成层),经渲染后的 WebView 是一个完整的视图,因此需要采用其他的方案来实现「同层渲染」。经过我们的调研发现,chromium 支持 WebPlugin 机制,WebPlugin 是浏览器内核的一个插件机制,主要用来解析和描述embed 标签。Android 端的同层渲染就是基于 embed 标签结合 chromium 内核扩展来实现的。 实现的技术方向关键就是最后一句话:Android 端的同层渲染就是基于 embed 标签结合 chromium 内核扩展来实现的。

实现原理关键点

Android 端「同层渲染」的大致流程如下:
1. WebView 侧创建一个 embed DOM 节点并指定组件类型;
2. chromium 内核会创建一个 WebPlugin 实例,并生成一个 RenderLayer;
3. Android 客户端初始化一个对应的原生组件;
4. Android 客户端将原生组件的画面绘制到步骤2创建的 RenderLayer 所绑定的 SurfaceTexture 上;
5. 通知 chromium 内核渲染该 RenderLayer;
6. chromium 渲染该 embed 节点并上屏。 注意这里的几个关键词:插件机制,SurfaceTexture,渲染。
总结一下,我们需要借助插件机制,把camera预览的内容借助SurfaceTexture绘制到网页的一个标签里面去,以此实现同层渲染效果。

(二)Android端的Camera预览

使用SurfaceView/TextureView显示预览界面

常见的写法,这里不具体介绍了,直接输出到View上,无法二次处理摄像头数据。

使用GLSurfaceView显示预览界面

接触过camera预览的开发人员,应该都知道camera.setPreviewTexture(SurfaceTexture texture)这个方法,它可以把camera的数据输出到SurfaceTexture,而TextureView内正好可以提供SurfaceTexture,所以我们经常把TextureView作为camera的预览界面。

那么,不使用TextureView,可以生成SurfaceTexture吗?答案是可以的,使用opengles的glGenTextures,生成EXTERNAL_OES类型的纹理,基于这个纹理id创建SurfaceTexture对象,最终再用opengles绘制即可看见camera的预览界面。 实际上这种方式更灵活,可以通过这种方式,二次处理(比如滤镜,美颜)后再显示出来。

这里,我们使用类似的原理把camera的图像数据绘制到embed标签对应的区域,参与整个网页的合成,从而达到同层渲染的效果。

二、关键点实现分析及记录

第一步:插件机制简单了解

chromium的插件机制就是指ppapi(全称Pepper Plugin Application Programming Interface),这个名称可能大家不熟悉,但大家熟悉的flash插件就是基于这个ppapi机制实现的,此外chromium的pdf预览也是如此。但是,在Android平台上,chromium默认是不支持ppapi的,所以我们得调整编译参数及配置,修改源码,让ppapi在Android上也能跑起来,这个不是同层渲染的重点,就不详细说明了。

第二步:准备opengles的绘制环境

我们先来了解chromium的两个概念:
Command Buffer 是一个跨平台跨进程的机制,所有对OpenGLES的调用可以被缓存并传输到GPU进程中去执行。
Graphics3D ppapi提供的Graphics3D接口,支持在插件侧使用opengles进行绘制。

直接上图,看一下插件的opengles上下文环境是如何创建的

图一.jpg

第三步:准备标签对应的绘制区域

chromium的官方文档How cc Works中提到,TextureLayer这种图层,用于插件,canvas,WebGL这些图层内容的光栅化是自己负责而不是通过合成器的光栅化器。 网页是由多个图层合成后渲染显示的,而插件就是其中一个图层。 每一个标签对应一个PepperPluginInstanceImpl对象,它的成员变量texture_layer_指向的是一个cc::TextureLayer对象,前一步的opengles接口就是把内容绘制到此。

图二.jpg

第四步:绘制camera数据

我们先来了解chromium的SharedImage。本想找个官方文档看看,却意外发现有个大佬的文章分析得很好,Chromium GPU Resource Share (Shared Image)。引用其中一段:

SharedImage 机制本质上抽象了 GPU 的数据存储能力。即允许应用直接把数据存储到 GPU (GPU 能访问到的内存)中,以及直接从 GPU 中读取数据,并且允许跨过shared group边界。理解了这一点,应该比较容易想到哪些场景可以使用 SharedImage 机制,下面这些是 Chromium 中使用 SharedImage 机制的一些场景:
1. CC模块: 先将画面 Raster 到 SharedImage,然后再发送给 Viz 进行合成。
2. OffscreenCanvas: 先将 Canvas 的内容 Raster 到 SharedImage,然后再发送给 Viz 进行合成。
3. 图片处理/渲染: 一个线程将图片解码到 GPU 中,另一个线程使用 GPU 来修改或者渲染图片。
4. 视频播放: 一个线程将视频解码到 GPU 中,另一个线程来渲染。 我们这里就是把camera数据Raster到SharedImage,,然后再发送给 Viz 进行合成。

图三.jpg

三、做一个demo看看效果

录屏做了一个40秒的大gif图 图四.gif

可以看到网页缩放效果同时应用到了camera插件,在inspect调试工具里面也能实时预览到camera插件的数据。下面是html的实现,把img标签显示在camera插件上层。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>camera</title>
    <script>
        function openCamera(){
              var plugin = document.getElementById('plugin');
              plugin.postMessage('Create');
        }
    </script>
    <style type="text/css">
        img
        {
        position:absolute;
        left:168px;
        top:600px;
        z-index:1;
        }
    </style>

</head>
<body>
<embed id="plugin" type="application/x-ppapi-camera"
       width="400" height="680"/>
<img src="222.png" height="64" width="64" />
<button type="button" onclick="openCamera()" style="width:100px;height:100px">测试</button>
</body>
</html>

四、总结

同层渲染功能进一步提升Hybrid App的体验及开发效率,是app平台化开发模式和小程序模式的底层支撑。
实现同层渲染功能,需要改动或者了解chromium的多个模块,比如cc,viz,content,ppapi,gpu等,以及一些机制,比如IPC,跨进程共享资源机制等,这篇文章只简单介绍了一些直接关联的点,实现途中遇到的各种各样的问题,还是需要更深入的了解细节和chromium整体设计架构,否则有时容易一头雾水,不知道从哪里入手。

五、下期预告:

看一下UC内核发布的文章Web 技术发展趋势及 U4 内核技术演进其中有一段介绍了“混合渲染”,其实和同层渲染是一个概念。

混合渲染主要是想解决这样的一个业务痛点:
某些业务之前是用 native 实现的,后面想用 Web 去实现,但是使用的部分第三方组件只有 native 版本。如果使用 Web 重新开发第三方组件,成本会比较高,而且效果也会打个问号。最典型的一个场景就是地图控件。
为了解决这个痛点,我们实现了混合渲染方案。该方案提供了在页面中嵌入 native 组件的能力,将页面和 native 组件无缝地融合起来,并且保证整体效果和交互非常自然,业务不需要做额外的适配。 小程序使用地图简直太常见了。好,下一期就研究Map组件同层渲染了。

传送门:修改chromium内核,实现小程序的Map组件同层渲染(Android端)