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的预览怎么做呢?实现起来可能也非常easy,假设预览之前,要做美颜、磨皮或者加水印等等处理呢?实现后又怎样保证使用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在图像上加两个其它图片,相似水印、贴纸的效果。随便两幅图贴上去。也不管好看不好看了,重点是功能。

依次为先贴纸然后灰色滤镜。先灰色滤镜然后贴纸,仅仅有贴纸。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvanVuemlh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描写叙述" title="">

详细实现

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

  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() {
@Override
public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig
config, Object window) {
//这里的surface由外部传入,能够为Surface、SurfaceTexture或者SurfaceHolder
return egl.eglCreateWindowSurface(display,config,surface,null);
} @Override
public 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) {
@Override
protected 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<>();
} @Override
protected 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++;
}
} @Override
public int getOutputTexture(){
return size==0?getTextureId():fTexture[(textureIndex-1)%2];
} @Override
protected void onCreate() { } @Override
protected void onSizeChanged(int width, int height) {
this.width=width;
this.height=height;
updateFilter();
createFrameBuffer();
} //创建离屏buffer
private 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; //创建FrameBuffer
private 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;
} //生成Textures
private 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);
}
} //取消绑定Texture
private 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的输入就可以:

 @Override
public 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. android Camera拍照 及 MediaRecorder录像 预览图像差90度

    Camera拍照: 今天做照相机程序,结果写好了发现出问题了,预览的图像差90度.相关源代码如下: Camera.Parameters params = camera.getParameters(); ...

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

    源码:http://files.cnblogs.com/android100/StandardCamera2013-10-18.zip 废话不说了,就是加个seekbar,拖动的话能够调节焦距,让画面 ...

  3. 在Android中使用OpenGL ES开发第(四)节:相机预览

    笔者之前写了三篇Android中使用OpenGL ES入门级的文章,从OpenGL ES的相关概念出发,分析了利用OpenGL ES实现3D绘图的重要的两个步骤:定义形状和绘制形状,简单的绘制了一个三 ...

  4. 【腾讯优测干货分享】Android 相机预览方向及其适配探索

    本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/583ba1df25d735cd2797004d 由于Android系统的开放策略 ...

  5. Android开启相机预览获取Yuv视频流数据

    自定义SurfaceView 主要步骤: 实现SurfaceHolder.Callback接口,创建SurfaceView的生命周期 实现Camera.PreviewCallback接口,创建预览回调 ...

  6. 谷歌发布 Android 8.1 首个开发者预览版,优化内存效率

    今晨,谷歌推出了 Android 8.1 首个开发者预览版,此次升级涵盖了针对多个功能的提升优化,其中包含对 Android Go (设备运行内存小于等于 1 GB)和加速设备上对机器学习的全新神经网 ...

  7. Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整(原理:底层SurfaceView+上层绘制ImageView)

    Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整实现(原理:底层SurfaceView+上层绘制ImageView) 分类: Android开发 Androi ...

  8. Android : Camera之CHI API

    一.CAM CHI API功能介绍: CHI API建立在Google HAL3的灵活性基础之上,目的是将Camera2/HAL3接口分离出来用于使用相机功能,它是一个灵活的图像处理驱动程序(摄像头硬 ...

  9. Android开发 Camera2开发_2_预览分辨率或拍照分辨率的计算

    前言 不管在Camera1或者Camera2在适配不同手机/不同使用场景的情况下都需要计算摄像头里提供的分辨率列表中最合适的那一个分辨率.所以在需要大量机型适配的app,是不建议不经过计算直接自定义分 ...

随机推荐

  1. 高级需求分析UML建模设计模式笔记

    1.REQ->HLR 分析 全系统性质->AD设计 Context,BOM,Conception 2.REQ->LLR 分析 模块分析->DD设计 + 编码 Feature,B ...

  2. MERGE新特性(UPDATE WHERE,DELETE WHERE,INSERT WHERE)

    MERGE语句是Oracle9i新增的语法,用来合并UPDATE和INSERT语句.通过MERGE语句,根据一张表或子查询的连接条件对另外一张表进行查询,连接条件匹配上的进行UPDATE,无法匹配的执 ...

  3. ajax成功返回数据中存在多余字符的处理

    ajax里有需要判断反馈的字符串是否为“ok”,在浏览器里调试,看到返回的内容明明是“ok”,但是if(“ok”==data)判断为false,用alert打印内容也是ok,但是打印长度的时候却是3. ...

  4. CSS学习要点

    目标 掌握CSS基本语法,了解如何应用CSS到Html元素上并能熟练使用CSS进行元素布局. 要点 CSS基本概念.存在的意义 CSS 指层叠样式表 (Cascading Style Sheets), ...

  5. Shell编程(脚本)的经常使用命令和语句

    一些经常使用的Shell编程(脚本)命令和语句,能够满足一般需求. 接收到的命令參数: 參数个数: $# 參数值: 命令本身:$0 第一个參数:$1 第二个參数:$2 -- 退出命令: exit ec ...

  6. vs开发工具报错:参数错误 异常来自 HRESULT:0x80070057 E_INVALIDARG

    解决方法: 4.0 删除 c:\windows\microsoft.net\framework\v4.0.30319\temporary asp.net files 2..0 删除 c:\window ...

  7. C++ 函数参数中“ *&代表什么? ”

    typedef struct BitNode  {      char value;      BitNode *lchild,*rchild;    }BitNode,*BiTree; void C ...

  8. Powerdesigner数据库建模--概念模型--ER图

    目标: 本文主要介绍PowerDesigner中概念数据模型 CDM的基本概念. 一.概念数据模型概述 数据模型是现实世界中数据特征的抽象.数据模型应该满足三个方面的要求: 1)能够比较真实地模拟现实 ...

  9. json 数组操作

    用js有很久了,但都没有深究过js的数组形式.这段时间做的一个项目,用到数组的地方很多,自以为js还可以的自己居然无从下手,一下狠心,我学!呵呵. 1.数组的创建 var arrayObj = new ...

  10. Echarts动画效果:实现数据左右移动

    1.业务背景 图形实时从后台获取数据,让图形从最右边出现,每隔一秒向左移一位,当最左边的数据移到Y轴时,最左边的数据移出屏幕,最右边增加一个数.实现一个从右往左动画的效果 2.先看下项目中的demo解 ...