注: このページでは、Camera2 パッケージについて説明します。アプリが Camera2 の特定の低レベルの機能を必要とする場合を除き、CameraX を使用することをおすすめします。CameraX と Camera2 は、どちらも Android 5.0(API レベル 21)以降に対応しています。
カメラアプリは、複数のフレーム ストリームを同時に使用できます。場合によっては、ストリームごとに異なるフレーム解像度やピクセル形式が必要になることもあります。一般的なユースケースは次のとおりです。
- 動画の録画: プレビュー用のストリームと、エンコードされてファイルに保存されるストリーム。
- バーコード スキャン: プレビュー用とバーコード検出用のストリームがあります。
- 計算写真学: プレビュー用のストリームと顔やシーンの検出用のストリーム。
フレーム処理のパフォーマンス コストはごくわずかであり、並列ストリーム処理やパイプライン処理を行うとコストが乗算されます。
CPU、GPU、DSP などのリソースはフレームワークの再処理機能を利用できる場合がありますが、メモリなどのリソースは直線的に増加します。
リクエストごとに複数のターゲット
複数のカメラ ストリームを 1 つの CameraCaptureRequest
に統合できます。次のコード スニペットは、カメラ プレビュー用のストリームと画像処理用のストリームを使用して、カメラ セッションを設定する方法を示しています。
Kotlin
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD val requestTemplate = CameraDevice.TEMPLATE_PREVIEW val combinedRequest = session.device.createCaptureRequest(requestTemplate) // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface) combinedRequest.addTarget(imReaderSurface) // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null)
Java
CameraCaptureSession session = …; // from CameraCaptureSession.StateCallback // You will use the preview capture template for the combined streams // because it is optimized for low latency; for high-quality images, use // TEMPLATE_STILL_CAPTURE, and for a steady frame rate use TEMPLATE_RECORD CaptureRequest.Builder combinedRequest = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // Link the Surface targets with the combined request combinedRequest.addTarget(previewSurface); combinedRequest.addTarget(imReaderSurface); // In this simple case, the SurfaceView gets updated automatically. ImageReader // has its own callback that you have to listen to in order to retrieve the // frames so there is no need to set up a callback for the capture request session.setRepeatingRequest(combinedRequest.build(), null, null);
ターゲット サーフェスを正しく設定すると、このコードは StreamComfigurationMap.GetOutputMinFrameDuration(int, Size)
と StreamComfigurationMap.GetOutputStallDuration(int, Size)
によって決定される最小 FPS を満たすストリームのみを生成します。実際のパフォーマンスはデバイスによって異なりますが、Android では、3 つの変数(出力タイプ、出力サイズ、ハードウェア レベル)に応じて特定の組み合わせをサポートすることが保証されています。
サポートされていない変数の組み合わせを使用すると、低フレームレートで機能する可能性があります。そうでない場合は、失敗コールバックのいずれかがトリガーされます。動作が保証されているものについては、createCaptureSession
のドキュメントをご覧ください。
出力タイプ
出力タイプとは、フレームがエンコードされる形式を意味します。指定できる値は PRIV、YUV、JPEG、RAW です。これらの機能については、createCaptureSession
のドキュメントをご覧ください。
アプリケーションの出力タイプを選択する際、互換性を最大化することが目標の場合は、フレーム分析に ImageFormat.YUV_420_888
を使用し、静止画像に ImageFormat.JPEG
を使用します。プレビューと録画のシナリオでは、SurfaceView
、TextureView
、MediaRecorder
、MediaCodec
、RenderScript.Allocation
を使用する可能性があります。このような場合、画像形式を指定しないでください。互換性のため、内部で使用される実際の形式に関係なく、ImageFormat.PRIVATE
としてカウントされます。指定された CameraCharacteristics
のデバイスでサポートされている形式をクエリするには、次のコードを使用します。
Kotlin
val characteristics: CameraCharacteristics = ... val supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats
Java
CameraCharacteristics characteristics = …; int[] supportedFormats = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputFormats();
出力サイズ
利用可能なすべての出力サイズは StreamConfigurationMap.getOutputSizes()
ごとに表示されますが、互換性に関連するのは PREVIEW
と MAXIMUM
の 2 つのみです。このサイズは上限として機能します。PREVIEW
サイズが機能する場合は、PREVIEW
より小さいサイズも使用できます。MAXIMUM
についても同様です。これらのサイズについては、CameraDevice
のドキュメントをご覧ください。
利用可能な出力サイズは、選択した形式によって異なります。CameraCharacteristics
と形式を指定すると、次のように利用可能な出力サイズをクエリできます。
Kotlin
val characteristics: CameraCharacteristics = ... val outputFormat: Int = ... // such as ImageFormat.JPEG val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
カメラ プレビューと録画のユースケースでは、ターゲット クラスを使用して、サポートされているサイズを判断します。この形式はカメラ フレームワーク自体によって処理されます。
Kotlin
val characteristics: CameraCharacteristics = ... val targetClass: Class <T> = ... // such as SurfaceView::class.java val sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(targetClass)
Java
CameraCharacteristics characteristics = …; int outputFormat = …; // such as ImageFormat.JPEG Size[] sizes = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(outputFormat);
MAXIMUM
サイズを取得するには、出力サイズを領域順に並べ替えて最も大きいサイズを返します。
Kotlin
fun <T>getMaximumOutputSize( characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null): Size { val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) // If image format is provided, use it to determine supported sizes; or else use target class val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) return allSizes.maxBy { it.height * it.width } }
Java
@RequiresApi(api = Build.VERSION_CODES.N) <T> Size getMaximumOutputSize(CameraCharacteristics characteristics, Class <T> targetClass, Integer format) { StreamConfigurationMap config = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // If image format is provided, use it to determine supported sizes; else use target class Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } return Arrays.stream(allSizes).max(Comparator.comparing(s -> s.getHeight() * s.getWidth())).get(); }
PREVIEW
は、デバイスの画面解像度または 1080p(1920x1080)のいずれか小さい方に最適なサイズです。アスペクト比は画面のアスペクト比と正確に一致しない場合があるため、全画面表示モードで表示するには、ストリームにレターボックス表示または切り抜きを適用する必要があります。適切なプレビュー サイズを見積もるには、ディスプレイが回転することを考慮しながら、使用可能な出力サイズとディスプレイ サイズを比較します。
次のコードは、サイズの比較を少し簡単にするヘルパークラス SmartSize
を定義しています。
Kotlin
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize(width: Int, height: Int) { var size = Size(width, height) var long = max(size.width, size.height) var short = min(size.width, size.height) override fun toString() = "SmartSize(${long}x${short})" } /** Standard High Definition size for pictures and video */ val SIZE_1080P: SmartSize = SmartSize(1920, 1080) /** Returns a [SmartSize] object for the given [Display] */ fun getDisplaySmartSize(display: Display): SmartSize { val outPoint = Point() display.getRealSize(outPoint) return SmartSize(outPoint.x, outPoint.y) } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ fun <T>getPreviewOutputSize( display: Display, characteristics: CameraCharacteristics, targetClass: Class <T>, format: Int? = null ): Size { // Find which is smaller: screen or 1080p val screenSize = getDisplaySmartSize(display) val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short val maxSize = if (hdScreen) SIZE_1080P else screenSize // If image format is provided, use it to determine supported sizes; else use target class val config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!! if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)) else assert(config.isOutputSupportedFor(format)) val allSizes = if (format == null) config.getOutputSizes(targetClass) else config.getOutputSizes(format) // Get available sizes and sort them by area from largest to smallest val validSizes = allSizes .sortedWith(compareBy { it.height * it.width }) .map { SmartSize(it.width, it.height) }.reversed() // Then, get the largest output size that is smaller or equal than our max size return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size }
Java
/** Helper class used to pre-compute shortest and longest sides of a [Size] */ class SmartSize { Size size; double longSize; double shortSize; public SmartSize(Integer width, Integer height) { size = new Size(width, height); longSize = max(size.getWidth(), size.getHeight()); shortSize = min(size.getWidth(), size.getHeight()); } @Override public String toString() { return String.format("SmartSize(%sx%s)", longSize, shortSize); } } /** Standard High Definition size for pictures and video */ SmartSize SIZE_1080P = new SmartSize(1920, 1080); /** Returns a [SmartSize] object for the given [Display] */ SmartSize getDisplaySmartSize(Display display) { Point outPoint = new Point(); display.getRealSize(outPoint); return new SmartSize(outPoint.x, outPoint.y); } /** * Returns the largest available PREVIEW size. For more information, see: * https://d.android.com/reference/android/hardware/camera2/CameraDevice */ @RequiresApi(api = Build.VERSION_CODES.N) <T> Size getPreviewOutputSize( Display display, CameraCharacteristics characteristics, Class <T> targetClass, Integer format ){ // Find which is smaller: screen or 1080p SmartSize screenSize = getDisplaySmartSize(display); boolean hdScreen = screenSize.longSize >= SIZE_1080P.longSize || screenSize.shortSize >= SIZE_1080P.shortSize; SmartSize maxSize; if (hdScreen) { maxSize = SIZE_1080P; } else { maxSize = screenSize; } // If image format is provided, use it to determine supported sizes; else use target class StreamConfigurationMap config = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (format == null) assert(StreamConfigurationMap.isOutputSupportedFor(targetClass)); else assert(config.isOutputSupportedFor(format)); Size[] allSizes; if (format == null) { allSizes = config.getOutputSizes(targetClass); } else { allSizes = config.getOutputSizes(format); } // Get available sizes and sort them by area from largest to smallest List <Size> sortedSizes = Arrays.asList(allSizes); List <SmartSize> validSizes = sortedSizes.stream() .sorted(Comparator.comparing(s -> s.getHeight() * s.getWidth())) .map(s -> new SmartSize(s.getWidth(), s.getHeight())) .sorted(Collections.reverseOrder()).collect(Collectors.toList()); // Then, get the largest output size that is smaller or equal than our max size return validSizes.stream() .filter(s -> s.longSize <= maxSize.longSize && s.shortSize <= maxSize.shortSize) .findFirst().get().size; }
サポートされているハードウェア レベルを確認する
実行時に使用可能な機能を確認するには、CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL
を使用して、サポートされているハードウェア レベルを確認します。
CameraCharacteristics
オブジェクトを使用すると、1 つのステートメントでハードウェア レベルを取得できます。
Kotlin
val characteristics: CameraCharacteristics = ... // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 val hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
Java
CameraCharacteristics characteristics = ...; // Hardware level will be one of: // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, // - CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 Integer hardwareLevel = characteristics.get( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
すべての要素を組み合わせる
有効なストリームの組み合わせは、出力タイプ、出力サイズ、ハードウェア レベルによって判断できます。次のグラフは、LEGACY
ハードウェア レベルで CameraDevice
でサポートされている構成のスナップショットです。
ターゲット 1 | ターゲット 2 | ターゲット 3 | サンプルのユースケース | |||
---|---|---|---|---|---|---|
タイプ | 最大サイズ | タイプ | 最大サイズ | タイプ | 最大サイズ | |
PRIV |
MAXIMUM |
シンプルなプレビュー、GPU 動画処理、プレビューなしの動画録画。 | ||||
JPEG |
MAXIMUM |
ビューファインダーなしの静止画撮影。 | ||||
YUV |
MAXIMUM |
アプリケーション内での動画/画像処理。 | ||||
PRIV |
PREVIEW |
JPEG |
MAXIMUM |
標準静止画撮影。 | ||
YUV |
PREVIEW |
JPEG |
MAXIMUM |
アプリ内処理と静止画撮影 | ||
PRIV |
PREVIEW |
PRIV |
PREVIEW |
標準の録画。 | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
プレビューとアプリ内処理。 | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
プレビューとアプリ内処理。 | ||
PRIV |
PREVIEW |
YUV |
PREVIEW |
JPEG |
MAXIMUM |
静止画撮影とアプリ内処理。 |
LEGACY
は、使用可能な最も低いハードウェア レベルです。この表は、Camera2(API レベル 21 以降)をサポートするすべてのデバイスが、適切な構成を使用して最大 3 つの同時ストリームを出力できることを示しています。また、メモリ、CPU、熱の制約など、パフォーマンスに過剰なオーバーヘッド制限がない場合にも必要です。
また、アプリではターゲット出力バッファを構成する必要があります。たとえば、LEGACY
ハードウェア レベルのデバイスをターゲットにするには、ImageFormat.PRIVATE
と ImageFormat.YUV_420_888
を使用して、2 つのターゲット出力サーフェスをセットアップします。PREVIEW
サイズを使用する場合、これはサポートされる組み合わせです。このトピックで前述した関数を使用してカメラ ID に必要なプレビュー サイズを取得するには、次のコードが必要です。
Kotlin
val characteristics: CameraCharacteristics = ... val context = this as Context // assuming you are inside of an activity val surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView::class.java) val imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader::class.java, format = ImageFormat.YUV_420_888)
Java
CameraCharacteristics characteristics = ...; Context context = this; // assuming you are inside of an activity Size surfaceViewSize = getPreviewOutputSize( context, characteristics, SurfaceView.class); Size imageReaderSize = getPreviewOutputSize( context, characteristics, ImageReader.class, format = ImageFormat.YUV_420_888);
この場合、提供されたコールバックを使用して SurfaceView
の準備が整うまで待つ必要があります。
Kotlin
val surfaceView = findViewById <SurfaceView>(...) surfaceView.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... })
Java
SurfaceView surfaceView = findViewById <SurfaceView>(...); surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { // You do not need to specify image format, and it will be considered of type PRIV // Surface is now ready and you could use it as an output target for CameraSession } ... });
SurfaceHolder.setFixedSize()
を呼び出すことで、SurfaceView
を強制的にカメラ出力サイズに合わせることができます。または、GitHub のカメラサンプルの共通モジュールにある AutoFitSurfaceView
と同様のアプローチを取ることもできます。ここでは、アスペクト比と使用可能なスペースの両方を考慮しながら、アクティビティの変更がトリガーされたときに自動的に調整しながら、絶対サイズを設定します。
待機するコールバックがないため、ImageReader
から目的のフォーマットでもう一方のサーフェスをセットアップするのは簡単です。
Kotlin
val frameBufferCount = 3 // just an example, depends on your usage of ImageReader val imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount)
Java
int frameBufferCount = 3; // just an example, depends on your usage of ImageReader ImageReader imageReader = ImageReader.newInstance( imageReaderSize.width, imageReaderSize.height, ImageFormat.YUV_420_888, frameBufferCount);
ImageReader
などのブロッキング ターゲット バッファを使用する場合は、使用後にフレームを破棄します。
Kotlin
imageReader.setOnImageAvailableListener({ val frame = it.acquireNextImage() // Do something with "frame" here it.close() }, null)
Java
imageReader.setOnImageAvailableListener(listener -> { Image frame = listener.acquireNextImage(); // Do something with "frame" here listener.close(); }, null);
LEGACY
のハードウェア レベルは、最小公分母のデバイスをターゲットにします。LIMITED
ハードウェア レベルのデバイスでは、条件分岐を追加して出力ターゲット サーフェスの 1 つに RECORD
サイズを使用し、ハードウェア レベルの FULL
デバイスでは MAXIMUM
サイズに増やすことができます。