Camera学习--Camera2应用分析

Posted by Bill on June 9, 2020

1. 概述

为了更好的为学习Camera HAL做铺垫,首先从应用Camera2作为切入点,了解应用启动Camera,拍照,录像的流程,从而更好的学习整条通路。对比以往的笔记,省略大篇幅的代码的粘贴,将关键步骤保留以流程图展示,保证清晰的学习思路。所有学习代码均截取自https://cs.android.com/.

本文涉及的内容在Framework层以上,包括如下目录:

1
2
3
4
5
6
7
8
android/packages/apps/Camera2 #应用目录
android/frameworks/ex/camera2 # Camera2 API
android/frameworks/base/core/java/android/hardware/camera2 # Camera涉及的Framework代码
    - frameworks/base/core/java/android/hardware/camera2/CameraManager.java
    - frameworks/base/core/java/android/hardware/camera2/CameraDevice.java
    - frameworks/base/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
    - frameworks/base/core/java/android/hardware/camera2/CameraCaptureSession.java
    - frameworks/base/core/java/android/hardware/camera2/CameraCaptureSession.java

Framework中涉及重要类包括如下:

1
2
3
4
- CameraManager: 管理手机上的所有摄像头设备,它的作用主要是获取摄像头列表和打开指定的摄像头
- CameraDevice: 具体的摄像头设备,它有一系列参数(预览尺寸、拍照尺寸等),可以通过CameraManager的getCameraCharacteristics()方法获取。它的作用主要是创建CameraCaptureSession和CaptureRequest
- CameraCaptureSession: 相机捕获会话,用于处理拍照和预览的工作(很重要)
- CaptureRequest: 捕获请求,定义输出缓冲区以及显示界面(TextureView或SurfaceView)等

Camera2中的Activity包括如下:

1
2
3
4
5
1. com.android.camera.CameraActivity(应用启动Activity)
2. com.android.camera.PermissionsActivity(获取应用权限)
3. com.android.camera.CaptureActivity(空实现,用于接收IMAGE_CAPTURE,VIDEO_CAMERA,VIDEO_CAPTURE
4. com.android.camera.SecureCameraActivity(空实现,用于接收STILL_IMAGE_CAMERA_SECUREIMAGE_CAPTURE_SECURE)
5. com.android.camera.settings.CameraSettingsActivity(设置Activity)

其中CaptureActivity和SecureCameraActivity都为空实现,只是在AndroidManifest.xml中接收intents,是为了不影响CameraActivity的工作。

如果需要在其他应用中调用相机,只需要实现如下逻辑:

1
2
3
4
5
6
7
8
9
10
    /*
    新建MediaStore.ACTION_IMAGE_CAPTURE的intent,MediaStore.ACTION_IMAGE_CAPTURE是标准的
    获取图片并返回结果的Intent。
    */
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    //应用需要将具体图片路径转化为URI,并作为参数传入intent
    fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
    //启动能够处理该类型intent的应用功能,即调用Camera2。应用在onActivityResult中处理返回结果
    startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);

2. Camera2初始化

Camera2启动时,对应的Activity为CameraActivity,包括对照相机的打开,参数设置,预览等,都需要在该Activit中实现,现对其进行分析。

2.1 CameraActivity简述

1.CameraActivity继承了QuickActivity,该类实现了基本的Activity操作,子类只需要实现如下方法即可(CaptureActivity同样继承了QuickActivity):

  • onCreateTasks
  • onStartTasks
  • onResumeTasks
  • onPauseTasks
  • onStopTasks
  • onDestroyTasks

2.CameraActivity实现回调类CameraAgent.CameraOpenCallback,该方法定义了以下回调方法,从而能够通知到上层相机的连接状态:

  • public void onCameraOpened(CameraProxy camera);
  • public void onCameraDisabled(int cameraId);
  • public void onDeviceOpenFailure(int cameraId, String info);
  • public void onDeviceOpenedAlready(int cameraId, String info);
  • public void onReconnectionFailure(CameraAgent mgr, String info);

2.2 Profiler分析器

