一篇文章学会Android Camera2 Api,实现自定义相机入门。

前言

     最近在做相机相关的项目,考虑到调用系统相机部分功能不能满足项目需求,于是考虑自定相机这一块,由于之前没有接触过自定义相机这一块,于是查阅了相关资料,结合自身实践,做一个简单的总结。

切入正题

camera2包架构示意图:

一篇文章学会Android Camera2 Api,实现自定义相机入门。_第1张图片)


 camera2包中的主要API结构图:

一篇文章学会Android Camera2 Api,实现自定义相机入门。_第2张图片

主要API详解:

  • CameraManager:摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。除此之外,调用CameraManagergetCameraCharacteristics(String)方法即可获取指定摄像头的相关特性。
  • CameraCharacteristics:摄像头特性。该对象通过CameraManager来获取,用于描述特定摄像头所支持的各种特性。
  • CameraDevice:代表系统摄像头。该类的功能类似于早期的Camera类。
  • CameraCaptureSession:这是一个非常重要的API,当程序需要预览、拍照时,都需要先通过该类的实例创建Session。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest();控制拍照的方法为capture()
  • 为了监听CameraCaptureSession的创建过程,以及监听CameraCaptureSession的拍照过程,Camera v2 API为CameraCaptureSession提供了StateCallbackCaptureCallback等内部类。
  • CameraRequestCameraRequest.Builder:当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CameraRequest参数。CameraRequest代表了一次捕获请求,用于描述捕获图片的各种参数设置,比如对焦模式、曝光模式……总之,程序需要对照片所做的各种控制,都通过CameraRequest参数进行设置。CameraRequest.Builder则负责生成CameraRequest对象。

自定义相机拍照大致流程:

一篇文章学会Android Camera2 Api,实现自定义相机入门。_第3张图片

1. 打开指定摄像头

