このページでは、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
は、Preview
、ImageCapture
、ImageAnalysis
です。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 つのライフサイクル対応コンポーネント(AppCompatActivity
や AppCompat
フラグメントなど)のライフサイクルに対応している場合、使用するすべてのユースケースをバインドするときにそのコンポーネントのライフサイクルを利用することで、ライフサイクル対応コンポーネントがアクティブなときにカメラ機能をすぐに使用できます。アクティブでないときは、安全にカメラ機能を破棄してリソースの消費を避けられます。
カスタムの 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)); }
次の構成の組み合わせはサポートが保証されています(プレビューまたは動画キャプチャが必要であるが、同時に両方が必要でない場合)。
プレビューまたは動画キャプチャ | 画像キャプチャ | 分析 | 説明 |
---|---|---|---|
プレビューの表示または動画の撮影、写真の撮影、画像ストリームの解析を行います。 | |||
写真の撮影、画像ストリームの解析を行います。 | |||
プレビューの表示または動画の撮影、写真の撮影を行います。 | |||
プレビューの表示または動画の撮影、画像ストリームの解析を行います。 |
プレビューと動画キャプチャの両方が必要な場合、以下のユースケースの組み合わせが条件付きでサポートされます。
プレビュー | 動画キャプチャ | 画像キャプチャ | 分析 | 特別な要件 |
---|---|---|---|---|
すべてのカメラで保証されます | ||||
LIMITED(またはそれ以上)のカメラデバイス。 | ||||
LEVEL_3(またはそれ以上)のカメラデバイス。 |
加えて次の点にもご注意ください。
- すべてのユースケースは単独で機能します。たとえば、アプリはプレビューを使用せずに動画を撮影できます。
- 拡張機能が有効になっている場合、
ImageCapture
とPreview
の組み合わせのみ、機能することが保証されます。OEM の実装によっては、ImageAnalysis
も追加できない場合があります。VideoCapture
ユースケースでは拡張機能を有効にできません。詳しくは、拡張機能のリファレンス ドキュメントをご覧ください。 - カメラの機能によっては、一部のカメラで低解像度モードで組み合わせをサポートする場合もありますが、高解像度では同じ組み合わせをサポートできない場合があります。
サポートされているハードウェア レベルは 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\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-alpha01" // 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-alpha01" // 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 つの方法のいずれかで書き込むこともできます。
Camera2CameraControl
を使用する: オートフォーカス モードなど、基盤となるCaptureRequest
のプロパティを設定できます。CameraX の
UseCase
をCamera2Interop.Extender
で拡張する:Camera2CameraControl
と同様に、CaptureRequest のプロパティを設定できます。また、ストリーミングのユースケースを設定して使用シナリオに合わせてカメラを最適化するなど、そのほかの設定も行えます。詳しくは、ストリームのユースケースを使用してパフォーマンスを向上させるをご覧ください。
次のコードサンプルでは、ストリーミングのユースケースを使用してビデオ通話向けに最適化しています。
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
コードサンプル