基于GLSurfaceView实现自定义Camera
来源:互联网 发布:图片动态特效制作软件 编辑:程序博客网 时间:2024/06/14 09:02
Android中的Camera简介
这里主要将一些camera的一些简单用法,当然如果是用camera2的童鞋,可以参考:http://blog.csdn.net/vinicolor/article/details/50992692 。这里主要讲解:
* 如何利用camera开启相机实现预览
* 实现拍照功能,以及图片大小设定
* 切换前后置摄像头
* 闪光灯的开启
* 前置镜像
* 给拍照的图片加水印
* 自动对焦与手动对焦
* 相机的放大缩小
* 相机拍照的横竖方向的监听
好了,我们先来先看看预览的效果
在之前,我先定义了一个接口ICameraHelper,用于控制整个相机的功能:
/** * @CreadBy :DramaScript * @date 2017/7/10 */public interface ICameraHelper { //打开相机 boolean open(int cameraId); //设置相机宽高比 图片和预览大小 void setConfig(Config config); //是否开启预览 boolean preview(); // 切换前后摄像头 boolean switchTo(int cameraId); // 拍照 void takePhoto(TakePhotoCallback callback,int nRotation); // 相机是否关闭 boolean close(); //设置预览的纹理 void setPreviewTexture(SurfaceTexture texture); //获得预览大小 Point getPreviewSize(); //获得图片的大小 Point getPictureSize(); //设置预览时每一帧的回调 void setOnPreviewFrameCallback(PreviewFrameCallback callback); void setFlash(int flashMode); class Config{ float rate; //宽高比 int minPreviewWidth; int minPictureWidth; } interface TakePhotoCallback{ void onTakePhoto(Bitmap bitmap); } interface PreviewFrameCallback{ void onPreviewFrame(byte[] bytes, int width, int height); }}
下面我们定义CameraHelper实现ICameraHelper,然后来实现这些方法:
首先实现打开相机方法open(int cameraId):
@Override public boolean open(int cameraId) { mCamera = Camera.open(cameraId); this.cameraId = cameraId; if (mCamera != null) { Camera.Parameters param = mCamera.getParameters(); picSize = getPropPictureSize(param.getSupportedPictureSizes(), mConfig.rate, mConfig.minPictureWidth); preSize = getPropPreviewSize(param.getSupportedPreviewSizes(), mConfig.rate, mConfig .minPreviewWidth); //设置照片生成的大小 picWide = picSize.width; picHeight = picSize.height; param.setPictureSize(picWide, picHeight); //生成照片预览大小 param.setPreviewSize(preSize.width, preSize.height); mCamera.setParameters(param); Camera.Size pre = param.getPreviewSize(); Camera.Size pic = param.getPictureSize(); mPicSize = new Point(pic.height, pic.width); mPreSize = new Point(pre.height, pre.width); Log.e("wuwang", "camera previewSize:" + mPreSize.x + "/" + mPreSize.y); return true; } return false; } /** * 获得生成照片的大小 * * @param list * @param th * @param minWidth * @return */ private Camera.Size getPropPictureSize(List<Camera.Size> list, float th, int minWidth) { Collections.sort(list, sizeComparator); int i = 0; for (Camera.Size s : list) { if ((s.height >= minWidth) && equalRate(s, th)) { break; } i++; } if (i == list.size()) { i = 0; } return list.get(i); /** * 获得预览大小 * * @param list * @param th * @param minWidth * @return */ private Camera.Size getPropPreviewSize(List<Camera.Size> list, float th, int minWidth) { Collections.sort(list, sizeComparator); int i = 0; for (Camera.Size s : list) { if ((s.height >= minWidth) && equalRate(s, th)) { break; } i++; } if (i == list.size()) { i = 0; } return list.get(i); }
这个方法,通过Camera.open(cameraId);来获取Camera对象,其中cameraId可以为0也可以为1,前者代表后置摄像头。然后通过Camera.Parameters来设置相机的一些参数,比如预览大小,图片生成大小,当然这里使根据手机找出了最合适的尺寸大小,可以根据自己的需求设定图片的大小,至于预览的大小,建议不要太大,容易造成机型不适配,我记得HTC的U的手机,最大支持720的。设置太大也容易消化GPU,造成手机的发烫。
接下来就是拍照方法实现:
@Override public void takePhoto(final TakePhotoCallback callback, final int nRotation) { mCamera.takePicture(null, null, new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { Log.e("tag", "拍照的角度:" + nRotation); Bitmap bm = null; if (nRotation==0){ bm = setTakePicktrueOrientation(cameraId, BitmapFactory.decodeByteArray(data, 0, data.length)); }else { bm = BitmapFactory.decodeByteArray(data, 0, data.length); if (isFrontCamera) { bm = rotateBitmapByDegree(bm, 270 - nRotation, false); } else { bm = rotateBitmapByDegree(bm, nRotation + 90, false); } } callback.onTakePhoto(bm); } }); }
这里拍照方法,调用takePicture方法,实现Camera.PictureCallback接口,为了后面对bitmap操作,直接将byte数组转成bitmap返回。这里有一个问题,那就是拍照的图片并不是拍照时的样子,这里我们通过:
private class AlbumOrientationEventListener extends OrientationEventListener { public AlbumOrientationEventListener(Context context) { super(context); } public AlbumOrientationEventListener(Context context, int rate) { super(context, rate); } @Override public void onOrientationChanged(int orientation) { if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) { return; } //保证只返回四个方向 int newOrientation = ((orientation + 45) / 90 * 90) % 360; if (newOrientation != mOrientation) { mOrientation = newOrientation; Log.e("MJHTEST", "mOrientation = " + mOrientation); //返回的mOrientation就是手机方向,为0°、90°、180°和270°中的一个 } } }
来监听手机的横竖变化,根据相应的角度进行调整。下面你是矫正角度的方法:
/** * 将图片按照某个角度进行旋转 * * @param bm 需要旋转的图片 * @param degree 旋转角度 * @return 旋转后的图片 */ public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree, boolean bFlip) { if (degree >= 360) degree -= 360; if (degree < 0) degree += 360; Bitmap returnBm = null; // 根据旋转角度,生成旋转矩阵 android.graphics.Matrix matrix = new android.graphics.Matrix(); matrix.postRotate(degree); try { // 将原始图片按照旋转矩阵进行旋转,并得到新的图片 returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); } catch (OutOfMemoryError e) { } if (returnBm == null) { returnBm = bm; } if (bm != returnBm) { bm.recycle(); } return returnBm; }
至于切换摄像头,其实就是根据cameraId的变化,重新开启预览相机:
@Override public boolean switchTo(int cameraId) { close(); open(cameraId); if (cameraId == 1) { isFrontCamera = true; } else { isFrontCamera = false; } return false; }
然后开启闪光灯的操作,有三种模式:开启常亮的闪光灯,拍照时根据环境亮度开启闪光灯,关闭闪光灯三种模式:
/** * 开启闪光灯 */ public void turnLightOn() { if (mCamera == null) { return; } Camera.Parameters parameters = mCamera.getParameters(); if (parameters == null) { return; } List<String> flashModes = parameters.getSupportedFlashModes(); // Check if camera flash exists if (flashModes == null) { // Use the screen as a flashlight (next best thing) return; } String flashMode = parameters.getFlashMode(); if (!Camera.Parameters.FLASH_MODE_ON.equals(flashMode)) { // Turn on the flash if (flashModes.contains(Camera.Parameters.FLASH_MODE_ON)) { parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON); mCamera.setParameters(parameters); } else { } } } //关闭闪光灯 public void turnLightOff() { if (mCamera == null) { return; } Camera.Parameters parameters = mCamera.getParameters(); if (parameters == null) { return; } List<String> flashModes = parameters.getSupportedFlashModes(); String flashMode = parameters.getFlashMode(); // Check if camera flash exists if (flashModes == null) { return; } if (!Camera.Parameters.FLASH_MODE_OFF.equals(flashMode)) { // Turn off the flash if (flashModes.contains(Camera.Parameters.FLASH_MODE_OFF)) { parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); mCamera.setParameters(parameters); } else { } } }
接下来就是前置摄像头镜像功能,意思就是我们的手机拍照,前置拍出来的照片都是左右反的,需要将图片进行相应的调整:
/** * 拍摄照片自动镜像 * @param bmp * @return */ public static Bitmap convertBmp(Bitmap bmp) { int w = bmp.getWidth(); int h = bmp.getHeight(); Matrix matrix = new Matrix(); matrix.postScale(-1, 1); // 镜像水平翻转 Bitmap convertBmp = Bitmap.createBitmap(bmp, 0, 0, w, h, matrix, true); return convertBmp; }
拍好照片,可能需要考虑给照片加个水印,这里不讨论实时的给相机每一帧加,因为没必要,又不是做贴纸,所以只是在拍照后加入水印操作:
/** * 设置水印图片在右下角 * @param src * @param watermark * @param paddingRight * @param paddingBottom * @return */ public static Bitmap createWaterMaskRightBottom( Context context, Bitmap src, Bitmap watermark, int paddingRight, int paddingBottom) { return createWaterMaskBitmap(src, watermark, src.getWidth() - watermark.getWidth() - dp2px(context, paddingRight), src.getHeight() - watermark.getHeight() - dp2px(context, paddingBottom)); } private static Bitmap createWaterMaskBitmap(Bitmap src, Bitmap watermark, int paddingLeft, int paddingTop) { if (src == null) { return null; } int width = src.getWidth(); int height = src.getHeight(); //创建一个bitmap Bitmap newb = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);// 创建一个新的和SRC长度宽度一样的位图 //将该图片作为画布 Canvas canvas = new Canvas(newb); //在画布 0,0坐标上开始绘制原始图片 canvas.drawBitmap(src, 0, 0, null); //在画布上绘制水印图片 canvas.drawBitmap(watermark, paddingLeft, paddingTop, null); // 保存 canvas.save(Canvas.ALL_SAVE_FLAG); // 存储 canvas.restore(); return newb; }
每个相机基本上都有自动对焦和手动对焦功能,自动对焦其实就是在初始化相机的设置参数:
camera.cancelAutoFocus();//只有加上了这一句,才会自动对焦
手动对焦,需要获取屏幕收触控的点,根据点来设置对焦的地方:
/** * 手动聚焦 * * @param point 触屏坐标 */ protected boolean onFocus(Point point, Camera.AutoFocusCallback callback) { mCamera = manager.getmCamera(); if (mCamera == null) { Log.e("tag","------------------------1"); return false; } Camera.Parameters parameters = null; try { parameters = mCamera.getParameters(); } catch (Exception e) { e.printStackTrace(); Log.e("tag","------------------------2"); return false; } //不支持设置自定义聚焦,则使用自动聚焦,返回 if(Build.VERSION.SDK_INT >= 14) { if (parameters.getMaxNumFocusAreas() <= 0) { return focus(callback); } Log.i("tag", "onCameraFocus:" + point.x + "," + point.y); List<Camera.Area> areas = new ArrayList<Camera.Area>(); int left = point.x - 300; int top = point.y - 300; int right = point.x + 300; int bottom = point.y + 300; left = left < -1000 ? -1000 : left; top = top < -1000 ? -1000 : top; right = right > 1000 ? 1000 : right; bottom = bottom > 1000 ? 1000 : bottom; areas.add(new Camera.Area(new Rect(left, top, right, bottom), 100)); parameters.setFocusAreas(areas); try { //本人使用的小米手机在设置聚焦区域的时候经常会出异常,看日志发现是框架层的字符串转int的时候出错了, //目测是小米修改了框架层代码导致,在此try掉,对实际聚焦效果没影响 mCamera.setParameters(parameters); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); Log.e("tag","------------------------3"); return false; } } return focus(callback); } private boolean focus(Camera.AutoFocusCallback callback) { try { mCamera.autoFocus(callback); } catch (Exception e) { e.printStackTrace(); Log.e("tag","------------------------4"); return false; } return true; }
最后就是相机的缩放了,直接上代码了:
public void setZoom(int zoom) { if (mCamera == null) return; Camera.Parameters parameters; //注意此处为录像模式下的setZoom方式。在Camera.unlock之后,调用getParameters方法会引起android框架底层的异常 //stackoverflow上看到的解释是由于多线程同时访问Camera导致的冲突,所以在此使用录像前保存的mParameters。 parameters = mCamera.getParameters(); if (!parameters.isZoomSupported()) return; parameters.setZoom(zoom); mCamera.setParameters(parameters); mZoom = zoom; }
好了,相机camera的基本操作都在这里。
基于GlSurfaceView自定义CamerView自定义相机
我们相机的数据有两种常用的方法来预览,第一种就是利用SurfaceView控件,设置SurfaceHolder句柄来预览,这种方式相对比较简单,但是却不能很好的满足我们一些需求,比如在自定义相机中设置自定义的滤镜,贴纸,甚至加上AR的效果。所以,我们选择第二种方式,利用Opengl来绘制摄像头的数据。这样方便后面的拓展,其中的流程如下图:
这里面的关键是生成的纹理id如何通过opengl来绑定在一起,然后通过纹理id来生成SurfaceTexture,然后将摄像数据与GLSurfaceView绑定在一起显示出来。下面我们通过代码来分析。首先如何生成纹理id:
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]; }
此时的纹理id是没有和opengl绑定相关的,这个时候需要在GLSurfaceView的Render中的onSurfaceCreated进行相应的绑定操作:
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { int texture = createTextureID(); surfaceTexture=new SurfaceTexture(texture); mOesFilter.create(); mOesFilter.setTextureId(texture); }
上面通过纹理id生成了一个SurfaceTexture对象,然后把这个对象交给相机的CameraHelper,通过一下方法设置:
@Override public void setPreviewTexture(SurfaceTexture texture) { if (mCamera != null) { try { mCamera.setPreviewTexture(texture); } catch (IOException e) { e.printStackTrace(); } } }
这是时候摄像头的数据就已经和GLSurfaceView完成一半的工作了,不了解GLSurfaceView的绘图方式的请参考:http://www.jianshu.com/p/442682fda917
接下来,就是需要通过opengl的一些操作将摄像头数据正常显示出来了,其实主要还是实现onDrawFrame方法:
/** * 启用顶点坐标和纹理坐标进行绘制 */ protected void onDraw(){ GLES20.glEnableVertexAttribArray(mHPosition); GLES20.glVertexAttribPointer(mHPosition,2, GLES20.GL_FLOAT, false, 0,mVerBuffer); GLES20.glEnableVertexAttribArray(mHCoord); GLES20.glVertexAttribPointer(mHCoord, 2, GLES20.GL_FLOAT, false, 0, mTexBuffer); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4); GLES20.glDisableVertexAttribArray(mHPosition); GLES20.glDisableVertexAttribArray(mHCoord); }
这里的主要opengl指令相关的类在于CameraFilter:
/** * @CreadBy :DramaScript * @date 2017/7/10 */public class CameraFilter extends BaseFilter { //连续矩阵句柄 private int mHCoordMatrix; //连续矩阵值 private float[] mCoordMatrix= Arrays.copyOf(OM,16); public CameraFilter(Resources mRes) { super(mRes); } @Override protected void onCreate() { //创建opengl2 createProgramByAssetsFile("shader/oes_base_vertex.sh","shader/oes_base_fragment.sh"); mHCoordMatrix= GLES20.glGetUniformLocation(mProgram,"vCoordMatrix"); } public void setCoordMatrix(float[] matrix){ this.mCoordMatrix=matrix; } @Override protected void onBindTexture() { GLES20.glActiveTexture(GLES20.GL_TEXTURE0+getTextureType()); GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,getTextureId()); GLES20.glUniform1i(mHTexture,getTextureType()); } @Override protected void onSetExpandData() { super.onSetExpandData(); GLES20.glUniformMatrix4fv(mHCoordMatrix,1,false,mCoordMatrix,0); } @Override protected void onSizeChanged(int width, int height) { }}
上面通过加载Assets目录下的sh文件来显示,sh写的是opengl相关的代码,其中的顶点着色器和片元着色器:
#extension GL_OES_EGL_image_external : requireprecision mediump float;varying vec2 textureCoordinate;uniform samplerExternalOES vTexture;void main() { gl_FragColor = texture2D( vTexture, textureCoordinate );}
attribute vec4 vPosition;attribute vec2 vCoordinate;uniform mat4 vMatrix;varying vec2 aCoordinate;void main(){ gl_Position=vMatrix*vPosition; aCoordinate=vCoordinate;}
其实主要还是整个流程,以及对opengl的一些相关术语的理解,如果不明opengl的相关术语描述。就这样,代码下载:
项目源码
- 基于GLSurfaceView实现自定义Camera
- Android平台Camera实时滤镜实现方法探讨(五)--GLSurfaceView实现Camera预览
- Android平台Camera实时滤镜实现方法探讨(五)--GLSurfaceView实现Camera预览
- Android平台Camera实时滤镜实现方法探讨(五)--GLSurfaceView实现Camera预览
- SurfaceView预览Camera+GLSurfaceView绘制
- 基于tornado实现web camera
- Android SurfaceTexture和GLSurfaceView做Camera预览
- Android SurfaceTexture和GLSurfaceView做Camera预览
- Android SurfaceTexture和GLSurfaceView做Camera预览
- 使用Opengl ES GlSurfaceView Render Camera preview
- 使用GLSurfaceView预览Camera 基础拍照demo
- WINCE下实现基于USB的camera
- WINCE下实现基于USB的camera
- WINCE下实现基于USB的camera
- Android调用Camera实现自定义照相
- Android Camera+SurfaceView实现自定义拍照
- android调用camera实现自定义照相
- 使用自定义Camera实现简单拍照功能
- 返回到前一个页面时显示前一个页面中ajax获取的数据
- linux查看/修改文件编码
- 集合(京东2017秋招真题)
- Help Jimmy
- 2017 Android github上优秀开源项目分类汇总
- 基于GLSurfaceView实现自定义Camera
- JSONObject的使用
- Windows下运行ipconfig出现“不是内部命令或外部命令”
- windows命令窗口打开记事本
- Maven+SSM框架(Spring+SpringMVC+MyBatis)
- HDU2133 What day is it
- 重回C++,error LNK1123
- struts2原理
- python 面向对象