Примечание. На этой странице рассматривается пакет Camera2. Если вашему приложению не требуются определенные низкоуровневые функции Camera2, рекомендуем использовать CameraX. CameraX и Camera2 поддерживают Android 5.0 (уровень API 21) и более поздние версии.
Поддержка нескольких камер появилась в Android 9 (уровень API 28). С момента выпуска API на рынке появились устройства, которые его поддерживают. Многие сценарии использования нескольких камер тесно связаны с конкретной конфигурацией оборудования. Другими словами, не все сценарии использования совместимы с каждым устройством, что делает функции с несколькими камерами хорошим кандидатом для Play Feature Delivery.
Вот несколько примеров:
- Масштабирование. Переключение между камерами в зависимости от области кадрирования или выбранного фокусного расстояния.
- Глубина. Использование нескольких камер для создания карты глубины.
- Боке. Имитация малой глубины резкости, как у зеркальных фотоаппаратов, на основе информации о глубине.
Разница между логическими и физическими камерами
Чтобы понять, как работает API для нескольких камер, нужно разобраться в разнице между логическими и физическими камерами. Для примера рассмотрим устройство с тремя задними камерами. В этом примере каждая из трех задних камер считается физической. Логическая камера – это группа из двух или более физических камер. Выходные данные логической камеры могут представлять собой поток, поступающий от одной из физических камер, или объединенный поток, поступающий одновременно от нескольких физических камер. В любом случае поток обрабатывается аппаратно-зависимым уровнем (HAL) камеры.
Многие производители телефонов разрабатывают собственные приложения для камеры, которые обычно предустановлены на их устройствах. Чтобы использовать все возможности оборудования, они могут применять закрытые или скрытые API или получать особые права от драйвера, недоступные другим приложениям. Некоторые устройства реализуют концепцию логических камер, предоставляя объединенный поток кадров с разных физических камер, но только для определенных привилегированных приложений. Часто фреймворку доступна только одна из физических камер. На схеме ниже показано, как обстояли дела у сторонних разработчиков до выхода Android 9.
Начиная с Android 9, в приложениях для Android больше не разрешается использовать закрытые API. Поскольку фреймворк поддерживает несколько камер, производителям телефонов настоятельно рекомендуется предоставлять логическую камеру для всех физических камер, направленных в одну сторону. Ниже описано, что сторонние разработчики увидят на устройствах с Android 9 и более поздними версиями.
Возможности логической камеры полностью зависят от реализации HAL камеры OEM-производителем. Например, на устройстве 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 (Android Compatibility Definition Document) к разработчикам, использующим Multi-Camera 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<DualCamera> {
val dualCameras = MutableList<DualCamera>()
// 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;
}
}
List<DualCamera> findDualCameras(CameraManager manager, Integer facing) {
List<DualCamera> dualCameras = new ArrayList<>();
List<String> 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<Surface>?, MutableList<Surface>?, MutableList<Surface>?>
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 using the callback
it.createCaptureSession(sessionConfiguration)
}
}
Java
/**
* Helper class definition that encapsulates 3 sets of output targets:
* <p>
* 1. Logical camera
* 2. First physical camera
* 3. Second physical camera
*/
final class DualCameraOutputs {
private final List<Surface> logicalCamera;
private final List<Surface> firstPhysicalCamera;
private final List<Surface> secondPhysicalCamera;
public DualCameraOutputs(List<Surface> logicalCamera, List<Surface> firstPhysicalCamera, List<Surface> third) {
this.logicalCamera = logicalCamera;
this.firstPhysicalCamera = firstPhysicalCamera;
this.secondPhysicalCamera = third;
}
public List<Surface> getLogicalCamera() {
return logicalCamera;
}
public List<Surface> getFirstPhysicalCamera() {
return firstPhysicalCamera;
}
public List<Surface> 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<OutputConfiguration> outputConfigsLogical = targets.getLogicalCamera().stream()
.map(OutputConfiguration::new)
.collect(Collectors.toList());
List<OutputConfiguration> outputConfigsPhysical1 = targets.getFirstPhysicalCamera().stream()
.map(s -> {
OutputConfiguration outputConfiguration = new OutputConfiguration(s);
outputConfiguration.setPhysicalCameraId(dualCamera.physicalId1);
return outputConfiguration;
})
.collect(Collectors.toList());
List<OutputConfiguration> 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<OutputConfiguration> 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 using the 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(), ...);
Установка запроса на захват в этом режиме может повлиять на частоту кадров, которую может производить камера. Вы можете включить коррекцию искажений только для фотографий.