Android Camera2

1.Camera2

Android API 21新增了Camera2,与之前的Camera架构完全不同,使用起来也比较复杂,但是功能变得很强大。

Camera2中几个比较重要的类:

①CameraManager: 管理手机上的所有摄像头设备。它其实是一个系统服务,通过getSystemService(Context.CAMERA_SERVICE)获取,它的作用主要是获取摄像头列表和打开指定的摄像头。

通过CameraManager对象可以得到一些相机的基本信息,这个信息就存储在CameraCharacteristic对象中。获取到相机的一些基本信息:

CameraCharacteristics cameraInfo = mCameraManager.getCameraCharacteristics(cameraId);

除了获取Camera的属性信息外,CameraManager对象最重要的作用就是打开相机(openCamera),通过CameraManager才能真正的拿到CameraDevice对象去操作相机:

mCameraManager.openCamera(cameraId, mCameraDeviceStateCallback, null);

cameraId:需要打开的摄像头的id

mCameraDeviceStateCallback:一个CameraDevice的状态回调类,在这个类回调方法里,会告知Camera设备的打开状态,成功还是失败

Handler:openCamera的操作在哪个线程执行,null即为在主线程执行

②CameraDevice:表示一个打开的相机设备,在CameraDevice.StateCallback的onOpened回调里返回此实例对象。它的作用主要是创建CameraCaptureSession和CaptureRequest。

有了CameraDevice对象,就可以创建上层与Camera设备之间的一个会话,在Camera2中即为CameraCaptureSession:

mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface,mImagerReader.getSurface()),mCaptureSessionStateCallback,null);

第一个参数List outputs:Arrays.asList( mPreviewSurface,mMediaRecorder.getSurface()):代表要配置的流的数量(这里是一个预览流和拍照流)。

第二个参数mCaptureSessionStateCallback:CameraCaptureSession创建的对象的状态回调

第三个参数Handler:createCaptureSession的操作在哪个线程执行,null即为在主线程执行

③CameraCaptureSession: 相机捕获会话,代表上层与底层之间的一个会话,通过这个会话可以下发指令给相机,让相机执行预览、拍照录像等操作,在mCameraDevice.createCaptureSession的回调中获得。主要作用是用于处理拍照和预览的工作(很重要)。

想从相机设备中获取Image,首先要创建一个CameraCaptureSession把接收数据的载体传给相机设备,目前能接收相机数据的载体是Surface和SurfaceTexture。

通过情况下,相机的预览数据可以使用SurfaceView和TextureView接收。拍照使用ImageReader、录视频使用MediaCodec或MediaRecorder。

④CaptureRequest和CaptureResult

CaptureRequest代表相机捕获请求,当CaptureCaptureSession创建好之后,就可以使用这个会话来下发指令,表明当前是需要预览、拍照、还是录像。CaptureRequest对象可以携带设置的参数,比如是否自动对焦、自动曝光、自动白平衡等。CaptureRequest对象的创建使用了建造者模式,需要通过CaptureRequest.Builder来创建,通过调用build方法:

PreviewRequest = mCaptureRequestBuilder.build();

mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//自动连续对焦

mCaptureRequestBuilder.addTarget(mPreviewSurface); //预览流,底层回来的数据放在哪里

mCaptureRequestBuilder.addTarget(mImageReader.getSurface());拍照流

PreviewRequest = mCaptureRequestBuilder.build();

//这里是下发预览,如果是拍照可以调用capture()方法

mCameraCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureSessionCaptureCallback, null);

CaptureRequest定义了输出缓冲区以及显示界面(TextureView或SurfaceView)等。

CaptureResut代表捕获请求返回的一些结果信息,从里面可以获取一些Metadata数据信息。

 

2.Camera2实现预览和拍照

使用Camera2进行预览和拍照的主要流程:

Android Camera2_第1张图片

 ①设置TextureView监听

在布局文件中加入TextureView控件作为预览界面,然后实现其监听事件:

textureView.setSurfaceTextureListener(textureListener);

TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {

    @Override

    public void onSurfaceTextureAvailable( SurfaceTexture surface, int width, int height) {

        //当SurefaceTexture可用的时候,设置相机参数并打开相机

        setupCamera(width, height);

        openCamera();

    }

    @Override

    public void onSurfaceTextureSizeChanged( SurfaceTexture surface, int i, int i1) {

    }

    @Override

    public void onSurfaceTextureDestroyed( SurfaceTexture surface) {

    }

    @Override

    public void onSurfaceTextureUpdated( SurfaceTexture surface) {

    }

};

当SurefaceTexture准备好后会回调listener的onSurfaceTextureAvailable()方法,在该方法中设置设置相机参数并打开相机。

②设置相机参数

Camera2中使用CameraManager来管理摄像头。为了更好地预览,根据TextureView的尺寸设置预览尺寸。

private void setupCamera(int width, int height) {

    //获取摄像头的管理者CameraManager

    CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

    try {

        //遍历所有摄像头

        for (String cameraId: manager.getCameraIdList()) {

            CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);

            //默认打开后置摄像头

            if (characteristics.get( CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)

                continue;

            //获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸

            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

            //根据TextureView的尺寸设置预览尺寸

            mPreviewSize = getOptimalSize( map.getOutputSizes(SurfaceTexture.class), width, height);

            mCameraId = cameraId;

            break;

        }

    } catch (CameraAccessException e) {

        e.printStackTrace();

    }

}

    /** 选择sizeMap中大于并且最接近width和height的size **/

    private Size getOptimalSize(Size[] sizeMap, int width, int height) {

        List sizeList = new ArrayList<>();

        for (Size option : sizeMap) {

            if (width > height) {

                if (option.getWidth() > width && option.getHeight() > height) {

                    sizeList.add(option);

                }

            } else {

                if (option.getWidth() > height && option.getHeight() > width) {

                    sizeList.add(option);

                }

            }

        }

        if (sizeList.size() > 0) {

            return Collections.min(sizeList, new Comparator() {

                @Override

                public int compare(Size lhs, Size rhs) {

                    return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight());

                }

            });

        }

        return sizeMap[0];

    }

