Camera Api2 预览,拍照,录像

1引言

1.1编写目的

Camera2 主要类的介绍,预览,拍照,录像的流程介绍。

1.2适用范围

Camera API2的使用。

2 主要类的介绍

  1. CameraManager是一个用于检测、连接和描述相机设备的系统服务,负责管理所有的CameraDevice相机设备。
    可以通过调用Context.getSystemService(java.lang.String)方法来获取一个CameraManager的实例:
    CameraManager manager=(CameraManager)getSystemService(Context.CAMERA_SERVICE);


  1. CameraDevice连接到 Android 设备的单个摄像头的表示,描述一个照相机设备,
    一个 Android 设备可能会有多个摄像头,通过 CameraId 可以进行区别,当相机
    状态回调函数执行的时候,可以从回调中拿到当前 CameraDevice,并通过
    CameraDevice 对当前相机进行一些列操作。

  2. CameraCharacteristics 是描述相机设备的属性类,其中的属性都是固定的,继承自 CameraMetadata 类。类比于旧 API 中的 CameraInfo 类。

包括:曝光补偿(Exposure compensation)、自动曝光/自动对焦/自动白平衡模式(AE / AF / AWB mode)、自动曝光/自动白平衡锁(AE / AWB lock)、自动对焦触发器(AF trigger)、拍摄前自动曝光触发器(Precapture AE trigger)、测量区域(Metering regions)、闪光灯触发器(Flash trigger)、曝光时间(Exposure time)、感光度(ISO Sensitivity)、帧间隔(Frame duration)、镜头对焦距离(Lens focus distance)、色彩校正矩阵(Color correction matrix)、JPEG 元数据(JPEG metadata)、色调映射曲线(Tonemap curve)、裁剪区域(Crop region)、目标 FPS 范围(Target FPS range)、拍摄意图(Capture intent)、硬件视频防抖(Video stabilization)等。

通过 CameraManager 的 getCameraCharacteristics(String cameraId) 方法获取指定相机设备的 CameraCharacteristics 对象。

  1. CameraCaptureSession 是一个事务,用来向相机设备发送获取图像的请求。

主要有 setRepeatingRequest()capture() 方法。
setRepeatingRequest() 是重复请求获取图像数据,常用于预览或连拍,capture() 是获取一次,常用于单张拍照。

CameraCaptureSession 类是一个抽象类.

  1. CameraRequest 代表了一次捕获请求,从相机设备捕获单个图像所需的不可变的设置和输出包,当程序调用 setRepeatingRequest()方法进行预览时,或调用 capture()方法进行拍照时,都需要传入 CameraRequest 参数。
    CameraRequest.Builder 则负责生成CameraRequest 对象

如:预览前设置自动对焦
CaptureRequestBuilder.set(CaptureRequest.CONTORL_AF_MODE,CaptureRequest.CONTROL_AF_MOODE_CONTINUOUS_PICTURE);

CaptureRequest = CaptureRequestBuilder.build();
CameraCaptureSession.setRepeatingRequest(CaptureRequest,CaptureCallback,Handler);

  1. CaptureResult
    CaptureRequest描述是从图像传感器捕获单个图像的结果的子集的对象。(CaptureResults are produced by a CameraDevice after processing a CaptureRequest)当CaptureRequest被处理之后由CameraDevice生成。

  2. CameraMetadata Camera API2/HAL3架构下使用了全新的CameraMetadata结构取代了之前的SetParameter/Paramters等操作,实现了Java到native到HAL3的参数传递。

3 app 层的主要流程

3.1 预览的流程

3.1.1 先上图,看图讲故事

图1 预览流程图

3.1.2 预览流程说明

  1. 获取CameraManager
//获取摄像头的管理者CameraManager
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  1. openCamera 打开相应ID的相机

