カメラのレンズと機能

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

最近の Android デバイスの多くは、デバイスの前面、背面、または両面に 2 つ以上のカメラを搭載しています。レンズごとに、バースト撮影、手動制御、モーショントラッキングなどの独自の機能があります。小切手入金アプリは最初の背面カメラのみを使用するのに対し、ソーシャル メディア アプリはデフォルトで前面カメラを使用しますが、ユーザーは使用可能なすべてのレンズを切り替えることができます。選択内容を記憶することもできます。

このページでは、カメラレンズとその機能を一覧表示して、特定の状況でどのレンズを使用するかをアプリ内で判断する方法について説明します。 次のコード スニペットは、すべてのカメラのリストを取得して、それらを反復処理します。

Kotlin

try {
    val cameraIdList = cameraManager.cameraIdList // may be empty

    // iterate over available camera devices
    for (cameraId in cameraIdList) {
        val characteristics = cameraManager.getCameraCharacteristics(cameraId)
        val cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
        val cameraCapabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)

        // check if the selected camera device supports basic features
        // ensures backward compatibility with the original Camera API
        val isBackwardCompatible = cameraCapabilities?.contains(
            CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
        ...
    }
} catch (e: CameraAccessException) {
    e.message?.let { Log.e(TAG, it) }
    ...
}

Java

try {
    String[] cameraIdList = cameraManager.getCameraIdList(); // may be empty

    // iterate over available camera devices
    for (String cameraId : cameraIdList) {
        CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
        int cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
        int[] cameraCapabilities =
            characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);

        // check if the selected camera device supports basic features
        // ensures backward compatibility with the original Camera API
        boolean isBackwardCompatible = false;
        for (int capability : cameraCapabilities) {
            if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) {
                isBackwardCompatible = true;
                break;
            }
        }
        ...
    }
} catch (CameraAccessException e) {
    Log.e(TAG, e.getMessage());
    ...
}

cameraLensFacing 変数は、デバイス画面に対するカメラの向きを表し、次のいずれかの値になります。

レンズの向きの構成の詳細については、 CameraCharacteristics.LENS_FACING をご覧ください。

上記のコードサンプルの cameraCapabilities 変数には、カメラが標準フレームを出力できるかどうか(深度センサーデータのみを出力する場合など)など、さまざまな機能に関する情報が含まれています。 CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE がカメラのリストされている機能の 1 つであるかどうかを確認できます。これは isBackwardCompatibleにフラグとして保存されます。

適切なデフォルトを選択する

アプリでは、特定のカメラをデフォルトで開くことが望ましいでしょう(使用可能な場合)。たとえば、セルフィー アプリは前面カメラを開き、拡張現実アプリは背面カメラから開始します。次の関数は、指定された方向を向いている最初のカメラを返します。

Kotlin

fun getFirstCameraIdFacing(cameraManager: CameraManager,
                           facing: Int = CameraMetadata.LENS_FACING_BACK): String? {
    try {
        // Get list of all compatible cameras
        val cameraIds = cameraManager.cameraIdList.filter {
            val characteristics = cameraManager.getCameraCharacteristics(it)
            val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
            capabilities?.contains(
                    CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
        }

        // Iterate over the list of cameras and return the first one matching desired
        // lens-facing configuration
        cameraIds.forEach {
            val characteristics = cameraManager.getCameraCharacteristics(it)
            if (characteristics.get(CameraCharacteristics.LENS_FACING) == facing) {
                return it
            }
        }

        // If no camera matched desired orientation, return the first one from the list
        return cameraIds.firstOrNull()
    } catch (e: CameraAccessException) {
        e.message?.let { Log.e(TAG, it) }
    }
}

Java

public String getFirstCameraIdFacing(CameraManager cameraManager, @Nullable Integer facing) {
    if (facing == null) facing = CameraMetadata.LENS_FACING_BACK;
    String cameraId = null;

    try {
        // Get a list of all compatible cameras
        String[] cameraIdList = cameraManager.getCameraIdList();

        // Iterate over the list of cameras and return the first one matching desired
        // lens-facing configuration and backward compatibility
        for (String id : cameraIdList) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
            for (int capability : capabilities) {
                if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
                        && characteristics.get(CameraCharacteristics.LENS_FACING).equals(facing)) {
                    cameraId = id;
                    break;
                }
            }
        }

        // If no camera matches the desired orientation, return the first one from the list
        cameraId = cameraIdList[0];
    } catch (CameraAccessException e) {
        Log.e(TAG, "getFirstCameraIdFacing: " + e.getMessage());
    }

    return cameraId;
}

カメラの切り替えを有効にする

多くのカメラアプリでは、カメラを切り替えることができます。

図 1. Google カメラ アプリのカメラ切り替えボタン

多くのデバイスには、同じ方向を向いている複数のカメラが搭載されています。外部 USB カメラを搭載しているものもあります。ユーザーがさまざまな向きのカメラを切り替えられる UI を提供するには、レンズの向きの構成ごとに使用可能な最初のカメラを選択します。

次のカメラを選択するための汎用的なロジックはありませんが、次のコードはほとんどのユースケースで機能します。

Kotlin

fun filterCompatibleCameras(cameraIds: Array<String>,
                            cameraManager: CameraManager): List<String> {
    return cameraIds.filter {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)?.contains(
                CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) ?: false
    }
}