③开启相机

Camera2中打开相机也需要通过CameraManager类:

private void openCamera() {

    //获取相机的管理者CameraManager

    CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

    //检查权限

    try {

        if (checkSelfPermission( Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {

            //打开相机,第一个参数表示打开哪个摄像头,第二个参数stateCallback为相机的状态回调接口,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行

            manager.openCamera(mCameraId, stateCallback, null);

        } else {

            requestPermissions(new String[]{ Manifest.permission.CAMERA}, 101);

        }

    } catch (CameraAccessException e) {

        e.printStackTrace();

    }

}

实现StateCallback接口,当相机打开后会回调onOpened方法,在该方法里面开启预览:

private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {

    @Override

    public void onOpened(CameraDevice camera){

        mCameraDevice = camera;

        startPreview(); //开启预览

    }

}

④开启相机预览

使用TextureView显示相机预览数据,Camera2的预览和拍照数据都是使用CameraCaptureSession会话来请求的:

private void startPreview() {

    setupImageReader(); //用来实现拍照功能

    SurfaceTexture mSurfaceTexture = textureView.getSurfaceTexture();

    //设置TextureView的缓冲区大小

    mSurfaceTexture.setDefaultBufferSize( mPreviewSize.getWidth(), mPreviewSize.getHeight());

    //获取Surface显示预览数据

    mPreviewSurface = new Surface( mSurfaceTexture);

    try {

        getPreviewRequestBuilder(); //创建预览请求的builder

        //创建相机捕获会话CameraCaptureSession 该对象负责管理处理预览请求和拍照请求。第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行

        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);

    } catch (CameraAccessException e) {

        e.printStackTrace();

    }

}

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) {

            Log.i(TAG, "Image Available!");

            Image image = reader.acquireLatestImage(); //注意:一定要调用reader.acquireLatestImage()和close()方法,否则预览画面就会卡住(close在线程里用完之后调用的)

            // 开启线程异步保存图片

            new Thread(new ImageSaver(image) ).start();

        }

    }, null);

}

/**创建预览请求的Builder(TEMPLATE_PREVIEW表示预览请求)**/

private void getPreviewRequestBuilder() {

    try {

        mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

    } catch (CameraAccessException e) {

        e.printStackTrace();

    }

    //设置预览的显示界面

    mPreviewRequestBuilder.addTarget( mPreviewSurface);

    MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_REGIONS);

    if (meteringRectangles != null && meteringRectangles.length > 0) {

        Log.d(TAG, "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);

}

private void repeatPreview() {

    mPreviewRequestBuilder.setTag( TAG_PREVIEW);

    mPreviewRequest = mPreviewRequestBuilder.build();

    //设置反复捕获数据的请求,这样预览界面就会一直有数据显示

    try {

        mCaptureSession.setRepeatingRequest( mPreviewRequest, mPreviewCaptureCallback, null);

    } catch (CameraAccessException e) {

        e.printStackTrace();

    }

}

⑤实现PreviewCallback

Camera2没有提供Camera中的PreviewCallback,但Camera2中提供了CameraCaptureSession.CaptureCallback用于实现预览帧数据:

private CameraCaptureSession.CaptureCallback mPreviewCaptureCallback = new CameraCaptureSession.CaptureCallback() {

    @Override

    public void onCaptureCompleted( CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {

    }

    @Override

    public void onCaptureProgressed( CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) {

    }

};

到此为止,就已经实现了预览流的功能。接下来继续看拍照功能的实现。

⑥实现拍照操作

Camera2拍照是通过ImageReader来实现的,首先先做些准备工作,比如设置拍照参数,如方向、尺寸等。

private static final SparseIntArray ORIENTATION = new SparseIntArray();

static {

    ORIENTATION.append(Surface.ROTATION_0, 90);

  ORIENTATION.append( Surface.ROTATION_90,0);

    ORIENTATION.append( Surface.ROTATION_180, 270);

    ORIENTATION.append( Surface.ROTATION_270, 180);

}

在上面的setupImageReaer()方法中已经创建了ImageReader对象,并设置了它的监听事件。

注意:一定要调用reader.acquireLatestImage()和close()方法,否则预览画面就会卡住。

最后,创建保存图片的线程:

public static class ImageSaver implements Runnable {

    private Image mImage;

    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;

            mImage.close(); //必须close,否则连续拍会崩溃

            if (fos != null) {

                try {

                    fos.close();

                    fos = null;

                } catch (IOException e) {

                        e.printStackTrace();

                }

            }

        }

    }

}

现在准备工作做好了,还需要响应点击拍照事件,设置点击拍照按钮调用capture()方法,capture()方法即实现拍照:

private void capture() {

    try {

        //首先创建请求拍照的CaptureRequest

        final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);

        int rotation = getWindowManager().getDe faultDisplay().getRotation(); //获取屏幕方向

        mCaptureBuilder.addTarget( mPreviewSurface);

        mCaptureBuilder.addTarget( mImageReader.getSurface());

        mCaptureBuilder.set( CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation)); //设置拍照方向

        //停止预览

        mCaptureSession.stopRepeating();

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

        CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

            @Override

            public void onCaptureCompleted( CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {

                repeatPreview();

            }

        };

        mCaptureSession.capture( mCaptureBuilder.build(), captureCallback, null);

    } catch (CameraAccessException e) {

        e.printStackTrace();

    }

}

 

你可能感兴趣的:(android)