Android Camera2

Android Camera2: 输出预览画面到SurfaceT

2018-08-18  本文已影响162人  NVision

项目地址:https://github.com/wangcan26/CameraCVGLView

简介

Android 官方在Android Api21也就是Android 5.0系统之后推出了Camera2摄像机类包, 该类包正式取代了原先的Camera类。相比于Camera,我认为Camera2有两方面明显的优势:

  1. Camera2提供了更加强大的对画面处理的功能,包括预处理, 预览,保存,后处理,录制等。
    Camera Api对摄像头画面的处理仅限预览,保存和录制,并且,其这些处理没有一种统一的模式, 如对预览的处理,是通过setPreviewDisplay或者setPreviewTexture,对预览画面的保存通过tackPicture[参考1]和PreviewCallback[参考2], 而对录制功能的处理,需要借助MediaRecoder的setCamera来完成[参考3]。
    Camera2对画面处理,都是通过addTarget的方式来完成的, 然后根据不同处理方式选择创建不同的任务请求(CameraRequest)。这样Camera2不仅可以通过添加 SurfaceHolder和SurfaceTexture的Surface来完成摄像头的预览,也可以通过添加ImageReader的Surface来完成对预览画面的保存,以及通过添加MediaCodec的Surface来完成录制,甚至通过添加Allocation的Surface来完成对预览画面的处理[参考4]。
    从上述分析也可以看出, Camera2的Api相比于Camera的 Api,更加松散耦合。
  2. Camera2 提供了对摄像头设备属性更多的控制,如曝光的时长,白平衡的范围等,当然有些属性需要手机设备的支持[参考5]。
    因此,Android对于Camera Api的废弃是必然,接下来本文以及本文相关的一系列文章都是对Camera2 Api的使用进行经验介绍。

目的

目前,Camera2 Api可以通过多种方式对摄像头的预览画面进行显示:

  1. 通过SurfaceView的SurfaceHolder直接显示[参考6], 该方法对于只是显示预览画面的功能足够了,但却不能满足对预览画面进一步的直接处理;
  2. 通过TextureView的SurfaceTexture来显示[参考7], 这种方法是google官方给出的sample, 该方法实现了显示的目的,同时可以将画面交给TextureView进一步做处理,但是实际应用中,使用TextureView 的例子并不多。
  3. 通过SurfaceTexture和OpenGL 渲染来完成对画面的显示。

上述第三种方法,能满足通过渲染进一步处理预览画面的需求,但是网上能看到实用的实现并不多, 这种不实用体现在不能很好的匹配Android生命周期流程(不同手机在Home键退出再进,锁屏等机制的差异化)。

本文针对第三种方法,提出了一种通过SurfaceView和SurfaceTexture组合的方法,并将实现提交到了GitHub[项目地址]以供大家参考。

流程

本文关于摄像头画面显示的java层全部封装在CameraRenderView.java类中, 关于实现的流程主要遵照Activity的生命周期和SurfaceHolder.Callback的生命周期, 如下所示:

Activity生命周期

public class MainActivity extends AppCompatActivity {

    private CameraRenderView mCameraView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        PermissionHelper.requestCameraPermission(this, true);


        setContentView(R.layout.activity_main);

        mCameraView = (CameraRenderView) findViewById(R.id.camera_view);
        mCameraView.init(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if(mCameraView != null)
        {
            mCameraView.onResume();
        }
    }

    @Override
    protected void onPause() {
        if(mCameraView != null)
        {
            mCameraView.onPause();
        }
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mCameraView != null){
            mCameraView.deinit();
        }
    }
}

以上,是响应Android Activity的生命周期方法, 注意在onCreate方法里,我们需要手动对Camera权限和读写权限进行申请, 另外关于权限的申请还需要在AndroidManifest.xml里进行声明,详情请参考[项目地址]。

SurfaceView.Callback

CameraRenderView类是基于SurfaceView的定制类, 一般我们使用SurfaceView最多的情况是利用其SurfaceHolder所提供的Surface创建一个OpenGL渲染环境(一个创建了EGLContext和EGLSurface的线程),关于这种使用用例,可参考GLSurfaceView.java的实现。

创建OpenGL渲染环境

public void init(Activity activity)
    {
        mWeakActivity = new WeakReference<>(activity);
        mSurfaceHolder = this.getHolder();
        mSurfaceHolder.addCallback(this);
        mSurfaceHolder.setKeepScreenOn(true);
        mIsSurfaceAvailable = false;
        //Create a App
        nativeCreateApp();
    }

在该方法里,
首先,需要添加一个SurfaceHolder的回调,这样才能保证Callback方法被执行到
然后,创建一个OpenGL运行的线程。由于OpenGL涉及到渲染,所以其整个实现,本例全部在native层实现。 nativeCreateApp创建一个OpenGL线程。

public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
        mWidth = width;
        mHeight = height;

        //This method may block the ui thread until the gl context and surface texture id created
        nativeSetSurface(surfaceHolder.getSurface());
        ...
       mIsSurfaceAvailable = true;
    }

surfaceChanged和surfaceCreate其实并没有本质的区别,唯一的区别就是当窗口大小改变时,就会调用surfaceChanged一次,所以,一般我们只在surfaceChanged里做创建的工作,另外用一个变量来纪录是否是第一次。

@Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        nativeSetSurface(null);
       ...
      mIsSurfaceAvailable = false;
    }

surfaceDestroyed在每次Activity 退到后台的时候会执行到, surfaceCreate和surfaceChanged会在Activity进入时被执行到。

nativeSetSurface根据传入surface的有无来判断是否在OpenGL线程里创建OpenGL环境。

关于OpenGL线程的销毁,我们放在deinit方法里去做。

Camera2预览实现

