CameraX のアーキテクチャ

このページでは、CameraX の構造、API との連携方法、ライフサイクルとの連携方法、ユースケースを組み合わせる方法を含め、CameraX のアーキテクチャについて説明します。

CameraX の構造

CameraX を使用すると、ユースケースと呼ばれる抽象化を通じてデバイスのカメラとインターフェースすることができます。以下のユースケースを利用できます。

  • プレビュー: プレビューを表示するためのサーフェスを受け入れます(PreviewView など)。
  • 画像解析: CPU からアクセス可能な解析用(機械学習用など)のバッファを提供します。
  • 画像キャプチャ: 写真をキャプチャして保存します。
  • 動画キャプチャ: VideoCapture で動画と音声をキャプチャします。

ユースケースは、組み合わせて同時にアクティブにすることができます。たとえばアプリにおいて、プレビュー ユースケースを使用してカメラから見える画像をユーザーが確認できるようにし、画像解析ユースケースで被写体の人が笑顔かどうかを判断して、画像キャプチャ ユースケースで被写体の人が笑顔になったときに写真を撮る、といったことが可能です。

API モデル

ライブラリを使用する場合は以下を指定します。

  • 使用するユースケースと設定オプション
  • 出力データの処理方法(リスナーをアタッチする)
  • カメラを有効にするタイミング、データを生成するタイミングなどの対象とするフロー(ユースケースを Android アーキテクチャのライフサイクルにバインドする)

CameraX アプリの作成方法は、CameraController(CameraX の最も簡単な使用方法)、または、CameraProvider(柔軟性が必要な場合)の 2 つです。

CameraController

CameraController は、CameraX のコア機能のほとんどを 1 つのクラスで提供します。セットアップ コードをほとんど必要とせず、カメラの初期化、ユースケース管理、ターゲットの回転、タップしてフォーカス、ピンチ操作によるズームなどを自動的に処理します。CameraController を拡張する具象クラスは、LifecycleCameraController です。

Kotlin

val previewView: PreviewView = viewBinding.previewView
var cameraController = LifecycleCameraController(baseContext)
cameraController.bindToLifecycle(this)
cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
previewView.controller = cameraController

Java

PreviewView previewView = viewBinding.previewView;
LifecycleCameraController cameraController = new LifecycleCameraController(baseContext);
cameraController.bindToLifecycle(this);
cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA);
previewView.setController(cameraController);

CameraController のデフォルトの UseCase は、PreviewImageCaptureImageAnalysis です。ImageCapture または ImageAnalysis をオフにする、または VideoCapture をオンにするには、setEnabledUseCases() メソッドを使用します。

CameraController のその他の使用方法については、QR コードスキャナのサンプルまたは CameraController の基本に関する動画をご覧ください。

CameraProvider

CameraProvider は簡単に使用できますが、アプリ デベロッパーはより多くの設定を処理するため、ImageAnalysis での出力画像の回転の有効化や出力画像形式の設定など、構成をカスタマイズする機会が増えます。また、カメラ プレビューではカスタムの Surface を使用して、柔軟性を高めることができますが、CameraController では PreviewView を使用する必要があります。既存の Surface コードの使用は、アプリの他の部分ですでに入力されている場合、便利です。

ユースケースは set() メソッドを使用して設定され、build() メソッドで確定されます。各ユースケース オブジェクトは、ユースケースに固有の一連の API を提供します。たとえば、画像キャプチャのユースケースは、takePicture() メソッドの呼び出しを提供します。

アプリでは、onResume()onPause() で明示的に開始および停止メソッドを呼び出すのではなく、cameraProvider.bindToLifecycle() を使用して、カメラを関連付けるライフサイクルを指定します。 このライフサイクルからカメラのキャプチャ セッションを設定するタイミングが CameraX に通知され、ライフサイクルの遷移に合わせてカメラの状態が適切に変更されます。

各ユースケースの実装手順については、プレビューを実装する画像解析画像キャプチャ動画キャプチャをご覧ください。

プレビュー ユースケースは、ディスプレイ用の Surface を操作します。アプリは次のコードを使用して、設定オプションが指定されたユースケースを作成します。

Kotlin

val preview = Preview.Builder().build()
val viewFinder: PreviewView = findViewById(R.id.previewView)

// The use case is bound to an Android Lifecycle with the following code
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// PreviewView creates a surface provider and is the recommended provider
preview.setSurfaceProvider(viewFinder.getSurfaceProvider())

Java

Preview preview = new Preview.Builder().build();
PreviewView viewFinder = findViewById(R.id.view_finder);

// The use case is bound to an Android Lifecycle with the following code
Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview);

