構成オプション

CameraX ユースケースを個別に構成すると、ユースケースのさまざまな処理を制御できます。

たとえば、画像キャプチャ ユースケースでは、ターゲット アスペクト比とフラッシュ モードを設定できます。次のコードはその一例を示しています。

Kotlin

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

Java

ImageCapture imageCapture =
    new ImageCapture.Builder()
        .setFlashMode(...)
        .setTargetAspectRatio(...)
        .build();

一部のユースケースでは、構成オプションに加えて、ユースケースの作成後に設定を動的に変更する API も公開されています。個々のユースケースに固有の構成については、プレビューを実装する画像を解析する画像キャプチャをご覧ください。

CameraXConfig

簡便化のために、CameraX には、ほとんどの使用シナリオに適した内部エグゼキュータやハンドラなどのデフォルト構成が用意されています。ただし、アプリに特別な要件がある場合や、デフォルト構成をカスタマイズする場合には、CameraXConfig がインターフェースになります。

CameraXConfig を使用すると、アプリで次のことができます。

使用モデル

CameraXConfig を使用する手順は次のとおりです。

  1. カスタマイズした構成を使用して、CameraXConfig オブジェクトを作成します。
  2. ApplicationCameraXConfig.Provider インターフェースを実装し、getCameraXConfig()CameraXConfig オブジェクトを返します。
  3. こちらに記載されている手順に沿って、Application クラスを AndroidManifest.xml ファイルに追加します。

たとえば、次のコードサンプルでは、CameraX のロギングの対象をエラー メッセージに限定しています。

Kotlin

class CameraApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
           .setMinimumLoggingLevel(Log.ERROR).build()
   }
}

CameraX の構成を設定した後でアプリがその構成を認識する必要がある場合は、CameraXConfig オブジェクトのローカルコピーを保持します。

カメラ リミッター

ProcessCameraProvider.getInstance() の初回呼び出しの際に、CameraX はデバイスで使用できるカメラの特性を列挙および照会します。CameraX はハードウェア コンポーネントと通信する必要があるため、特にローエンドのデバイスでは、カメラごとに行われるこのプロセスにかなりの時間がかかることがあります。アプリがデバイスの特定のカメラ(デフォルトの前面カメラなど)のみを使用する場合は、他のカメラを無視するよう CameraX を設定することで、アプリが使用するカメラの起動レイテンシを短縮できます。

CameraXConfig.Builder.setAvailableCamerasLimiter() に渡される CameraSelector によってカメラが除外されると、CameraX はそのカメラが存在しないかのように動作します。たとえば、次のコードは、アプリが使用するカメラをデバイスのデフォルトの背面カメラに限定しています。

Kotlin

class MainApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
              .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
              .build()
   }
}

スレッド

CameraX が構築されるたいていのプラットフォーム API では、ハードウェアとのプロセス間通信(IPC)のブロックが必要です。この処理には、応答まで数百ミリ秒かかる場合があります。このため、CameraX はバックグラウンド スレッドからのみこれらの API を呼び出します。そうするとメインスレッドはブロックされず、滑らかな UI を維持できます。この動作が透過的に見えるように、CameraX はこれらのバックグラウンド スレッドを内部的に管理します。ただし、アプリの中にはスレッドの厳密な制御を必要とするものもあります。CameraXConfig を使用すると、アプリは CameraXConfig.Builder.setCameraExecutor()CameraXConfig.Builder.setSchedulerHandler() を通じて使用されるバックグラウンド スレッドを設定できます。

カメラ エグゼキュータ

カメラ エグゼキュータは、すべての内部的な Camera プラットフォーム API 呼び出しと、これらの API からのコールバックに使用されます。CameraX は、このタスクを実行するために内部 Executor を割り当てて管理します。ただし、アプリがスレッドのより厳密な制御を必要とする場合は、CameraXConfig.Builder.setCameraExecutor() を使用してください。

スケジューラ ハンドラ

スケジューラ ハンドラは、内部タスクを一定の間隔でスケジュールするために使用されます。たとえば、カメラが使用できずカメラを開く処理を再試行する場合などです。このハンドラはジョブを実行せず、カメラ エグゼキュータにジョブをディスパッチするだけです。コールバック用に Handler を必要とする従来の API プラットフォームで使用される場合もあります。その場合も、コールバックはカメラ エグゼキュータに直接ディスパッチされるだけです。CameraX はこのタスクを実行するために内部 HandlerThread を割り当てて管理しますが、これは CameraXConfig.Builder.setSchedulerHandler() でオーバーライドできます。

