Android Camera-CameraX源碼分析

在前面一篇文章中,已經介紹瞭如何使用 CameraX,這篇文章就分析下 CameraX 主要流程的源碼。
本篇分析的源碼版本是1.0.0-alpha06,目前最新的 CameraX 版本是1.0.0-alpha10java

引用

在 build.gradle 中聲明android

def camerax_version = "1.0.0-alpha06"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"

def camerax_view_version = "1.0.0-alpha03"
def camerax_ext_version = "1.0.0-alpha03"
//other
// If you to use the Camera View class
implementation "androidx.camera:camera-view:$camerax_view_version"
// If you to use Camera Extensions
implementation "androidx.camera:camera-extensions:$camerax_ext_version"
複製代碼
  • camera-core:Camera核心庫,設計架構的實現
  • camera-camera2:Camera2的配置和操做封裝
  • camera-view:自定義的 CameraView 組件
  • camera-extensions:Camera的擴展,用於訪問設備專屬供應商效果(例如散景、HDR 及其餘功能)的 API

其中camera-corecamera-camera2是必須使用的庫,使用該庫,能夠輕鬆地使用Camera2 API的功能緩存

CameraX結構

首先看下 CameraX 的屬性:session

private static final CameraX INSTANCE = new CameraX();
final CameraRepository mCameraRepository = new CameraRepository();
private final AtomicBoolean mInitialized = new AtomicBoolean(false);
private final UseCaseGroupRepository mUseCaseGroupRepository = new UseCaseGroupRepository();
private final ErrorHandler mErrorHandler = new ErrorHandler();
private CameraFactory mCameraFactory;
private CameraDeviceSurfaceManager mSurfaceManager;
private UseCaseConfigFactory mDefaultConfigFactory;
private Context mContext;
複製代碼

主要看其中幾個重要屬性:架構

  • CameraRepository:Camera倉庫,保存可用 Camera 的列表
  • UseCaseGroupRepository:UseCaseGroupLifecycleController實例倉庫,每一個UseCaseGroupLifecycleController都與一個LifecycleOwner相關聯,該LifecycleOwner調節組中全部用例共享的通用生命週期
  • CameraFactory:Camera抽象工廠,Camera2CameraFactory是具體的實現類
  • CameraDeviceSurfaceManager:Camera設備與對應數據流管理,具體實現是Camera2DeviceSurfaceManager
  • UseCaseConfigFactory:UseCase配置工廠

CameraX主要使用UseCase的概念與相機設備進行交互,目前提供的UseCaseapp

  • 預覽(Preview)
  • 圖片拍攝(ImageCapture)
  • 圖片分析(ImageAnalysis)

CameraX初始化

GXwNU1.png

Camera2Initializer

CameraX初始化方法:initide

public static void init(Context context, @NonNull AppConfig appConfig) {
   INSTANCE.initInternal(context, appConfig);
}
複製代碼

init 是經過ContentProvier配置初始化,具體實現類Camera2Initializeroop

public final class Camera2Initializer extends ContentProvider {
    private static final String TAG = "Camera2Initializer";

    @Override
    public boolean onCreate() {
        Log.d(TAG, "CameraX initializing with Camera2 ...");

        CameraX.init(getContext(), Camera2AppConfig.create(getContext()));
        return false;
    }
    ...
}
複製代碼

AndroidMainifest.xml會自動生成provider配置,ContentProvider的OnCreate調用比Applicantion的 onCreate調用更早。post

<provider android:name="androidx.camera.camera2.impl.Camera2Initializer" android:exported="false" android:multiprocess="true" android:authorities="${applicationId}.camerax-init" android:initOrder="100" />
複製代碼

Camera2AppConfig

init 方法傳入的 AppConfig 的 create:gradle

public static AppConfig create(Context context) {
   // Create the camera factory for creating Camera2 camera objects
   CameraFactory cameraFactory = new Camera2CameraFactory(context);

   // Create the DeviceSurfaceManager for Camera2
   CameraDeviceSurfaceManager surfaceManager = new Camera2DeviceSurfaceManager(context);

   // Create default configuration factory
   ExtendableUseCaseConfigFactory configFactory = new ExtendableUseCaseConfigFactory();
   configFactory.installDefaultProvider(
            ImageAnalysisConfig.class, new ImageAnalysisConfigProvider(cameraFactory, context));
   configFactory.installDefaultProvider(
            ImageCaptureConfig.class, new ImageCaptureConfigProvider(cameraFactory, context));
   configFactory.installDefaultProvider(
            VideoCaptureConfig.class, new VideoCaptureConfigProvider(cameraFactory, context));
   configFactory.installDefaultProvider(
            PreviewConfig.class, new PreviewConfigProvider(cameraFactory, context));

   AppConfig.Builder appConfigBuilder =
            new AppConfig.Builder()
                  .setCameraFactory(cameraFactory)
                  .setDeviceSurfaceManager(surfaceManager)
                  .setUseCaseConfigFactory(configFactory);

   return appConfigBuilder.build();
}
複製代碼

經過 AppConfig.Builder 進行構建,CameraX中的默認屬性都在這裏初始化。後面具體講到某個 UseCase 的時候,詳細分析下具體的ConfigProvider

CameraX.initInternal

