Объективы и возможности камеры

Примечание: Эта страница относится к пакету Camera2 . Если вашему приложению не требуются специфические низкоуровневые функции Camera2, мы рекомендуем использовать CameraX . И CameraX, и Camera2 поддерживают Android 5.0 (уровень API 21) и выше.

Многие современные устройства Android имеют две или более камер на передней, задней или боковых сторонах устройства. Каждый объектив может обладать уникальными возможностями, такими как серийная съемка, ручное управление или отслеживание движения. Приложение для внесения чеков может использовать только первую заднюю камеру, в то время как приложение для социальных сетей может по умолчанию использовать фронтальную камеру, но предоставлять пользователям возможность переключаться между всеми доступными объективами. Оно также может запоминать их выбор.

На этой странице описано, как перечислить объективы камер и их возможности, чтобы вы могли принимать решения в своем приложении о том, какой объектив использовать в той или иной ситуации. Следующий фрагмент кода получает список всех камер и перебирает их:

Котлин

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 одной из перечисленных возможностей камеры, которая хранится в виде флага в переменной isBackwardCompatible .

Выберите разумные значения по умолчанию.

В вашем приложении, вероятно, потребуется по умолчанию открывать определенную камеру (если она доступна). Например, приложение для селфи, скорее всего, откроет фронтальную камеру, а приложение дополненной реальности может начать с задней камеры. Следующая функция возвращает первую камеру, направленную в заданном направлении:

Котлин

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-камеры. Чтобы предоставить пользователям интерфейс, позволяющий переключаться между камерами, направленными в разные стороны, для каждой возможной конфигурации объектива выбирается первая доступная камера.

Хотя универсальной логики для выбора следующей камеры не существует, следующий код работает в большинстве случаев:

Котлин

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 .

Создавайте совместимые приложения

Для приложений, все еще использующих устаревший API камеры, количество камер, возвращаемое методом Camera.getNumberOfCameras() зависит от реализации производителя оборудования. Если в системе присутствует логическая многокамерная система, для сохранения обратной совместимости приложения этот метод будет отображать только одну камеру для каждой логической камеры и базовой группы физических камер. Используйте API Camera2, чтобы увидеть все камеры.

Для получения более подробной информации об ориентации камеры см. Camera.CameraInfo.orientation .

Как правило, используйте API Camera.getCameraInfo() для запроса всех orientation камер и предоставляйте пользователям, переключающимся между камерами, доступ только к одной камере для каждой доступной ориентации.

Подходит для всех типов устройств

Не следует предполагать, что ваше приложение всегда будет работать на портативном устройстве с одной или двумя камерами. Вместо этого выберите наиболее подходящие для приложения камеры. Если вам не нужна конкретная камера, выберите первую камеру, направленную в нужном направлении. Если камера, направленная в определенном направлении, недоступна, подумайте, сможет ли пользователь завершить свой маршрут с помощью другой камеры. Не ограничивайте доступность вашего приложения на определенных устройствах в зависимости от аппаратного обеспечения камеры. Если подключена внешняя камера, пользователь, вероятно, предпочтет использовать ее по умолчанию.