カメラのキャプチャ セッションとリクエスト

注: このページでは、Camera2 パッケージについて説明します。アプリが Camera2 の特定の低レベルの機能を必要とする場合を除き、CameraX を使用することをおすすめします。CameraX と Camera2 は、どちらも Android 5.0(API レベル 21)以降に対応しています。

1 台の Android 搭載デバイスに複数のカメラを設定できます。各カメラは CameraDevice であり、CameraDevice は複数のストリームを同時に出力できます。

これを行う理由の 1 つは、CameraDevice から送られる 1 つのストリーム(連続したカメラフレーム)が、ビューファインダーの表示などの特定のタスク用に最適化され、他のストリームが写真の撮影や録画に使用される場合があるためです。ストリームは、カメラから出力される RAW フレームを一度に 1 フレームずつ処理する並列パイプラインとして機能します。

図 1. ユニバーサル カメラアプリの構築(Google I/O ‘18)のイラスト

並列処理では、CPU、GPU、その他のプロセッサで使用可能な処理能力に応じてパフォーマンスの上限が上がる可能性があります。パイプラインが受信フレームについていけなくなると、そのフレームのドロップを開始します。

各パイプラインには独自の出力形式があります。取り込まれた元データは、各パイプラインに関連付けられた暗黙的なロジックによって自動的に適切な出力形式に変換されます。このページのコードサンプル全体で使用される CameraDevice は特定のものではないため、先に進む前に、まず使用可能なカメラをすべて列挙します。

CameraDevice を使用して、その CameraDevice に固有の CameraCaptureSession を作成できます。CameraDevice は、CameraCaptureSession を使用して各未加工フレームのフレーム構成を受け取る必要があります。この構成では、オートフォーカス、絞り、エフェクト、露出などのカメラ属性を指定します。ハードウェアの制約のため、カメラセンサーで常にアクティブになる構成は 1 つだけです。これはアクティブ構成と呼ばれます。

ただし、ストリーミングのユースケースでは、CameraDevice を使用してキャプチャ セッションをストリーミングする以前の方法を拡張し、特定のユースケースに合わせてカメラ ストリームを最適化できます。たとえば、ビデオ通話を最適化する際のバッテリー駆動時間を改善できます。

CameraCaptureSession は、CameraDevice にバインドされる可能性があるすべてのパイプラインを記述します。セッションの作成中にパイプラインを追加または削除することはできません。CameraCaptureSessionCaptureRequest のキューを維持します。このキューがアクティブな構成になります。

CaptureRequest はキューに構成を追加し、CameraDevice からフレームを受信するために使用可能なパイプラインを 1 つ、複数、またはすべて選択します。キャプチャ セッションの間、多くのキャプチャ リクエストを送信できます。各リクエストで、有効な構成と、未加工の画像を受け取る出力パイプラインのセットを変更できます。

ストリームのユースケースを使用してパフォーマンスを高める

ストリームのユースケースは、Camera2 のキャプチャ セッションのパフォーマンスを向上させる方法です。これにより、ハードウェア デバイスにパラメータを調整するためのより多くの情報が提供されるため、特定のタスクに適したカメラ エクスペリエンスが提供されます。

これにより、カメラデバイスは、各ストリームのユーザー シナリオに基づいて、カメラのハードウェアとソフトウェアのパイプラインを最適化できます。ストリームのユースケースの詳細については、setStreamUseCase をご覧ください。

ストリームのユースケースでは、CameraDevice.createCaptureRequest() でテンプレートを設定するだけでなく、特定のカメラ ストリームの使用方法をより詳細に指定できます。これにより、カメラ ハードウェアは、特定のユースケースに適した品質やレイテンシのトレードオフに基づいて、チューニング、センサーモード、カメラセンサー設定などのパラメータを最適化できます。