CameraX真正初始化方法:initInternal

private void initInternal(Context context, AppConfig appConfig) {
   if (mInitialized.getAndSet(true)) {
      return;
   }

   mContext = context.getApplicationContext();
   mCameraFactory = appConfig.getCameraFactory(null);
   if (mCameraFactory == null) {
      throw new IllegalStateException(
               "Invalid app configuration provided. Missing CameraFactory.");
   }

   mSurfaceManager = appConfig.getDeviceSurfaceManager(null);
   if (mSurfaceManager == null) {
      throw new IllegalStateException(
               "Invalid app configuration provided. Missing CameraDeviceSurfaceManager.");
   }

   mDefaultConfigFactory = appConfig.getUseCaseConfigRepository(null);
   if (mDefaultConfigFactory == null) {
      throw new IllegalStateException(
               "Invalid app configuration provided. Missing UseCaseConfigFactory.");
   }

   mCameraRepository.init(mCameraFactory);
}
複製代碼

直接從 AppConfig 中獲取到具體實例,mCameraFactory對應的實例是Camera2CameraFactory,mCameraRepository.init(mCameraFactory)進行 Camera 相關的初始化

CameraRepository.init

public void init(CameraFactory cameraFactory) {
   synchronized (mCamerasLock) {
      try {
            Set<String> camerasList = cameraFactory.getAvailableCameraIds();
            for (String id : camerasList) {
               Log.d(TAG, "Added camera: " + id);
               mCameras.put(id, cameraFactory.getCamera(id));
            }
      ...
   }
}
複製代碼

getAvailableCameraIds獲取可用 Camera Id列表,Camera2CameraFactory的getCamera真正初始化Camera

public BaseCamera getCamera(@NonNull String cameraId) {
   Camera camera = new Camera(mCameraManager, cameraId,
            mAvailabilityRegistry.getAvailableCameraCount(), sHandler);
   mAvailabilityRegistry.registerCamera(camera);
   return camera;
}
複製代碼

經過CameraAvailabilityRegistryregisterCamera方法進行Camera註冊
到此爲止,CameraX 相關屬性就初始化完成了

bindToLifecycle

從第一個UseCase預覽(preview)來說解CameraX 生命週期過程,以及數據傳輸流程。
前面一篇文章已經講解過 CameraX 的使用,其中預覽(preivew),會先聲明PreviewConfig,經過 config 生成Preivewpreview.setOnPreviewOutputUpdateListener設置監聽Camera數據流。這一系列流程可以實現,主要經過CameraX.bindToLifecycle實現
具體流程:

GXRjuq.png

public static void bindToLifecycle(LifecycleOwner lifecycleOwner, UseCase... useCases) {
   Threads.checkMainThread();
   UseCaseGroupLifecycleController useCaseGroupLifecycleController =
            INSTANCE.getOrCreateUseCaseGroup(lifecycleOwner);
   UseCaseGroup useCaseGroupToBind = useCaseGroupLifecycleController.getUseCaseGroup();

   Collection<UseCaseGroupLifecycleController> controllers =
            INSTANCE.mUseCaseGroupRepository.getUseCaseGroups();
   //檢查UseCase 只能在一個lifecycle上
   for (UseCase useCase : useCases) {
      for (UseCaseGroupLifecycleController controller : controllers) {
            UseCaseGroup useCaseGroup = controller.getUseCaseGroup();
            if (useCaseGroup.contains(useCase) && useCaseGroup != useCaseGroupToBind) {
               throw new IllegalStateException(
                        String.format(
                              "Use case %s already bound to a different lifecycle.",
                              useCase));
            }
      }
   }

   //onBind監聽回調
   for (UseCase useCase : useCases) {
      useCase.onBind();
   }

   calculateSuggestedResolutions(lifecycleOwner, useCases);

   for (UseCase useCase : useCases) {
      useCaseGroupToBind.addUseCase(useCase);
      for (String cameraId : useCase.getAttachedCameraIds()) {
            attach(cameraId, useCase);
      }
   }

   useCaseGroupLifecycleController.notifyState();
}
複製代碼

UseCaseGroupLifecycleController

建立 UseCaseGroupLifecycleController,UseCaseGroup控制器,經過Lifecycle組件進行 start 和 stop 操做

UseCaseGroupLifecycleController useCaseGroupLifecycleController =
            INSTANCE.getOrCreateUseCaseGroup(lifecycleOwner);
...
private UseCaseGroupLifecycleController getOrCreateUseCaseGroup(LifecycleOwner lifecycleOwner) {
   return mUseCaseGroupRepository.getOrCreateUseCaseGroup(
            lifecycleOwner, new UseCaseGroupRepository.UseCaseGroupSetup() {
               @Override
               public void setup(UseCaseGroup useCaseGroup) {
                  useCaseGroup.setListener(mCameraRepository);
               }
            });
}
複製代碼

經過UseCaseGroupRepository建立UseCaseGroupLifecycleController

UseCaseGroupLifecycleController getOrCreateUseCaseGroup( LifecycleOwner lifecycleOwner, UseCaseGroupSetup groupSetup) {
   UseCaseGroupLifecycleController useCaseGroupLifecycleController;
   synchronized (mUseCasesLock) {
      //若是有緩存,則直接返回,不然進行建立
      useCaseGroupLifecycleController = mLifecycleToUseCaseGroupControllerMap.get(
               lifecycleOwner);
      if (useCaseGroupLifecycleController == null) {
            useCaseGroupLifecycleController = createUseCaseGroup(lifecycleOwner);
            groupSetup.setup(useCaseGroupLifecycleController.getUseCaseGroup());
      }
   }
   return useCaseGroupLifecycleController;
}
...
 private UseCaseGroupLifecycleController createUseCaseGroup(LifecycleOwner lifecycleOwner) {
   ...
   // Need to add observer before creating UseCaseGroupLifecycleController to make sure
   // UseCaseGroups can be stopped before the latest active one is started.
   lifecycleOwner.getLifecycle().addObserver(createLifecycleObserver());
   UseCaseGroupLifecycleController useCaseGroupLifecycleController =
            new UseCaseGroupLifecycleController(lifecycleOwner.getLifecycle());
   //建立後,放入 map 緩存
   synchronized (mUseCasesLock) {
      mLifecycleToUseCaseGroupControllerMap.put(lifecycleOwner,
               useCaseGroupLifecycleController);
   }
   return useCaseGroupLifecycleController;
}
複製代碼

建立UseCaseGroupLifecycleController,並增長Lifecycle生命週期控制:

UseCaseGroupLifecycleController(Lifecycle lifecycle) {
   this(lifecycle, new UseCaseGroup());
}

/** Wraps an existing {@link UseCaseGroup} so it is controlled by lifecycle transitions. */
UseCaseGroupLifecycleController(Lifecycle lifecycle, UseCaseGroup useCaseGroup) {
   this.mUseCaseGroup = useCaseGroup;
   this.mLifecycle = lifecycle;
   //綁定Lifecycle
   lifecycle.addObserver(this);
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart(LifecycleOwner lifecycleOwner) {
   synchronized (mUseCaseGroupLock) {
      mUseCaseGroup.start();
   }
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onStop(LifecycleOwner lifecycleOwner) {
   synchronized (mUseCaseGroupLock) {
      mUseCaseGroup.stop();
   }
}

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroy(LifecycleOwner lifecycleOwner) {
   synchronized (mUseCaseGroupLock) {
      mUseCaseGroup.clear();
   }
}
複製代碼

上面的代碼,增長了ON_STARTON_STOPON_DESTROY的生命週期監聽

calculateSuggestedResolutions

根據傳入的配置,生成各個UseCase的最佳解決方案。後面的代碼會以Preview這個 UseCase 展開,其餘 UseCase 代碼邏輯相似。

private static void calculateSuggestedResolutions(LifecycleOwner lifecycleOwner, UseCase... useCases) {
   // There will only one lifecycleOwner active. Therefore, only collect use cases belong to
   // same lifecycleOwner and calculate the suggested resolutions.
   ...
   // Collect new use cases for different camera devices
   for (UseCase useCase : useCases) {
      String cameraId = null;
      try {
            cameraId = getCameraWithCameraDeviceConfig(
                  (CameraDeviceConfig) useCase.getUseCaseConfig());
      } catch (CameraInfoUnavailableException e) {
            throw new IllegalArgumentException(
                  "Unable to get camera id for the camera device config.", e);
      }
   }
   ...
   // Get suggested resolutions and update the use case session configuration
   for (String cameraId : newCameraIdUseCaseMap.keySet()) {
      Map<UseCase, Size> suggestResolutionsMap =
               getSurfaceManager()
                        .getSuggestedResolutions(
                              cameraId,
                              originalCameraIdUseCaseMap.get(cameraId),
                              newCameraIdUseCaseMap.get(cameraId));

      for (UseCase useCase : newCameraIdUseCaseMap.get(cameraId)) {
            Size resolution = suggestResolutionsMap.get(useCase);
            Map<String, Size> suggestedCameraSurfaceResolutionMap = new HashMap<>();
            suggestedCameraSurfaceResolutionMap.put(cameraId, resolution);
            //更新配置
            useCase.updateSuggestedResolution(suggestedCameraSurfaceResolutionMap);
      }
   }
}
複製代碼

每一個 UseCase 會去更新對應配置 updateSuggestedResolution->onSuggestedResolutionUpdated

public void updateSuggestedResolution(Map<String, Size> suggestedResolutionMap) {
   Map<String, Size> resolutionMap = onSuggestedResolutionUpdated(suggestedResolutionMap);
   ...
}
複製代碼

onSuggestedResolutionUpdated針對不一樣的 UseCase 有不一樣的實現,這裏以Preview爲例

protected Map<String, Size> onSuggestedResolutionUpdated( Map<String, Size> suggestedResolutionMap) {
   //獲取前面配置的 config
   PreviewConfig config = (PreviewConfig) getUseCaseConfig();
   String cameraId = getCameraIdUnchecked(config);
   Size resolution = suggestedResolutionMap.get(cameraId);
   ...
   //設置 config
   updateConfigAndOutput(config, resolution);
   return suggestedResolutionMap;
}
...
private void updateConfigAndOutput(PreviewConfig config, Size resolution) {
   String cameraId = getCameraIdUnchecked(config);
   //初始化pipeline
   mSessionConfigBuilder = createPipeline(config, resolution);
   attachToCamera(cameraId, mSessionConfigBuilder.build());
   updateOutput(mSurfaceTextureHolder.getSurfaceTexture(), resolution);
}
複製代碼

Preview.createPipeline

建立Preview管道,經過 PreviewConfig 的配置,建立對應的顯示Surface和SessionConfig

SessionConfig.Builder createPipeline(PreviewConfig config, Size resolution) {
   Threads.checkMainThread();
   SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);

   final CaptureProcessor captureProcessor = config.getCaptureProcessor(null);
   //擴展的 extensions實現
   if (captureProcessor != null) {
      CaptureStage captureStage = new CaptureStage.DefaultCaptureStage();
      // TODO: To allow user to use an Executor for the processing.
      ...
   } else {
      final ImageInfoProcessor processor = config.getImageInfoProcessor(null);
      if (processor != null) {
            sessionConfigBuilder.addCameraCaptureCallback(new CameraCaptureCallback() {
               @Override
               public void onCaptureCompleted( @NonNull CameraCaptureResult cameraCaptureResult) {
                  super.onCaptureCompleted(cameraCaptureResult);
                  if (processor.process(
                           new CameraCaptureResultImageInfo(cameraCaptureResult))) {
                        notifyUpdated();
                  }
               }
            });
      }
      //默認的 Surface
      CheckedSurfaceTexture checkedSurfaceTexture = new CheckedSurfaceTexture(resolution);

      mSurfaceTextureHolder = checkedSurfaceTexture;
      sessionConfigBuilder.addSurface(checkedSurfaceTexture);
   }
   ...
}
複製代碼

這裏就能夠看到咱們熟悉的味道,在Camera2中 用到的SurfaceSession相關配置,後面會用到相關配置。
CheckedSurfaceTexture中會建立FixedSizeSurfaceTexture用來顯示圖像。

Preview.updateOutput

增長數據的監聽

void updateOutput(SurfaceTexture surfaceTexture, Size resolution) {
   PreviewConfig useCaseConfig = (PreviewConfig) getUseCaseConfig();
   ...
   PreviewOutput newOutput =
            PreviewOutput.create(surfaceTexture, resolution, relativeRotation);

   // Only update the output if something has changed
   if (!Objects.equals(mLatestPreviewOutput, newOutput)) {
      SurfaceTexture oldTexture =
               (mLatestPreviewOutput == null)
                        ? null
                        : mLatestPreviewOutput.getSurfaceTexture();
      OnPreviewOutputUpdateListener outputListener = getOnPreviewOutputUpdateListener();
      ...

      if (outputListener != null) {
            mSurfaceDispatched = true;
            updateListener(outputListener, newOutput);
      }
   }
}
複製代碼

根據Preview設置的setOnPreviewOutputUpdateListener,獲取到對應的Listener,經過updateListener方法回調數據。

private void updateListener(OnPreviewOutputUpdateListener listener, PreviewOutput output) {
   ...
   mOutputUpdateExecutor.execute(() -> listener.onUpdated(output));
   ...
}
複製代碼

notifyState

調用UseCaseGroupLifecycleController的notifyState,激活 UseCase 狀態,在UseCaseGroupLifecycleController中有增長生命週期的監聽,在ON_START狀態會調用mUseCaseGroup.start方法。

void notifyState() {
   synchronized (mUseCaseGroupLock) {
      if (mLifecycle.getCurrentState().isAtLeast(State.STARTED)) {
            mUseCaseGroup.start();
      }
      for (UseCase useCase : mUseCaseGroup.getUseCases()) {
            useCase.notifyState();
      }
   }
}
複製代碼

UseCaseGroup.start

void start() {
   synchronized (mListenerLock) {
      if (mListener != null) {
            mListener.onGroupActive(this);
      }
      mIsActive = true;
   }
}
複製代碼

啓動 start 狀態,調用CameraRepositoryonGroupActive方法:

public void onGroupActive(UseCaseGroup useCaseGroup) {
   synchronized (mCamerasLock) {
      Map<String, Set<UseCase>> cameraIdToUseCaseMap = useCaseGroup.getCameraIdToUseCaseMap();
      for (Map.Entry<String, Set<UseCase>> cameraUseCaseEntry :
               cameraIdToUseCaseMap.entrySet()) {
            BaseCamera camera = getCamera(cameraUseCaseEntry.getKey());
            attachUseCasesToCamera(camera, cameraUseCaseEntry.getValue());
      }
   }
}
...
private void attachUseCasesToCamera(BaseCamera camera, Set<UseCase> useCases) {
   camera.addOnlineUseCase(useCases);
}
複製代碼

camera.addOnlineUseCase關聯UseCase 和 Camera。

Camera.addOnlineUseCase

public void addOnlineUseCase(@NonNull final Collection<UseCase> useCases) {
   if (useCases.isEmpty()) {
      return;
   }

   // Attaches the surfaces of use case to the Camera (prevent from surface abandon crash)
   // addOnlineUseCase could be called with duplicate use case, so we need to filter out
   // use cases that are either pending for addOnline or are already online.
   // It's ok for two thread to run here, since it‘ll do nothing if use case is already
   // pending.
   synchronized (mPendingLock) {
      for (UseCase useCase : useCases) {
            boolean isOnline = isUseCaseOnline(useCase);
            if (mPendingForAddOnline.contains(useCase) || isOnline) {
               continue;
            }

            notifyAttachToUseCaseSurfaces(useCase);
            mPendingForAddOnline.add(useCase);
      }
   }
   ...
   updateCaptureSessionConfig();
   resetCaptureSession(/*abortInFlightCaptures=*/false);

   if (mState == InternalState.OPENED) {
      openCaptureSession();
   } else {
      open();
   }

   updateCameraControlPreviewAspectRatio(useCases);
}
複製代碼

在addOnlineUseCase方法中,open會去打開Camera設備。

Camera.open

public void open() {
   ...
   switch (mState) {
      case INITIALIZED:
            openCameraDevice();
            break;
      case CLOSING:
            setState(InternalState.REOPENING);
            // If session close has not yet completed, then the camera is still open. We
            // can move directly back into an OPENED state.
            // If session close is already complete, then the camera is closing. We'll reopen
            // the camera in the camera state callback.
            // If the camera device is currently in an error state, we need to close the
            // camera before reopening, so we cannot directly reopen.
            if (!isSessionCloseComplete() && mCameraDeviceError == ERROR_NONE) {
               Preconditions.checkState(mCameraDevice != null,
                        "Camera Device should be open if session close is not complete");
               setState(InternalState.OPENED);
               openCaptureSession();
            }
            break;
      default:
            Log.d(TAG, "open() ignored due to being in state: " + mState);
   }
}
...
void openCameraDevice() {
   // Check that we have an available camera to open here before attempting
   // to open the camera again.
   if (!mCameraAvailability.isCameraAvailable()) {
      Log.d(TAG, "No cameras available. Waiting for available camera before opening camera: "
               + mCameraId);
      setState(InternalState.PENDING_OPEN);
      return;
   } else {
      setState(InternalState.OPENING);
   }
   ...
   //真正打開相機
   mCameraManager.openCamera(mCameraId, mExecutor, createDeviceStateCallback());
   ...
}
複製代碼

接下來就是Camera2的預覽流程

Camera

CameraX封裝了Camera2的標準預覽流程,這些類都是在 CameraX 庫中

GXLFRP.png

CameraDevice.StateCallback

openCameraDevice的stateCallback

final class StateCallback extends CameraDevice.StateCallback {
   @Override
   public void onOpened(CameraDevice cameraDevice) {
      Log.d(TAG, "CameraDevice.onOpened(): " + cameraDevice.getId());
      mCameraDevice = cameraDevice;
      mCameraDeviceError = ERROR_NONE;
      switch (mState) {
            case CLOSING:
            case RELEASING:
               // No session should have yet been opened, so close camera directly here.
               Preconditions.checkState(isSessionCloseComplete());
               mCameraDevice.close();
               mCameraDevice = null;
               break;
            case OPENING:
            case REOPENING:
               setState(InternalState.OPENED);
               openCaptureSession();
               break;
            default:
               throw new IllegalStateException(
                        "onOpened() should not be possible from state: " + mState);
      }
   }
   ...
}
...
void openCaptureSession() {
   ...
   mCaptureSession.open(validatingBuilder.build(), mCameraDevice);
   ...
}
複製代碼

CaptureSession.open

建立CaptureSession

void open(SessionConfig sessionConfig, CameraDevice cameraDevice) throws CameraAccessException, DeferrableSurface.SurfaceClosedException {
   synchronized (mStateLock) {
      switch (mState) {
            case UNINITIALIZED:
               throw new IllegalStateException(
                        "open() should not be possible in state: " + mState);
            case INITIALIZED:
               //Camera中傳入的essionConfig,默認有TEMPLATE_PREVIEW的 surface 實現
               List<DeferrableSurface> surfaces = sessionConfig.getSurfaces();
               ...
               //狀態更新
               notifySurfaceAttached();
               mState = State.OPENING;
               ...
               SessionConfigurationCompat sessionConfigCompat =
                        new SessionConfigurationCompat(
                              SessionConfigurationCompat.SESSION_REGULAR,
                              outputConfigList,
                              getExecutor(),
                              comboCallback);

               CaptureRequest captureRequest =
                        Camera2CaptureRequestBuilder.buildWithoutTarget(
                              captureConfigBuilder.build(),
                              cameraDevice);

               if (captureRequest != null) {
                  sessionConfigCompat.setSessionParameters(captureRequest);
               }
               //建立CaptureSession,CameraDeviceCompat根據 Android 版本有不一樣的實現
               CameraDeviceCompat.createCaptureSession(cameraDevice, sessionConfigCompat);

      ...
      }
   }
}  
複製代碼

在Camera2的使用中,CameraDevice的createCaptureSession能夠建立預覽畫面,CameraX的CaptureSession很好的封裝了這些實現。
CaptureSession.open傳入的SessionConfig,是在Camera2AppConfig.create建立的時候生成

public static AppConfig create(Context context) {
   // Create the camera factory for creating Camera2 camera objects
   CameraFactory cameraFactory = new Camera2CameraFactory(context);
   // PreviewConfig 配置
   configFactory.installDefaultProvider(
            PreviewConfig.class, new PreviewConfigProvider(cameraFactory, context));

   AppConfig.Builder appConfigBuilder =
            new AppConfig.Builder()
                  .setCameraFactory(cameraFactory)
                  .setDeviceSurfaceManager(surfaceManager)
                  .setUseCaseConfigFactory(configFactory);

   return appConfigBuilder.build();
}
//PreviewConfigProvider配置getConfig
@Override
public PreviewConfig getConfig(LensFacing lensFacing) {
   PreviewConfig.Builder builder =
            PreviewConfig.Builder.fromConfig(Preview.DEFAULT_CONFIG.getConfig(lensFacing));
   // SessionConfig containing all intrinsic properties needed for Preview
   SessionConfig.Builder sessionBuilder = new SessionConfig.Builder();
   // createCaptureSession中的 preview 設置
   sessionBuilder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW);

   // Add options to UseCaseConfig
   builder.setDefaultSessionConfig(sessionBuilder.build());
   builder.setSessionOptionUnpacker(Camera2SessionOptionUnpacker.INSTANCE);

   CaptureConfig.Builder captureBuilder = new CaptureConfig.Builder();
   captureBuilder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW);
   builder.setDefaultCaptureConfig(captureBuilder.build());
   builder.setCaptureOptionUnpacker(Camera2CaptureOptionUnpacker.INSTANCE);
   ...
}
複製代碼