// PreviewView creates a surface provider, using a Surface from a different
// kind of view will require you to implement your own surface provider.
preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();

その他のサンプルコードについては、CameraX の公式サンプルアプリをご覧ください。

CameraX のライフサイクル

CameraX はライフサイクルを監視して、カメラを起動するタイミング、キャプチャ セッションを作成するタイミング、停止およびシャットダウンするタイミングを決定します。ユースケースの API は、進行状況を監視するためのメソッド呼び出しとコールバックを提供します。

ユースケースを組み合わせるで説明するように、ユースケースを組み合わせて 1 つのライフサイクルにまとめることができます。組み合わせることができないユースケースをアプリでサポートする必要がある場合は、以下のいずれかを行うことができます。

  • 互換性のあるユースケースを複数のフラグメントにグループ化して、フラグメントを切り替える
  • カスタムのライフサイクル コンポーネントを作成し、それを使ってカメラのライフサイクルを手動で管理する

ビューとカメラの各ユースケースのライフサイクル オーナーを分ける場合(カスタム ライフサイクルを使用する場合やフラグメントを保持する場合)は、すべてのユースケースを CameraX からバインド解除する必要があります。そのためには、ProcessCameraProvider.unbindAll() を使用するか、または各ユースケースを個別にバインド解除します。また、ユースケースをライフサイクルにバインドしている場合は、キャプチャ セッションの開始と終了、ユースケースのバインド解除を CameraX に管理させることができます。

すべてのカメラ機能が 1 つのライフサイクル対応コンポーネント(AppCompatActivityAppCompat フラグメントなど)のライフサイクルに対応している場合、使用するすべてのユースケースをバインドするときにそのコンポーネントのライフサイクルを利用することで、ライフサイクル対応コンポーネントがアクティブなときにカメラ機能をすぐに使用できます。アクティブでないときは、安全にカメラ機能を破棄してリソースの消費を避けられます。

カスタムの LifecycleOwner

高度な手法として、標準の Android LifecycleOwner を使用する代わりに、カスタムの LifecycleOwner を作成することで、CameraX のセッション ライフサイクルをアプリで明示的に制御できます。

次のコードサンプルは、シンプルなカスタムの LifecycleOwner を作成する方法を示しています。

Kotlin

class CustomLifecycle : LifecycleOwner {
    private val lifecycleRegistry: LifecycleRegistry

    init {
        lifecycleRegistry = LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED)
    }
    ...
    fun doOnResume() {
        lifecycleRegistry.markState(State.RESUMED)
    }
    ...
    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

Java

public class CustomLifecycle implements LifecycleOwner {
    private LifecycleRegistry lifecycleRegistry;
    public CustomLifecycle() {
        lifecycleRegistry = new LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED);
    }
   ...
   public void doOnResume() {
        lifecycleRegistry.markState(State.RESUMED);
    }
   ...
    public Lifecycle getLifecycle() {
        return lifecycleRegistry;
    }
}

アプリでこの LifecycleOwner を使用すると、コード内の任意の位置で状態遷移を行うことができます。この機能をアプリに実装する方法について詳しくは、カスタムの LifecycleOwner の実装をご覧ください。

ユースケースを同時に実行する

複数のユースケースを同時に実行できます。ユースケースはライフサイクルに連続的にバインドできますが、CameraProcessProvider.bindToLifecycle() の 1 回の呼び出しですべてのユースケースをバインドするほうが効率的です。設定の変更に関するおすすめの方法について詳しくは、設定の変更への対処をご覧ください。

次のコードサンプルでは、アプリで 2 つのユースケースを作成して同時に実行するように指定しています。また、両方のユースケースに使用するライフサイクルを指定して、どちらもそのライフサイクルに従って開始および停止するようにしています。

Kotlin

private lateinit var imageCapture: ImageCapture

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

    cameraProviderFuture.addListener(Runnable {
        // Camera provider is now guaranteed to be available
        val cameraProvider = cameraProviderFuture.get()

        // Set up the preview use case to display camera preview.
        val preview = Preview.Builder().build()

        // Set up the capture use case to allow users to take photos.
        imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                .build()

        // Choose the camera by requiring a lens facing
        val cameraSelector = CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
                .build()

        // Attach use cases to the camera with the same lifecycle owner
        val camera = cameraProvider.bindToLifecycle(
                this as LifecycleOwner, cameraSelector, preview, imageCapture)

        // Connect the preview use case to the previewView
        preview.setSurfaceProvider(
                previewView.getSurfaceProvider())
    }, ContextCompat.getMainExecutor(this))
}

Java