Camera2的应用采用了Profile进行日志打印,以CameraActivity方法为例,在onCreateTasks的使用流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
初始化Profiler,Profilers内嵌了多个LogginProfiler和GuardingProfiler,前者用于区分不同打印等级,可以进行日志打印。
后者可以限定时间,在该限定时间内的打印不进行打印,超过的则将其日志打印出来。
*/
private final Profiler mProfiler = Profilers.instance().guard();
...
@Override
public void onCreateTasks(Bundle state) {
    Profile profile = mProfiler.create("CameraActivity.onCreateTasks").start();
    ...//初始化
    profile.mark();//记录上次时间
    ....//另一部分初始化 
    profile.mark("Glide.setup");
    .... //另一部分初始化 
    profile.stop();
}

其优势是可以统计启动过程的时间耗费,start()时开始计时,调用mark时将会计算由start()到mark()阶段使用的时间,并从mark开始新的计时开端,直到下一个mark或者stop。以下为其中的日志打印实例:

1
2
3
4
CameraActivity.onCreateTasks - START
CameraActivity.onCreateTasks - [0.048ms] Glide.setup
.....
CameraActivity.onCreateTasks - STOP

通过该分析器,能够对比出应用启动流程,在性能优化时可以作为重要参考。

2.3 CameraActivity启动流程

2.3.1 CamerActivity.onCreateTasks

CamerActivity的onCreateTasks流程图如下所示:

CameraActivity启动流程图

1.CameraActivity启动时调用父类QuickActivity的onCreate,随后进入CameraActivity的onCreateTasks方法。

1
2
3
4
5
6
7
8
9
#   packages/apps/Camera2/src/com/android/camera/util/QuickActivity.java
    @Override
    protected final void onCreate(Bundle bundle) {
        ...
        super.onCreate(bundle);
        mMainHandler = new Handler(getMainLooper());
        onCreateTasks(bundle);
        ...
    }

2.通过OneCameraModule分别创建了两个对象,mOneCameraOpener以及mOneCameraManager。前者用于打开Camera设备,后者为管理硬件Camera的Manager。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#   packages/apps/Camera2/src/com/android/camera/CameraActivity.java
    ...
    try {
           mOneCameraOpener = OneCameraModule.provideOneCameraOpener(
                   mFeatureConfig,
                   mAppContext,
                   mActiveCameraDeviceTracker,
                   ResolutionUtil.getDisplayMetrics(this));
           mOneCameraManager = OneCameraModule.provideOneCameraManager();
       } catch (OneCameraException e) {
           ...
           mFatalErrorHandler.onGenericCameraAccessFailure();
       }
    ...

3.新建了CameraController,可用于获取相机参数(getCharacteristics()),关闭相机(closeCamera()),获取相机个数(getNumberOfCameras()),判断是前置摄像头还是后置摄像头(isFrontFacingCamera()/isBackFacingCamera()),获取第一个前置摄像头/后置摄像头的ID(getFirstFrontCameraId()/getFirstBackCameraId())等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#   packages/apps/Camera2/src/com/android/camera/CameraActivity.java
    ...
    try {
        mCameraController = new CameraController(mAppContext, this, mMainHandler,
                CameraAgentFactory.getAndroidCameraAgent(mAppContext,
                           CameraAgentFactory.CameraApi.API_1),
        CameraAgentFactory.getAndroidCameraAgent(mAppContext,
                           CameraAgentFactory.CameraApi.AUTO),
                   mActiveCameraDeviceTracker);
        mCameraController.setCameraExceptionHandler(
                   new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
       } catch (AssertionError e) {
           mFatalErrorHandler.onGenericCameraAccessFailure();
       }
    ...

当然CameraController能够获到这些相机的参数属性,大部分都离不开CameraManager的协助。通过传入CameraAgentFactor工厂类,最终获取到CameraManager。为了看清CameraController是怎么调用其中的getCharacteristics接口,可以参考如下流程图:

CameraController

4.新建了ModuleManagerImpl,并作为参数传入ModulesInfo.setupModules中。

1
2
3
4
5
#   packages/apps/Camera2/src/com/android/camera/CameraActivity.java
    ...
    mModuleManager = new ModuleManagerImpl();
    ModulesInfo.setupModules(mAppContext, mModuleManager, mFeatureConfig);
    ...

ModuleInfo的setupModules会注册多种Module,包括CaptureModule,PhotoModule,VideoModule等等。这是为下一步Module的初始化做准备。

5.设置当前Module并初始化。期间CamerAppUI准备初始化工作。

1
2
3
4
5
6
#   packages/apps/Camera2/src/com/android/camera/CameraActivity.java
    ...
    setModuleFromModeIndex(getModeIndex());
    mCameraAppUI.prepareModuleUI();
    mCurrentModule.init(this, isSecureCamera(), isCaptureIntent());
    ...

setModuleFromModeIndex会根据getModeIndex的值,获取对应的ModuleAgent。紧接着通过ModuleAgent调用getModuleId获取mCurrentModeIndex。最后通过ModuleAgent的createModule创建对应Module对。以拍照为例,即新建了CaptureModule.紧接着,调用CaptureModule的init方法进行初始化。后续的拍照按键的点击CaptureModule关系紧密。

此外,mCameraAppUI调用prepareModuoleUI,初始化应用控件,其中如下的mTextureView将用于相机的预览。

1
    mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);

