Примечание. На этой странице речь идет о пакете Camera2. Если вашему приложению не требуются определенные низкоуровневые функции Camera2, рекомендуем использовать CameraX. CameraX и Camera2 поддерживают Android 5.0 (уровень API 21) и более поздние версии.
Поддержка нескольких камер появилась в Android 9 (уровень API 28). С момента выпуска API на рынке появились устройства, которые его поддерживают. Многие варианты использования нескольких камер тесно связаны с определенной конфигурацией оборудования. Другими словами, не все варианты использования совместимы со всеми устройствами, поэтому функции с несколькими камерами хорошо подходят для доставки функций через Google Play.
Вот несколько примеров использования:
- Масштабирование. Переключение между камерами в зависимости от области кадрирования или желаемого фокусного расстояния.
- Глубина. Использование нескольких камер для создания карты глубины.
- Боке – эффект размытия фона, который достигается за счет использования информации о глубине.
Разница между логическими и физическими камерами
Чтобы понять, как работает API для нескольких камер, нужно знать, чем логические камеры отличаются от физических. Например, на устройстве может быть три задних камеры. В этом примере каждая из трех задних камер считается физической. Логическая камера – это группа из двух или более физических камер. Выходные данные логической камеры могут представлять собой поток, поступающий с одной из физических камер, или объединенный поток, поступающий одновременно с нескольких физических камер. В любом случае обработка потока выполняется на уровне абстрагирования оборудования (HAL) камеры.
Многие производители телефонов разрабатывают собственные приложения для камеры, которые обычно предустанавливаются на устройства. Чтобы использовать все возможности оборудования, они могут применять частные или скрытые API или получать особые права от реализации драйвера, недоступные другим приложениям. В некоторых устройствах реализована концепция логических камер, которые предоставляют объединенный поток кадров с разных физических камер, но только определенным привилегированным приложениям. Часто фреймворку доступна только одна из физических камер. На следующей схеме показано, как это работало для сторонних разработчиков до выхода Android 9:
Начиная с Android 9, в приложениях для Android запрещено использовать частные API. Поскольку в фреймворке реализована поддержка нескольких камер, производителям телефонов рекомендуется использовать логическую камеру для всех физических камер, направленных в одну сторону. Ниже описано, что сторонние разработчики могут увидеть на устройствах с Android 9 и более поздних версий.
Функциональность логической камеры полностью зависит от реализации аппаратно-зависимого уровня камеры производителем устройства. Например, логическая камера устройства Pixel 3 выбирает одну из физических камер в зависимости от запрошенного фокусного расстояния и области кадрирования.
API для нескольких камер
В новом API добавлены следующие константы, классы и методы:
CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERACameraCharacteristics.getPhysicalCameraIds()CameraCharacteristics.getAvailablePhysicalCameraRequestKeys()CameraDevice.createCaptureSession(SessionConfiguration config)CameraCharacteritics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPEOutputConfigurationиSessionConfiguration
Из-за изменений в документе CDD (Compatibility Definition Document) для Android разработчики должны соблюдать определенные требования при использовании многокамерного 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. В конфигурации сеанса есть несколько конфигураций вывода, в каждой из которых задан набор целевых объектов вывода и, при необходимости, идентификатор физической камеры.
У запросов на запись есть целевой выход. Фреймворк определяет, на какую физическую (или логическую) камеру отправлять запросы, на основе подключенного целевого устройства вывода. Если целевой выход соответствует одному из целевых выходов, отправленных в качестве конфигурации выхода вместе с идентификатором физической камеры, то эта физическая камера получает и обрабатывает запрос.
Использование двух физических камер
Ещё одно дополнение к API камеры для нескольких камер – возможность идентифицировать логические камеры и находить физические камеры, которые за ними стоят. Вы можете определить функцию, которая поможет найти потенциальные пары физических камер, которые можно использовать для замены одного из логических потоков камеры:
Kotlin
/** * 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 }
Java
/** * 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; } } ListfindDualCameras(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; }
Управление состоянием физических камер осуществляется логической камерой. Чтобы открыть двойную камеру, откройте логическую камеру, соответствующую физическим камерам:
Kotlin
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() }) }
Java
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 конфигурации сеанса фреймворк связывает определенные цели с конкретными идентификаторами физических камер:
Kotlin
/** * 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) } }
Java
/** * 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()); L ist 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 arrayList 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
Можно объединить несколько физических камер в один поток, чтобы пользователи могли переключаться между ними и менять угол обзора, фактически выбирая другой уровень масштабирования.
Сначала выберите пару физических камер, между которыми пользователи смогут переключаться. Для максимального эффекта можно выбрать пару камер с минимальным и максимальным доступным фокусным расстоянием.
Kotlin
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 }
Java
// 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 меняются в зависимости от действий пользователя, поэтому в любой момент времени виден только один из них.
В приведенном ниже коде показано, как открыть логическую камеру, настроить ее выходные данные, создать сеанс камеры и запустить два потока предварительного просмотра:
Kotlin
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) }
Java
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.
Коррекция искажений по умолчанию включена на большинстве устройств.
Kotlin
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(), ...)
Java
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(), ...);
Запрос на съемку в этом режиме может повлиять на частоту кадров, которую может обеспечить камера. Вы можете включить коррекцию искажений только для фотографий.