private ImageCapture imageCapture;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    PreviewView previewView = findViewById(R.id.previewView);

    ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
            ProcessCameraProvider.getInstance(this);

    cameraProviderFuture.addListener(() -> {
        try {
            // Camera provider is now guaranteed to be available
            ProcessCameraProvider cameraProvider = cameraProviderFuture.get();

            // Set up the view finder use case to display camera preview
            Preview preview = new Preview.Builder().build();

            // Set up the capture use case to allow users to take photos
            imageCapture = new ImageCapture.Builder()
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                    .build();

            // Choose the camera by requiring a lens facing
            CameraSelector cameraSelector = new CameraSelector.Builder()
                    .requireLensFacing(lensFacing)
                    .build();

            // Attach use cases to the camera with the same lifecycle owner
            Camera camera = cameraProvider.bindToLifecycle(
                    ((LifecycleOwner) this),
                    cameraSelector,
                    preview,
                    imageCapture);

            // Connect the preview use case to the previewView
            preview.setSurfaceProvider(
                    previewView.getSurfaceProvider());
        } catch (InterruptedException | ExecutionException e) {
            // Currently no exceptions thrown. cameraProviderFuture.get()
            // shouldn't block since the listener is being called, so no need to
            // handle InterruptedException.
        }
    }, ContextCompat.getMainExecutor(this));
}

CameraX では、PreviewVideoCaptureImageAnalysisImageCapture の各インスタンスを 1 つずつ同時に使用できます。加えて次の点にもご注意ください。

  • すべてのユースケースは単独で機能します。たとえば、アプリはプレビューを使用せずに動画を撮影できます。
  • 拡張機能が有効になっている場合、ImageCapturePreview の組み合わせのみ、機能することが保証されます。OEM の実装によっては、ImageAnalysis も追加できない場合があります。VideoCapture ユースケースでは拡張機能を有効にできません。詳しくは、拡張機能のリファレンス ドキュメントをご覧ください。
  • カメラの機能によっては、一部のカメラで低解像度モードで組み合わせをサポートする場合もありますが、高解像度では同じ組み合わせをサポートできない場合があります。
  • カメラのハードウェア レベルが FULL 以前のデバイスでは、PreviewVideoCaptureImageCapture または ImageAnalysis を組み合わせると、CameraX が PreviewVideoCapture に対してカメラの PRIV ストリームを強制的に複製することがあります。この重複(ストリーム共有)により、これらの機能を同時に使用できますが、処理要件が増加します。その結果、レイテンシが若干長くなり、バッテリー駆動時間が短くなる可能性があります。

サポートされているハードウェア レベルCamera2CameraInfo から取得できます。たとえば、次のコードでは、デフォルトの背面カメラが LEVEL_3 のデバイスかどうかを確認しています。

Kotlin

@androidx.annotation.OptIn(ExperimentalCamera2Interop::class)
fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        return CameraSelector.DEFAULT_BACK_CAMERA
            .filter(cameraProvider.availableCameraInfos)
            .firstOrNull()
            ?.let { Camera2CameraInfo.from(it) }
            ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
    }
    return false
}

Java

@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class)
Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        List\<CameraInfo\> filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA
                .filter(cameraProvider.getAvailableCameraInfos());
        if (!filteredCameraInfos.isEmpty()) {
            return Objects.equals(
                Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic(
                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL),
                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3);
        }
    }
    return false;
}

権限

アプリには CAMERA 権限が必要です。画像をファイルに保存するには、Android 10 以降を実行しているデバイスを除き、WRITE_EXTERNAL_STORAGE 権限も必要になります。

アプリの権限の設定方法について詳しくは、アプリの権限をリクエストするをご覧ください。

要件

CameraX には以下の最小バージョン要件があります。

  • Android API レベル 21
  • Android アーキテクチャ コンポーネント 1.1.1

ライフサイクル対応のアクティビティには、FragmentActivity または AppCompatActivity を使用します。

依存関係の宣言

CameraX への依存関係を追加するには、Google の Maven リポジトリをプロジェクトに追加する必要があります。

プロジェクトの settings.gradle ファイルを開き、下記のように google() リポジトリを追加します。

Groovy

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Kotlin

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Android ブロックの末尾に以下を追加します。

Groovy

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Kotlin

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

アプリの各モジュールの build.gradle ファイルに以下を追加します。

Groovy

