Android Lollipop 增加了Camera2 API,并将原来的Camera API标记为废弃了。相对原来的Camera API来说,Camera2是重新定义的相机 API,也重构了相机 API 的架构。初看之下,可能会感觉Camera2使用起来比Camera要复杂,然而使用过后,你也许就会喜欢上使用Camera2了。无论是Camera还是Camera2,当相机遇到OpenGL就比较好玩了。

问题及思路

Camera的预览比较常见的是使用SurfaceHolder来预览,Camera2比较常见的是使用TextureView来预览。如果利用SurfaceHolder作为Camera2的预览,利用TextureView作为Camera的预览怎么做呢?实现起来可能也很简单,如果预览之前,要做美颜、磨皮或者加水印等等处理呢?实现后又如何保证使用Camera还是Camera2 API,使用SurfaceHolder还是TextureView预览,或者是直接编码不预览都可以迅速的更改呢?
本篇博客示例将数据、处理过程、预览界面分离开,使得无论使用Camera还是Camera2,只需关注相机自身。无论最终期望显示在哪里,都只需提供一个显示的载体。详细点说来就是:

  1. 以一个SurfaceTexture作为接收相机预览数据的载体,这个SurfaceTexture就是处理器的输入。
  2. SurfaceView、TextureView或者是Surface,提供SurfaceTexture或者Surface给处理器作为输出,来接收处理结果。
  3. 重点就是处理器了。处理器利用GLSurfaceView提供的GL环境,以相机数据作为输入,进行处理,处理的结果渲染到视图提供的输出点上,而不是GLSurfaceView内部的Surface上。当然,也可以不用GLSurfaceView,自己利用EGL来实现GL环境,问题也不大。具体实现就参照GLSurfaceView的源码来了。

处理效果

既然是用OpenGL来处理,索性利用OpenGL在图像上加两个其他图片,类似水印、贴纸的效果。随便两幅图贴上去,也不管好看不好看了,重点是功能。依次为先贴纸然后灰色滤镜,先灰色滤镜然后贴纸,只有贴纸。

具体实现

根据上述思路来,主要涉及到以下问题:

  1. 使用GLSurfaceView创建GL环境,但是要让这个环境为离屏渲染服务,而不是直接渲染到GLSurfaceView的Surface上。在这其中还涉及到其他的一些问题,具体的问题,在下面再说。
  2. OpenGL 的使用。相关文章
  3. 务必使相机、处理过程、显示视图分离。以便能够自由的替换数据源、显示视图,只需要关注处理过程。

GLSurfaceView的利用

通常我们在Android中使用OpenGL环境,只需要在GLSurfaceView的Renderer接口中,调用GL函数就好了。这是因为GLSurfaceView在内部帮我们创建了GL环境,如果我们要抛开GLSurfaceView的话,只需要根据GLSurfaceView创建GL环境的过程在,做相同实现就可了,也就是EGL的使用。也就是说,OpenGL是离不开EGL的。EGL的使用步骤参考。
首先,我们使用GLSurfaceView,是希望利用它的GL环境,而不是它的视图,所以,我们需要改变它的渲染位置为我们期望的位置:

//这句是必要的,避免GLSurfaceView自带的Surface影响渲染
getHolder().addCallback(null);
//指定外部传入的surface为渲染的window surface
setEGLWindowSurfaceFactory(new GLSurfaceView.EGLWindowSurfaceFactory() {@Overridepublic EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfigconfig, Object window) {//这里的surface由外部传入,可以为Surface、SurfaceTexture或者SurfaceHolderreturn egl.eglCreateWindowSurface(display,config,surface,null);}@Overridepublic void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {egl.eglDestroySurface(display, surface);}
});

另外,GLSurfaceView的GL环境是受View的状态影响的,比如View的可见与否,创建和销毁,等等。我们需要尽可能的让GL环境变得可控。因此,GLSurfaceView有两个方法一顶要暴露出来:

public void attachedToWindow(){super.onAttachedToWindow();
}public void detachedFromWindow(){super.onDetachedFromWindow();
}

这里就又有问题了,因为GLSurfaceView的onAttachedToWindow和onDetachedFromWindow是需要保证它有parent的。所以,在这里必须给GLSurfaceView一个父布局

//自定义的GLSurfaceView
mGLView=new GLView(mContext);//避免GLView的attachToWindow和detachFromWindow崩溃
new ViewGroup(mContext) {@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {}
}.addView(mGLView);

另外,GLSurfaceView的其他设置:

setEGLContextClientVersion(2);
setRenderer(TextureController.this);
setRenderMode(RENDERMODE_WHEN_DIRTY);
setPreserveEGLContextOnPause(true);