CameraDeviceCompat.createCaptureSession的CameraCaptureSession回調

final class StateCallback extends CameraCaptureSession.StateCallback {
   @Override
   public void onConfigured(@NonNull CameraCaptureSession session) {
      synchronized (mStateLock) {
            switch (mState) {
               case UNINITIALIZED:
               case INITIALIZED:
               case OPENED:
               case RELEASED:
                  throw new IllegalStateException(
                           "onConfigured() should not be possible in state: " + mState);
               case OPENING:
                  ...
                  // Issue capture request of enableSession if exists.
                  if (mSessionConfig != null) {
                        Config implOptions = mSessionConfig.getImplementationOptions();
                        CameraEventCallbacks eventCallbacks = new Camera2Config(
                              implOptions).getCameraEventCallback(
                              CameraEventCallbacks.createEmptyCallback());
                              //可配置CameraEventCallback的EnableSession回調
                        List<CaptureConfig> list =
                              eventCallbacks.createComboCallback().onEnableSession();
                        if (!list.isEmpty()) {
                           issueCaptureRequests(setupConfiguredSurface(list));
                        }
                  }
                  //
                  issueRepeatingCaptureRequests();
                  issueBurstCaptureRequest();
                  break;
               ...
            }
      }
   }
}
複製代碼

