最近总想写写,但又不知写些什么,想了想,今天写个Camera拍照的教程吧。本例子的流程为 首先通过SurfaceView将Camera的实时画面显示在屏幕上,然后通过点击拍照对当前画面进行捕捉,最后将获得的图片保存至本地。
- 首先创建一个SurfaceHolder实现对SurfaceView的回调,然后重写SurfaceCreate函数,实现对Camera的初始化等一系列工作:代码如下:
@Override public void surfaceCreated(SurfaceHolder holder) { Log.e("TAG","------surfaceCreated------"); try { //这里我优先找后置摄像头,找不到再找前面的 int cameraIndex = findBackOrFrontCamera(Camera.CameraInfo.CAMERA_FACING_BACK); if (cameraIndex == -1) { cameraIndex = findBackOrFrontCamera(Camera.CameraInfo.CAMERA_FACING_FRONT); if (cameraIndex == -1) { Log.e("TAG", "No Camera!"); currentCameraType = CAMERA_NOTEXIST; currentCameraIndex = -1; return; } else { currentCameraType = FRONT; } } else { currentCameraType = BACK; } //找到想要的摄像头后,就打开 if (mCamera == null) { mCamera = openCamera(currentCameraType); } } catch (Exception e) { e.printStackTrace(); } }
本例子中,我首先找到想要打开的摄像头,这里的优先寻找后置摄像头,如果没有找到,再找前置的,代码如下:
/** * 按要求查找摄像头 * * @param camera_facing 按要求查找,镜头是前还是后 * @return -1表示找不到 */ private int findBackOrFrontCamera(int camera_facing) { int cameraCount = 0; Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); cameraCount = Camera.getNumberOfCameras(); for (int camIdx = 0; camIdx < cameraCount; camIdx++) { Camera.getCameraInfo(camIdx, cameraInfo); if (cameraInfo.facing == camera_facing) { return camIdx; } } return -1; }
当找到摄像头后,便打开Camera,其实打开Camera可以直接用open(CameraId)函数即可,但我在重新封装了一下,直接帖代码:
/** * 按照type的类型打开相应的摄像头 * * @param type 标志当前打开前还是后的摄像头 * @return 返回当前打开摄像机的对象 */ private Camera openCamera(int type) { int frontIndex = -1; int backIndex = -1; int cameraCount = Camera.getNumberOfCameras(); Camera.CameraInfo info = new Camera.CameraInfo(); for (int cameraIndex = 0; cameraIndex < cameraCount; cameraIndex++) { Camera.getCameraInfo(cameraIndex, info); if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { frontIndex = cameraIndex; } else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { backIndex = cameraIndex; } } currentCameraType = type; if (type == FRONT && frontIndex != -1) { currentCameraIndex = frontIndex; return Camera.open(frontIndex); } else if (type == BACK && backIndex != -1) { currentCameraIndex = backIndex; return Camera.open(backIndex); } return null; }
- 然后在SurfaceChange对Camera进行一系列初始化(对摄像头初始化,就是设定图片格式,图片尺寸等赋值,要打开摄像头才可以初始化,否则会报错)
/** * 初始化摄像头 * @param holder */ private void initCamera(SurfaceHolder holder){ Log.e("TAG","initCamera"); if (mPreviewRunning) mCamera.stopPreview(); Camera.Parameters parameters; try{ //获取预览的各种分辨率 parameters = mCamera.getParameters(); }catch (Exception e){ e.printStackTrace(); return; } //这里我设为480*800的尺寸 parameters.setPreviewSize(480,800); // 设置照片格式 parameters.setPictureFormat(PixelFormat.JPEG); //设置图片预览的格式 parameters.setPreviewFormat(PixelFormat.YCbCr_420_SP); setCameraDisplayOrientation(this,currentCameraIndex,mCamera); try{ mCamera.setPreviewDisplay(holder); }catch(Exception e){ if(mCamera != null){ mCamera.release(); mCamera = null; } e.printStackTrace(); } mCamera.startPreview(); mPreviewRunning = true; } /** * 设置旋转角度 * @param activity * @param cameraId * @param camera */ private void setCameraDisplayOrientation(Activity activity,int cameraId,Camera camera){ Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(cameraId,info); int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); int degrees = 0; switch(rotation){ case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } int result; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){ result = (info.orientation + degrees) % 360; result = (360 - result) % 360; }else{ result = (info.orientation - degrees +360) % 360; } camera.setDisplayOrientation(result); }
- 当这些工作完成后,便可以对当前摄像头捕捉的画面进行拍照了:
/** * 实现拍照功能 */ public void takePhoto(){ Camera.Parameters parameters; try{ parameters = mCamera.getParameters(); }catch(Exception e){ e.printStackTrace(); return; } //获取摄像头支持的各种分辨率,因为摄像头数组不确定是按降序还是升序,这里的逻辑有时不是很好找得到相应的尺寸 //可先确定是按升还是降序排列,再进对对比吧,我这里拢统地找了个,是个不精确的... List<Camera.Size> list = parameters.getSupportedPictureSizes(); int size = 0; for (int i =0 ;i < list.size() - 1;i++){ if (list.get(i).width >= 480){ //完美匹配 size = i; break; } else{ //找不到就找个最接近的吧 size = i; } } //设置照片分辨率,注意要在摄像头支持的范围内选择 parameters.setPictureSize(list.get(size).width,list.get(size).height); //设置照相机参数 mCamera.setParameters(parameters); //使用takePicture()方法完成拍照 mCamera.autoFocus(new Camera.AutoFocusCallback() { //自动聚焦完成后拍照 @Override public void onAutoFocus(boolean success, Camera camera) { if (success && camera != null){ mCamera.takePicture(new ShutterCallback(), null, new Camera.PictureCallback() { //拍照回调接口 @Override public void onPictureTaken(byte[] data, Camera camera) { savePhoto(data); //停止预览 mCamera.stopPreview(); //重启预览 mCamera.startPreview(); } }); } } }); } /* *//** * 快门回调接口,如果不想拍照声音,直接将new ShutterCallback()修改为null即可 */ private class ShutterCallback implements Camera.ShutterCallback { @Override public void onShutter() { MediaPlayer mPlayer = new MediaPlayer(); mPlayer = MediaPlayer.create(getApplicationContext(), R.raw.shutter); try{ mPlayer.prepare(); }catch (IllegalStateException e){ e.printStackTrace(); }catch (IOException e){ e.printStackTrace(); } mPlayer.start(); } }
这里要注意下,有些手机调用onAutoFocus函数,会返回失败,因为如果该手机无自动对焦,则无法执行对焦成功后的函数了。。。
-
当捕捉到数据后,便可以将这些数据保存至设定的地方了:
/** * 设置照片的路径,具体路径可自定义 * @return */ private String setPicSaveFile(){ //创建保存的路径 File storageDir = getOwnCacheDirectory(this,"MyCamera/photos"); //返回自定义的路径 return storageDir.getPath(); } private File getOwnCacheDirectory(Context context, String cacheDir) { File appCacheDir = null; //判断SD卡正常挂载并且拥有根限的时候创建文件 if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)){ appCacheDir = new File(Environment.getExternalStorageDirectory(),cacheDir); } if (appCacheDir == null || !appCacheDir.exists() && !appCacheDir.mkdirs()){ appCacheDir = context.getCacheDir(); } return appCacheDir; } /** * 检查是否有权限 * @param context * @return */ private boolean hasExternalStoragePermission(Context context) { int permission = context.checkCallingOrSelfPermission("android.permission.WRITE_EXTERNAL_STORAGE"); //PERMISSION_GRANTED=0 return permission == 0; }
由于调用了系统Camera和对SD卡读写,所以在AndroidManifest需要申请权限:
<!--摄像头相关权限--> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <!-- 在SDCard中创建与删除文件权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <!-- 往SDCard写入数据权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
后记:google在Android5.0后推出了Camera的升级版---Camera2:
按照Android的官方说明,camera 2支持以下5点新特性,有兴趣的可以研究下:
(1)支持每秒30帧的全高清连拍。
(2)支持在每帧之间使用不同的设置。
(3)支持原生格式的图像输出。
(4)支持零延迟快门和电影速拍。
(5)支持相机在其他方面的手动控制,比如设置噪音消除的级别。
最后也贴出本例子的源码吧,有兴趣的可以到Github上下载本例子;如果支持本文,也可以直接在优快云本站上下载github的链接地址:优快云地址:https://download.youkuaiyun.com/download/toyauko/10636251