这样,我们就可以愉快的使用GLSurfaceView来提供GL环境,给指定的Surface或者SurfaceTexture渲染图像了。

数据的接收

我们针对的是相机的处理,相机的图像数据和视频的图像数据,在Android中都可以直接利用SurfaceTexture来接收,所以我们可以提供一个SurfaceTexture给相机,然后将SurfaceTexture的数据拿出来,调整好方向,作为原始数据。具体处理和相机普通的预览类似,不同的是,我们是不希望直接显示到屏幕上的,而且在后续我们还会对这个图像做其他处理。所以我们时将相机的当前帧数据渲染到一个2d的texture上,作为后续处理过程的输入。所以在渲染时,需要绑定FrameBuffer。

@Override
public void draw() {boolean a=GLES20.glIsEnabled(GLES20.GL_DEPTH_TEST);if(a){GLES20.glDisable(GLES20.GL_DEPTH_TEST);}if(mSurfaceTexture!=null){mSurfaceTexture.updateTexImage();mSurfaceTexture.getTransformMatrix(mCoordOM);mFilter.setCoordMatrix(mCoordOM);}EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]);GLES20.glViewport(0,0,width,height);mFilter.setTextureId(mCameraTexture[0]);mFilter.draw();Log.e("wuwang","textureFilter draw");EasyGlUtils.unBindFrameBuffer();if(a){GLES20.glEnable(GLES20.GL_DEPTH_TEST);}
}

上面所使用的mFilter就是用来渲染相机数据的Filter,该Filter所起的作用就是将相机数据的方向调整正确。然后通过绑定FrameBuffer并制定接受渲染的Texture,就可以将相机数据以一个正确的方向渲染到这个指定的Texture上了。
mFilter的顶点着色器为:

attribute vec4 vPosition;
attribute vec2 vCoord;
uniform mat4 vMatrix;
uniform mat4 vCoordMatrix;
varying vec2 textureCoordinate;void main(){gl_Position = vMatrix*vPosition;textureCoordinate = (vCoordMatrix*vec4(vCoord,0,1)).xy;
}

其片元着色器为:

#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 textureCoordinate;
uniform samplerExternalOES vTexture;
void main() {gl_FragColor = texture2D( vTexture, textureCoordinate );
}

渲染相机数据到指定窗口上

在之前利用OpenGLES预览Camera的博客中,我们是直接将相机的数据“draw”到屏幕上了。在上面的处理中,我们在绘制之前调用了EasyGlUtils.bindFrameTexture(fFrame[0],fTexture[0]),这个方法是让我们后续的渲染,渲染到fTexture[0]这个纹理上。具体可以参考前面的博客FBO离屏渲染。
所以,通过上面的方式接收数据,然后利用自定义的GLSurfaceView指定渲染窗口并执行渲染后,我们依旧无法看到相机的预览效果。为了将相机的数据渲染到屏幕上,我们需要将fTexture[0]的内容再渲染到制定的窗口上。这个渲染比之前的接收相机数据,渲染到fTexture[0]上更为简单:

AFilter filter=new NoFilter(getResource());...void onDrawFrame(GL10 gl){GLES20.glViewPort(0,0,width,height)filter.setMatrix(matrix);filter.setTexture(fTexture[0]);filter.draw();
}...

NoFilter的顶点着色器为:

attribute vec4 vPosition;
attribute vec2 vCoord;
uniform mat4 vMatrix;varying vec2 textureCoordinate;void main(){gl_Position = vMatrix*vPosition;textureCoordinate = vCoord;
}

片元着色器为:

precision mediump float;
varying vec2 textureCoordinate;
uniform sampler2D vTexture;
void main() {gl_FragColor = texture2D( vTexture, textureCoordinate );
}

没错,就是显示超简单的渲染一张图片的着色器,看过前面的博客的应该见过这段着色器代码。

增加滤镜、贴纸效果

如果仅仅是预览相机,我们这种做法简直就是多次一句,直接指定渲染窗口,渲染出来就万事大吉了。但是仅仅是这样的话,就太没意思了。而现在要做的就是相机有意思的起点了。很多有意思的相机应用,都可以通过这样的方式去实现,比如我们常见美妆、美颜、色彩处理(滤镜),甚至瘦脸、大眼,或者其他的让脸变胖的,以及一些给相机中的人带眼镜、帽子、发箍(这些一般需要做人脸识别特征点定位)等等等等。
通过上面的接收数据,和渲染相机数据到指定的窗口上,我们是已经可以看到渲染的结果了的。
然后我们要在渲染到指定窗口前,增加其他的Filter,为了保证易用性,我们增加一个GroupFilter,让其他的Filter,直接加入到GroupFilter中来完成处理。

