Android Camera模块解析之拍照

《Android Camera架构》
《Android Camera进程间通信类总结》
《Android Camera模块解析之拍照》
《Android Camera模块解析之视频录制》
《Android Camera原理之CameraDeviceCallbacks回调模块》
《Android Camera原理之openCamera模块(一)》
《Android Camera原理之openCamera模块(二)》
《Android Camera原理之createCaptureSession模块》
《Android Camera原理之setRepeatingRequest与capture模块》
《Android Camera原理之编译》
《Android Camera原理之camera provider启动》
《Android Camera原理之cameraserver与cameraprovider是怎样联系的》
《Android Camera原理之camera service与camera provider session会话与capture request轮转》
《Android Camera原理之camera HAL底层数据结构与类总结》
《Android Camera原理之camera service类与接口关系》

最近学习Android的camera模块,本文先介绍一下camera2的api,然后给出android camera拍照的例子,讲解一下camera 拍照的原因知识,与大家共勉。

  • camera2 介绍
  • android camera拍照功能介绍

一、camera2 介绍



  • Camera api部分:
    frameworks/base/core/java/android/hardware/camera2
  • Camera JNI部分:
    frameworks/base/core/jni/android_hardware_Camera.cpp
    编译选项在目录下的Android.bp
    make libandroid_runtime -j1
  • Camera UI库部分:
    frameworks/av/camera/
    编译选项在目录下的Android.bp
    make libcamera_client -j1
  • Camera服务部分:
    frameworks/av/services/camera/libcameraservice/
    编译选项在目录下的Android.mk
    make libcameraservice -j1
  • Camera HAL部分:
    hardware/qcom/camera/


android.hardware.camera2开发包给开发者提供了一个操作相机的开发包,是api-21提供的,用于替代之前的Camera操作控类。该软件包将摄像机设备建模为管道,它接收捕获单个帧的输入请求,根据请求捕获单个图像,然后输出一个捕获结果元数据包,以及一组请求的输出图像缓冲区。请求按顺序处理,多个请求可以立即进行。由于相机设备是具有多个阶段的管道,因此需要在移动中处理多个捕捉请求以便在大多数Android设备上保持完全帧率。

  • TextureView可用于显示内容流。这样的内容流可以例如是视频或OpenGL场景。内容流可以来自应用程序的进程以及远程进程。
  • TextureView只能在硬件加速窗口中使用。在软件中渲染时,TextureView将不会绘制任何内容。
  • TextureView不会创建单独的窗口,但表现为常规视图。这一关键差异允许TextureView移动,转换和使用动画
  • 之后,应用程序需要构建CaptureRequest,在捕获单个图片的时候,这些request请求需要定义一些请求的参数。
  • 一旦设置了请求,就可以将其传递到活动捕获会话,以进行一次捕获或无休止地重复使用。两种方法还具有接受用作突发捕获/重复突发的请求列表的变体。重复请求的优先级低于捕获,因此在配置重复请求时通过capture()提交的请求将在当前重复(突发)捕获的任何新实例开始捕获之前捕获。
  • 处理完请求后,摄像机设备将生成一个TotalCaptureResult
    对象,该对象包含有关捕获时摄像机设备状态的信息以及使用的最终设置。如果需要舍入或解决相互矛盾的参数,这些可能与请求有所不同。相机设备还将一帧图像数据发送到请求中包括的每个输出表面。这些是相对于输出CaptureResult异步生成的,有时基本上稍晚。

根据camera2的工作示意图,画出下面的camera类关系图。下面使用camera功能的时候会详细介绍一下camera2 的api的主要功能。


camera2 api关系图.jpg

二、android camera拍照功能介绍

2.1 设置camera preview预览页面

我们打开android camera一般会出现一个预览页面,通过这个预览页面,我么调用摄像头,将前方的景放在这个预览界面中,然后移动camera,预览界面会随之出现变化,实现这个预览界面的view,一般有两种选择,SurfaceView或者TextureView,上面我们也介绍了两种view之间的区别,本文我们选择TextureView,因为TextureView设置动画比较方便,我们移动或者旋转手机的时候,TextureView也应用相应的旋转,这样符合用户的体验。
可以通过设置TextureView.SurfaceTextureListener来对TextureView代表的surface监听。

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

    };