CaptureSession.issueRepeatingCaptureRequests

開啓Camera預覽

void issueRepeatingCaptureRequests() {
   ...
   CaptureConfig captureConfig = mSessionConfig.getRepeatingCaptureConfig();
   ...
   // The override priority for implementation options
   // P1 CameraEventCallback onRepeating options
   // P2 SessionConfig options
   CaptureConfig.Builder captureConfigBuilder = CaptureConfig.Builder.from(captureConfig);

   //建立CaptureRequest
   CaptureRequest captureRequest = Camera2CaptureRequestBuilder.build(
            captureConfigBuilder.build(), mCameraCaptureSession.getDevice(),
            mConfiguredSurfaceMap);
   if (captureRequest == null) {
         Log.d(TAG, "Skipping issuing empty request for session.");
         return;
   }

   //設置Capture回調
   CameraCaptureSession.CaptureCallback comboCaptureCallback =
            createCamera2CaptureCallback(
                     captureConfig.getCameraCaptureCallbacks(),
                     mCaptureCallback);

   CameraCaptureSessionCompat.setSingleRepeatingRequest(mCameraCaptureSession,
            captureRequest, mExecutor, comboCaptureCallback);
}
複製代碼

CameraCaptureSessionCompat.setSingleRepeatingRequest 也是區分 Android 版本