ロギング

CameraX のロギングを使用すると、アプリは logcat メッセージをフィルタできます。これは、本番環境のコードで冗長なメッセージを回避するためのおすすめの方法です。CameraX は、最も詳細なレベルから最も重大なレベルまで 4 段階のロギングレベルをサポートしています。

  • Log.DEBUG(デフォルト)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

これらのログレベルについて詳しくは、Android の Log のドキュメントをご覧ください。アプリに適切なロギングレベルを設定するには、CameraXConfig.Builder.setMinimumLoggingLevel(int) を使用します。

自動選択

CameraX は、アプリが実行されているデバイスに固有の機能を自動的に提供します。たとえば、解像度を指定していない場合や、指定した解像度がサポートされていない場合、使用する最適な解像度を自動的に決定します。これらはすべてライブラリによって処理されるため、デバイス固有のコードを記述する必要はありません。

CameraX の目標は、カメラ セッションを正常に初期化することです。そのために、CameraX はデバイス機能に基づいて解像度とアスペクト比を低下させることがあります。これは次のような場合に発生します。

  • デバイスが、リクエストされた解像度に対応していない。
  • デバイスに互換性の問題がある。たとえば、古いデバイスの中には、正常に動作するために特定の解像度を必要とするものがあります。
  • 一部のデバイスで、特定のアスペクト比でのみ使用可能な特定の形式がある。
  • デバイスで、JPEG または動画エンコードについて「mod16 に最も近い」値が優先される。 詳細については、SCALER_STREAM_CONFIGURATION_MAP をご覧ください。

CameraX はセッションを作成し管理しますが、コードのユースケース出力で返される画像サイズを常に確認し、それに応じて調整しています。

回転

カメラの回転はデフォルトでは、ユースケースの作成時にデフォルトのディスプレイの回転と一致するように設定されます。このデフォルトのケースでは、CameraX は、ユーザーが期待するプレビューとアプリとが一致するよう出力を生成します。マルチディスプレイ デバイスに対応するため、回転をカスタム値に変更できます。そのためには、ユースケース オブジェクトの構成時に、またはユースケース オブジェクトが作成されてから動的に、現在のディスプレイの向きを渡します。

アプリは、構成設定を使用してターゲットの回転を設定できます。その後、ライフサイクルが実行状態であっても、ユースケース API のメソッド(ImageAnalysis.setTargetRotation() など)を使用して回転設定を更新できます。この方法は、アプリが縦向きにロックされている(そのため、回転時に再構成が行われない)場合に使用できます。ただし、写真または解析ユースケースがデバイスの現在の回転を認識できる必要があります。たとえば、顔の向きを正しく検出する場合や、写真を横向きまたは縦向きに設定する場合は、回転の認識が必要になります。

キャプチャした画像のデータは、回転情報なしで保存されることがあります。EXIF データには回転情報が含まれるため、保存後にギャラリー アプリは画像を正しい向きで表示できます。

プレビュー データを正しい向きで表示するには、Preview.PreviewOutput() のメタデータ出力を使用して変換を作成します。

次のコードサンプルは、向きのイベントで回転を設定する方法を示しています。

