Android(安卓)Camer2与Camera1 自定义相机拍照封装实例讲解
引言
我们在Android开发中肯定都免不了要使用到相机拍照功能,一般正常情况下使用Android自带相机就能满足很大一部分需求了。不过某些情况下我们还是需要用到自定义的相机功能。而笔者所在公司恰好又有一个需求就是要针对不同厂商的定制的TV盒子做人脸拍照识别功能,而摄像头是外置摄像头,电视盒子因为定制化的原因调用原生相机总是会有各种的问题,因此就不得不区分使用Camera1和Camera2 API来实现相机拍照的功能。 这篇文章则是笔者根据自己的经验将Camer1和Camera2分别封装到不同的View中来实现相机的适配功能。
(注:Camera1在使用中的源码为android.hardware.Camera,这里方便做区分统一都写为Camer1)
下面是项目的源码地址,小伙伴们可根据需要自行取用:
Android Camer2与Camera1 自定义相机拍照封装实例
正文
首先Camera1应该是我们刚开始接触Andorid 自定义相机的时候就会了解到的API了,因为Andorid系统的升级更新,从Android 5.0之后官方就基本推荐使用Camera2 API来实现自定义相机。关于Camera2与Camera1相对比的具体优势和实现细节,基本都在从我之前写的这篇文章中都有解答: Android Camera2相机使用流程讲解
这里我就直接开始代码封装的解读了,首先我们知道,使用Camera2 API必须是在Android 5.0之后,同时设备必须支持Camera2。所以一般情况下我们需要在针对使用Camera1还是使用Camera2需要做一个判断:只有Android 5.0及其以上设备,同时能够支持Camera2的设备。我们才可使用Cmaera2 API来实现自定义相机,否则就使用Camera1 API。
首先我做了一个CameraView通用接口,定义好相机使用过程中需要的通用的方法(拍照,切换相机等)。这样可以通过这个通用接口实现面向接口编程啦。
public interface CameraView { /** * 与生命周期onResume调用 */ void onResume(); /** * 与生命周期onPause调用 */ void onPause(); /** * 拍照 */ void takePicture(); /** * 拍照(有回调) */ void takePicture(TakePictureCallback takePictureCallback); /** * 设置保存的图片文件 * * @param pictureSavePath 拍摄的图片返回的绝对路径 */ void setPictureSavePath(String pictureSavePath); /** * 切换相机摄像头 */ void switchCameraFacing(); interface TakePictureCallback { void success(String picturePath); void error(final String error); }}
接口中定义了onResume,onPause方法 因为相机需要通过这两个方法去判断启用还是暂停,通过Activity的回调的这两个生命周期。我们能够很好的去处理相机逻辑。其他的几个方法就是一般相机通用的方法啦。这里说一下拍照方法我加入了有回调和没回调方法,这个也是考虑到了业务逻辑的原因。
比如有时候我们只是想要调用拍照接口,然后让相机拍照后自动保存图片到文件即可,至于是否成功我们都可以不用考虑就调用无回调接口即可。
然后就是setPictureSavePath,设备保存的图片文件接口。通过此接口可以将相机拍照图片保存到我们指定的路径下,如果不设置这里我会保存在一个默认的路径下。
下面的代码分别就是对 Camera1 API 和 Camera2 API 实现的自定义View的核心代码部分了。
源码直接贴出来啦,其中对应API的解释也有,小伙伴们可以直接根据通过撸代码了解对应API啦。
Camera1 API的封装及其实现
Camera1 的显示View我是用到的SurfaceView。用过自定义相机的小伙伴应该对这个很熟悉了,另外有关Camera1的API网上也有很多。
我这里主要重点提一下使用相机的时候判断相机的旋转方向的方法,选择最佳的相机预览大小和最佳的拍照图片大小。
关于相机预览旋转方向,我这里用到了google官方推荐的方式,传入的参数CameraId就是相机面对方向(前置方向或后置方向)。对相机预览方向还有疑问的小伙伴可以看这里哦: Android相机预览方向深入探究
另外一点就是关于选择相机最佳预览方向,我这里也是使用了谷歌官方推荐的方案。
大致实现流程就是通过 Camerad的Parameters.getSupportedPreviewSizes() 获取相机预览支持的大小列表,通过Camerad的Parameters.getSupportedPictureSizes() 获取相机照片支持的大小列表。然后再根据我们相机的长宽比(比如4:3还是16:9),筛选出最大的符合长宽比的尺寸列表,然后再中这些符合条件的尺寸列表中选出最大的即可。
具体实现可下面的参考源码
public class Camera1Preview extends SurfaceView implements CameraView, SurfaceHolder.Callback { private static final String TAG = "Camera1Preview"; private Camera mCamera; private Context mContext; private int mCameraCount; private int mCurrentCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK; private SurfaceHolder mSurfaceHolder; /** * 标识相机是否正在拍照过程中 */ private final AtomicBoolean isPictureCaptureInProgress = new AtomicBoolean(false); private int mRatioWidth = 0; private int mRatioHeight = 0; public Camera1Preview(Context context) { this(context, null); } public Camera1Preview(Context context, AttributeSet attrs) { this(context, attrs, 0); } public Camera1Preview(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; initCamera(); } private void initCamera() { mCamera = getCameraInstance(); if (mCamera == null) return; //得到摄像头数量 mCameraCount = Camera.getNumberOfCameras(); mSurfaceHolder = getHolder(); // 设置SurfaceHolder.Callback回调,这样我们可以在创建或销毁Surface时处理相应的逻辑 mSurfaceHolder.addCallback(this); //设置屏幕常亮 mSurfaceHolder.setKeepScreenOn(true); //点击自动对焦 setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mCamera != null) { mCamera.autoFocus(null); } } }); } @Override public void onResume() { if (mCamera == null) mCamera = getCameraInstance(); } @Override public void onPause() { releaseCamera(); } /** * 拍照方法(无回调,默认保存到文件中) */ @Override public void takePicture() { if (mCamera == null) { throw new IllegalStateException( "Camera is not ready. Call start() before takePicture()."); } takePictureInternal(); } /** * 拍照方法(有回调) * * @param callback */ @Override public void takePicture(TakePictureCallback callback) { if (mCamera == null) { throw new IllegalStateException( "Camera is not ready. Call start() before takePicture()."); } takePictureCallback = callback; takePictureInternal(); } private void takePictureInternal() { //如果正在拍照处理中,则不能调用takePicture方法,否则应用会崩溃 if (!isPictureCaptureInProgress.get()) { isPictureCaptureInProgress.set(true); mCamera.takePicture(null, null, mPictureCallback); } } /** * 设置图片的保存路径 * * @param pictureSavePath */ @Override public void setPictureSavePath(String pictureSavePath) { mPictureSaveDir = pictureSavePath; } /*** * 切换相机摄像头 */ @Override public void switchCameraFacing() { if (mCameraCount > 1) { mCurrentCameraFacing = (mCurrentCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK; releaseCamera(); startPreview(mSurfaceHolder); } else { //手机不支持前置摄像头 } } /** * 设置此视图的宽高比。 * 视图的大小将基于从参数计算的比率来测量。 * 请注意,参数的实际大小并不重要,也就是说,setAspectRatio(2, 3)setAspectRatio(4, 6)会得到相同的结果。 * * @param width Relative horizontal size * @param height Relative vertical size */ public void setAspectRatio(int width, int height) { if (width < 0 || height < 0) { throw new IllegalArgumentException("Size cannot be negative."); } mRatioWidth = width; mRatioHeight = height; requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); if (0 == mRatioWidth || 0 == mRatioHeight) { setMeasuredDimension(width, height); } else { if (width < height * mRatioWidth / mRatioHeight) { setMeasuredDimension(width, width * mRatioHeight / mRatioWidth); } else { setMeasuredDimension(height * mRatioWidth / mRatioHeight, height); } } } /** * 获取相机实例 */ private Camera getCameraInstance() { Camera camera = null; try { // 获取相机实例, 注意:某些设备厂商可能需要用 Camera.open() 方法才能打开相机。 camera = Camera.open(mCurrentCameraFacing); } catch (Exception e) { // 相机不可用或不存在 Log.e(TAG, "error open(int cameraId) camera1 : " + e.getMessage()); } try { if (null == camera) camera = Camera.open(); } catch (Exception e) { Log.e(TAG, "error open camera1() : " + e.getMessage()); } return camera; } /** * 释放相机 */ private void releaseCamera() { if (mCamera != null) { mCamera.stopPreview(); mCamera.release(); // release the camera1 for other applications mCamera = null; } } @Override public void surfaceCreated(SurfaceHolder holder) { Log.i(TAG, "surfaceCreated"); // Surface创建完成, 现在即可设置相机预览 startPreview(holder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.i(TAG, "surfaceChanged format" + format + ", width =" + width + " | height=" + height); // surface在改变大小或旋转时触发此时间 // 确保在调整或重新格式化之前停止视频预览 if (mSurfaceHolder.getSurface() == null) { // 预览Surface不存在 return; } // 改变前先停止预览 try { mCamera.stopPreview(); startPreview(holder); setCameraParameters(width, height); } catch (Exception e) { Log.d(TAG, "error starting camera1 preview: " + e.getMessage()); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.i(TAG, "surfaceDestroyed"); //释放相机 releaseCamera(); } private void startPreview(SurfaceHolder holder) { if (mCamera == null) mCamera = getCameraInstance(); try { mCamera.setPreviewDisplay(holder); //设置预览的旋转角度 mCamera.setDisplayOrientation(calcDisplayOrientation(mCurrentCameraFacing)); mCamera.startPreview(); } catch (IOException e) { Log.e(TAG, "error setting camera1 preview:" + e.getMessage()); } } private int calcDisplayOrientation(int cameraId) { Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(cameraId, info); int rotation = ((Activity) mContext).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; } mDisplayOrientation = degrees; int result; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360; result = (360 - result) % 360; // compensate the mirror } else { // back-facing result = (info.orientation - degrees + 360) % 360; } return result; } private Camera.Parameters mCameraParameters; private final SizeMap mPreviewSizes = new SizeMap(); private final SizeMap mPictureSizes = new SizeMap(); private AspectRatio mAspectRatio; private int mDisplayOrientation; private AspectRatio chooseAspectRatio() { AspectRatio r = null; for (AspectRatio ratio : mPreviewSizes.ratios()) { r = ratio; if (ratio.equals(Constants.DEFAULT_ASPECT_RATIO)) { return ratio; } } return r; } private Size chooseOptimalSize(SortedSet sizes, int surfaceWidth, int surfaceHeight) { int desiredWidth; int desiredHeight; if (isLandscape(mDisplayOrientation)) { desiredWidth = surfaceHeight; desiredHeight = surfaceWidth; } else { desiredWidth = surfaceWidth; desiredHeight = surfaceHeight; } Size result = null; for (Size size : sizes) { // Iterate from small to large if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) { return size; } result = size; } return result; } /** * Test if the supplied orientation is in landscape. * * @param orientationDegrees Orientation in degrees (0,90,180,270) * @return True if in landscape, false if portrait */ private boolean isLandscape(int orientationDegrees) { return (orientationDegrees == Constants.LANDSCAPE_90 || orientationDegrees == Constants.LANDSCAPE_270); } /*** * 设置相机参数 * * @param width * @param height */ private void setCameraParameters(int width, int height) { if (mCamera == null) return; mCameraParameters = mCamera.getParameters(); // 相机预览支持的大小 mPreviewSizes.clear(); for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) { mPreviewSizes.add(new Size(size.width, size.height)); } // 相机照片支持的大小 mPictureSizes.clear(); for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) { mPictureSizes.add(new Size(size.width, size.height)); } // AspectRatio 默认长宽比为4:3 if (mAspectRatio == null) { mAspectRatio = Constants.DEFAULT_ASPECT_RATIO; } SortedSet sizes = mPreviewSizes.sizes(mAspectRatio); if (sizes == null) { // Not supported mAspectRatio = chooseAspectRatio(); sizes = mPreviewSizes.sizes(mAspectRatio); } Size previewSize = chooseOptimalSize(sizes, width, height); // Always re-apply camera1 parameters // Largest picture size in this ratio final Size pictureSize = mPictureSizes.sizes(mAspectRatio).last(); mCameraParameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight()); mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); mCameraParameters.setPictureFormat(ImageFormat.JPEG); // 设置图片格式 mCameraParameters.setJpegQuality(100); // 设置照片质量 mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//自动对焦 //parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);//连续对焦 //camera1.cancelAutoFocus();//如果要实现连续的自动对焦,这一句必须加上 mCamera.setParameters(mCameraParameters); //根据我们选中的预览相机大小的宽高比调整View的大小 int orientation = getResources().getConfiguration().orientation; if (orientation == Configuration.ORIENTATION_LANDSCAPE) { setAspectRatio( previewSize.getWidth(), previewSize.getHeight()); } else { setAspectRatio( previewSize.getHeight(), previewSize.getWidth()); } } private String mPictureSaveDir; private TakePictureCallback takePictureCallback; private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(final byte[] data, Camera camera) { Log.d(TAG, "onPictureTaken start timestemp :" + System.currentTimeMillis()); savePictureToSDCard(data); startPreview(mSurfaceHolder); isPictureCaptureInProgress.set(false); Log.d(TAG, "onPictureTaken end timestemp :" + System.currentTimeMillis()); } }; /** * 将拍下来的照片存放在SD卡中 * * @param data */ private void savePictureToSDCard(byte[] data) { File pictureFile; //检测外部存储是否存在 if (FileUtils.checkSDCard()) { if (mPictureSaveDir == null) { pictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE); } else { pictureFile = FileUtils.getTimeStampMediaFile(mPictureSaveDir, FileUtils.MEDIA_TYPE_IMAGE); } if (pictureFile == null) { Log.e(TAG, "error creating media file, check storage permissions"); if (takePictureCallback != null) { takePictureCallback.error("error creating media file, check storage permissions"); } return; } } else { pictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE); } try { FileOutputStream outputStream = new FileOutputStream(pictureFile); //由于在预览的时候,我们调整了预览的方向,所以在保存的时候我们要旋转回来,不然保存的图片方向是不正确的 Matrix matrix = new Matrix(); if (mCurrentCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) { matrix.setRotate(90); } else { matrix.setRotate(-90); } Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream); outputStream.write(data); outputStream.close(); } catch (Exception e) { Log.e(TAG, "savePictureToSDCard error :" + e.getMessage()); if (takePictureCallback != null) { takePictureCallback.error(e.getMessage()); } return; } if (takePictureCallback != null) takePictureCallback.success(pictureFile.getAbsolutePath()); //这个的作用是让系统去扫描刚拍下的这个图片文件,以利于在MediaSore中能及时更新, // 可能会存在部分手机不用使用的情况(众所周知,现在国内的Rom厂商已把原生Rom改的面目全非) //mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + mPictureSavePath)));// MediaScannerConnection.scanFile(mContext, new String[]{// pictureFile.getAbsolutePath()},// null, new MediaScannerConnection.OnScanCompletedListener() {// @Override// public void onScanCompleted(String path, Uri uri) {//// Log.e(TAG, "扫描完成");// }// }); }}
Camera2 API的封装及其实现
Camera2的封装这里我用了TextureView 。其中有关Camera2 API的具体的使用方式和解读可在我的这一篇博文中找到: Android Camera2相机使用流程讲解
这里我就直接贴出源码啦。
public class Camera2Preview extends TextureView implements CameraView { private static final String TAG = "Camera2Preview"; private Context mContext; private WindowManager mWindowManager; private int mRatioWidth = 0; private int mRatioHeight = 0; private int mCameraCount; private int mCurrentCameraFacing = CameraCharacteristics.LENS_FACING_BACK; public Camera2Preview(Context context) { this(context, null); } public Camera2Preview(Context context, AttributeSet attrs) { this(context, attrs, 0); } public Camera2Preview(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; init(); } private void init() { mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } /** * 同生命周期 onResume */ @Override public void onResume() { startBackgroundThread(); // 当关闭并打开屏幕时,SurfaceTexture已经可用,并且不会调用“onSurfaceTextureAvailable”。 // 在这种情况下,我们可以打开相机并从这里开始预览(否则,我们将等待,直到Surface在SurfaceTextureListener中准备好)。 if (isAvailable()) { openCamera(getWidth(), getHeight()); } else { setSurfaceTextureListener(mSurfaceTextureListener); } } /** * 同生命周期 onPause */ @Override public void onPause() { closeCamera(); stopBackgroundThread(); } /** * 启动拍照的方法 */ @Override public void takePicture() { takePictureInternal(); } private TakePictureCallback mTakePictureCallback; @Override public void takePicture(TakePictureCallback takePictureCallback) { mTakePictureCallback = takePictureCallback; takePictureInternal(); } private void takePictureInternal() { if (mCurrentCameraFacing == CameraCharacteristics.LENS_FACING_BACK) { lockFocus(); } else { runPrecaptureSequence(); } } /** * 设置图片的保存路径(文件夹) * * @param pictureSavePath */ private String mPictureSaveDir; @Override public void setPictureSavePath(String pictureSavePath) { mPictureSaveDir = pictureSavePath; } @Override public void switchCameraFacing() { if (mCameraCount > 1) { mCurrentCameraFacing = mCurrentCameraFacing == CameraCharacteristics.LENS_FACING_BACK ? CameraCharacteristics.LENS_FACING_FRONT : CameraCharacteristics.LENS_FACING_BACK; closeCamera(); openCamera(getWidth(), getHeight()); } } /** * 设置此视图的宽高比。 * 视图的大小将基于从参数计算的比率来测量。 * 请注意,参数的实际大小并不重要,也就是说,setAspectRatio(2, 3)setAspectRatio(4, 6)会得到相同的结果。 * * @param width Relative horizontal size * @param height Relative vertical size */ public void setAspectRatio(int width, int height) { if (width < 0 || height < 0) { throw new IllegalArgumentException("Size cannot be negative."); } mRatioWidth = width; mRatioHeight = height; requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); if (0 == mRatioWidth || 0 == mRatioHeight) { setMeasuredDimension(width, height); } else { if (width < height * mRatioWidth / mRatioHeight) { setMeasuredDimension(width, width * mRatioHeight / mRatioWidth); } else { setMeasuredDimension(height * mRatioWidth / mRatioHeight, height); } } } private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } /** * 相机状态: 显示相机预览 */ private static final int STATE_PREVIEW = 0; /** * 相机状态: 等待焦点被锁定 */ private static final int STATE_WAITING_LOCK = 1; /** * 相机状态: 等待曝光前的状态。 */ private static final int STATE_WAITING_PRECAPTURE = 2; /** * 相机状态: 等待曝光状态非预先捕获的东西. */ private static final int STATE_WAITING_NON_PRECAPTURE = 3; /** * 相机状态: 照片拍摄 */ private static final int STATE_PICTURE_TAKEN = 4; /** * 最大的预览宽度 */ private static final int MAX_PREVIEW_WIDTH = 1920; /** * 最大的预览高度 */ private static final int MAX_PREVIEW_HEIGHT = 1080; /** * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a * {@link TextureView}. */ private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture texture) { } }; /** * ID of the current {@link CameraDevice}. */ private String mCameraId; /** * 相机预览要用到的{@link CameraCaptureSession } . */ private CameraCaptureSession mCaptureSession; /** * A reference to the opened {@link CameraDevice}. */ private CameraDevice mCameraDevice; /** * 相机预览的 {@link android.util.Size} */ private Size mPreviewSize; /** * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state. */ private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice cameraDevice) { // 当相机打开时调用此方法。我们在这里开始相机预览。 mCameraOpenCloseLock.release(); mCameraDevice = cameraDevice; createCameraPreviewSession(); } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { mCameraOpenCloseLock.release(); cameraDevice.close(); mCameraDevice = null; } @Override public void onError(@NonNull CameraDevice cameraDevice, int error) { mCameraOpenCloseLock.release(); cameraDevice.close(); mCameraDevice = null; Log.e(TAG, "CameraDevice.StateCallback onError errorCode= " + error); } }; /** * 用于运行不应该阻止UI的任务的附加线程。 */ private HandlerThread mBackgroundThread; /** * 在后台运行任务的Handler。 */ private Handler mBackgroundHandler; /** * An {@link ImageReader} 用于处理图像捕获(抓拍). */ private ImageReader mImageReader; /** * 图片文件 */ private File mPictureFile; /** * 这是{@link ImageReader}的回调对象. 当静态图像准备好保存时,将回调"onImageAvailable" */ private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { //检测外部存储是否存在 //根据时间戳生成图片名称 if (FileUtils.checkSDCard()) { if (mPictureSaveDir == null) { mPictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE); } else { mPictureFile = FileUtils.getTimeStampMediaFile(mPictureSaveDir, FileUtils.MEDIA_TYPE_IMAGE); } if (mPictureFile == null) { Log.e(TAG, "error creating media file, check storage permissions"); if (mTakePictureCallback != null) { mTakePictureCallback.error("error creating media file, check storage permissions"); } return; } } else { mPictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE); } mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mPictureFile)); } }; /** * {@link CaptureRequest.Builder} for the camera1 preview */ private CaptureRequest.Builder mPreviewRequestBuilder; /** * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder} */ private CaptureRequest mPreviewRequest; /** * 拍照相机的当前状态 * * @see #mCaptureCallback */ private int mState = STATE_PREVIEW; /** * A {@link Semaphore} 防止相机关闭前退出应用程序。 */ private Semaphore mCameraOpenCloseLock = new Semaphore(1); /** * 当前的相机设备是否支持Flash。 */ private boolean mFlashSupported; /** * 相机传感器的方向 */ private int mSensorOrientation; /** * A {@link CameraCaptureSession.CaptureCallback} 处理JPEG捕获的相关事件 */ private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() { private void process(CaptureResult result) { switch (mState) { case STATE_PREVIEW: { // We have nothing to do when the camera1 preview is working normally. break; } case STATE_WAITING_LOCK: { Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); if (afState == null) { captureStillPicture(); } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { // CONTROL_AE_STATE can be null on some devices Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { mState = STATE_PICTURE_TAKEN; captureStillPicture(); } else { runPrecaptureSequence(); } } break; } case STATE_WAITING_PRECAPTURE: { // CONTROL_AE_STATE can be null on some devices Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { mState = STATE_WAITING_NON_PRECAPTURE; } break; } case STATE_WAITING_NON_PRECAPTURE: { // CONTROL_AE_STATE can be null on some devices Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { mState = STATE_PICTURE_TAKEN; captureStillPicture(); } break; } } } @Override public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { process(partialResult); } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { process(result); } }; /** * 给定相机支持的{@code Size}的{@code choices}, * 选择至少与相应TextureView大小一样大、最多与相应最大大小一样大、且纵横比与指定值匹配的最小一个。 * 如果不存在这样的尺寸,则选择最大尺寸与相应的最大尺寸一样大,并且其纵横比与指定值匹配的最大尺寸。 * * @param choices 摄像机支持预期输出类的大小列表。 * @param textureViewWidth TextureView相对于传感器坐标的宽度 * @param textureViewHeight TextureView相对于传感器坐标的高度 * @param maxWidth 可选择的最大宽度 * @param maxHeight 可选择的最大高度 * @param aspectRatio 宽高比 * @return 最佳的 {@code Size}, 或任意一个,如果没有足够大的话 */ private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) { // 收集至少与预览表面一样大的支持分辨率。 List bigEnough = new ArrayList<>(); // 收集小于预览表面的支持的分辨率 List notBigEnough = new ArrayList<>(); int w = aspectRatio.getWidth(); int h = aspectRatio.getHeight(); for (Size option : choices) { if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight && option.getHeight() == option.getWidth() * h / w) { if (option.getWidth() >= textureViewWidth && option.getHeight() >= textureViewHeight) { bigEnough.add(option); } else { notBigEnough.add(option); } } } // 挑那些最小中的足够大的。如果没有足够大的,选择最大的那些不够大的。 if (bigEnough.size() > 0) { return Collections.min(bigEnough, new CompareSizesByArea()); } else if (notBigEnough.size() > 0) { return Collections.max(notBigEnough, new CompareSizesByArea()); } else { Log.e(TAG, "Couldn't find any suitable preview size"); return choices[0]; } } /** * 设置与摄像机相关的成员变量。 * * @param width 相机预览可用尺寸的宽度 * @param height 相机预览可用尺寸的高度 */ @SuppressWarnings("SuspiciousNameCombination") private void setUpCameraOutputs(int width, int height) { CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); try { String[] cameraIdList = manager.getCameraIdList(); mCameraCount = cameraIdList.length; for (String cameraId : cameraIdList) { CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); //判断当前摄像头是前置还是后置摄像头 Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); if (facing != null && facing != mCurrentCameraFacing) { continue; } StreamConfigurationMap map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { continue; } // 对于静态图像捕获,我们使用最大可用的大小。 Size largest = Collections.max( Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea()); mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, /*maxImages*/2); mImageReader.setOnImageAvailableListener( mOnImageAvailableListener, mBackgroundHandler); // 找出是否需要交换尺寸以获得相对与传感器坐标的预览大小 int displayRotation = mWindowManager.getDefaultDisplay().getRotation(); //检验条件 mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); boolean swappedDimensions = false; switch (displayRotation) { case Surface.ROTATION_0: case Surface.ROTATION_180: if (mSensorOrientation == 90 || mSensorOrientation == 270) { swappedDimensions = true; } break; case Surface.ROTATION_90: case Surface.ROTATION_270: if (mSensorOrientation == 0 || mSensorOrientation == 180) { swappedDimensions = true; } break; default: Log.e(TAG, "Display rotation is invalid: " + displayRotation); } Point displaySize = new Point(); mWindowManager.getDefaultDisplay().getSize(displaySize); int rotatedPreviewWidth = width; int rotatedPreviewHeight = height; int maxPreviewWidth = displaySize.x; int maxPreviewHeight = displaySize.y; if (swappedDimensions) { rotatedPreviewWidth = height; rotatedPreviewHeight = width; maxPreviewWidth = displaySize.y; maxPreviewHeight = displaySize.x; } if (maxPreviewWidth > MAX_PREVIEW_WIDTH) { maxPreviewWidth = MAX_PREVIEW_WIDTH; } if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) { maxPreviewHeight = MAX_PREVIEW_HEIGHT; } // 危险,W.R.!尝试使用太大的预览大小可能超过相机总线的带宽限制,导致高清的预览,但存储垃圾捕获数据。 mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, maxPreviewHeight, largest); // 我们将TextureView的宽高比与我们选择的预览大小相匹配。 int orientation = getResources().getConfiguration().orientation; if (orientation == Configuration.ORIENTATION_LANDSCAPE) { setAspectRatio( mPreviewSize.getWidth(), mPreviewSize.getHeight()); } else { setAspectRatio( mPreviewSize.getHeight(), mPreviewSize.getWidth()); } //检验是否支持flash Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); mFlashSupported = available == null ? false : available; mCameraId = cameraId; return; } } catch (CameraAccessException e) { e.printStackTrace(); } catch (NullPointerException e) { //抛出空指针一般代表当前设备不支持Camera2API Log.e(TAG, "This device doesn't support Camera2 API."); } } /** * 打开指定的相机(mCameraId) */ private void openCamera(int width, int height) { setUpCameraOutputs(width, height); configureTransform(width, height); CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); try { if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { throw new RuntimeException("Time out waiting to lock camera1 opening."); } if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { return; } manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } catch (InterruptedException e) { throw new RuntimeException("Interrupted while trying to lock camera1 opening.", e); } } /** * 关闭相机 */ private void closeCamera() { try { mCameraOpenCloseLock.acquire(); if (null != mCaptureSession) { mCaptureSession.close(); mCaptureSession = null; } if (null != mCameraDevice) { mCameraDevice.close(); mCameraDevice = null; } if (null != mImageReader) { mImageReader.close(); mImageReader = null; } } catch (InterruptedException e) { throw new RuntimeException("Interrupted while trying to lock camera1 closing.", e); } finally { mCameraOpenCloseLock.release(); } } /** * 启动后台线程和Handler. */ private void startBackgroundThread() { mBackgroundThread = new HandlerThread("CameraBackground"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); } /** * 停止后台线程和Handler. */ private void stopBackgroundThread() { mBackgroundThread.quitSafely(); try { mBackgroundThread.join(); mBackgroundThread = null; mBackgroundHandler = null; } catch (InterruptedException e) { e.printStackTrace(); } } /** * 创建一个新的 {@link CameraCaptureSession} 用于相机预览. */ private void createCameraPreviewSession() { try { SurfaceTexture texture = getSurfaceTexture(); assert texture != null; // 我们将默认缓冲区的大小设置为我们想要的相机预览的大小。 texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // 我们需要开始预览输出Surface Surface surface = new Surface(texture); // 我们建立了一个具有输出Surface的捕获器。 mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mPreviewRequestBuilder.addTarget(surface); // 这里,我们创建了一个用于相机预览的CameraCaptureSession mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { // 相机已经关闭 if (null == mCameraDevice) { return; } // 当session准备好后,我们开始显示预览 mCaptureSession = cameraCaptureSession; try { // 相机预览时应连续自动对焦 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 设置闪光灯在必要时自动打开 setAutoFlash(mPreviewRequestBuilder); // 最终,显示相机预览 mPreviewRequest = mPreviewRequestBuilder.build(); mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed( @NonNull CameraCaptureSession cameraCaptureSession) { Log.e(TAG, "CameraCaptureSession.StateCallback onConfigureFailed"); } }, null ); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 配置必要的 {@link android.graphics.Matrix} 转换为 `mTextureView`. * * 该方法应该在setUpCameraOutputs中确定相机预览大小以及“mTextureView”的大小固定之后调用。 * * @param viewWidth The width of `mTextureView` * @param viewHeight The height of `mTextureView` */ private void configureTransform(int viewWidth, int viewHeight) { if (null == mPreviewSize || null == mContext) { return; } int rotation = mWindowManager.getDefaultDisplay().getRotation(); Matrix matrix = new Matrix(); RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); float centerX = viewRect.centerX(); float centerY = viewRect.centerY(); if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); float scale = Math.max( (float) viewHeight / mPreviewSize.getHeight(), (float) viewWidth / mPreviewSize.getWidth()); matrix.postScale(scale, scale, centerX, centerY); matrix.postRotate(90 * (rotation - 2), centerX, centerY); } else if (Surface.ROTATION_180 == rotation) { matrix.postRotate(180, centerX, centerY); } setTransform(matrix); } /** * 锁定焦点作为静态图像捕获的第一步 */ private void lockFocus() { try { // 这里是让相机锁定焦点 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); // 告知 #mCaptureCallback 等待锁 mState = STATE_WAITING_LOCK; mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 运行预捕获序列捕获一张静态图片。 *
* 这个方法应该在我们从得到mCaptureCallback的响应后调用 */ private void runPrecaptureSequence() { try { // 这就是如何告诉相机触发。 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); // 告知 #mCaptureCallback 等待设置预捕获序列。 mState = STATE_WAITING_PRECAPTURE; mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 捕获一张静态图片 * 这个方法应该在我们从得到mCaptureCallback的响应后调用 */ private void captureStillPicture() { try { if (null == mCameraDevice) { return; } // 这是 CaptureRequest.Builder ,我们用它来进行拍照 final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(mImageReader.getSurface()); // 使用相同的AE和AF模式作为预览。 captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); setAutoFlash(captureBuilder); // 方向 int rotation = mWindowManager.getDefaultDisplay().getRotation(); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { Log.d(TAG, "CameraCaptureSession.CaptureCallback onCaptureCompleted 图片保存地址为:" + mPictureFile.toString()); if (mTakePictureCallback != null) { mTakePictureCallback.success(mPictureFile.getAbsolutePath()); } unlockFocus(); } }; mCaptureSession.stopRepeating(); mCaptureSession.abortCaptures(); mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 从指定的屏幕旋转中检索JPEG方向。 * * @param rotation 图片旋转 * @return The JPEG orientation (one of 0, 90, 270, and 360) */ private int getOrientation(int rotation) { // 对于大多数设备,传感器定向是90,对于某些设备(例如Nexus 5X)是270。 //我们必须考虑到这一点,并适当的旋转JPEG。 //对于取向为90的设备,我们只需从方向返回映射即可。 //对于方向为270的设备,我们需要旋转JPEG 180度。 return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; } /** * 解锁焦点. *
* 此方法应该在静态图片捕获序列结束后调用 */ private void unlockFocus() { try { // 重置自动对焦触发 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); setAutoFlash(mPreviewRequestBuilder); mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler); // 在此之后,相机将回到正常的预览状态。 mState = STATE_PREVIEW; mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } private void setAutoFlash(CaptureRequest.Builder requestBuilder) { if (mFlashSupported) { requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); } } /** * 将JPEG{@link Image}保存并放到指定的文件中 */ private static class ImageSaver implements Runnable { /** * The JPEG image */ private final Image mImage; /** * The file we save the image into. */ private final File mFile; ImageSaver(Image image, File file) { mImage = image; mFile = file; } @Override public void run() { ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { output = new FileOutputStream(mFile); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { mImage.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } } /** * 根据它们的区域比较两个的大小 {@code Size}。 */ static class CompareSizesByArea implements Comparator { @Override public int compare(Size lhs, Size rhs) { // We cast here to ensure the multiplications won't overflow return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); } }
好了,Camera1和Camera2 我们已经用自定义View封装好了,其中用到的一些工具类方法小伙伴们可以直接从我的github源码中取得咯。而博客中我更想要分享的是我在封装代码中收获到的一些心得。
在封装相机API的过程中,我主要是从业务使用方式的简便性出发封装的Camera API。这里我也只是封装了相机的拍照功能,后续也可以加入相机的录像功能。
欢迎小伙伴们提出自己的想法和意见,我们可以互相交流共同进步。
更多相关文章
- Android(安卓)Imageview图片旋转和大小变化
- android 获取屏幕大小
- android webview设置自适应任意大小的pc网页
- android 检测应用程序信息
- Android文件夹大小
- android 字体大小,样式 ,像素的设置
- android 字体大小像素的设置
- android获取设备屏幕大小的方法
- Android一键拍照功能