Многокамерный API

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

Мультикамера появилась в Android 9 (уровень API 28). С момента его выпуска на рынке появились устройства, поддерживающие API. Многие варианты использования нескольких камер тесно связаны с конкретной конфигурацией оборудования. Другими словами, не все варианты использования совместимы со всеми устройствами, что делает функции нескольких камер хорошим кандидатом на Play Feature Delivery .

Некоторые типичные случаи использования включают в себя:

  • Масштаб : переключение между камерами в зависимости от области обрезки или желаемого фокусного расстояния.
  • Глубина : использование нескольких камер для построения карты глубины.
  • Боке : использование предполагаемой информации о глубине для имитации узкого диапазона фокусировки, подобного зеркальной камере.

Разница между логическими и физическими камерами

Понимание многокамерного API требует понимания разницы между логическими и физическими камерами. Для справки рассмотрим устройство с тремя задними камерами. В этом примере каждая из трех задних камер считается физической камерой. Логическая камера представляет собой группу из двух или более физических камер. Выходными данными логической камеры может быть поток, исходящий от одной из базовых физических камер, или объединенный поток, поступающий от нескольких базовых физических камер одновременно. В любом случае поток обрабатывается уровнем аппаратной абстракции камеры (HAL).

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

Рисунок 1. Возможности камеры обычно доступны только привилегированным приложениям.

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

Рис. 2. Полный доступ разработчика ко всем устройствам с камерами, начиная с Android 9.

Возможности логической камеры полностью зависят от OEM-реализации Camera HAL. Например, такое устройство, как Pixel 3, реализует свою логическую камеру таким образом, что оно выбирает одну из своих физических камер на основе запрошенного фокусного расстояния и области обрезки.

Многокамерный API

Новый API добавляет следующие новые константы, классы и методы:

Из-за изменений в документе определения совместимости Android (CDD) разработчики также ожидают от многокамерного API определенных ожиданий. Устройства с двумя камерами существовали до Android 9, но одновременное открытие более одной камеры требовало проб и ошибок. В Android 9 и более поздних версиях функция «Мультикамера» предоставляет набор правил, определяющих возможность открытия пары физических камер, являющихся частью одной логической камеры.

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

Несколько потоков одновременно

Одновременное использование нескольких потоков камер охватывает правила одновременного использования нескольких потоков в одной камере. С одним примечательным дополнением: одни и те же правила применяются к нескольким камерам. CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA объясняет, как заменить логический YUV_420_888 или необработанный поток двумя физическими потоками. То есть каждый поток типа YUV или RAW можно заменить двумя потоками идентичного типа и размера. Вы можете начать с потока камеры следующей гарантированной конфигурации для однокамерных устройств:

  • Поток 1: тип YUV, MAXIMUM размер из логического id = 0

Затем устройство с поддержкой нескольких камер позволяет вам создать сеанс, заменяя этот логический поток YUV двумя физическими потоками:

  • Поток 1: тип YUV, MAXIMUM размер из id = 1
  • Поток 2: тип YUV, MAXIMUM размер из id = 2

Вы можете заменить поток YUV или RAW двумя эквивалентными потоками тогда и только тогда, когда эти две камеры являются частью логической группы камер, которая указана в разделе CameraCharacteristics.getPhysicalCameraIds() .

Гарантии, предоставляемые инфраструктурой, — это лишь минимум, необходимый для одновременного получения кадров с нескольких физических камер. Дополнительные потоки поддерживаются большинством устройств, иногда даже позволяя независимо открывать несколько физических камер. Поскольку это не является жесткой гарантией со стороны платформы, для этого необходимо выполнить тестирование и настройку каждого устройства методом проб и ошибок.

Создание сеанса с несколькими физическими камерами

При использовании физических камер на устройстве с поддержкой нескольких камер откройте одно CameraDevice (логическую камеру) и взаимодействуйте с ним в рамках одного сеанса. Создайте один сеанс с помощью API CameraDevice.createCaptureSession(SessionConfiguration config) , который был добавлен на уровне API 28. Конфигурация сеанса имеет несколько выходных конфигураций, каждая из которых имеет набор выходных целей и, при необходимости, желаемую физическую камеру. ИДЕНТИФИКАТОР.

Рисунок 3. Модель SessionConfiguration и OutputConfiguration.

Запросы на захват имеют связанную с ними цель вывода. Платформа определяет, на какую физическую (или логическую) камеру отправляются запросы, в зависимости от того, какая цель вывода прикреплена. Если цель вывода соответствует одной из целей вывода, которая была отправлена ​​в качестве конфигурации вывода вместе с идентификатором физической камеры, то эта физическая камера получает и обрабатывает запрос.

