注: このページでは、Camera2 パッケージについて説明します。アプリが Camera2 の特定の低レベルの機能を必要とする場合を除き、CameraX を使用することをおすすめします。CameraX と Camera2 は、どちらも Android 5.0(API レベル 21)以降に対応しています。
1 台の Android 搭載デバイスに複数のカメラを設定できます。各カメラは CameraDevice
であり、CameraDevice
は複数のストリームを同時に出力できます。
これを行う理由の 1 つは、CameraDevice
から送られる 1 つのストリーム(連続したカメラフレーム)が、ビューファインダーの表示などの特定のタスク用に最適化され、他のストリームが写真の撮影や録画に使用される場合があるためです。ストリームは、カメラから出力される RAW フレームを一度に 1 フレームずつ処理する並列パイプラインとして機能します。
並列処理では、CPU、GPU、その他のプロセッサで使用可能な処理能力に応じてパフォーマンスの上限が上がる可能性があります。パイプラインが受信フレームについていけなくなると、そのフレームのドロップを開始します。
各パイプラインには独自の出力形式があります。取り込まれた元データは、各パイプラインに関連付けられた暗黙的なロジックによって自動的に適切な出力形式に変換されます。このページのコードサンプル全体で使用される CameraDevice
は特定のものではないため、先に進む前に、まず使用可能なカメラをすべて列挙します。
CameraDevice
を使用して、その CameraDevice
に固有の CameraCaptureSession
を作成できます。CameraDevice
は、CameraCaptureSession
を使用して各未加工フレームのフレーム構成を受け取る必要があります。この構成では、オートフォーカス、絞り、エフェクト、露出などのカメラ属性を指定します。ハードウェアの制約のため、カメラセンサーで常にアクティブになる構成は 1 つだけです。これはアクティブ構成と呼ばれます。
ただし、ストリーミングのユースケースでは、CameraDevice
を使用してキャプチャ セッションをストリーミングする以前の方法を拡張し、特定のユースケースに合わせてカメラ ストリームを最適化できます。たとえば、ビデオ通話を最適化する際のバッテリー駆動時間を改善できます。
CameraCaptureSession
は、CameraDevice
にバインドされる可能性があるすべてのパイプラインを記述します。セッションの作成中にパイプラインを追加または削除することはできません。CameraCaptureSession
は CaptureRequest
のキューを維持します。このキューがアクティブな構成になります。
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 が単一フレーム キャプチャ リクエストの場合、セッションはこのようにしてリクエスト キューを処理します。
リクエスト B がアクティブになる前の最後のリクエスト A から繰り返されるリクエストから、次に A が再び使用されるまでのレイテンシには保証がないため、フレームがスキップされる可能性があります。この問題を軽減する方法をいくつか紹介します。
リクエスト A の出力ターゲットをリクエスト B に追加します。これにより、B のフレームの準備ができると、A の出力ターゲットにコピーされます。たとえば、動画のスナップショットを作成して安定したフレームレートを維持する場合、これは重要です。上記のコードでは、リクエストを作成する前に
singleRequest.addTarget(previewSurface)
を追加します。ゼロシャッター ラグなど、この特定のシナリオに対応するように設計されたテンプレートを組み合わせて使用します。