调用 CameraManageropenCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)方法打开指定摄像头。该方法的第一个参数 cameraId代表要打开的摄像头ID(摄像头ID(通常0代表后置摄像头,1代表前置摄像头);第二个参数用于监听摄像头的状态;第三个参数代表执行 callbackHandler,如果程序希望直接在当前线程中执行 callback,则可将 handler参数设为 null

 2. 获取CameraDevice对象

当摄像头被打开之后,程序即可获取 CameraDevice—即根据摄像头ID获取了指定摄像头设备,然后调用 CameraDevicecreateCaptureSession(List outputs, CameraCaptureSession. StateCallback callback,Handler handler)方法来创建 CameraCaptureSession。该方法的第一个参数是一个 List集合,封装了所有需要从该摄像头获取图片的 Surface,第二个参数用于监听 CameraCaptureSession的创建过程;第三个参数代表执行 callbackHandler,如果程序希望直接在当前线程中执行 callback,则可将 handler参数设为 null
3. 设置设置摄像头模式
不管预览还是拍照,程序都调用 CameraDevicecreateCaptureRequest(int templateType)方法创建 CaptureRequest.Builder,该方法支持 TEMPLATE_PREVIEW(预览)TEMPLATE_RECORD(拍摄视频)TEMPLATE_STILL_CAPTURE(拍照)等参数。
通过第3步所调用方法返回的 CaptureRequest.Builder设置拍照的各种参数,比如对焦模式、曝光模式等。
调用 CaptureRequest.Builderbuild()方法即可得到 CaptureRequest对象,接下来程序可通过 CameraCaptureSessionsetRepeatingRequest()方法开始预览,或调用 capture()方法拍照。

案例代码

配置静态权限
6.0之后记得在代码中添加相机和读写存储卡的动态权限
    
    
     
    
    
    

  1. 相机界面简单布局
 界面很简单,就一个TextureView和一个拍照按钮
activity_camera.xml




    


    


CameraActivity 
代码实现,项目中使用了 Butterknife 简单注解
该文件注释比较清楚,相信大家能够看懂,就不分方法细讲了
/**
 * @author Marlon
 * @desc Camera2Activity 基于Camera2 API 自定义相机
 * @date 2018/6/13
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class Camera2Activity extends AppCompatActivity {


    @BindView(R.id.texture)
    AutoFitTextureView texture;
    @BindView(R.id.takephoto)
    ImageView takephoto;
    @BindView(R.id.change)
    Switch change;
    @BindView(R.id.auto)
    RadioButton auto;
    @BindView(R.id.open)
    RadioButton open;
    @BindView(R.id.close)
    RadioButton close;
    @BindView(R.id.flash_rg)
    RadioGroup flashRg;


    /*** 相机管理类*/
    CameraManager mCameraManager;


    /*** 指定摄像头ID对应的Camera实体对象*/
    CameraDevice mCameraDevice;




    /**
     * 预览尺寸
     */
    private Size mPreviewSize;
    private int mSurfaceWidth;
    private int mSurfaceHeight;


    /*** 打开摄像头的ID{@link CameraDevice}.*/
    private int mCameraId = CameraCharacteristics.LENS_FACING_FRONT;


    /*** 处理静态图像捕获的ImageReader。{@link ImageReader}*/
    private ImageReader mImageReader;


    /*** 用于相机预览的{@Link CameraCaptureSession}。*/
    private CameraCaptureSession mCaptureSession;


    /*** {@link CaptureRequest.Builder}用于相机预览请求的构造器*/
    private CaptureRequest.Builder mPreviewRequestBuilder;


    /***预览请求, 由上面的构建器构建出来*/
    private CaptureRequest mPreviewRequest;
    /**
     * 从屏幕旋转图片转换方向。
     */
    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 boolean mFlashSupported;


    /*** 用于运行不应阻塞UI的任务的附加线程。*/
    private HandlerThread mBackgroundThread;


    /*** 用于在后台运行任务的{@link Handler}。*/
    private Handler mBackgroundHandler;


    /**
     * 文件存储路径
     */
    private File mFile;
    /**
     * 预览请求构建器, 用来构建"预览请求"(下面定义的)通过pipeline发送到Camera device
     * 这是{@link ImageReader}的回调对象。 当静止图像准备保存时,将会调用“onImageAvailable”。
     */
    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {


        @Override
        public void onImageAvailable(ImageReader reader) {
            mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg");
            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
        }


    };


    /*** {@link CameraDevice.StateCallback}打开指定摄像头回调{@link CameraDevice}*/
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {


        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            mCameraDevice = cameraDevice;
            createCameraPreview();
        }


        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            cameraDevice.close();
            cameraDevice = null;
        }


        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            cameraDevice.close();
            cameraDevice = null;
        }


    };


    /**
     * TextureView 生命周期响应
     */
    private final TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override //创建
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            //当TextureView创建完成,打开指定摄像头相机
            openCamera(width, height, mCameraId);
        }


        @Override //尺寸改变
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {


        }


        @Override //销毁
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            return false;
        }


        @Override //更新
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {


        }
    };
    private int CONTROL_AE_MODE;




    /**
     * 打开指定摄像头ID的相机
     *
     * @param width
     * @param height
     * @param cameraId
     */
    private void openCamera(int width, int height, int cameraId) {
        if (ActivityCompat.checkSelfPermission(Camera2Activity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            return;
        }
        try {
            mSurfaceWidth = width;
            mSurfaceHeight = height;
//            getCameraId(cameraId);
            CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(mCameraId + "");
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            // 获取设备方向
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            int totalRotation = sensorToDeviceRotation(characteristics, rotation);
            boolean swapRotation = totalRotation == 90 || totalRotation == 270;
            int rotatedWidth = mSurfaceWidth;
            int rotatedHeight = mSurfaceHeight;
            if (swapRotation) {
                rotatedWidth = mSurfaceHeight;
                rotatedHeight = mSurfaceWidth;
            }
            // 获取最佳的预览尺寸
            mPreviewSize = getPreferredPreviewSize(map.getOutputSizes(SurfaceTexture.class), rotatedWidth, rotatedHeight);
            if (swapRotation) {
                texture.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            } else {
                texture.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
            }
            if (mImageReader == null) {
                // 创建一个ImageReader对象,用于获取摄像头的图像数据,maxImages是ImageReader一次可以访问的最大图片数量
                mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
                        ImageFormat.JPEG, 2);
                mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
            }
            //检查是否支持闪光灯
            Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
            mFlashSupported = available == null ? false : available;
            mCameraManager.openCamera(mCameraId + "", mStateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }




    /**
     * 设置相机闪关灯模式
     *
     * @param AE_MODE 闪关灯的模式
     * @throws CameraAccessException
     */
    private void setFlashMode(int AE_MODE) {
        if (mFlashSupported) {
            this.CONTROL_AE_MODE = AE_MODE;
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, AE_MODE);
            if (AE_MODE == CaptureRequest.CONTROL_AE_MODE_OFF) {
                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
            }
        }


        // 构建上述的请求
        mPreviewRequest = mPreviewRequestBuilder.build();
        // 重复进行上面构建的请求, 用于显示预览
        try {
            mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }




    /**
     * 创建预览对话
     */
    private void createCameraPreview() {
        try {
            // 获取texture实例
            SurfaceTexture surfaceTexture = texture.getSurfaceTexture();
            assert surfaceTexture != null;
            //我们将默认缓冲区的大小配置为我们想要的相机预览的大小。
            surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            // 用来开始预览的输出surface
            Surface surface = new Surface(surfaceTexture);
            //创建预览请求构建器
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            //将TextureView的Surface作为相机的预览显示输出
            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;
                            }
                            //会话准备就绪后,我们开始显示预览。
                            // 会话可行时, 将构建的会话赋给field
                            mCaptureSession = cameraCaptureSession;


                            //相机预览应该连续自动对焦。
                            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                            //设置闪关灯模式
                            setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                        }


                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                            showToast("预览失败了");


                        }
                    }, null
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 拍照时调用方法
     */
    private void captureStillPicture() {
        try {
            if (mCameraDevice == null) {
                return;
            }
            // 创建作为拍照的CaptureRequest.Builder
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            // 将imageReader的surface作为CaptureRequest.Builder的目标
            mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
/*            // 设置自动对焦模式
            mBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 设置自动曝光模式
            mBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);*/
            //设置为自动模式
//            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);


            setFlashMode(CONTROL_AE_MODE);
            // 停止连续取景
            mCaptureSession.stopRepeating();
            // 捕获静态图像
            mCaptureSession.capture(mPreviewRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
                // 拍照完成时激发该方法
                @Override
                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
                    //重新打开预览
                    createCameraPreview();
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取设备方向
     *
     * @param characteristics
     * @param deviceOrientation
     * @return
     */
    private static int sensorToDeviceRotation(CameraCharacteristics characteristics, int deviceOrientation) {
        int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        deviceOrientation = ORIENTATIONS.get(deviceOrientation);
        return (sensorOrientation + deviceOrientation + 360) % 360;
    }


    /**
     * 获取可用设备可用摄像头列表
     */
    private void getCameraId(int ID) {
        try {
            for (String cameraId : mCameraManager.getCameraIdList()) {
                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
                if (characteristics.get(CameraCharacteristics.LENS_FACING) == ID) {
                    continue;
                }
                mCameraId = Integer.valueOf(cameraId);
                return;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 设置最佳尺寸
     *
     * @param sizes
     * @param width
     * @param height
     * @return
     */
    private Size getPreferredPreviewSize(Size[] sizes, int width, int height) {
        List collectorSizes = new ArrayList<>();
        for (Size option : sizes) {
            if (width > height) {
                if (option.getWidth() > width && option.getHeight() > height) {
                    collectorSizes.add(option);
                }
            } else {
                if (option.getHeight() > width && option.getWidth() > height) {
                    collectorSizes.add(option);
                }
            }
        }
        if (collectorSizes.size() > 0) {
            return Collections.min(collectorSizes, new Comparator() {
                @Override
                public int compare(Size s1, Size s2) {
                    return Long.signum(s1.getWidth() * s1.getHeight() - s2.getWidth() * s2.getHeight());
                }
            });
        }
        return sizes[0];
    }




    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera2);
        ButterKnife.bind(this);
        initView();
    }


    private void initView() {
        change.setOnCheckedChangeListener((buttonView, isChecked) -> {
            closeCamera();
            if (!isChecked) {
                //后置摄像头
                mCameraId = CameraCharacteristics.LENS_FACING_FRONT;
                if (texture.isAvailable()) {
                    openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
                } else {
                    texture.setSurfaceTextureListener(textureListener);
                }
            } else {
                //前置摄像头
                mCameraId = CameraCharacteristics.LENS_FACING_BACK;
                if (texture.isAvailable()) {
                    openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
                } else {
                    texture.setSurfaceTextureListener(textureListener);
                }
            }
        });


        flashRg.setOnCheckedChangeListener((group, checkedId) -> {
            switch (checkedId) {
                case R.id.auto:
                    //自动闪光灯
                    setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                    break;
                case R.id.open:
                    //开启闪光灯
                    setFlashMode(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
                    break;
                case R.id.close:
                    //关闭闪光灯
                    setFlashMode(CaptureRequest.CONTROL_AE_MODE_OFF);


                    break;
                default:
                    break;


            }
        });
        // 获取CameraManager 相机设备管理器
        mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    }


    /**
     * 初试化拍照线程
     */
    public void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("Camera Background");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }


    public void stopBackgroundThread() {
        if (mBackgroundThread != null) {
            mBackgroundThread.quitSafely();
            try {
                mBackgroundThread.join();
                mBackgroundThread = null;
                mBackgroundHandler = null;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * Closes the current {@link CameraDevice}.
     * 关闭正在使用的相机
     */
    private void closeCamera() {
        // 关闭捕获会话
        if (null != mCaptureSession) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        // 关闭当前相机
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        // 关闭拍照处理器
        if (null != mImageReader) {
            mImageReader.close();
            mImageReader = null;
        }
    }


    @Override
    protected void onResume() {
        super.onResume();
        if (texture.isAvailable()) {
            openCamera(texture.getWidth(), texture.getHeight(), mCameraId);
        } else {
            texture.setSurfaceTextureListener(textureListener);
        }
        startBackgroundThread();
    }


    @Override
    protected void onPause() {
        closeCamera();
        stopBackgroundThread();
        super.onPause();
    }


    @OnClick(R.id.takephoto)
    public void onViewClicked() {
        captureStillPicture();
    }


    /**
     * Shows a {@link Toast} on the UI thread.
     * 在UI上显示Toast的方法
     *
     * @param text The message to show
     */
    /**
     * Shows a {@link Toast} on the UI thread.
     * 在UI上显示Toast的方法
     *
     * @param text The message to show
     */
    private void showToast(final String text) {
        runOnUiThread(() -> Toast.makeText(Camera2Activity.this, text, Toast.LENGTH_SHORT).show());
    }


    /**
     * Saves a JPEG {@link Image} into the specified {@link File}.
     * 保存图片到自定目录
     * 保存jpeg到指定的文件夹下, 开启子线程执行保存操作
     */
    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();
                    }
                }
            }
        }


    }
}
具体代码请查看项目 demo地址

总结

本文主要总结使用Android Camera2 API 自定义相机,及实现闪光灯的打开,关闭,自动,前后摄像头的切换等功能的集成等)。
demo中包含了使用 Android Camera1/ Camera2  API 自定义相机及相关功能,同时也包含了使用 google框架 cameraView 自定义相机的 demo,欢迎大家浏览和Star!

笔者水平有限,如有任何疑问,请大神多多赐教!

感谢下面文章提供的参考

android camera2 详解说明(一)
android.hardware.camera2 使用指南
Android 用 camera2 API 自定义相机
谷歌官方demo

你可能感兴趣的:(Android)