Использование пары физических камер

Еще одним дополнением к API-интерфейсам многокамерных камер является возможность идентифицировать логические камеры и находить физические камеры, стоящие за ними. Вы можете определить функцию, помогающую идентифицировать потенциальные пары физических камер, которые можно использовать для замены одного из потоков логических камер:

Котлин

/**
     * Helper class used to encapsulate a logical camera and two underlying
     * physical cameras
     */
    data class DualCamera(val logicalId: String, val physicalId1: String, val physicalId2: String)

    fun findDualCameras(manager: CameraManager, facing: Int? = null): List {
        val dualCameras = MutableList()

        // Iterate over all the available camera characteristics
        manager.cameraIdList.map {
            Pair(manager.getCameraCharacteristics(it), it)
        }.filter {
            // Filter by cameras facing the requested direction
            facing == null || it.first.get(CameraCharacteristics.LENS_FACING) == facing
        }.filter {
            // Filter by logical cameras
            // CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
            it.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)!!.contains(
                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)
        }.forEach {
            // All possible pairs from the list of physical cameras are valid results
            // NOTE: There could be N physical cameras as part of a logical camera grouping
            // getPhysicalCameraIds() requires API >= 28
            val physicalCameras = it.first.physicalCameraIds.toTypedArray()
            for (idx1 in 0 until physicalCameras.size) {
                for (idx2 in (idx1 + 1) until physicalCameras.size) {
                    dualCameras.add(DualCamera(
                        it.second, physicalCameras[idx1], physicalCameras[idx2]))
                }
            }
        }

        return dualCameras
    }

Ява

/**
     * Helper class used to encapsulate a logical camera and two underlying
     * physical cameras
     */
    final class DualCamera {
        final String logicalId;
        final String physicalId1;
        final String physicalId2;

        DualCamera(String logicalId, String physicalId1, String physicalId2) {
            this.logicalId = logicalId;
            this.physicalId1 = physicalId1;
            this.physicalId2 = physicalId2;
        }
    }
    List findDualCameras(CameraManager manager, Integer facing) {
        List dualCameras = new ArrayList<>();

        List cameraIdList;
        try {
            cameraIdList = Arrays.asList(manager.getCameraIdList());
        } catch (CameraAccessException e) {
            e.printStackTrace();
            cameraIdList = new ArrayList<>();
        }

        // Iterate over all the available camera characteristics
        cameraIdList.stream()
                .map(id -> {
                    try {
                        CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
                        return new Pair<>(characteristics, id);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                        return null;
                    }
                })
                .filter(pair -> {
                    // Filter by cameras facing the requested direction
                    return (pair != null) &&
                            (facing == null || pair.first.get(CameraCharacteristics.LENS_FACING).equals(facing));
                })
                .filter(pair -> {
                    // Filter by logical cameras
                    // CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA requires API >= 28
                    IntPredicate logicalMultiCameraPred =
                            arg -> arg == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
                    return Arrays.stream(pair.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES))
                            .anyMatch(logicalMultiCameraPred);
                })
                .forEach(pair -> {
                    // All possible pairs from the list of physical cameras are valid results
                    // NOTE: There could be N physical cameras as part of a logical camera grouping
                    // getPhysicalCameraIds() requires API >= 28
                    String[] physicalCameras = pair.first.getPhysicalCameraIds().toArray(new String[0]);
                    for (int idx1 = 0; idx1 < physicalCameras.length; idx1++) {
                        for (int idx2 = idx1 + 1; idx2 < physicalCameras.length; idx2++) {
                            dualCameras.add(
                                    new DualCamera(pair.second, physicalCameras[idx1], physicalCameras[idx2]));
                        }
                    }
                });
return dualCameras;
}

Управление состоянием физических камер контролируется логической камерой. Чтобы открыть «двойную камеру», откройте логическую камеру, соответствующую физическим камерам:

Котлин

fun openDualCamera(cameraManager: CameraManager,
                       dualCamera: DualCamera,
        // AsyncTask is deprecated beginning API 30
                       executor: Executor = AsyncTask.SERIAL_EXECUTOR,
                       callback: (CameraDevice) -> Unit) {

        // openCamera() requires API >= 28
        cameraManager.openCamera(
            dualCamera.logicalId, executor, object : CameraDevice.StateCallback() {
                override fun onOpened(device: CameraDevice) = callback(device)
                // Omitting for brevity...
                override fun onError(device: CameraDevice, error: Int) = onDisconnected(device)
                override fun onDisconnected(device: CameraDevice) = device.close()
            })
    }