Kotlin

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation : Int) {
            // Monitors orientation values to determine the target rotation value
            val rotation : Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

Java

@Override
public void onCreate() {
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) {
       @Override
       public void onOrientationChanged(int orientation) {
           int rotation;

           // Monitors orientation values to determine the target rotation value
           if (orientation >= 45 && orientation < 135) {
               rotation = Surface.ROTATION_270;
           } else if (orientation >= 135 && orientation < 225) {
               rotation = Surface.ROTATION_180;
           } else if (orientation >= 225 && orientation < 315) {
               rotation = Surface.ROTATION_90;
           } else {
               rotation = Surface.ROTATION_0;
           }

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

各ユースケースは、設定された回転に基づいて、画像データを直接回転させるか、または回転していない画像データの一般ユーザーに回転メタデータを提供します。

  • Preview: Preview.getTargetRotation() によりターゲット解像度の回転を認識できるようなメタデータ出力が提供されます。
  • ImageAnalysis: 画像バッファ座標と表示座標の相対関係を認識できるようなメタデータ出力が提供されます。
  • ImageCapture: 画像の EXIF メタデータまたはバッファ、あるいはメタデータとバッファの両方が変更され、回転設定が記録されます。変更される値は HAL 実装によって異なります。

切り抜き範囲

デフォルトでは、切り抜き範囲はバッファ範囲と一致します。切り抜き範囲は ViewPortUseCaseGroup を使ってカスタマイズできます。ユースケースをグループ化してビューポートを設定すると、CameraX でグループ内のすべてのユースケースの切り抜き範囲がカメラセンサーの同じ領域をポイントするようになります。

次のコード スニペットは、これら 2 つのクラスの使用方法を示しています。

Kotlin

val viewPort =  ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

Java

ViewPort viewPort = new ViewPort.Builder(
         new Rational(width, height),
         getDisplay().getRotation()).build();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);

ViewPort は、エンドユーザーに表示されるバッファ範囲を定義します。CameraX は、ビューポートのプロパティと接続されたユースケースに基づいて、最大切り抜き範囲を計算します。通常、WYSIWYG 効果を実現するには、プレビューのユースケースに基づいてビューポートを構成できます。ビューポートを簡単に取得するには、PreviewView を使用します。

次のコード スニペットの例は、ViewPort オブジェクトを取得する方法を示しています。

Kotlin

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

Java

ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();

上記の例では、PreviewView のスケールタイプがデフォルトの FILL_CENTER に設定されている場合、アプリが ImageAnalysisImageCapture から取得する範囲と PreviewView でエンドユーザーに表示される範囲とが一致します。切り抜き範囲と回転を出力バッファに適用すると、すべてのユースケースの画像は解像度の違いはあるものの、同じになります。変換情報の適用方法について詳しくは、出力を変換するをご覧ください。

カメラの選択

CameraX は、アプリケーションの要件とユースケースに最適なカメラデバイスを自動的に選択します。選択されたデバイスとは異なるデバイスを使用する場合は、いくつかの方法があります。

次のコードサンプルは、デバイスの選択に影響を与える CameraSelector の作成方法を示しています。

Kotlin

fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? {
   val cam2Infos = provider.availableCameraInfos.map {
       Camera2CameraInfo.from(it)
   }.sortedByDescending {
       // HARDWARE_LEVEL is Int type, with the order of:
       // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL
       it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
   }

   return when {
       cam2Infos.isNotEmpty() -> {
           CameraSelector.Builder()
               .addCameraFilter {
                   it.filter { camInfo ->
                       // cam2Infos[0] is either EXTERNAL or best built-in camera
                       val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                       thisCamId == cam2Infos[0].cameraId
                   }
               }.build()
       }
       else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera)
val selector = selectExternalOrBestCamera(processCameraProvider)
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

複数のカメラを同時に選択する

CameraX 1.3 以降では、複数のカメラを同時に選択することもできます。たとえば、前面カメラと背面カメラにバインドすることで、両方向から同時に写真や動画を撮影できます。

同時カメラ機能を使用すると、デバイスは異なる向きのレンズで 2 つのカメラを同時に動作させるか、2 つの背面カメラを同時に動作させることができます。次のコードブロックは、bindToLifecycle を呼び出す際に 2 つのカメラを設定する方法と、返された ConcurrentCamera オブジェクトから両方のカメラ オブジェクトを取得する方法を示しています。

Kotlin

// Build ConcurrentCameraConfig
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

Java

// Build ConcurrentCameraConfig
SingleCameraConfig primary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

SingleCameraConfig secondary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

ConcurrentCamera concurrentCamera =  
    mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary));

Camera primaryCamera = concurrentCamera.getCameras().get(0);
Camera secondaryCamera = concurrentCamera.getCameras().get(1);

カメラの解像度

デバイスの機能、デバイスがサポートされているハードウェア レベル、ユースケース、指定されたアスペクト比の組み合わせに基づいて、CameraX が画像解像度を設定するように選択できます。または、特定のターゲット解像度や特定のアスペクト比を、その構成をサポートするユースケースに設定することもできます。

自動解像度

CameraX は、cameraProcessProvider.bindToLifecycle() で指定されたユースケースに基づいて最適な解像度設定を自動的に決定します。可能な限り、1 回のセッションで同時に実行する必要のあるユースケースを、1 回の bindToLifecycle() 呼び出しですべて指定してください。CameraX は、デバイスがサポートされているハードウェア レベルや、デバイス固有の差異(デバイスが利用可能なストリーム構成の基準を超えている、または満たしていない)を考慮して、バインドされているユースケースのセットに基づいて解像度を決定します。これは、デバイス固有のコードパスを最小限に抑えつつ、アプリをさまざまなデバイスで実行可能にするためです。

画像キャプチャ ユースケースと画像解析ユースケースのデフォルトのアスペクト比は 4:3 です。

UI の設計に基づいてアプリで目的のアスペクト比を指定できるようにするために、ユースケースには設定可能なアスペクト比があります。CameraX の出力は、リクエストされたアスペクト比とデバイスがサポートするアスペクト比ができる限り一致するように生成されます。完全に一致する解像度がサポートされていない場合、条件に最も近い解像度が選択されます。このようにしてアプリは、アプリ内におけるカメラのアスペクト比を決定し、CameraX は、各デバイスでそのアスペクト比に最も適合するカメラ解像度の設定を決定します。

たとえば、アプリは以下のような操作を実行できます。

  • ユースケースのターゲット解像度を 4:3 または 16:9 に指定する
  • カスタム解像度を指定する(CameraX はこれに最も近い解像度を探します)
  • ImageCapture の切り抜きのアスペクト比を指定する

CameraX は、内部の Camera2 サーフェス解像度を自動的に選択します。次の表にその解像度を示します。

ユースケース 内部のサーフェス解像度 出力データの解像度
試聴 アスペクト比: 設定目標に最も適合する解像度。 内部のサーフェス解像度。メタデータは、ターゲット アスペクト比に合わせてビューを切り抜き、スケーリングし、回転させる目的で提供されます。
デフォルトの解像度: プレビューの最大解像度、またはプレビューのアスペクト比に適合するデバイス推奨の最大解像度。
最大解像度: プレビュー サイズ。つまり、デバイスの画面解像度に最適なサイズまたは 1080p(1920x1080)のうち、いずれか小さいほうのサイズ。
画像解析 アスペクト比: 設定目標に最も適合する解像度。 内部のサーフェス解像度。
デフォルトの解像度: ターゲット解像度のデフォルト設定は 640x480 です。ターゲット解像度と、対応するアスペクト比の両方を調整することで、サポートされる最適な解像度に設定されます。
最大解像度: StreamConfigurationMap.getOutputSizes() から取得する、YUV_420_888 形式のカメラデバイスの最大出力解像度。 ターゲット解像度はデフォルトで 640x480 に設定されているため、640x480 より大きい解像度が必要な場合は、setTargetResolution()setTargetAspectRatio() を使用して、サポートされている解像度の中から最も近いものを選択します。
画像キャプチャ アスペクト比: 設定に最も適合するアスペクト比。 内部のサーフェス解像度。
デフォルトの解像度: 使用可能な最大解像度、または ImageCapture のアスペクト比に適合するデバイス推奨の最大解像度。
最大解像度: JPEG 形式でのカメラデバイスの最大出力解像度。これを取得するには、StreamConfigurationMap.getOutputSizes() を使用します。

解像度を指定する

次のコードサンプルに示すように、ユースケースを作成する際に setTargetResolution(Size resolution) メソッドを使用して特定の解像度を設定できます。

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

Java

ImageAnalysis imageAnalysis =
  new ImageAnalysis.Builder()
    .setTargetResolution(new Size(1280, 720))
    .build();

同じユースケースに、ターゲット アスペクト比とターゲット解像度の両方を設定することはできません。これを行うと、構成オブジェクトの作成時に IllegalArgumentException がスローされます。

解像度 Size を、サポートされているサイズをターゲット回転で回転させた後の座標フレームで表してください。たとえば、自然な向きが縦向きであるデバイスがあるとします。ターゲットの回転が自然な縦向きになっているときに、縦向きの画像のリクエストに対して 480x640 を指定したら、同じデバイスを 90 度回転して横向きをターゲットにします。その場合は 640x480 を指定します。

ターゲット解像度を使用して、画像解像度の最小値を確立する試みです。実際の画像解像度は、ターゲット解像度以上で最も近い解像度になります。これはカメラの実装により決まります。

ただし、ターゲット解像度以上の解像度が存在しない場合は、ターゲット解像度よりも小さく最も近い解像度が選択されます。指定された Size と同じアスペクト比の解像度は、異なるアスペクト比の解像度よりも優先されます。

CameraX では、リクエストに基づいて最適な解像度が適用されます。アスペクト比を満たすことが最も重要である場合は、setTargetAspectRatio のみを指定します。CameraX は、デバイスに応じた適切な解像度を決定します。アプリの画像処理を効率化することが最も重要で、そのために解像度を指定する必要がある場合(デバイスの処理能力に応じて小サイズや中サイズの画像を扱う場合など)は、setTargetResolution(Size resolution) を使用します。

アプリで正確な解像度が必要な場合は、createCaptureSession() の表で、各ハードウェア レベルでサポートされている最大解像度を確認してください。現行のデバイスでサポートされている解像度を確認するには、StreamConfigurationMap.getOutputSizes(int) をご覧ください。

Android 10 以上でアプリを実行している場合は、isSessionConfigurationSupported() を使用して特定の SessionConfiguration を確認できます。

カメラ出力の制御

CameraX では、個々のユースケースごとに必要に応じてカメラ出力を構成できるだけでなく、以下のように、バインドされたすべてのユースケースに共通のカメラ処理をサポートするインターフェースも実装されています。

  • CameraControl: 一般的なカメラ機能を設定できます。
  • CameraInfo: 上記の一般的なカメラ機能の状態をクエリできます。

CameraControl は以下のカメラ機能に対応しています。

  • ズーム
  • トーチ
  • フォーカスと測光(タップしてフォーカス)
  • 露出補正

CameraControl と CameraInfo のインスタンスを取得する

ProcessCameraProvider.bindToLifecycle() から返された Camera オブジェクトを使って、CameraControlCameraInfo のインスタンスを取得します。次のコードは、その一例です。

Kotlin

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

Java

Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
CameraControl cameraControl = camera.getCameraControl()
// For querying information and states.
CameraInfo cameraInfo = camera.getCameraInfo()

たとえば、bindToLifecycle() を呼び出した後にズームなどの CameraControl 処理を送信できます。カメラ インスタンスをバインドするために使用されるアクティビティを停止または破棄すると、CameraControl は処理を実行できなくなり、エラーの ListenableFuture を返します。

ズーム

CameraControl には、ズームレベルを変更する 2 つのメソッドがあります。

  • setZoomRatio() は、ズーム倍率でズームを設定します。

    倍率は CameraInfo.getZoomState().getValue().getMinZoomRatio() から CameraInfo.getZoomState().getValue().getMaxZoomRatio() までの範囲内にする必要があります。そうでない場合、関数はエラーの ListenableFuture を返します。

  • setLinearZoom() は、現在のズームを 0~1.0 の線形ズーム値で設定します。

    線形ズームの利点は、ズームの変化に応じて画角(FOV)をスケーリングできることです。そのため、Slider ビューでの使用に最適です。

CameraInfo.getZoomState() は、現在のズーム状態の LiveData を返します。この値は、カメラの初期化時や、setZoomRatio() または setLinearZoom() を使ってズームレベルが設定されたときに変化します。どちらのメソッドを呼び出しても、ZoomState.getZoomRatio()ZoomState.getLinearZoom() を裏付ける値が設定されます。これは、スライダーとともにズーム倍率をテキスト表示する場合に適しています。 ZoomState LiveData を監視するだけで、変換を必要とせずに両方を更新できます。

両方の API から返される ListenableFuture は、指定されたズーム値でリクエストが繰り返されたとき、それが完了したことをアプリケーションに通知するオプションを提供します。また、前の処理の実行中に新しいズーム値を設定すると、前のズーム処理の ListenableFuture は即座にエラーを報告します。

トーチ

CameraControl.enableTorch(boolean) は、トーチ(ライト)を有効または無効にします。

CameraInfo.getTorchState() を使用すると、現在のトーチの状態をクエリできます。トーチが使用できるかどうかは、CameraInfo.hasFlashUnit() から返される値で確認できます。使用できない場合、CameraControl.enableTorch(boolean) を呼び出すと、返された ListenableFuture は即座にエラーで終了し、トーチの状態は TorchState.OFF に設定されます。

トーチを有効にすると、flashMode 設定に関係なく、写真や動画のキャプチャ中もオンのままになります。ImageCaptureflashMode は、トーチが無効になっている場合にのみ機能します。

フォーカスと測光

CameraControl.startFocusAndMetering() は、指定された FocusMeteringAction に基づいて AF/AE/AWB 測光領域を設定することで、オートフォーカスと露出測定をトリガーします。これは、多くのカメラアプリで「タップしてフォーカス」機能を実装する際によく使用されます。

MeteringPoint

まず、MeteringPointFactory.createPoint(float x, float y, float size) を使用して MeteringPoint を作成します。MeteringPoint は、カメラ Surface 上の一点を表します。正規化された形式で保存されるため、AF/AE/AWB 領域を指定するためのセンサー座標に簡単に変換できます。

MeteringPoint のサイズは 0~1 で、デフォルト サイズは 0.15f です。MeteringPointFactory.createPoint(float x, float y, float size) を呼び出すと、CameraX により、size で指定されたサイズで (x, y) を中心とする長方形の領域が作成されます。

次のコードは、MeteringPoint の作成方法を示しています。

Kotlin

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)
…
}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for
// preview. Please note that if the preview is scaled or cropped in the View,
// it’s the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