2.3.2 Camera设备open流程

当CaptureModule初始化时,会初始化一个PreviewStatusListener类型的listener,用于监听TextureView是否准备完毕。该TextureView上述提到的mCameraAppUI中的preview_content控件,专门用于预览。

1
2
3
4
5
6
7
8
9
10
11
12
13
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/camera_app_root"
    android:background="@android:color/black"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextureView
        android:id="@+id/preview_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
...
/>

Camera open

当SurfaceTexture已准备好时,CaptureModule调用reopenCamera,并最终触发打开Camera设备。当打开完成后,回调onCameraOpened被调用,上层应用此时可以进行预览动作。

2.4 拍照流程

当设备打开完毕后,CaptureModule的回调方法onCameraOpened被调用,会在回调中调用startPreview,表明可以进行预览操作了,如下:

1
2
3
4
5
6
7
8
9
10
11
12
#   packages/apps/Camera2/src/com/android/camera/CaptureModule.java
    camera.startPreview(new Surface(getPreviewSurfaceTexture()),
                            new CaptureReadyCallback() {
                                @Override
                                public void onSetupFailed() {
                                ...
                                }
                                @Override
                                public void onReadyForCapture() {
                                ...
                                }
                            }); 

经过重重调用,最终会在OneCameraImpl中创建一个CaptureSession,后续正是依靠这个CaptureSession才完成拍照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#   packages/apps/Camera2/src/com/android/camera/CaptureModule.java
    private void setup(Surface previewSurface, final CaptureReadyCallback listener) {
    ...
        //创建CapturSession,成功后,通过onConfigured获取session。该session是拍照的关键
        mDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigureFailed(CameraCaptureSession session) {
                    listener.onSetupFailed();
                }

                @Override
                public void onConfigured(CameraCaptureSession session) {
                    mCaptureSession = session;
                    ....
                }

                @Override
                public void onClosed(CameraCaptureSession session) {
                    super.onClosed(session);
                }
            }, mCameraHandler);
    ...
    }

之后,通过点击应用的拍照按键表明拍照动作的开始,其流程图如下:

Camera拍照

1.点击按键,触发CameraActivity的onKeyDown方法,直调用到CapturModule的onKeyDown方法。 2.CaptureModule的onKeyDown方法调用onShutterButtonClick,从而重复啊一连串拍照流程takePicture。 3.利用之前提到的CameraCaptureSession,完成capture动作 4.拍照完成后,回调方法onCaptureCompleted调用。

至此,简单的分析了下Camera2的初始化和拍照流程,后续将会从Framework的角度进行分析。

3. 参考文献

  1. Android 使用 Camera2 完成预览和拍照