引言


我们在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。这里我也只是封装了相机的拍照功能,后续也可以加入相机的录像功能。

欢迎小伙伴们提出自己的想法和意见,我们可以互相交流共同进步。

更多相关文章

  1. Android(安卓)Imageview图片旋转和大小变化
  2. android 获取屏幕大小
  3. android webview设置自适应任意大小的pc网页
  4. android 检测应用程序信息
  5. Android文件夹大小
  6. android 字体大小,样式 ,像素的设置
  7. android 字体大小像素的设置
  8. android获取设备屏幕大小的方法
  9. Android一键拍照功能

随机推荐

  1. Android(安卓)Architecture Components
  2. Android Camera OMXCameraAdapter.cpp初
  3. Android获取屏幕高度、标题高度、状态栏
  4. android adb启动失败问题 adb server is
  5. Android总Activity的启动模式分为四种
  6. Android学习笔记Android线程模型解析
  7. 安卓-菜单简述
  8. 关于Android的Animation使用(XML)
  9. android 资源文件命名规则 drawable mipm
  10. 什么是aidl?Android AIDL详解