dependencies {
  // CameraX core library using the camera2 implementation
  def camerax_version = "1.5.0-alpha03"
  // The following line is optional, as the core library is included indirectly by camera-camera2
  implementation "androidx.camera:camera-core:${camerax_version}"
  implementation "androidx.camera:camera-camera2:${camerax_version}"
  // If you want to additionally use the CameraX Lifecycle library
  implementation "androidx.camera:camera-lifecycle:${camerax_version}"
  // If you want to additionally use the CameraX VideoCapture library
  implementation "androidx.camera:camera-video:${camerax_version}"
  // If you want to additionally use the CameraX View class
  implementation "androidx.camera:camera-view:${camerax_version}"
  // If you want to additionally add CameraX ML Kit Vision Integration
  implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
  // If you want to additionally use the CameraX Extensions library
  implementation "androidx.camera:camera-extensions:${camerax_version}"
}

Kotlin

dependencies {
    // CameraX core library using the camera2 implementation
    val camerax_version = "1.5.0-alpha03"
    // The following line is optional, as the core library is included indirectly by camera-camera2
    implementation("androidx.camera:camera-core:${camerax_version}")
    implementation("androidx.camera:camera-camera2:${camerax_version}")
    // If you want to additionally use the CameraX Lifecycle library
    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
    // If you want to additionally use the CameraX VideoCapture library
    implementation("androidx.camera:camera-video:${camerax_version}")
    // If you want to additionally use the CameraX View class
    implementation("androidx.camera:camera-view:${camerax_version}")
    // If you want to additionally add CameraX ML Kit Vision Integration
    implementation("androidx.camera:camera-mlkit-vision:${camerax_version}")
    // If you want to additionally use the CameraX Extensions library
    implementation("androidx.camera:camera-extensions:${camerax_version}")
}

これらの要件に準拠するようにアプリを設定する方法について詳しくは、依存関係の宣言についての説明をご覧ください。

CameraX と Camera2 との相互運用性

CameraX は Camera2 の上にビルドされ、Camera2 の実装のプロパティを読み取る方法に加え、書き込む方法も公開します。詳しくは、相互運用パッケージについての説明をご覧ください。

CameraX が Camera2 のプロパティをどのように設定しているかについて詳しくは、Camera2CameraInfo を使用して基盤となる CameraCharacteristics を読み取ってください。また、基盤となる Camera2 のプロパティを、次の 2 つの方法のいずれかで書き込むこともできます。

次のコードサンプルでは、ストリーミングのユースケースを使用してビデオ通話向けに最適化しています。 Camera2CameraInfo を使用して、ビデオ通話ストリーミングのユースケースが利用可能かどうかをフェッチします。次に、Camera2Interop.Extender を使用して、基盤となるストリーミングのユースケースを設定します。

Kotlin

// Set underlying Camera2 stream use case to optimize for video calls.

val videoCallStreamId =
    CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong()

// Check available CameraInfos to find the first one that supports
// the video call stream use case.
val frontCameraInfo = cameraProvider.getAvailableCameraInfos()
    .first { cameraInfo ->
        val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo)
            .getCameraCharacteristic(
                CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
            )?.contains(videoCallStreamId)
        val isFrontFacing = (cameraInfo.getLensFacing() == 
                             CameraSelector.LENS_FACING_FRONT)
        (isVideoCallStreamingSupported == true) && isFrontFacing
    }

val cameraSelector = frontCameraInfo.cameraSelector

// Start with a Preview Builder.
val previewBuilder = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation)

// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId)

// Bind the Preview UseCase and the corresponding CameraSelector.
val preview = previewBuilder.build()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)

Java

// Set underlying Camera2 stream use case to optimize for video calls.

Long videoCallStreamId =
    CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong();

// Check available CameraInfos to find the first one that supports
// the video call stream use case.
List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos();
CameraInfo frontCameraInfo = null;
for (cameraInfo in cameraInfos) {
    Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo)
        .getCameraCharacteristic(
            CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
        );
    boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases)
                .contains(videoCallStreamId);
    boolean isFrontFacing = (cameraInfo.getLensFacing() ==
                             CameraSelector.LENS_FACING_FRONT);

    if (isVideoCallStreamingSupported && isFrontFacing) {
        frontCameraInfo = cameraInfo;
    }
}

if (frontCameraInfo == null) {
    // Handle case where video call streaming is not supported.
}

CameraSelector cameraSelector = frontCameraInfo.getCameraSelector();

// Start with a Preview Builder.
Preview.Builder previewBuilder = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation);

// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId);

// Bind the Preview UseCase and the corresponding CameraSelector.
Preview preview = previewBuilder.build()
Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)

参考情報

CameraX について詳しくは、以下の参考情報をご確認ください。

Codelab

  • CameraX のスタートガイド
  • コードサンプル

  • CameraX のサンプルアプリ