ストリームのユースケースの例:

  • DEFAULT: 既存のアプリの動作をすべて網羅します。これは、ストリーム ユースケースを設定しないのと同じです。

  • PREVIEW: ビューファインダーまたはアプリ内画像解析に適しています。

  • STILL_CAPTURE: 高品質の高解像度キャプチャ向けに最適化されます。プレビューと同様のフレームレートを維持できるわけではありません。

  • VIDEO_RECORD: 高画質な手ぶれ補正など、高画質の動画撮影向けに最適化されています(デバイスが対応しており、アプリが対応している場合)。このオプションでは、最高品質の安定化やその他の処理を可能にするために、リアルタイムからかなりの遅れのある出力フレームが生成される場合があります。

  • VIDEO_CALL: 消費電力が懸念される、長時間稼働するカメラに推奨されます。

  • PREVIEW_VIDEO_STILL: ソーシャル メディア アプリまたは単一ストリームのユースケースに推奨されます。多目的配信である。

  • VENDOR_START: OEM が定義するユースケースに使用されます。

CameraCaptureSession を作成する

カメラ セッションを作成するには、アプリが出力フレームを書き込むことができる出力バッファを 1 つ以上指定します。各バッファはパイプラインを表します。フレームワークでデバイスの内部パイプラインを構成し、必要な出力ターゲットにフレームを送信するためのメモリバッファを割り当てることができるように、カメラの使用を開始する前にこの操作を行う必要があります。

次のコード スニペットは、2 つの出力バッファ(1 つは SurfaceView に属する、もう 1 つは ImageReader に属する)を使用してカメラ セッションを準備する方法を示しています。PREVIEW ストリームのユースケースを previewSurface に、STILL_CAPTURE ストリームのユースケースを imReaderSurface に追加すると、デバイス ハードウェアはこれらのストリームをさらに最適化できます。

Kotlin


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
// analysis
// 3. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
// 4. RenderScript.Allocation, if you want to do parallel processing
val surfaceView = findViewById<SurfaceView>(...)
val imageReader = ImageReader.newInstance(...)

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)

// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready
// Setup Stream Use Case while setting up your Output Configuration.
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun configureSession(device: CameraDevice, targets: List<Surface>){
    val configs = mutableListOf<OutputConfiguration>()
    val streamUseCase = CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    targets.forEach {
        val config = OutputConfiguration(it)
        config.streamUseCase = streamUseCase.toLong()
        configs.add(config)
    }
    ...
    device.createCaptureSession(session)
}

Java


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
      analysis
// 3. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// Create a capture session using the predefined targets; this also involves defining the
// session state callback to be notified of when the session is ready
private void configureSession(CameraDevice device, List<Surface> targets){
    ArrayList<OutputConfiguration> configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        configs.add(config)
}

device.createCaptureSession(session)
}

この時点では、カメラのアクティブな構成は定義されていません。セッションが構成されたら、そのためにキャプチャ リクエストを作成してディスパッチできます。

バッファに書き込まれるときに入力に適用される変換は、各ターゲットの型によって決まります。これは Surface である必要があります。Android フレームワークは、アクティブな構成の未加工画像を各ターゲットに適した形式に変換する方法を認識しています。変換は特定の Surface のピクセル形式とサイズによって制御されます。

フレームワークは最善を尽くそうとしますが、一部の Surface 構成の組み合わせが機能せず、セッションが作成されない、リクエストのディスパッチ時にランタイム エラーがスローされる、パフォーマンスが低下するなどの問題が発生する可能性があります。フレームワークは、デバイス、サーフェス、リクエストのパラメータの特定の組み合わせを保証します。詳しくは、createCaptureSession() のドキュメントをご覧ください。

単一の CaptureRequest

各フレームに使用される構成は、カメラに送信される CaptureRequest でエンコードされます。キャプチャ リクエストを作成するには、事前定義されたテンプレートのいずれかを使用するか、TEMPLATE_MANUAL を使用してフル コントロールを行います。テンプレートを選択する場合は、リクエストで使用する 1 つ以上の出力バッファを指定する必要があります。使用するキャプチャ セッションですでに定義されているバッファのみを使用できます。