startFocusAndMetering と FocusMeteringAction

startFocusAndMetering() を呼び出すには、アプリで FocusMeteringAction を作成する必要があります。これは、1 つ以上の MeteringPoints と省略可能な FLAG_AFFLAG_AEFLAG_AWB を組み合わせた測光モードで構成されています。使用方法を次のコードで示します。

Kotlin

val meteringPoint1 = meteringPointFactory.createPoint(x1, x1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB
      // Optionally add meteringPoint2 for AF/AE.
      .addPoint(meteringPoint2, FLAG_AF | FLAG_AE)
      // The action is canceled in 3 seconds (if not set, default is 5s).
      .setAutoCancelDuration(3, TimeUnit.SECONDS)
      .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering result.
result.addListener({
   // result.get().isFocusSuccessful returns if the auto focus is successful or not.
}, ContextCompat.getMainExecutor(this)

上記のコードに示すように、startFocusAndMetering()は、AF/AE/AWB 測光領域の一つの MeteringPoint と、AF/AE 限定の別の MeteringPoint から成る FocusMeteringAction を取得します。

内部的には、それを CameraX が Camera2 MeteringRectangles に変換し、対応する CONTROL_AF_REGIONS / CONTROL_AE_REGIONS / CONTROL_AWB_REGIONS パラメータをキャプチャ リクエストに設定します。

すべてのデバイスが AF/AE/AWB と複数領域をサポートしているわけではないため、CameraX はベスト エフォートで FocusMeteringAction を実行します。CameraX では、サポートされている MeteringPoint の最大数が、追加された順に使用されます。最大数を超えて追加された MeteringPoint はすべて無視されます。たとえば FocusMeteringAction が、2 つしかサポートしていないプラットフォームに 3 つの MeteringPoint を与えた場合、初めの 2 つの MeteringPoint のみが使用されます。最後の MeteringPoint は無視されます。

露出補正

露出補正は、自動露出(AE)の出力結果以外にも、さらにアプリで露出値(EV)の微調整が必要な場合に便利です。次のように露出補正値を組み合わせて、現在の画像状態に必要な露出を決定します。

Exposure = ExposureCompensationIndex * ExposureCompensationStep

CameraX には、露出補正をインデックス値として設定する Camera.CameraControl.setExposureCompensationIndex() 関数が用意されています。

正の値を指定すると画像が明るくなり、負の値を指定すると画像が暗くなります。次のセクションで説明する CameraInfo.ExposureState.exposureCompensationRange() で、アプリがサポートしている範囲をクエリできます。値がサポートされている場合、キャプチャ リクエストでその値が正常に有効になると、返された ListenableFuture は終了します。指定したインデックスがサポート範囲外の場合、setExposureCompensationIndex() は返された ListenableFuture を直ちにエラーで終了させます。

CameraX では、最新の setExposureCompensationIndex() リクエストのみが保持され、前のリクエストの実行前にこの関数が複数回呼び出されると、それはキャンセルされます。

次のスニペットは露出補正インデックスを設定し、露出の変更リクエスト実行時に呼び出されるコールバックを登録します。

Kotlin

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
   .addListener({
      // Get the current exposure compensation index, it might be
      // different from the asked value in case this request was
      // canceled by a newer setting request.
      val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
      …
   }, mainExecutor)
  • Camera.CameraInfo.getExposureState() は、以下を含む現在の ExposureState を取得します。

    • 露出補正制御のサポート状況
    • 現在の露出補正インデックス
    • 露出補正インデックスの範囲
    • 露出補正値の計算に使用される露出補正ステップ

たとえば、次のコードは、露出の SeekBar の設定を現在の ExposureState 値で初期化します。

Kotlin

val exposureState = camera.cameraInfo.exposureState
binding.seekBar.apply {
   isEnabled = exposureState.isExposureCompensationSupported
   max = exposureState.exposureCompensationRange.upper
   min = exposureState.exposureCompensationRange.lower
   progress = exposureState.exposureCompensationIndex
}

参考情報

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

Codelab

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

  • CameraX のサンプルアプリ
  • デベロッパー コミュニティ

    Android CameraX ディスカッション グループ