public class GroupFilter extends AFilter{private Queue<AFilter> mFilterQueue;private List<AFilter> mFilters;private int width=0, height=0;private int size=0;public GroupFilter(Resources res) {super(res);mFilters=new ArrayList<>();mFilterQueue=new ConcurrentLinkedQueue<>();}@Overrideprotected void initBuffer() {}public void addFilter(final AFilter filter){//绘制到frameBuffer上和绘制到屏幕上的纹理坐标是不一样的//Android屏幕相对GL世界的纹理Y轴翻转MatrixUtils.flip(filter.getMatrix(),false,true);mFilterQueue.add(filter);}public boolean removeFilter(AFilter filter){boolean b=mFilters.remove(filter);if(b){size--;}return b;}public AFilter removeFilter(int index){AFilter f=mFilters.remove(index);if(f!=null){size--;}return f;}public void clearAll(){mFilterQueue.clear();mFilters.clear();size=0;}public void draw(){updateFilter();textureIndex=0;if(size>0){for (AFilter filter:mFilters){GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fFrame[0]);GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,GLES20.GL_TEXTURE_2D, fTexture[textureIndex%2], 0);GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,GLES20.GL_RENDERBUFFER, fRender[0]);GLES20.glViewport(0,0,width,height);if(textureIndex==0){filter.setTextureId(getTextureId());}else{filter.setTextureId(fTexture[(textureIndex-1)%2]);}filter.draw();unBindFrame();textureIndex++;}}}private void updateFilter(){AFilter f;while ((f=mFilterQueue.poll())!=null){f.create();f.setSize(width,height);mFilters.add(f);size++;}}@Overridepublic int getOutputTexture(){return size==0?getTextureId():fTexture[(textureIndex-1)%2];}@Overrideprotected void onCreate() {}@Overrideprotected void onSizeChanged(int width, int height) {this.width=width;this.height=height;updateFilter();createFrameBuffer();}//创建离屏bufferprivate int fTextureSize = 2;private int[] fFrame = new int[1];private int[] fRender = new int[1];private int[] fTexture = new int[fTextureSize];private int textureIndex=0;//创建FrameBufferprivate boolean createFrameBuffer() {GLES20.glGenFramebuffers(1, fFrame, 0);GLES20.glGenRenderbuffers(1, fRender, 0);genTextures();GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fFrame[0]);GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, fRender[0]);GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width,height);GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,GLES20.GL_TEXTURE_2D, fTexture[0], 0);GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,GLES20.GL_RENDERBUFFER, fRender[0]);
//        int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
//        if(status==GLES20.GL_FRAMEBUFFER_COMPLETE){//            return true;
//        }unBindFrame();return false;}//生成Texturesprivate void genTextures() {GLES20.glGenTextures(fTextureSize, fTexture, 0);for (int i = 0; i < fTextureSize; i++) {GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fTexture[i]);GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height,0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);}}//取消绑定Textureprivate void unBindFrame() {GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0);GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);}private void deleteFrameBuffer() {GLES20.glDeleteRenderbuffers(1, fRender, 0);GLES20.glDeleteFramebuffers(1, fFrame, 0);GLES20.glDeleteTextures(1, fTexture, 0);}}

将这个FilterGroup加入到已有的流程中,只需要将保持的相机数据的Texture作为FilterGroup的输入,然后将FilterGroup的输出作为渲染到指定窗口的Filter的输入即可:

 @Overridepublic void onDrawFrame(GL10 gl) {if(isParamSet.get()){mCameraFilter.draw();mGroupFilter.setTextureId(mCameraFilter.getOutputTexture());mGroupFilter.draw();GLES20.glViewport(0,0,mWindowSize.x,mWindowSize.y);mShowFilter.setMatrix(SM);mShowFilter.setTextureId(mGroupFilter.getOutputTexture());mShowFilter.draw();if(mRenderer!=null){mRenderer.onDrawFrame(gl);}callbackIfNeeded();}}

然后将需要增加的其他的Filter,依次增加到GroupFilter中即可:

WaterMarkFilter filter=new WaterMarkFilter(getResources());filter.setWaterMark(BitmapFactory.decodeResource(getResources(),R.mipmap.logo));
filter.setPosition(300,50,300,150);
mController.addFilter(filter);
//mController.addFilter(new GrayFilter(getResources()));
mController.setFrameCallback(720, 1280, Camera2Activity.this);

其他

如果之前没有接触过OpenGL,这篇博客看下来可能也是云里雾里。主要是因为篇幅有限,加上之前的博客分享的也是从零开始学习OpenGLES的内容,所以在这篇博客中没有赘述,如有问题,可在评论区留言,或给我发邮件,共同探讨。另外,分享一下我们公司的项目——AiyaEffectSDK,可以快速实现各种好玩的美颜、贴纸效果,欢迎Star和Fork。

源码

所有的代码全部在一个项目中,托管在Github上——Android OpenGLES 2.0系列博客的Demo


欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/61207844]


Android Camera API/Camera2 API 相机预览及滤镜、贴纸等处理相关推荐

