Android Camera开发实践(4) SurfaceTexture与特效

4,394 阅读5分钟

欢迎关注公众号:sumsmile /专注图像处理的移动开发老兵~~

系列的最后一篇,讲SurfaceTexture的原理,并把前面涉及到的camera预览、OpenGL接入、EGL、帧缓冲、shader串起来,实现一个特效demo。

demo效果

算法参考shadertoy 特效

SurfaceTexture介绍

SurfaceTexture、SurfaceView、GLSurfaceView、TextureView区别

SurfaceTexture相关有三个类,不注意容易混淆。记住一条,SurfaceTexture不能直接显示,可以简单理解成“生产纹理的中间工具”,一般需要配合其他view或者功能模块发挥作用。

注意SurfaceView Texture区别

一个window中所有的view最后会合成一个图像绘制到屏幕上,但SurfaceView除外,SurfaceView有自己的独立窗口,所以SurfaceView性能较好,但是功能上不如TextureView操作方便,不支持旋转,平移等操作。

参考
SurfaceView实现原理分析
developer training
SurfaceFlinger 详解

SurfaceView TextureView对比:

SurfaceView VS TextureView

详细区别参阅 SurfaceView、GLSurfaceView、SurfaceTexture、TextureView 区别

SurfaceTexture工作原理

Android开发文档里对SurfaceTexture的工作流有简单的介绍

相机采集的图像经SurfaceTexture处理后,输送到OpenGLES,gl处理完再输送到SurfaceView展示,最后由SurfaceFlinger合成;或经OpenGLES处理后,输送到MediaCodec编码,编码成视频文件。

抽出SurfaceTexture的核心逻辑,如下图。SurfaceTexture中维护了一个队列,存储图像Buffer,从Camera、Decoder等模块输入数据,转换成纹理,输送到OpenGL处理。这是一个典型的生产-消费模式。

注意:在GL环境中处理Texture时,key是“GLES11Ext.GL_TEXTURE_EXTERNAL_OES”,什么是TEXTURE_EXTERNAL_OES呢?

如上图所示,camera等采集的图像数据,不能直接用于opengl,需要以拓展纹理的方式处理,实际经过EglImageKHR转换,这部分工作是EGL做的。我的理解是,图像的生产不在GL线程中,纹理不能共享给OpenGL线程,另外采集的图像数据是YUV格式的,需要转换成普通的RGB。

关于EglImageKHR,详细参考.

SurfaceTexture代码分析

代码分析参考谈一谈 Android 上的 SurfeceTexture

来看看SurfaceTexture里的代码,建立直观的认识.

SurfaceTexture构造函数中调用nativeInit初始化

// 构造函数
// singleBufferMode是否是单buffer,默认为false
public SurfaceTexture(int texName, boolean singleBufferMode) {
   mCreatorLooper = Looper.myLooper();
   mIsSingleBuffered = singleBufferMode;
   //native方法nativeInit
   nativeInit(false, texName, singleBufferMode, new WeakReference<SurfaceTexture>(this));
}

重点在nativeInit方法里,生成生产者和消费者,创建图像缓冲队列。调用updateTexImage,会读取缓冲队列里的数据到TextureId绑定的纹理内存中

// frameworks\base\core\jni\SurfaceTexture.cpp
// texName为应用创建texture名
// weakThiz为SurfaceTexture对象弱引用
static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
        jint texName, jboolean singleBufferMode, jobject weakThiz)
{
    sp<IGraphicBufferProducer> producer;
    sp<IGraphicBufferConsumer> consumer;
    // 创建IGraphicBufferProducer及IGraphicBufferConsumer
    
    BufferQueue::createBufferQueue(&producer, &consumer);

    if (singleBufferMode) {
        consumer->setMaxBufferCount(1);
    }

    sp<GLConsumer> surfaceTexture;
    // isDetached为false
    if (isDetached) {
        ....
    } else {
        // 将consumer和texName封装为GLConsumer类对象surfaceTexture
        surfaceTexture = new GLConsumer(consumer, texName,
                GL_TEXTURE_EXTERNAL_OES, true, !singleBufferMode);
    }
    ....
    // 收到帧数据是会触发ctx->onFrameAvailable方法
    surfaceTexture->setFrameAvailableListener(ctx);
    SurfaceTexture_setFrameAvailableListener(env, thiz, ctx);
}