几个监听事件监听当前surface的状态。onSurfaceTextureAvailable表示当前的surface状态可用,onSurfaceTextureSizeChanged表示当前的surface大小正在调整。

2.2 打开相机

    private void openCamera(int width, int height) {
        if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            requestCameraPermission();
            return;
        }
        setUpCameraOutputs(width, height);
        configureTransform(width, height);
        Activity activity = getActivity();
        CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
        try {
            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Time out waiting to lock camera opening.");
            }
            manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
        }
    }
  • 检查当前camera权限。
  • 设置相机当前属性和输出图片等等。
  • 设置TextureView 选装和移动的动画属性等等。
  • 使用CameraManager实例打开相机

2.3 相机打开设置回调

在执行manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);方法的时候,传入了三个参数:

  • mCameraId表示当前摄像头的标识,我们手机中有好多个摄像头,最新版的Mate20手机有3个后置摄像头和1个前置摄像,可以通过manager.getCameraIdList()来获取当前的cameraId集合。
  • StateCallback是CameraDevice.StateCallback,这是表示相机设备当前状态的回调。
    下面StateCallback的众多回调表示当前相机的状态,相机如果打开的话应该执行什么操作,相机如果断开连接的话应该执行什么操作等等。
  • 传入的Handler处理camera当前的消息。
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            // This method is called when the camera is opened.  We start camera preview here.
            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;
            Activity activity = getActivity();
            if (null != activity) {
                activity.finish();
            }
        }

    };

mCameraOpenCloseLock是一个信号量,一旦相机在openCamera到真正打开这段时间,相机必须被独占,其他线程不能介入处理,不然会出现线程错乱。一旦相机呈现出下一个状态,就可以释放这个信号量了。

private Semaphore mCameraOpenCloseLock = new Semaphore(1);

2.4 创建camera预览session

    private void createCameraPreviewSession() {
        try {
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            assert texture != null;

            // We configure the size of default buffer to be the size of camera preview we want.
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

            // This is the output Surface we need to start preview.
            Surface surface = new Surface(texture);

            // We set up a CaptureRequest.Builder with the output Surface.
            mPreviewRequestBuilder
                    = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);

            // Here, we create a CameraCaptureSession for camera preview.
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                    new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            // The camera is already closed
                            if (null == mCameraDevice) {
                                return;
                            }

                            // When the session is ready, we start displaying the preview.
                            mCaptureSession = cameraCaptureSession;
                            try {
                                // Auto focus should be continuous for camera preview.
                                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                                // Flash is automatically enabled when necessary.
                                setAutoFlash(mPreviewRequestBuilder);

                                // Finally, we start displaying the camera preview.
                                mPreviewRequest = mPreviewRequestBuilder.build();
                                mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                        mCaptureCallback, mBackgroundHandler);
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void onConfigureFailed(
                                @NonNull CameraCaptureSession cameraCaptureSession) {
                            showToast("Failed");
                        }
                    }, null
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
  • 设置SurfaceTexture缓存大小。
  • 通过mCameraDevice.createCaptureRequest获取CaptureResult.Builder对象,这个对象要和之前的Surface绑定,表示当前的capture请求是输出到这个surface上的。
  • mCameraDevice.createCaptureSession 创建capture session,在CameraCaptureSession.StateCallback的onConfigured回调函数中,就是对当前camera移动的回调,一旦移动,会触发这个回调,然后在回调中作出相应的改变。

2.5 拍照

    private void takePicture() {
        lockFocus();
    }

    /**
     * Lock the focus as the first step for a still image capture.
     */
    private void lockFocus() {
        try {
            // This is how to tell the camera to lock focus.
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                    CameraMetadata.CONTROL_AF_TRIGGER_START);
            // Tell #mCaptureCallback to wait for the lock.
            mState = STATE_WAITING_LOCK;
            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                    mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

Capture image的过程主要在 mCaptureSession.capture函数中,下面会从这个函数着手阐释一下camera capture的原理。

2.6 关闭相机

    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 camera closing.", e);
        } finally {
            mCameraOpenCloseLock.release();
        }
    }

主要释放camera相关的资源,手机中camera的资源具有独占性,如果在用完了不释放的话,会造成别人使用的时候camera被占用,session不关闭的,会造成严重的内存泄露。
大家也可以参考具体的源码https://github.com/googlesamples/android-Camera2Basic/

推荐阅读更多精彩内容