  1. Camera2 打开相机预览界面

    camera2 是21之后的api用于代替Camera,提供更加牛X的对相机hardware操作的api 参考资料:http://www.jcodecraeer.com/a/anzhuokaifa/a ...

  2. Android Camera开发:给摄像头预览界面加个ZoomBar(附完整代码下载)

    废话不说了,就是加个seekbar,拖动的话能够调节焦距,让画面变大或缩小.下面是核心程序: 一,camera的布局文件 <LinearLayout xmlns:android="ht ...

  3. 【Android -- 相机】Camera2 实现拍照 预览功能

    前言 上篇文章,我们已经用 Camera1 实现了预览和拍照的功能,但也说到,在API21的时候,Camera1已经被标注为弃用,因为它的API功能和灵活性满足不了现在日益复杂的相机开发了,所以在 A ...

  4. Android Camera2 相机预览、获取数据

    Camera2简要说明 在Google 推出Android 5.0的时候, Android Camera API 版本升级到了API2(android.hardware.camera2),大幅提高了A ...

  5. android surfaceview camera,android – 如何在SurfaceView上显示相机预览?

    要使用Camera2 API从相机显示预览,您应该执行以下步骤: >获得使用相机设备的权限 >使用CameraManager打开与相机的连接 准备表面预览 >使用打开的相机设备和所需 ...

  6. Android 自定义Camera(一)如何预览相机

    Android Camera之如何预览相机 1.官方Api描述 翻译后为: 使用android.hardware.Camera拍照,请使用以下步骤: 1.从open(int)获取一个Camera实例. ...

  7. Android Camera2相机预览画面放大缩小(数码变焦DigitalZoom)功能实现

    一.前言 Android自定义相机开发中,常常会有通过手势放大或缩小相机预览画面的需求,即数码变焦DigitalZoom. 二.接口说明 1. 获取最大的放大倍数 float maxZoom = mC ...

  8. Android 相机方向传感,Nexus 5x反向横向传感器修复在Android相机预览...

    我是Android开发中的新手,所以如果我的问题很简单,我会提前道歉.在我的应用程序的一部分,我需要我的后置摄像头的实时预览,所以我创建了一个自定义类,扩展SurfaceView并实现SurfaceH ...

  9. android 圆形相机预览拍照_Android多种方式实现相机圆形预览

    最终效果图如下: 一.为预览控件设置圆角 public RoundTextureView(Context context, AttributeSet attrs) { super(context, a ...

最新文章

  1. 织梦 百度sitemap制作教程
  2. PHPstorm配置同步服务器文件
  3. 【机器学习】LBP特征融合最大灰度差、平均灰度、平均梯度改善SVM检测效果
  4. Qt for Python Mac下使用 fbs 打包软件
  5. java 调用 js性能_太快了,太变态了:什么会影响Java中的方法调用性能?
  6. Attributes.Add用途与用法
  7. 利用反射获得类的public static/const成员的值
  8. python mro c3_Python 19 MRO和C3算法
  9. 怎样把电脑恢复出厂设置_数据蛙:苹果恢复出厂设置,彻底释放手机内存
  10. oraclr 和mysql的不同_Mysql和Oracle中的不同
  11. 如何高效地学习机器学习算法?
  12. plsqldev工具在使用过程中遇到的问题
  13. 几款杀毒软件下载和升级
  14. Java、对字符串中的字符排序
  15. 我们最畅销的30本好书,都在这了
  16. python卡方检验kf_Python 卡方检验
  17. 阿里P7被裁员,找工作小半年了,流程走着走着就没了
  18. TimeLine学习笔记
  19. Unity3D教程:回合制游戏实现 1
  20. 2021数学建模国赛B题复盘详细解析

热门文章

  1. NodeJS设计模式
  2. TextBox服务器控件怎么操作,文本框的多行换行
  3. 时序预测 | MATLAB实现基于Adam算法优化BiLSTM双向长短期记忆神经网络时间序列预测
  4. 常用cypher语句
  5. HBase最佳实践-HBase中的读性能优化策略
  6. 解析las文件_LAS格式的解析与转换
  7. 【ppt入门教程】PPT2013如何加上自己公司LOGO PPT中加上LOGO操作方法
  8. 安卓自动化之uiautomator(python篇)常用adb及图片对比
  9. 适合程序员的耳机_程序员编程用什么耳机?
  10. IPv4 IPv6共存技术-----NAT-PT