private static CameraCaptureSessionCompatImpl chooseImplementation() {
   if (Build.VERSION.SDK_INT >= 28) {
      return new CameraCaptureSessionCompatApi28Impl();
   }

   return new CameraCaptureSessionCompatBaseImpl();
}
//CameraCaptureSessionCompatBaseImpl,和平時使用的同樣
public int setSingleRepeatingRequest(@NonNull CameraCaptureSession captureSession, @NonNull CaptureRequest request, @NonNull Executor executor, @NonNull CameraCaptureSession.CaptureCallback listener) throws CameraAccessException {
   Preconditions.checkNotNull(captureSession);

   // Wrap the executor in the callback
   CameraCaptureSession.CaptureCallback cb =
            new CameraCaptureSessionCompat.CaptureCallbackExecutorWrapper(executor, listener);

   return captureSession.setRepeatingRequest(
            request, cb, MainThreadAsyncHandler.getInstance());
}
//CameraCaptureSessionCompatApi28Impl,新版本 API 有些變化
public int setSingleRepeatingRequest(@NonNull CameraCaptureSession captureSession, @NonNull CaptureRequest request, @NonNull Executor executor, @NonNull CameraCaptureSession.CaptureCallback listener) throws CameraAccessException {
   Preconditions.checkNotNull(captureSession);

   // Call through directly to executor API
   return captureSession.setSingleRepeatingRequest(request, executor, listener);
}
複製代碼