キャプチャ リクエストではビルダー パターンが使用され、デベロッパーは自動露出オートフォーカスレンズ絞りなど、さまざまなオプションを設定できます。フィールドを設定する前に、CameraCharacteristics.getAvailableCaptureRequestKeys() を呼び出してデバイスで特定のオプションが利用可能であることと、使用可能な自動露出モードなど、適切なカメラ特性をチェックして目的の値がサポートされていることを確認します。

プレビュー用に設計されたテンプレートを使用して、変更を加えずに SurfaceView のキャプチャ リクエストを作成するには、CameraDevice.TEMPLATE_PREVIEW を使用します。

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

定義したキャプチャ リクエストをカメラ セッションにディスパッチできるようになりました。

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed
// capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null);

出力フレームが特定のバッファに入れられると、キャプチャ コールバックがトリガーされます。多くの場合、含まれているフレームが処理されると、ImageReader.OnImageAvailableListener などの追加コールバックがトリガーされます。この時点で、指定したバッファから画像データを取得できます。

CaptureRequest の繰り返し

1 台のカメラ リクエストは簡単に実行できますが、ライブ プレビュー(動画)の表示にはあまり役に立ちません。その場合、単一のフレームだけでなく、フレームの連続ストリームを受信する必要があります。次のコード スニペットは、繰り返しリクエストをセッションに追加する方法を示しています。

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until
// the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null);

キャプチャ リクエストが繰り返されると、カメラデバイスは指定された CaptureRequest の設定を使用して画像を継続的にキャプチャします。また、Camera2 API を使用すると、GitHub の Camera2 サンプル リポジトリにあるように、CaptureRequests を繰り返し送信してカメラから動画をキャプチャできるようになります。また、GitHub の Camera2 スローモーション動画サンプルアプリで説明されているように、バーストの繰り返し CaptureRequests を使用して高速(スローモーション)動画をキャプチャすることで、スローモーション動画をレンダリングすることもできます。

キャプチャ リクエストをインターリーブする

繰り返しのキャプチャ リクエストがアクティブなときに(ビューファインダーを表示してユーザーが写真を撮影できるようにするなど)、2 回目のキャプチャ リクエストを送信するために、進行中の繰り返しリクエストを停止する必要はありません。代わりに、反復実行されていないキャプチャ リクエストを発行し、反復リクエストが継続的に実行されるようにします。

使用する出力バッファはすべて、セッションを最初に作成するときにカメラ セッションの一部として構成する必要があります。繰り返しリクエストは、単一フレーム リクエストやバースト リクエストよりも優先度が低く、次のサンプルが機能します。

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
val repeatingRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
val singleRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
repeatingRequest.addTarget(previewSurface);
session.setRepeatingRequest(repeatingRequest.build(), null, null);

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);

ただし、この方法には欠点があります。それは、単一のリクエストが発生するタイミングが正確にわからないことです。次の図で、A が反復キャプチャ リクエスト、B が単一フレーム キャプチャ リクエストの場合、セッションはこのようにしてリクエスト キューを処理します。

図 2. 進行中のカメラ セッションのリクエスト キューのイラスト

リクエスト B がアクティブになる前の最後のリクエスト A から繰り返されるリクエストから、次に A が再び使用されるまでのレイテンシには保証がないため、フレームがスキップされる可能性があります。この問題を軽減する方法をいくつか紹介します。

  • リクエスト A の出力ターゲットをリクエスト B に追加します。これにより、B のフレームの準備ができると、A の出力ターゲットにコピーされます。たとえば、動画のスナップショットを作成して安定したフレームレートを維持する場合、これは重要です。上記のコードでは、リクエストを作成する前に singleRequest.addTarget(previewSurface) を追加します。

  • ゼロシャッター ラグなど、この特定のシナリオに対応するように設計されたテンプレートを組み合わせて使用します。