原理先讲到这了,下面我们来一段实践,将前面所学的都结合起来,实现一个动效.

实践.基于SurfaceTexture预览+gl特效

完整工程:完整工程 CameraDemo

github.com/ChinaZeng/O…

注意替换fragment_shader_screen.glsl里的逻辑,默认是没有特效的

代码结构:

代码说明:

参考上图,做一些说明辅助理解。

CameraEglSurfaceView

CameraEglSurfaceView继承自GLSurfaceView,作为默认的渲染屏幕。

CameraFboRender

实现Renderer接口,创建surfaceTexture,绑定纹理id cameraRenderTextureId。


onSurfaceListener()回调里,打开相机预览,预览生成的数据绑定到surfaceTexture


onDrawFrame()回调里,更新纹理到cameraRenderTextureId

第一次绘制:调用OpenGL ES的API绘制,将cameraRenderTextureId的纹理绘制到FrameBuffer上,实际上是绘制到FrameBuffer关联的Texture(fboTextureId)。注意,这是一次空绘制,shader里啥也没干,如果你有啥创意,也可以改shader来实现。

相机采集的数据存放到SurfaceTexture的BufferQueue中,调用updateTexImage()更新buffer到textureId。

这个案例中,进行了两次绘制,第一次绘制到FrameBuffer上(离屏渲染),第二次绘制到GLSurfaceView上。这么做是为了展示可以拿到纹理,渲染到不同的缓冲中。

实际上只要你愿意,可以在中间像接火车一样多次渲染到FrameBuffer,每一层可以做特殊的处理,这就是GPUImage最核心的逻辑,将不同的特效滤镜连在一起,像搭积木一样拼凑成任意的酷炫效果,非常好的解耦及复用。当然拆成一个个独立的滤镜也有性能上的缺陷,每多绘制一次,就有一次性能的折损。

CameraRender

第二次绘制,将第一次绘制生成的fboTextureId,作为第二次绘制的输入,绘制到默认的设备上,即CameraEglSurfaceView,可以看到渲染的效果。

添加特效。 shader实现放在工程的raw目录里,可以借用android的API方便的读取。

核心逻辑在片元着色器里,glsl实现参考: www.shadertoy.com/view/7dXXWs

迁移到自己的工程里,需要做些改造。

注意,shadertoy里的实现是基于OpenGL 3.0实现的,迁移到ES 2.0有些API不一样,主要有:

  • fragColor(3.0) --> gl_FragColor(2.0)
  • 没有in out关键字(2.0)
  • texture(3.0) --> texture2D(2.0)
  • ...

GLES里用到外部纹理,需要在shader里声明

//申明使用扩展纹理
extension GL_OES_EGL_image_external : require 

剩下的代码细节,读者朋友们自己分析吧,耐心读完代码,相信你会有不少收获。

欢迎关注公众号:sumsmile /专注图像处理的移动开发老兵~~

参考

[1] shadertoy 特效: www.shadertoy.com/view/7dXXWs

[2] SurfaceView实现原理分析: juejin.cn/post/684490…

[3] developer training: google-developer-training.github.io/android-dev…

[4] SurfaceFlinger 详解: www.cnblogs.com/blogs-of-lx…

[5] SurfaceView、GLSurfaceView、SurfaceTexture、TextureView 区别: blog.csdn.net/qq_34712399…

[6] android SurfaceTexture: source.android.com/devices/gra…

[7] eglimage in surfaceflinger: waynewolf.github.io/2014/05/17/…

[8] 谈一谈 Android 上的 SurfeceTexture: mp.weixin.qq.com/s/7kwrlxyct…

[9] 完整工程 CameraDemo: github.com/ChinaZeng/O…