從Camera的開啓到預覽,以及讀取各類配置,整個過程到此就完成了,接下來介紹如何拍照,這個流程相對來講比較簡單

ImageCapture.takePicture

拍照的流程:

JZ52bd.png

sendImageCaptureRequest

建立 ImageCaptureRequest,設置cameraId、targetRatio、回調等

private void sendImageCaptureRequest( @Nullable Executor listenerExecutor, OnImageCapturedListener listener) {

   String cameraId = getCameraIdUnchecked(mConfig);

   // Get the relative rotation or default to 0 if the camera info is unavailable
   int relativeRotation = 0;
   try {
      CameraInfoInternal cameraInfoInternal = CameraX.getCameraInfo(cameraId);
      relativeRotation =
               cameraInfoInternal.getSensorRotationDegrees(
                        mConfig.getTargetRotation(Surface.ROTATION_0));
   } catch (CameraInfoUnavailableException e) {
      Log.e(TAG, "Unable to retrieve camera sensor orientation.", e);
   }

   Rational targetRatio = mConfig.getTargetAspectRatioCustom(null);
   targetRatio = ImageUtil.rotate(targetRatio, relativeRotation);

   mImageCaptureRequests.offer(
            new ImageCaptureRequest(relativeRotation, targetRatio, listenerExecutor, listener));
   if (mImageCaptureRequests.size() == 1) {
      issueImageCaptureRequests();
   }
}
複製代碼