Ява

void openDualCamera(CameraManager cameraManager,
                        DualCamera dualCamera,
                        Executor executor,
                        CameraDeviceCallback cameraDeviceCallback
    ) {

        // openCamera() requires API >= 28
        cameraManager.openCamera(dualCamera.logicalId, executor, new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice cameraDevice) {
               cameraDeviceCallback.callback(cameraDevice);
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                cameraDevice.close();
            }

            @Override
            public void onError(@NonNull CameraDevice cameraDevice, int i) {
                onDisconnected(cameraDevice);
            }
        });
    }

Помимо выбора камеры для открытия, процесс аналогичен открытию камеры в предыдущих версиях Android. Создание сеанса захвата с использованием нового API конфигурации сеанса указывает платформе связать определенные цели с конкретными идентификаторами физических камер:

Котлин

/**
 * Helper type definition that encapsulates 3 sets of output targets:
 *
 *   1. Logical camera
 *   2. First physical camera
 *   3. Second physical camera
 */
typealias DualCameraOutputs =
        Triple<MutableList?, MutableList?, MutableList?>

fun createDualCameraSession(cameraManager: CameraManager,
                            dualCamera: DualCamera,
                            targets: DualCameraOutputs,
                            // AsyncTask is deprecated beginning API 30
                            executor: Executor = AsyncTask.SERIAL_EXECUTOR,
                            callback: (CameraCaptureSession) -> Unit) {

    // Create 3 sets of output configurations: one for the logical camera, and
    // one for each of the physical cameras.
    val outputConfigsLogical = targets.first?.map { OutputConfiguration(it) }
    val outputConfigsPhysical1 = targets.second?.map {
        OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId1) } }
    val outputConfigsPhysical2 = targets.third?.map {
        OutputConfiguration(it).apply { setPhysicalCameraId(dualCamera.physicalId2) } }

    // Put all the output configurations into a single flat array
    val outputConfigsAll = arrayOf(
        outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2)
        .filterNotNull().flatMap { it }

    // Instantiate a session configuration that can be used to create a session
    val sessionConfiguration = SessionConfiguration(
        SessionConfiguration.SESSION_REGULAR,
        outputConfigsAll, executor, object : CameraCaptureSession.StateCallback() {
            override fun onConfigured(session: CameraCaptureSession) = callback(session)
            // Omitting for brevity...
            override fun onConfigureFailed(session: CameraCaptureSession) = session.device.close()
        })

    // Open the logical camera using the previously defined function
    openDualCamera(cameraManager, dualCamera, executor = executor) {

        // Finally create the session and return via callback
        it.createCaptureSession(sessionConfiguration)
    }
}

Ява