·String CameraId:传入要打开的摄像头的 Id
·CameraDevice.stateCallback:相机的状态回调接口
·Handler handler:用来确定Callback在哪个线程执行,为null的话就在当前线程执行

    public void openCamera( String cameraId,
            final CameraDevice.StateCallback callback,  Handler handler)
            throws CameraAccessException {
        openCameraForUid(cameraId, callback,
                CameraDeviceImpl.checkAndWrapHandler(handler), USE_CALLING_UID);
    }
  1. 实例化 CameraDevice.statCallback
    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            mCameraDevice = camera;
            //开启预览
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {

        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {

        }
    };
  1. 生成CaptureRequest.Builder
    private CaptureRequest.Builder mPreviewRequestBuilder;

    // 创建预览请求的Builder(TEMPLATE_PREVIEW表示预览请求)
    private void getPreviewRequestBuilder() {
        try {
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        //设置预览的显示界面,即将显示预览用的 surface 的实例,作为一个显示层添加到该请求的目标列表中.
        mPreviewRequestBuilder.addTarget(mPreviewSurface);
        MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_REGIONS);
        if (meteringRectangles != null && meteringRectangles.length > 0) {
            CamLog.d("PreviewRequestBuilder: AF_REGIONS=" + meteringRectangles[0].getRect().toString());
        }
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
    }


  1. mCameraDevice.createCaptureSession 创建CaptureSession

·List:捕获数据的输出Surface列表,此处传入用于显
示预览图像的 Surface 即可,即 Arrays.asList(surface),
·CameraCaptureSession.stateCallback:CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法.
·Handler:第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行

public abstract void createCaptureSession( List outputs,
             CameraCaptureSession.StateCallback callback,  Handler handler)
            throws CameraAccessException;
  1. CameraCaptureSession.StateCallback 回调

new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(CameraCaptureSession session) {
                            //该方法会从底层传回一个CameraCaptureSession,该 session 可以开始处理捕获请求,
                            mCaptureSession = session;
                            repeatPreview();
                        }

                        @Override
                        public void onConfigureFailed(CameraCaptureSession session) {

                        }
                    }

  1. CaptureRequest和mCaptureSession.setRepeatingRequest
private void repeatPreview() {
    mPreviewRequestBuilder.setTag(TAG_PREVIEW);
    mPreviewRequest = mPreviewRequestBuilder.build();
    //设置反复捕获数据的请求,这样预览界面就会一直有数据显示
    try {
        mCaptureSession.setRepeatingRequest(mPreviewRequest,mPreviewCaptureCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}



·CaptureReqeust:无限重复的请求,
·CameraCaptureSession.CaptureCallback:即 callback 的回调,不过预览中
该回调中不用进行任何处理
·Handler:第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行

public abstract int setRepeatingRequest( CaptureRequest request,
             CaptureCallback listener,  Handler handler)
            throws CameraAccessException;

3.2 拍照的流程

3.2.1 先上图,看图讲故事

图2 预览和拍照流程图

3.2.2 拍照流程说明

  1. ImageReader 的使用

点击拍照,使用 ImageReader 访问呈现到 Surface 中的图像,并进行保存。在预览的 Surface 捕获图像的同时,我们也需要 ImageReader 来同时捕获图像数据,所以在预览的第五步中,CameraDevice.CreateCaptureSession()方法中,我们将 ImageReader 的实例也传入第一个参数中,即 Arrays.asList(surface,ImageReader.getSurface())


    private ImageReader mImageReader;

    private void setupImageReader() {
        //前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据
        mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
                ImageFormat.JPEG, 1);
        //监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = reader.acquireLatestImage();
                // 开启线程异步保存图片
                new Thread(new ImageSaver(image)).start();
            }
        }, null);
    }


public static class ImageSaver implements Runnable {
        private Image mImage;
        private File mImageFile;

        public ImageSaver(Image image) {
            mImage = image;
        }

        @Override
        public void run() {
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            mImageFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg");
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(mImageFile);
                fos.write(data, 0, data.length);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImageFile = null;
                if (fos != null) {
                    try {
                        fos.close();
                        fos = null;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }




    //预览第五步的代码
    mCameraDevice.createCaptureSession(
            Arrays.asList(mPreviewSurface, mImageReader.getSurface()),
            new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession session) {
                            mCaptureSession = session;
                            repeatPreview();
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession session) {

                }
            }, null);

  1. 生成CaptureRequest
    //首先我们创建请求拍照的CaptureRequest
    final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);

    mCaptureBuilder.addTarget(mPreviewSurface);
    mCaptureBuilder.addTarget(mImageReader.getSurface());

    //获取屏幕方向
    int rotation = getWindowManager().getDefaultDisplay().getRotation();
    //设置拍照方向
    mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));

    CaptureRequest captureRequest = mCaptureBuilder.build();
  1. CameraCaputureSession.capture()方法进行拍照