takePictureInternal

void issueImageCaptureRequests() {
   if (mImageCaptureRequests.isEmpty()) {
      return;
   }
   takePictureInternal();
}
...
//拍照流程
private void takePictureInternal() {
   //自定義 Future 調用鏈
   FutureChain.from(preTakePicture(state))
      .transformAsync{
         ...
         return ImageCapture.this.issueTakePicture(state);
      })
      .transformAsync{
         ...
         return ImageCapture.this.postTakePicture(state);
      })
      .addCallback(
         ...
         onTakePictureFinish(null);
      )
}
複製代碼

自定義了整個拍照工做流,經過issueTakePicture進行拍照,postTakePicture是拍照成功,釋放資源,取消3A。下面重點看下issueTakePicture流程

issueTakePicture

ListenableFuture<Void> issueTakePicture(TakePictureState state) {
   ...
   getCurrentCameraControl().submitCaptureRequests(captureConfigs);
   ...
}
複製代碼

經過CameraControl提交Capture 請求,CameraControl具體實現是Camera2CameraControl

submitCaptureRequests

public void submitCaptureRequests(@NonNull final List<CaptureConfig> captureConfigs) {
   mExecutor.execute(new Runnable() {
      @Override
      public void run() {
            submitCaptureRequestsInternal(captureConfigs);
      }
   });
}
...
void submitCaptureRequestsInternal(final List<CaptureConfig> captureConfigs) {
   mControlUpdateListener.onCameraControlCaptureRequests(captureConfigs);
   //mControlUpdateListener是Camera 的回調,onCameraControlCaptureRequests 真正實如今 Camera 中
}
//Camera.java
public void onCameraControlUpdateSessionConfig(@NonNull SessionConfig sessionConfig) {
   mCameraControlSessionConfig = sessionConfig;
   updateCaptureSessionConfig();
}
...
private void updateCaptureSessionConfig() {
      ...
         SessionConfig sessionConfig = validatingBuilder.build();
         mCaptureSession.setSessionConfig(sessionConfig);
      ...
   }
}
Camera 獲取 Capture的SessionConfig,經過`CaptureSession`進行狀態控制
複製代碼

CaptureSession.setSessionConfig

void setSessionConfig(SessionConfig sessionConfig) {
   synchronized (mStateLock) {
      switch (mState) {
            case UNINITIALIZED:
               throw new IllegalStateException(
                        "setSessionConfig() should not be possible in state: " + mState);
            case INITIALIZED:
            case OPENING:
               mSessionConfig = sessionConfig;
               break;
            case OPENED:
               mSessionConfig = sessionConfig;

               if (!mConfiguredSurfaceMap.keySet().containsAll(sessionConfig.getSurfaces())) {
                  Log.e(TAG, "Does not have the proper configured lists");
                  return;
               }

               Log.d(TAG, "Attempting to submit CaptureRequest after setting");
               issueRepeatingCaptureRequests();
               break;
            case CLOSED:
            case RELEASING:
            case RELEASED:
               throw new IllegalStateException(
                        "Session configuration cannot be set on a closed/released session.");
      }
   }
}
複製代碼

在Camera的OPENED狀態,則進行拍照流程

issueRepeatingCaptureRequests

void issueRepeatingCaptureRequests() {
   ...
   CameraCaptureSession.CaptureCallback comboCaptureCallback =
                    createCamera2CaptureCallback(
                            captureConfig.getCameraCaptureCallbacks(),
                            mCaptureCallback);

   CameraCaptureSessionCompat.setSingleRepeatingRequest(mCameraCaptureSession,
                    captureRequest, mExecutor, comboCaptureCallback);
   ...
}
複製代碼

