使用Opengl ES GlSurfaceView Render Camera preview
来源:互联网 发布:protobuf数据解析 编辑:程序博客网 时间:2024/05/11 09:53
Camera 是手机当中最重要的多媒体模块之一,基于Camera可以做出许多非常有意思的feature,比如各种滤镜、图像识别以及最近非常热门的AR、VR。这些都涉及到图像处理,为了更加高效的处理和现实camera的图像,GPU是一个非常不错的选择,OpenGL接口是GPU硬件的访问接口。本文就介绍如何使用Opengl来render Camera的preview 数据。
一、Android Camera API
使用Android Camera API接口可以非常方便写出camera应用程序,在Android L之前可以使用android.hardware.camera API来编写Camera相关的应用程序,在Android L以后,google推出了android.hardware.camera2 API。相比较而言,camera2提供了更加灵活的API来访问camera底层硬件,能够控制并每一帧的参数(AEC/AWB/AF/等参数)。不过Camera2使用起来要稍微复杂一些,这里就使用camera API写camera。关键两点:open camera和start preview。这里参照了这个blog http://blog.csdn.net/yanzi1225627/article/details/33339965/ 写了camera 应用相关的类
package com.wfly.kui.magiccamera.camera;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.PixelFormat;import android.graphics.SurfaceTexture;import android.hardware.Camera;import android.util.Log;import android.view.SurfaceHolder;import com.wfly.kui.magiccamera.utils.CamParaUtil;import com.wfly.kui.magiccamera.utils.FileUtil;import com.wfly.kui.magiccamera.utils.ImageUtil;import java.io.IOException;import java.util.List;/** * Created by kuiw on 11/3/2016. */public class CameraInstance { private static final String TAG="CameraInstance"; private Camera mCamera; private Camera.Parameters mParams; private static CameraInstance mCameraInstance; private boolean isPreviewing=false; private boolean isOpened=false; private Camera.Size mPreviewSize; private Camera.Size mPictureSize; public interface CamOpenedCallback{ public void cameraHasOpened(); } public boolean isPreviewing(){ return isPreviewing; } public boolean isOpened(){ return isOpened; } public Camera.Size getmPreviewSize(){return mPreviewSize;} private CameraInstance(){ } public static synchronized CameraInstance getInstance(){ if(mCameraInstance == null){ mCameraInstance = new CameraInstance(); } return mCameraInstance; } public void doOpenCamera(CamOpenedCallback callback){ Log.i(TAG, "doOpenCamera...."); if(mCamera == null){ mCamera = Camera.open(); isOpened=true; Log.i(TAG, "Camera open over...."); if(callback != null){ callback.cameraHasOpened(); } }else{ Log.i(TAG, "Camera is in open status"); } } public void doStartPreview(SurfaceTexture surface, float previewRate){ Log.i(TAG, "doStartPreview..."); if(isPreviewing){ Log.e(TAG,"camera is in previewing state"); return ; } if(mCamera != null){ try { mCamera.setPreviewTexture(surface); } catch (IOException e) { e.printStackTrace(); } initCameraParams(); } } /** * 停止预览,释放Camera */ public void doStopCamera(){ if(null != mCamera) { isOpened=false; mCamera.setPreviewCallback(null); mCamera.stopPreview(); isPreviewing = false; mCamera.release(); mCamera = null; } } public void doStopPreview(){ Log.e(TAG,"doStopPreview...."); if (isPreviewing && null!=mCamera){ mCamera.stopPreview(); isPreviewing=false; }else{ Log.e(TAG,"camera is in not in previewing status"); } } public void doTakePicture(){ if(isPreviewing && (mCamera != null)){ mCamera.takePicture(mShutterCallback, null, mJpegPictureCallback); } } private void initCameraParams(){ if(mCamera != null){ mParams = mCamera.getParameters(); mParams.setPictureFormat(PixelFormat.JPEG); mPictureSize = CamParaUtil.getInstance().getPropPictureSize( mParams.getSupportedPictureSizes(),30, 800); mParams.setPictureSize(mPictureSize.width, mPictureSize.height); mPreviewSize = CamParaUtil.getInstance().getPropPreviewSize( mParams.getSupportedPreviewSizes(), 30, 800); mParams.setPreviewSize(mPreviewSize.width, mPreviewSize.height); mCamera.setDisplayOrientation(90); List<String> focusModes = mParams.getSupportedFocusModes(); if(focusModes.contains("continuous-video")){ mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); } mCamera.setParameters(mParams); mCamera.startPreview(); isPreviewing = true; mParams = mCamera.getParameters(); } } Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() //快门按下的回调,在这里我们可以设置类似播放“咔嚓”声之类的操作。默认的就是咔嚓。 { public void onShutter() { // TODO Auto-generated method stub Log.i(TAG, "myShutterCallback:onShutter..."); } }; Camera.PictureCallback mRawCallback = new Camera.PictureCallback() // 拍摄的未压缩原数据的回调,可以为null { public void onPictureTaken(byte[] data, Camera camera) { // TODO Auto-generated method stub Log.i(TAG, "myRawCallback:onPictureTaken..."); } }; Camera.PictureCallback mJpegPictureCallback = new Camera.PictureCallback () //对jpeg图像数据的回调,最重要的一个回调 { public void onPictureTaken(byte[] data, Camera camera) { // TODO Auto-generated method stub Log.i(TAG, "myJpegCallback:onPictureTaken..."); Bitmap b = null; if(null != data){ b = BitmapFactory.decodeByteArray(data, 0, data.length); mCamera.stopPreview(); isPreviewing = false; } if(null != b) { Bitmap rotaBitmap = ImageUtil.getRotateBitmap(b, 90.0f); FileUtil.saveBitmap(rotaBitmap); } mCamera.startPreview(); isPreviewing = true; } };}
二、GLSurfaceView
主要实现三个接口onSurfaceCreated,onSurfaceChanged,onDrawFrame。在surface创建成功后,调用camera start preview。
这里主要用三个类实现相关的调用:CameraSurfaceView, FilterRender, GLUtils
package com.wfly.kui.magiccamera.camera;import android.content.Context;import android.graphics.SurfaceTexture;import android.opengl.GLES11Ext;import android.opengl.GLES20;import android.opengl.GLSurfaceView;import android.opengl.GLSurfaceView;import android.util.AttributeSet;import android.util.Log;import javax.microedition.khronos.egl.EGLConfig;import javax.microedition.khronos.opengles.GL10;/** * Created by kuiw on 11/3/2016. */public class CameraSurfaceView extends GLSurfaceView implements GLSurfaceView.Renderer,SurfaceTexture.OnFrameAvailableListener, CameraInstance.CamOpenedCallback { public static final String TAG="CameraSurfaceView"; private Context mContext; private SurfaceTexture mSurface; private int mTextureID = -1; private DirectDrawer mDirectDrawer; private FilterRender mFilterDrawer; public CameraSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); setEGLContextClientVersion(2); setRenderer(this); setRenderMode(RENDERMODE_WHEN_DIRTY); this.mContext=context; } @Override public void onPause() { super.onPause(); CameraInstance.getInstance().doStopPreview(); } @Override public void onResume() { super.onResume(); } @Override public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { Log.i(TAG, "onSurfaceCreated..."); //mTextureID = createTextureID(); //mDirectDrawer = new DirectDrawer(mTextureID); mFilterDrawer=new FilterRender(mContext); mTextureID=mFilterDrawer.getmTexture(); mSurface = new SurfaceTexture(mTextureID); mSurface.setOnFrameAvailableListener(this); //CameraInstance.getInstance().doOpenCamera(null); } @Override public void onSurfaceChanged(GL10 gl10, int width, int height) { GLES20.glViewport(0, 0, width, height); if(!CameraInstance.getInstance().isPreviewing()){ CameraInstance.getInstance().doStartPreview(mSurface, 1.33f); } } @Override public void onDrawFrame(GL10 gl10) { GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); Log.i(TAG, "onDrawFrame..."); mSurface.updateTexImage(); mFilterDrawer.drawTexture(); /* float[] mtx = new float[16]; mSurface.getTransformMatrix(mtx); mDirectDrawer.draw(mtx);*/ } @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) { this.requestRender(); } @Override public void cameraHasOpened() { } private int createTextureID() { int[] texture = new int[1]; GLES20.glGenTextures(1, texture, 0); GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]); GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR); GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); return texture[0]; }}
package com.wfly.kui.magiccamera.camera;import android.content.Context;import android.hardware.Camera;import android.media.effect.Effect;import android.media.effect.EffectContext;import android.media.effect.EffectFactory;import android.opengl.GLES11Ext;import android.opengl.GLES20;import com.wfly.kui.magiccamera.utils.GLUtils;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.FloatBuffer;import javax.microedition.khronos.opengles.GL10;import static android.R.attr.bitmap;/** * Created by kuiw on 11/4/2016. */public class FilterRender { public static final String TAG="FilterRender"; private int mTexture=-1; private Context mContext; private final float vertices[] = { -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f, -1f,0f, 0f,0f, -1f,1f, 0f,1f, 0f,0f, 1f,0f, 0f,1f, 1f,1f, }; private final float texturevertices[]={ 0f,1f, 1f,1f, 0f,0f, 1f,0f, 0f,1f, 1f,1f, 0f,0f, 1f,0f, 0f,1f, 1f,1f, 0f,0f, 1f,0f, }; private FloatBuffer verticesbBuf; private FloatBuffer textureVerticesBuf; private String vertexShader; private String fragmentShader; private int aPositionHandle; private int uTextureHandle; private int aTexPositionHandle; private int program; private EffectContext effectContext; private Effect effect; private int mEffectTexture; private Camera.Size mPreviewSize; private void applyEffectForTexture(){ effect=effectContext.getFactory().createEffect(EffectFactory.EFFECT_BRIGHTNESS); //effect.setParameter("scale", .5f);; effect.setParameter("brightness", 4.0f); effect.apply(mTexture,CameraInstance.getInstance().getmPreviewSize().width,CameraInstance.getInstance().getmPreviewSize().height,mEffectTexture); } private void initFloatBuffer(){ ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length*4); byteBuffer.order(ByteOrder.nativeOrder()); verticesbBuf=byteBuffer.asFloatBuffer(); verticesbBuf.put(vertices); verticesbBuf.position(0); byteBuffer=ByteBuffer.allocateDirect(texturevertices.length*4); byteBuffer.order(ByteOrder.nativeOrder()); textureVerticesBuf=byteBuffer.asFloatBuffer(); textureVerticesBuf.put(texturevertices); textureVerticesBuf.position(0); } public void drawTexture(){ // GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0); GLES20.glUseProgram(program); //GLES20.glDisable(GLES20.GL_BLEND); //applyEffectForTexture(); aPositionHandle = GLES20.glGetAttribLocation(program, "aPosition"); aTexPositionHandle = GLES20.glGetAttribLocation(program, "aTexPosition"); uTextureHandle = GLES20.glGetUniformLocation(program, "uTexture"); GLES20.glVertexAttribPointer(aPositionHandle,2,GLES20.GL_FLOAT,false,0,verticesbBuf); GLES20.glEnableVertexAttribArray(aPositionHandle); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,mTexture); GLES20.glUniform1i(uTextureHandle,0); GLES20.glVertexAttribPointer(aTexPositionHandle, 2, GLES20.GL_FLOAT, false, 0, textureVerticesBuf); GLES20.glEnableVertexAttribArray(aTexPositionHandle); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); for(int index=0;index<3;index++){ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, index*4, 4); } GLES20.glDisableVertexAttribArray(aTexPositionHandle); GLES20.glDisableVertexAttribArray(aPositionHandle); } private int createTextureID() { int[] texture = new int[1]; GLES20.glGenTextures(1, texture, 0); GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]); GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR); GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); return texture[0]; } public FilterRender(Context context){ this.mTexture=createTextureID(); this.mContext=context; effectContext=EffectContext.createWithCurrentGlContext(); mEffectTexture=createTextureID(); initFloatBuffer(); mPreviewSize=CameraInstance.getInstance().getmPreviewSize(); vertexShader= GLUtils.loadFromAssetsFile("vertexshader.vs",mContext.getAssets()); fragmentShader=GLUtils.loadFromAssetsFile("fragmentshader.vs",mContext.getAssets()); program=GLUtils.createProgram(vertexShader,fragmentShader); } public int getmTexture(){return mTexture;}}package com.wfly.kui.magiccamera.utils;import android.content.Context;import android.content.res.AssetManager;import android.content.res.Resources;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.opengl.GLES20;import android.util.Log;import java.io.ByteArrayOutputStream;import java.io.InputStream;/** * Created by Administrator on 2016/10/22. */public class GLUtils { public static final String TAG="GLUtils"; /** * 加载着色器方法 * * 流程 : * * ① 创建着色器 * ② 加载着色器脚本 * ③ 编译着色器 * ④ 获取着色器编译结果 * * @param shaderType 着色器类型,顶点着色器(GLES20.GL_FRAGMENT_SHADER), 片元着色器(GLES20.GL_FRAGMENT_SHADER) * @param source 着色脚本字符串 * @return 返回的是着色器的引用, 返回值可以代表加载的着色器 */ public static int loadShader(int shaderType , String source){ //1.创建一个着色器, 并记录所创建的着色器的id, 如果id==0, 那么创建失败 int shader = GLES20.glCreateShader(shaderType); if(shader != 0){ //2.如果着色器创建成功, 为创建的着色器加载脚本代码 GLES20.glShaderSource(shader, source); //3.编译已经加载脚本代码的着色器 GLES20.glCompileShader(shader); int[] compiled = new int[1]; //4.获取着色器的编译情况, 如果结果为0, 说明编译失败 GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); if(compiled[0] == 0){ Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":"); Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader)); //编译失败的话, 删除着色器, 并显示log GLES20.glDeleteShader(shader); shader = 0; } }else{ Log.e(TAG,"create shader failed:"); } return shader; } /** * 检查每一步的操作是否正确 * * 使用GLES20.glGetError()方法可以获取错误代码, 如果错误代码为0, 那么就没有错误 * * @param op 具体执行的方法名, 比如执行向着色程序中加入着色器, * 使glAttachShader()方法, 那么这个参数就是"glAttachShader" */ public static void checkGLError(String op){ int error; //错误代码不为0, 就打印错误日志, 并抛出异常 while( (error = GLES20.glGetError()) != GLES20.GL_NO_ERROR ){ Log.e("ES20_ERROR", op + ": glError " + error); throw new RuntimeException(op + ": glError " + error); } } /** * 创建着色程序 * * ① 加载顶点着色器 * ② 加载片元着色器 * ③ 创建着色程序 * ④ 向着色程序中加入顶点着色器 * ⑤ 向着色程序中加入片元着色器 * ⑥ 链接程序 * ⑦ 获取链接程序结果 * * @param vertexSource定点着色器脚本字符串 * @param fragmentSource片元着色器脚本字符串 * @return */ public static int createProgram(String vertexSource , String fragmentSource){ //1. 加载顶点着色器, 返回0说明加载失败 int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); if(vertexShader == 0) { Log.e(TAG,"load vertex shader failed!"); return 0; } //2. 加载片元着色器, 返回0说明加载失败 int fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); if(fragShader == 0) { Log.e(TAG,"load fragment shader failed!"); return 0; } //3. 创建着色程序, 返回0说明创建失败 int program = GLES20.glCreateProgram(); if(program != 0){ //4. 向着色程序中加入顶点着色器 GLES20.glAttachShader(program, vertexShader); checkGLError("glAttachShader"); //5. 向着色程序中加入片元着色器 GLES20.glAttachShader(program, fragShader); checkGLError("glAttachShader"); //6. 链接程序 GLES20.glLinkProgram(program); int[] linkStatus = new int[1]; //获取链接程序结果 GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if(linkStatus[0] != GLES20.GL_TRUE){ Log.e("ES20.ERROR", "链接程序失败 : "); Log.e("ES20.ERROR", GLES20.glGetProgramInfoLog(program)); //如果链接程序失败删除程序 GLES20.glDeleteProgram(program); program = 0; } GLES20.glDeleteShader(vertexShader); GLES20.glDeleteShader(fragShader); } Log.e(TAG,"Create Programe Successfully"); return program; } /** * 从assets中加载着色脚本 * * ① 打开assets目录中的文件输入流 * ② 创建带缓冲区的输出流 * ③ 逐个字节读取文件数据, 放入缓冲区 * ④ 将缓冲区中的数据转为字符串 * * @param fileName assets目录中的着色脚本文件名 * @param resources应用的资源 * @return */ public static String loadFromAssetsFile(String fileName, Resources resources){ String result = null; try { //1. 打开assets目录中读取文件的输入流, 相当于创建了一个文件的字节输入流 InputStream is = resources.getAssets().open(fileName); int ch = 0; //2. 创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型 ByteArrayOutputStream baos = new ByteArrayOutputStream(); //3. 逐个字节读取数据, 并将读取的数据放入缓冲器中 while((ch = is.read()) != -1){ baos.write(ch); } //4. 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串 byte[] buffer = baos.toByteArray(); baos.close(); is.close(); result = new String(buffer, "UTF-8"); result = result.replaceAll("\\r\\n", "\n"); } catch (Exception e) { e.printStackTrace(); } return result; } public static String loadFromAssetsFile(String fileName, AssetManager assetManager){ String result = null; try { //1. 打开assets目录中读取文件的输入流, 相当于创建了一个文件的字节输入流 InputStream is = assetManager.open(fileName); int ch = 0; //2. 创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型 ByteArrayOutputStream baos = new ByteArrayOutputStream(); //3. 逐个字节读取数据, 并将读取的数据放入缓冲器中 while((ch = is.read()) != -1){ baos.write(ch); } //4. 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串 byte[] buffer = baos.toByteArray(); baos.close(); is.close(); result = new String(buffer, "UTF-8"); result = result.replaceAll("\\r\\n", "\n"); } catch (Exception e) { e.printStackTrace(); } return result; } public static Bitmap loadBitmapFromAssetsFile(String fileName,Resources resources){ Bitmap bitmap=null; try { //1. 打开assets目录中读取文件的输入流, 相当于创建了一个文件的字节输入流 InputStream is = resources.getAssets().open(fileName); BitmapFactory.Options options = new BitmapFactory.Options(); bitmap = BitmapFactory.decodeStream(is, null, options); if(bitmap==null){ Log.e(TAG,"load image from asset file:"+fileName); } } catch (Exception e) { e.printStackTrace(); } return bitmap; }}
- 使用Opengl ES GlSurfaceView Render Camera preview
- Android OpenGL ES->GLSurfaceView
- Android Camera使用OpenGL ES 2.0和GLSurfaceView对预览进行实时二次处理(黑白滤镜)
- Android Camera API 2使用OpenGL ES 2.0和GLSurfaceView对预览进行实时二次处理(黑白滤镜)
- Android OpenGL ES->Translucent GLSurfaceView
- OpenGL ES之GLSurfaceView学习
- OpenGL ES 和 GLSurfaceView 概述
- OpenGL ES之GLSurfaceView学习一:介绍
- OpenGL ES之GLSurfaceView学习一:介绍
- Android OpenGL ES(五):GLSurfaceView
- Android OpenGL ES 开发教程(6):GLSurfaceView
- Android OpenGL ES 开发教程(6):GLSurfaceView
- Android OpenGL ES 开发教程(6):GLSurfaceView
- Android OpenGL ES 开发教程(6):GLSurfaceView
- OpenGL ES之GLSurfaceView学习一:介绍
- Android OpenGL ES(五):GLSurfaceView
- Android OpenGL ES 开发教程(6):GLSurfaceView
- OpenGL ES之GLSurfaceView学习一:介绍
- SQL题
- Intelj Idea 的全键盘使用
- Ubuntu 16.0.4 安装deb文件
- thrift简单实例
- MATLAB从数组中以等概率随机选出不同的元素
- 使用Opengl ES GlSurfaceView Render Camera preview
- linux添加环境变量(path)
- classpath、path、JAVA_HOME的作用
- Session对象的使用
- MySQL学习之路_数据的组织方式-表
- [总结]联想笔记本E460/虚拟机VMware10.0+Ubuntu14.04+opencv+Ros+pcl+OpenNI+g2o+DSO+Pangolin+win7(Ubuntu双系统)安装过程整理
- 中序和先序创建树
- JAVA jar文件打包详解
- 采用链式结构实现任意多项式的存储,求两个多项式的和