CameraX のアーキテクチャ

Jetpack に追加された CameraX を使用すると、Camera2 API の機能を簡単に利用できます。このトピックでは、CameraX の構造、API との連携方法、ライフサイクルとの連携方法、ユースケースを組み合わせる方法を含め、CameraX のアーキテクチャについて説明します。

CameraX の構造

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

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

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

API モデル

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

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

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

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

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

API モデルの例

プレビュー ユースケースは、ディスプレイ用の 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));
}

サポートされている設定の組み合わせを以下に示します。

プレビュー 分析 画像キャプチャ ユースケースの組み合わせ
プレビューの表示、写真の撮影、画像ストリームの解析を行います。
  写真の撮影、画像ストリームの解析を行います。
  ストリーミングされている画像の解析結果に基づいて視覚効果を適用してプレビューを表示します。
  カメラから見える画像を表示し、ユーザーの操作に基づいて写真を撮影します。

拡張機能が有効になっている場合、ImageCapturePreview のみが保証されます。OEM の実装によっては、ImageAnalysis も使用できない場合があります。

ImageCapture は単独で機能しませんが、PreviewImageAnalysis は単独で機能します。

権限

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

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

要件

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

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

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

依存関係の宣言

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

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

Groovy

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

Kotlin

allprojects {
    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.0.2"
  // 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 View class
  implementation "androidx.camera:camera-view:1.0.0-alpha29"
  // If you want to additionally use the CameraX Extensions library
  implementation "androidx.camera:camera-extensions:1.0.0-alpha29"
}

Kotlin

dependencies {
    // CameraX core library using the camera2 implementation
    val camerax_version = "1.0.2"
    // 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 View class
    implementation("androidx.camera:camera-view:1.0.0-alpha29")
    // If you want to additionally use the CameraX Extensions library
    implementation("androidx.camera:camera-extensions:1.0.0-alpha29")
}

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

参考情報

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

Codelab

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

  • CameraX の公式サンプルアプリ