CameraCaptureSessionCompat根據 Android 版本有CameraCaptureSessionCompatBaseImplCameraCaptureSessionCompatApi28Impl兩種實現,最終經過CameraCaptureSession實現真正的拍照。
拍照完成後,經過最開始設置的 Listener 進行回調

ImageCapture.createPipeline

在 Preview 那小節,講解過 bindToLifecycle 流程,這裏的ImageCapture也是一個UseCase。在CameraX中的calculateSuggestedResolutions方法,最終會調用到各個UseCaseonSuggestedResolutionUpdated方法。在ImageCaptureonSuggestedResolutionUpdated方法,經過createPipeline建立了拍照數據的回調

SessionConfig.Builder createPipeline(ImageCaptureConfig config, Size resolution) {
   ...
   //和 Camera2的流程一致
   mProcessingImageResultThread = new HandlerThread("OnImageAvailableHandlerThread");
   mProcessingImageResultThread.start();
   mProcessingImageResultHandler = new Handler(mProcessingImageResultThread.getLooper());
   ...
   mImageReader.setOnImageAvailableListener(
      new ImageReaderProxy.OnImageAvailableListener() {
         @Override
         public void onImageAvailable(ImageReaderProxy imageReader) {
            ImageProxy image = null;
            try {
                  image = imageReader.acquireLatestImage();
            } catch (IllegalStateException e) {
                  Log.e(TAG, "Failed to acquire latest image.", e);
            } finally {
                  if (image != null) {
                     // Call the head request listener to process the captured image.
                     ImageCaptureRequest imageCaptureRequest;
                     if ((imageCaptureRequest = mImageCaptureRequests.peek()) != null) {
                        SingleCloseImageProxy wrappedImage = new SingleCloseImageProxy(
                                 image);
                        wrappedImage.addOnImageCloseListener(mOnImageCloseListener);
                        //ImageCaptureRequest設置 Listener
                        imageCaptureRequest.dispatchImage(wrappedImage);
                     } else {
                        // Discard the image if we have no requests.
                        image.close();
                     }
                  }
            }
         }
      },
      mProcessingImageResultHandler);
   ...
}
複製代碼

ImageReader設置了 Camera 數據會調用,並經過ImageCaptureRequestdispatchImage方法進行分發

ImageCaptureRequest.dispatchImage

void dispatchImage(final ImageProxy image) {
   try {
         mListenerExecutor.execute(new Runnable() {
            @Override
            public void run() {
               Size sourceSize = new Size(image.getWidth(), image.getHeight());
               if (ImageUtil.isAspectRatioValid(sourceSize, mTargetRatio)) {
                     image.setCropRect(
                           ImageUtil.computeCropRectFromAspectRatio(sourceSize,
                                    mTargetRatio));
               }
               //真正的回調
               mListener.onCaptureSuccess(image, mRotationDegrees);
            }
         });
   } catch (RejectedExecutionException e) {
         Log.e(TAG, "Unable to post to the supplied executor.");

         // Unable to execute on the supplied executor, close the image.
         image.close();
   }
}
複製代碼

mListener是一個封裝Listener,在ImageCapture中實現

Listener

Listener的關係圖:

+-----------------------+
 |                       |
 |ImageCapture.          |
 |OnImageCapturedListener|
 |                       |
 +-----------+-----------+
             |
             |
 +-----------v-----------+      +----------------------+
 |                       |      |                      |
 | ImageSaver.           |      | ImageCapture.        |
 | OnImageSavedListener  +------> OnImageSavedListener |
 |                       |      |                      |
 +-----------------------+      +----------------------+
複製代碼

OnImageCapturedListener的實現,其中經過ImageSaver設置的OnImageSavedListener回調到最上層的OnImageSavedListener

OnImageCapturedListener imageCaptureCallbackWrapper =
   new OnImageCapturedListener() {
      @Override
      public void onCaptureSuccess(ImageProxy image, int rotationDegrees) {
         CameraXExecutors.ioExecutor()
                  .execute(
                           new ImageSaver(
                                 image,
                                 saveLocation,
                                 rotationDegrees,
                                 metadata.isReversedHorizontal,
                                 metadata.isReversedVertical,
                                 metadata.location,
                                 executor,
                                 imageSavedListenerWrapper));
      }
   ...
   };
// ImageSaver是一個 Runnable,主要 run 的實現
final class ImageSaver implements Runnable {
   ...
   @Override
   public void run() {
      ...
      //圖像處理
      ...
      if (saveError != null) {
         postError(saveError, errorMessage, exception);
      } else {
         postSuccess();
      }
   }
   ...
   private void postSuccess() {
      mExecutor.execute(new Runnable() {
         @Override
         public void run() {
            //最外層回調
            mListener.onImageSaved(mFile);
         }
      });
   }
}
複製代碼

整個拍照流程和數據回調就講解完畢了。
經過對 CameraX的 Preview 和 ImageCapture的分析,CameraX對Camera2進行完整的封裝,統一參數配置,自動計算Resolution,簡化Camera2的開發,並增長了生命週期控制,對外只暴露了簡單接口。
使用該庫,只須要簡單的幾行代碼就能夠實現之前Camera2複雜的操做。

參考

相關文章
相關標籤/搜索