複数のカメラ ストリームを同時に使用する

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

カメラアプリは、複数のフレーム ストリームを同時に使用できます。イン 場合によっては、ストリームが異なれば、必要なフレーム解像度やピクセルも 使用できます。一般的なユースケースは次のとおりです。

  • 動画の録画: プレビュー用のストリームと、エンコードして保存されるストリーム 必要があります。
  • バーコード スキャン: プレビュー用のストリーム、バーコード検出用のストリーム。
  • 計算写真学: プレビュー用に 1 つ、顔やシーン用に 1 つのストリーム できます。

フレームの処理時にパフォーマンス コストが膨大になります。 何倍になります

CPU、GPU、DSP などのリソースは、 フレームワークの再処理 メモリなどのリソースは直線的に増加します。

リクエストごとに複数のターゲット

複数のカメラ ストリームを 1 つの CameraCaptureRequest。 次のコード スニペットは、1 つの ID を持つカメラ セッションを設定する方法を示しています。 別のストリームを画像処理に使用することもできます。

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);

ターゲット サーフェスを正しく設定すれば、このコードで 最小 FPS を満たすストリームを配信する必要があります。 StreamComfigurationMap.GetOutputMinFrameDuration(int, Size) および StreamComfigurationMap.GetOutputStallDuration(int, Size)。 実際のパフォーマンスはデバイスによって異なりますが、Android には 3 つの可変要素に応じて、特定の組み合わせがサポートされるようになります。 出力タイプ、出力サイズ、ハードウェア レベルです。

サポートされていない変数の組み合わせを使用すると、低フレームレートで動作する場合があります。 失敗すると、失敗を示すコールバックの 1 つがトリガーされます。 createCaptureSession のドキュメント では、確実に機能する仕組みについて説明します。

出力タイプ

出力タイプとは、フレームがエンコードされる形式のことです。「 PRIV、YUV、JPEG、RAW があります「 createCaptureSession 説明します。

アプリケーションの出力タイプを選択するときに、目的が 対応してから、 ImageFormat.YUV_420_888 フレーム分析や ImageFormat.JPEG(静止画) 作成します。プレビューと録画のシナリオでは、 SurfaceView TextureViewMediaRecorderMediaCodec 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()、 互換性に関連するのは PREVIEWMAXIMUM の 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 サイズを取得するには、出力サイズをエリアで並べ替えて、最も大きい値を返します。 1 つ:

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);

すべての要素を組み合わせる

出力タイプ、出力サイズ、ハードウェア レベルから、 ストリームの組み合わせが有効です。次のグラフは、Chronicle の 次を含む CameraDevice でサポートされる構成 LEGACY 保護します。

ターゲット 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 以降)をサポートするデバイスでは、 同時ストリーミングを複数のマシンで シームレスに実行できることがわかっています パフォーマンスを制限するオーバーヘッド(メモリ、CPU、温度の制約など)が含まれます。

アプリはターゲティング出力バッファも構成する必要があります。たとえば、 LEGACY ハードウェア レベルのデバイスをターゲットにする場合は、2 つのターゲット出力を設定できます。 1 つは ImageFormat.PRIVATE を、もう 1 つは使用しています。 ImageFormat.YUV_420_888。この組み合わせは、 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
            }
            ...
        });

次のように呼び出して、SurfaceView をカメラ出力サイズに強制的に一致させることができます。 SurfaceHolder.setFixedSize() 同様のアプローチを Common の AutoFitSurfaceView モジュール GitHub に用意されているカメラサンプルのうちの 1 つで、 アスペクト比と利用可能なスペースの両方を考慮して、 アクティビティの変更がトリガーされたタイミングを調整します。

他のサーフェスを 目的の形式の 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 ハードウェア レベルは、最小公分母デバイスをターゲットにします。Google Chat では 条件分岐を追加し、いずれかの出力ターゲットに RECORD サイズを使用する ハードウェア レベルが LIMITED のデバイスで ハードウェア レベルが FULL のデバイスの場合は MAXIMUM サイズ。