· CaptureRequest:此次拍照的参数设置
· CaptureCallback:callback 对象,当这个请求被处理的时候触发,如果
为 null,不会生成 matedate 信息,但是仍会生成图像信息
· Handler:为一个句柄,代表执行 callback 的 handler,如果程序希望直
接在当前线程中执行 callback,则可以将 handler 参数设为 null


public abstract int capture( CaptureRequest request,
            CaptureCallback listener,  Handler handler)
            throws CameraAccessException;

  1. CameraCaptureSession.CaptureCallback

//开始拍照,然后回调上面的接口重启预览,
// 因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                               @NonNull CaptureRequest request,
                               @NonNull TotalCaptureResult result) {
                    //恢复预览状态
                    repeatPreview();
                }
            };

3.3 录像的流程

3.3.1 先上图,看图讲故事

图3 预览、拍照、录像流程图

3.3.2 录像流程说明

  1. MediaRecorder 的使用

MediaRecorder是安卓提供的一个用于音视频采集的类.



        mMediaRecorder = new MediaRecorder();

        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        String mNextVideoAbsolutePath = Environment.getExternalStorageDirectory() + "/DCIM/videoBack.mp4";
        mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
        mMediaRecorder.setVideoEncodingBitRate(10000000);
        mMediaRecorder.setVideoFrameRate(30);
        mMediaRecorder.setVideoSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        mMediaRecorder.setOrientationHint(90);

        try {
            mMediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }

  1. CaptureRequest.Builder的生成和添加Target

将 MedioRecorder 和预览用的 Surface 的实例,添加到该请求的目标列表中。


    mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
    List surfaces = new ArrayList<>();
    SurfaceTexture texture = mTextureView.getSurfaceTexture();
    assert texture != null;
    texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

    // Set up Surface for the camera preview
    Surface previewSurface = new Surface(texture);
    surfaces.add(previewSurface);
    mPreviewRequestBuilder.addTarget(previewSurface);

    // Set up Surface for the MediaRecorder
    Surface recorderSurface = mMediaRecorder.getSurface();
    surfaces.add(recorderSurface);
    mPreviewRequestBuilder.addTarget(recorderSurface);

  1. 将预览时创建的 session,close 掉

    private void closePreviewSession() {
        if (mCaptureSession != null) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
    }

  1. createCaptureSession
·List:新的用于捕获图像信息的 Surface 集合,此处为显示预览
信息的 surface 实例,以及记录图像信息用的 MediaRecorder 的实例
·CameraCaptureSession.StateCallback:用于通知新捕获 session 的
callback
·Handler:为一个句柄,代表执行 callback 的 handler,如果程序希望直
接在当前线程中执行 callback,则可以将 handler 参数设为 null


public abstract void createCaptureSession( List outputs,
            CameraCaptureSession.StateCallback callback, 
            Handler handler)
            throws CameraAccessException;

  1. CameraCaptureSession.StateCallback

new CameraCaptureSession.StateCallback()
{

        @Override
        public void onConfigured (@NonNull CameraCaptureSession session){

        mCaptureSession = session;
        try {
            //调用CaptureRequestBuilder.set()方法,设置捕获的参数,此处设置3A算法
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
            CaptureRequest request = mPreviewRequestBuilder.build();

            //调用CameraCaptureSession.setRepeatingReqest()方法,
            //通过此捕获session,持续重复捕获图像
            mCaptureSession.setRepeatingRequest(request, null, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // Start recording
                mMediaRecorder.start();
            }
        });
    }

        @Override
        public void onConfigureFailed (@NonNull CameraCaptureSession session){
    }
}
  1. MediaRecorder 停止录制

    public void videoStop(View view) {
        mMediaRecorder.stop();
        mMediaRecorder.reset();

        Toast.makeText(this, "Video saved: 保存",
                Toast.LENGTH_SHORT).show();
        //重新开启预览
        startPreview();
    }

参考文章

  1. https://blog.csdn.net/qq_33529867/article/details/103484791
  2. https://www.cnblogs.com/renhui/p/8718758.html
  3. 官方Demo

你可能感兴趣的:(Camera Api2 预览,拍照,录像)