fun filterCameraIdsFacing(cameraIds: List<String>, cameraManager: CameraManager,
                          facing: Int): List<String> {
    return cameraIds.filter {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        characteristics.get(CameraCharacteristics.LENS_FACING) == facing
    }
}

fun getNextCameraId(cameraManager: CameraManager, currCameraId: String? = null): String? {
    // Get all front, back and external cameras in 3 separate lists
    val cameraIds = filterCompatibleCameras(cameraManager.cameraIdList, cameraManager)
    val backCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_BACK)
    val frontCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_FRONT)
    val externalCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_EXTERNAL)

    // The recommended order of iteration is: all external, first back, first front
    val allCameras = (externalCameras + listOf(
            backCameras.firstOrNull(), frontCameras.firstOrNull())).filterNotNull()

    // Get the index of the currently selected camera in the list
    val cameraIndex = allCameras.indexOf(currCameraId)

    // The selected camera may not be in the list, for example it could be an
    // external camera that has been removed by the user
    return if (cameraIndex == -1) {
        // Return the first camera from the list
        allCameras.getOrNull(0)
    } else {
        // Return the next camera from the list, wrap around if necessary
        allCameras.getOrNull((cameraIndex + 1) % allCameras.size)
    }
}

Java

public List<String> filterCompatibleCameras(CameraManager cameraManager, String[] cameraIds) {
    final List<String> compatibleCameras = new ArrayList<>();

    try {
        for (String id : cameraIds) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            int[] capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
            for (int capability : capabilities) {
                if (capability == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE) {
                    compatibleCameras.add(id);
                }
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "filterCompatibleCameras: " + e.getMessage());
    }

    return compatibleCameras;
}

public List<String> filterCameraIdsFacing(CameraManager cameraManager, List<String> cameraIds, int lensFacing) {
    final List<String> compatibleCameras = new ArrayList<>();

    try {
        for (String id : cameraIds) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            if (characteristics.get(CameraCharacteristics.LENS_FACING) == lensFacing) {
                compatibleCameras.add(id);
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "filterCameraIdsFacing: " + e.getMessage());
    }

    return compatibleCameras;
}

public String getNextCameraId(CameraManager cameraManager, @Nullable String currentCameraId) {
    String nextCameraId = null;

    try {
        // Get all front, back, and external cameras in 3 separate lists
        List<String> compatibleCameraIds = filterCompatibleCameras(cameraManager, cameraManager.getCameraIdList());
        List<String> backCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_BACK);
        List<String> frontCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_FRONT);
        List<String>externalCameras = filterCameraIdsFacing(cameraManager, compatibleCameraIds, CameraMetadata.LENS_FACING_EXTERNAL);

        // The recommended order of iteration is: all external, first back, first front
        List<String> allCameras = new ArrayList<>(externalCameras);
        if (!backCameras.isEmpty()) allCameras.add(backCameras.get(0));
        if (!frontCameras.isEmpty()) allCameras.add(frontCameras.get(0));

        // Get the index of the currently selected camera in the list
        int cameraIndex = allCameras.indexOf(currentCameraId);

        // The selected camera may not be in the list, for example it could be an
        // external camera that has been removed by the user
        if (cameraIndex == -1) {
            // Return the first camera from the list
            nextCameraId = !allCameras.isEmpty() ? allCameras.get(0) : null;
        else {
            if (!allCameras.isEmpty()) {
                // Return the next camera from the list, wrap around if necessary
                nextCameraId = allCameras.get((cameraIndex + 1) % allCameras.size());
            }
        }
    } catch (CameraAccessException e) {
        Log.e(TAG, "getNextCameraId: " + e.getMessage());
    }

    return nextCameraId;
}

このコードは、さまざまな構成の多くのデバイスで動作します。エッジケースの処理の詳細については、CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERAをご覧ください。

互換性のあるアプリを作成する

非推奨の Camera API を使用しているアプリの場合、カメラの数 が Camera.getNumberOfCameras() 返す数は OEM 実装によって異なります。システムに論理マルチカメラがある場合、アプリの下位互換性を維持するため、このメソッドは論理カメラと基盤となる物理カメラ グループごとに 1 つのカメラのみを公開します。 すべてのカメラを表示するには、Camera2 API を使用します。

カメラの向きの背景情報については、 Camera.CameraInfo.orientation をご覧ください。

通常、 Camera.getCameraInfo() API を使用してすべてのカメラの orientationをクエリし、 カメラを切り替えるユーザーに対して、使用可能な画面の向きごとに 1 つのカメラのみを公開します。

すべてのデバイスタイプに対応する

アプリが常に 1 つまたは 2 つのカメラを搭載したハンドヘルド デバイスで実行されるとは限りません。代わりに、アプリに最適なカメラを選択します。特定のカメラが必要ない場合は、目的の方向を向いている最初のカメラを選択します。特定の方向を向いているカメラが使用できない場合は、別のカメラでユーザーの操作を完了できるかどうかを検討してください。カメラ ハードウェアに基づいて、特定のデバイスでのアプリの利用を制限しないでください。 外部カメラが接続されている場合、ユーザーはそれをデフォルトとして使用することを好む可能性があります。