/**
 * Helper class definition that encapsulates 3 sets of output targets:
 * 

* 1. Logical camera * 2. First physical camera * 3. Second physical camera */ final class DualCameraOutputs { private final List logicalCamera; private final List firstPhysicalCamera; private final List secondPhysicalCamera; public DualCameraOutputs(List logicalCamera, List firstPhysicalCamera, List third) { this.logicalCamera = logicalCamera; this.firstPhysicalCamera = firstPhysicalCamera; this.secondPhysicalCamera = third; } public List getLogicalCamera() { return logicalCamera; } public List getFirstPhysicalCamera() { return firstPhysicalCamera; } public List getSecondPhysicalCamera() { return secondPhysicalCamera; } } interface CameraCaptureSessionCallback { void callback(CameraCaptureSession cameraCaptureSession); } void createDualCameraSession(CameraManager cameraManager, DualCamera dualCamera, DualCameraOutputs targets, Executor executor, CameraCaptureSessionCallback cameraCaptureSessionCallback) { // Create 3 sets of output configurations: one for the logical camera, and // one for each of the physical cameras. List outputConfigsLogical = targets.getLogicalCamera().stream() .map(OutputConfiguration::new) .collect(Collectors.toList()); List outputConfigsPhysical1 = targets.getFirstPhysicalCamera().stream() .map(s -> { OutputConfiguration outputConfiguration = new OutputConfiguration(s); outputConfiguration.setPhysicalCameraId(dualCamera.physicalId1); return outputConfiguration; }) .collect(Collectors.toList()); List outputConfigsPhysical2 = targets.getSecondPhysicalCamera().stream() .map(s -> { OutputConfiguration outputConfiguration = new OutputConfiguration(s); outputConfiguration.setPhysicalCameraId(dualCamera.physicalId2); return outputConfiguration; }) .collect(Collectors.toList()); // Put all the output configurations into a single flat array List outputConfigsAll = Stream.of( outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2 ) .filter(Objects::nonNull) .flatMap(Collection::stream) .collect(Collectors.toList()); // Instantiate a session configuration that can be used to create a session SessionConfiguration sessionConfiguration = new SessionConfiguration( SessionConfiguration.SESSION_REGULAR, outputConfigsAll, executor, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { cameraCaptureSessionCallback.callback(cameraCaptureSession); } // Omitting for brevity... @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { cameraCaptureSession.getDevice().close(); } }); // Open the logical camera using the previously defined function openDualCamera(cameraManager, dualCamera, executor, (CameraDevice c) -> // Finally create the session and return via callback c.createCaptureSession(sessionConfiguration)); }

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

Когда сеанс камеры готов, отправьте нужные запросы на захват . Каждая цель запроса захвата получает данные от связанной с ней физической камеры, если таковая используется, или возвращается к логической камере.

Пример использования Zoom

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

Рис. 4. Пример замены камер для варианта использования уровня масштабирования (из рекламы Pixel 3).

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

Котлин

fun findShortLongCameraPair(manager: CameraManager, facing: Int? = null): DualCamera? {

    return findDualCameras(manager, facing).map {
        val characteristics1 = manager.getCameraCharacteristics(it.physicalId1)
        val characteristics2 = manager.getCameraCharacteristics(it.physicalId2)

        // Query the focal lengths advertised by each physical camera
        val focalLengths1 = characteristics1.get(
            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
        val focalLengths2 = characteristics2.get(
            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)

        // Compute the largest difference between min and max focal lengths between cameras
        val focalLengthsDiff1 = focalLengths2.maxOrNull()!! - focalLengths1.minOrNull()!!
        val focalLengthsDiff2 = focalLengths1.maxOrNull()!! - focalLengths2.minOrNull()!!

        // Return the pair of camera IDs and the difference between min and max focal lengths
        if (focalLengthsDiff1 < focalLengthsDiff2) {
            Pair(DualCamera(it.logicalId, it.physicalId1, it.physicalId2), focalLengthsDiff1)
        } else {
            Pair(DualCamera(it.logicalId, it.physicalId2, it.physicalId1), focalLengthsDiff2)
        }

        // Return only the pair with the largest difference, or null if no pairs are found
    }.maxByOrNull { it.second }?.first
}

Ява

// Utility functions to find min/max value in float[]
    float findMax(float[] array) {
        float max = Float.NEGATIVE_INFINITY;
        for(float cur: array)
            max = Math.max(max, cur);
        return max;
    }
    float findMin(float[] array) {
        float min = Float.NEGATIVE_INFINITY;
        for(float cur: array)
            min = Math.min(min, cur);
        return min;
    }

DualCamera findShortLongCameraPair(CameraManager manager, Integer facing) {
        return findDualCameras(manager, facing).stream()
                .map(c -> {
                    CameraCharacteristics characteristics1;
                    CameraCharacteristics characteristics2;
                    try {
                        characteristics1 = manager.getCameraCharacteristics(c.physicalId1);
                        characteristics2 = manager.getCameraCharacteristics(c.physicalId2);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                        return null;
                    }

                    // Query the focal lengths advertised by each physical camera
                    float[] focalLengths1 = characteristics1.get(
                            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
                    float[] focalLengths2 = characteristics2.get(
                            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);

                    // Compute the largest difference between min and max focal lengths between cameras
                    Float focalLengthsDiff1 = findMax(focalLengths2) - findMin(focalLengths1);
                    Float focalLengthsDiff2 = findMax(focalLengths1) - findMin(focalLengths2);

                    // Return the pair of camera IDs and the difference between min and max focal lengths
                    if (focalLengthsDiff1 < focalLengthsDiff2) {
                        return new Pair<>(new DualCamera(c.logicalId, c.physicalId1, c.physicalId2), focalLengthsDiff1);
                    } else {
                        return new Pair<>(new DualCamera(c.logicalId, c.physicalId2, c.physicalId1), focalLengthsDiff2);
                    }

                }) // Return only the pair with the largest difference, or null if no pairs are found
                .max(Comparator.comparing(pair -> pair.second)).get().first;
    }

Разумной архитектурой для этого было бы иметь два SurfaceViews — по одному на каждый поток. Эти SurfaceViews меняются местами в зависимости от взаимодействия с пользователем, поэтому в любой момент времени виден только один из них.

Следующий код показывает, как открыть логическую камеру, настроить выходы камеры, создать сеанс камеры и запустить два потока предварительного просмотра:

Котлин

val cameraManager: CameraManager = ...

// Get the two output targets from the activity / fragment
val surface1 = ...  // from SurfaceView
val surface2 = ...  // from SurfaceView

val dualCamera = findShortLongCameraPair(manager)!!
val outputTargets = DualCameraOutputs(
    null, mutableListOf(surface1), mutableListOf(surface2))

// Here you open the logical camera, configure the outputs and create a session
createDualCameraSession(manager, dualCamera, targets = outputTargets) { session ->

  // Create a single request which has one target for each physical camera
  // NOTE: Each target receive frames from only its associated physical camera
  val requestTemplate = CameraDevice.TEMPLATE_PREVIEW
  val captureRequest = session.device.createCaptureRequest(requestTemplate).apply {
    arrayOf(surface1, surface2).forEach { addTarget(it) }
  }.build()

  // Set the sticky request for the session and you are done
  session.setRepeatingRequest(captureRequest, null, null)
}

Ява

CameraManager manager = ...;

        // Get the two output targets from the activity / fragment
        Surface surface1 = ...;  // from SurfaceView
        Surface surface2 = ...;  // from SurfaceView

        DualCamera dualCamera = findShortLongCameraPair(manager, null);
                DualCameraOutputs outputTargets = new DualCameraOutputs(
                null, Collections.singletonList(surface1), Collections.singletonList(surface2));

        // Here you open the logical camera, configure the outputs and create a session
        createDualCameraSession(manager, dualCamera, outputTargets, null, (session) -> {
            // Create a single request which has one target for each physical camera
            // NOTE: Each target receive frames from only its associated physical camera
            CaptureRequest.Builder captureRequestBuilder;
            try {
                captureRequestBuilder = session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                Arrays.asList(surface1, surface2).forEach(captureRequestBuilder::addTarget);

                // Set the sticky request for the session and you are done
                session.setRepeatingRequest(captureRequestBuilder.build(), null, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        });

Все, что осталось сделать, — это предоставить пользователю пользовательский интерфейс для переключения между двумя поверхностями, например, с помощью кнопки или двойного касания SurfaceView . Вы даже можете выполнить некий анализ сцены и автоматически переключаться между двумя потоками.

Искажение объектива

Все объективы производят определенное количество искажений. В Android вы можете запросить искажение, создаваемое объективами, с помощью CameraCharacteristics.LENS_DISTORTION , который заменяет устаревший CameraCharacteristics.LENS_RADIAL_DISTORTION . Для логических камер искажения минимальны, и ваше приложение может использовать кадры в большей или меньшей степени в том виде, в каком они поступают с камеры. Для физических камер потенциально существуют очень разные конфигурации объективов, особенно для широкоугольных объективов.

Некоторые устройства могут реализовывать автоматическую коррекцию искажений через CaptureRequest.DISTORTION_CORRECTION_MODE . Коррекция искажений по умолчанию включена для большинства устройств.

Котлин

val cameraSession: CameraCaptureSession = ...

        // Use still capture template to build the capture request
        val captureRequest = cameraSession.device.createCaptureRequest(
            CameraDevice.TEMPLATE_STILL_CAPTURE
        )

        // Determine if this device supports distortion correction
        val characteristics: CameraCharacteristics = ...
        val supportsDistortionCorrection = characteristics.get(
            CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
        )?.contains(
            CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
        ) ?: false

        if (supportsDistortionCorrection) {
            captureRequest.set(
                CaptureRequest.DISTORTION_CORRECTION_MODE,
                CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
            )
        }

        // Add output target, set other capture request parameters...

        // Dispatch the capture request
        cameraSession.capture(captureRequest.build(), ...)

Ява

CameraCaptureSession cameraSession = ...;

        // Use still capture template to build the capture request
        CaptureRequest.Builder captureRequestBuilder = null;
        try {
            captureRequestBuilder = cameraSession.getDevice().createCaptureRequest(
                    CameraDevice.TEMPLATE_STILL_CAPTURE
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        // Determine if this device supports distortion correction
        CameraCharacteristics characteristics = ...;
        boolean supportsDistortionCorrection = Arrays.stream(
                        characteristics.get(
                                CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES
                        ))
                .anyMatch(i -> i == CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY);
        if (supportsDistortionCorrection) {
            captureRequestBuilder.set(
                    CaptureRequest.DISTORTION_CORRECTION_MODE,
                    CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
            );
        }

        // Add output target, set other capture request parameters...

        // Dispatch the capture request
        cameraSession.capture(captureRequestBuilder.build(), ...);

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