Camera2预览的大体流程如下:
首先, 我们需要通过CameraManager类来打开摄像头设备, 其结果通过CameraDevice.StateCallback告知用户
然后,通过摄像头设备创建一个CaptureRequest和CaptureSession, CaptureSession创建成功与否是通过CaptureSession.StateCallback告知
最后,通过创建的CaptureSession的setRepeatingRequest来完成预览

关于Camera2预览的流程可以参考Android-Camera-Archnitecture的第22页。

由于创建Session是一个特别耗时的工作[参考8],因此我们需要将创建Session的任务放在另外一个线程去做,并从CameraDevice.StateCallback开始就需要由该线程来处理。 这里,我们需要将线程的创建和Camera的打开以及它们的销毁都在SurfaceHolder.Callback方法里完成(不然会导致Activity切换前后台卡顿的现象)。

public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
     ....
        configureCamera(width, height);
        //Only First time we open the camera 
        if(!mIsSurfaceAvailable)
        {
            if(mCameraId != null)
            {
                startCameraSessionThread();
                openCamera();
            }
        }
        ...
    }

我们需要选择打开摄像头的id, 是前置摄像头还是后置摄像头,以及为预览选择一个合适的大小

private void configureCamera(int width, int height)
    {
        //Assume it is a face back camera
        mCameraId = CAMERA_FACE_BACK;
        ///Configure camera output surfaces
        setupCameraOutputs(mWidth, mHeight);
    }

通过CameraManager 打开摄像头之前需要确认摄像头权限, 在CameraSession线程中通过mCamSessionHandler处理CameraDevice.StateCallback

private void openCamera()
    {
        if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            PermissionHelper.requestCameraPermission(getActivity(), true);
            return;
        }
      ...
        try{

            mCamManager.openCamera(mCameraId, mCameraDeviceCallback, mCamSessionHandler);
        }catch (CameraAccessException e)
        {
            e.printStackTrace();
        }
    }

如果摄像头打开,那么开始创建CaptureSession

private CameraDevice.StateCallback mCameraDeviceCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            ...
            try{
                createCameraPreviewSession();
            }catch (CameraAccessException e)
            {
                e.printStackTrace();
            }
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
           ...
        }
        ...
    };
private void createCameraPreviewSession() throws CameraAccessException
    {
        try{

            mSurface = getPreviewSurface(mPreviewSize);
            mPreviewBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); //This must called before createCaptureSession
            mCamera.createCaptureSession(Arrays.asList(mSurface, mImageReader.getSurface()), mSessionStateCallback,null);
        }catch (CameraAccessException e)
        {
            e.printStackTrace();
        }
    }

在上述方法里,首先获取根据OpenGL的textureId创建的SurfaceTexture, 然后根据该SurfaceTexture创建一个用于预览显示的Surface(后续,预览画面通过渲染到该Surface上,将画面更新到textureId上,再由OpenGL渲染显示)
注意,这里创建CaptureRequest的类型为TEMPLATE_PREVIEW
创建的CaptureSession需要将预览数据流返回到配置的Surface上。

最后,当CaptureSession.StateCallback返回正确结果后, 开启预览:

 // Session State Callback
    private CameraCaptureSession.StateCallback mSessionStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
            if(null == mCamera) return;
            mCaptureSession = cameraCaptureSession;
            try{
                ...
                startPreview(mCaptureSession);
            }catch (CameraAccessException e){
                e.printStackTrace();
            }
        }
        ...
    };
private  void startPreview(CameraCaptureSession session) throws CameraAccessException{
        //Set Surface of SurfaceView as the target of the builder
        mPreviewBuilder.addTarget(mSurface);
        mPreviewBuilder.addTarget(mImageReader.getSurface());
        session.setRepeatingRequest(mPreviewBuilder.build(), mSessionCaptureCallback, mCamSessionHandler);
    }

另外,关于摄像头的销毁工作:

@Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        ...
        closeCamera();
        destroyImageReader();
        stopCameraSessionThread();
        destroyPreviewSurface();
        ...
    }

注意,SurfaceTexture以及Suface的销毁需要放在CaptureSession销毁之后去做(不然会引起卡顿)。

结果

最终,本实现在坚果,华为Mate8, Google Pixel上都测试通过。

总结

本文实现并介绍了如何利用SurfaceTexture来渲染Camera2的预览画面并最终显示在SurfaceView。本文通过创建OpenGL渲染环境来实现对Camera2预览画面的渲染,渲染环境的线程以及SurfaceTexture是通过在native层实现的。SufaceView 以及渲染环境可以用GLSurfaceView来代替。该方法具有很强的实用性,用户即可以利用该方法实现OpenGL来处理摄像头画面,又可以利用该方法实现计算机视觉方面的工作。

欢迎 参考本文的项目地址: https://github.com/wangcan26/CameraCVGLView

参考

[参考1]: Camera Api. https://developer.android.com/reference/android/hardware/Camera .Android Developer
[参考2]: Camera.PreviewCallback. https://developer.android.com/reference/android/hardware/Camera.PreviewCallback .Android Developer
[参考3]: MediaRecoder. https://developer.android.com/reference/android/media/MediaRecorder#setCamera(android.hardware.Camera .Android Developer
[参考4]: Camera2 Api. https://developer.android.com/reference/android/hardware/camera2/package-summary .Android Developer
[参考5]: CameraMetaData. https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR .Android Developer
[参考6]: Android Camera2 拍照(一). https://blog.csdn.net/foolish0421/article/details/77732140 .hunter800421
[参考7]: android-Camera2Basic. https://github.com/googlesamples/android-Camera2Basic .google samples
[参考8]: CameraCaptureSession. https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession .Android Developer

上一篇下一篇

猜你喜欢

热点阅读