На этой странице описывается архитектура CameraX, включая ее структуру, как работать с API, как работать с жизненными циклами и как объединять варианты использования.
Структура CameraX
CameraX можно использовать для взаимодействия с камерой устройства посредством абстракции, называемой вариантом использования. Доступны следующие варианты использования:
- Preview : принимает поверхность для отображения предварительного просмотра, например
PreviewView
. - Анализ изображений : предоставляет доступные ЦП буферы для анализа, например, для машинного обучения.
- Захват изображения : делает снимок и сохраняет его.
- Видеозахват : захват видео и звука с помощью
VideoCapture
Варианты использования можно комбинировать и использовать одновременно. Например, приложение может позволить пользователю просматривать изображение, полученное камерой, с помощью варианта использования предварительного просмотра, использовать вариант использования анализа изображения, чтобы определить, улыбаются ли люди на фотографии, а также вариант использования захвата изображения, чтобы сделать снимок, когда люди улыбаются.
API-модель
Для работы с библиотекой необходимо указать следующие параметры:
- Желаемый вариант использования с вариантами конфигурации.
- Что делать с выходными данными, прикрепив прослушиватели.
- Предполагаемый поток, например, когда включать камеры и когда производить данные, путем привязки варианта использования к жизненным циклам архитектуры Android .
Существует 2 способа написать приложение CameraX: CameraController
(отлично подходит, если вам нужен самый простой способ использования CameraX) или CameraProvider
(отлично подходит, если вам нужна большая гибкость).
CameraController
Класс CameraController
предоставляет большую часть основных функций CameraX в одном классе. Он требует минимального кода настройки и автоматически управляет инициализацией камеры, управлением вариантами использования, вращением цели, фокусировкой касанием, масштабированием сведением и разведением пальцев и другими функциями. Конкретный класс, расширяющий CameraController
, — это LifecycleCameraController
.
Котлин
val previewView: PreviewView = viewBinding.previewView var cameraController = LifecycleCameraController(baseContext) cameraController.bindToLifecycle(this) cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA previewView.controller = cameraController
Ява
PreviewView previewView = viewBinding.previewView; LifecycleCameraController cameraController = new LifecycleCameraController(baseContext); cameraController.bindToLifecycle(this); cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA); previewView.setController(cameraController);
Используемые по умолчанию варианты UseCase
для CameraController
— Preview
, ImageCapture
и ImageAnalysis
. Чтобы отключить ImageCapture
или ImageAnalysis
или включить VideoCapture
, используйте метод setEnabledUseCases()
.
Для получения дополнительных сведений об использовании CameraController
см. пример сканера QR-кодов или видео с основами CameraController
.
CameraProvider
CameraProvider
по-прежнему прост в использовании, но, поскольку разработчик приложения выполняет большую часть настройки, появляется больше возможностей для настройки конфигурации, например, включения поворота выходного изображения или настройки формата выходного изображения в ImageAnalysis
. Вы также можете использовать собственный Surface
для предварительного просмотра камеры, что обеспечивает большую гибкость, тогда как при использовании CameraController требуется PreviewView
. Использование существующего кода Surface
может быть полезно, если он уже является входными данными для других частей вашего приложения.
Варианты использования настраиваются с помощью методов set()
и завершаются методом build()
. Каждый объект варианта использования предоставляет набор API, специфичных для конкретного варианта использования. Например, вариант использования захвата изображения обеспечивает вызов метода takePicture()
.
Вместо того, чтобы помещать вызовы конкретных методов запуска и остановки в onResume()
и onPause()
, приложение определяет жизненный цикл, с которым связана камера, с помощью cameraProvider.bindToLifecycle()
. Этот жизненный цикл затем сообщает CameraX, когда следует настраивать сеанс съёмки камеры, и обеспечивает правильное изменение состояния камеры в соответствии с переходами между жизненными циклами.
Шаги реализации для каждого варианта использования см. в разделах Реализация предварительного просмотра , Анализ изображений , Захват изображений и Захват видео.
Предварительный вариант использования взаимодействует с Surface
для отображения. Приложения создают вариант использования с параметрами конфигурации, используя следующий код:
Котлин
val preview = Preview.Builder().build() val viewFinder: PreviewView = findViewById(R.id.previewView) // The use case is bound to an Android Lifecycle with the following code val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview) // PreviewView creates a surface provider and is the recommended provider preview.setSurfaceProvider(viewFinder.getSurfaceProvider())
Ява
Preview preview = new Preview.Builder().build(); PreviewView viewFinder = findViewById(R.id.view_finder); // The use case is bound to an Android Lifecycle with the following code Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview); // PreviewView creates a surface provider, using a Surface from a different // kind of view will require you to implement your own surface provider. preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();
Дополнительный пример кода смотрите в официальном примере приложения CameraX .
Жизненные циклы CameraX
CameraX отслеживает жизненный цикл камеры, чтобы определить, когда открывать камеру, когда создавать сеанс съёмки и когда останавливать и завершать работу. API вариантов использования предоставляют вызовы методов и обратные вызовы для отслеживания хода выполнения.
Как объясняется в разделе «Объединение вариантов использования» , вы можете объединить несколько вариантов использования в один жизненный цикл. Если вашему приложению требуется поддержка вариантов использования, которые невозможно объединить, вы можете выполнить одно из следующих действий:
- Группируйте совместимые варианты использования в несколько фрагментов , а затем переключайтесь между фрагментами.
- Создайте собственный компонент жизненного цикла и используйте его для ручного управления жизненным циклом камеры.
Если вы разделяете владельцев жизненных циклов вариантов использования представления и камеры (например, если вы используете настраиваемый жизненный цикл или фрагмент сохранения ), необходимо убедиться, что все варианты использования отвязаны от CameraX с помощью ProcessCameraProvider.unbindAll()
или отвязав каждый вариант использования по отдельности. В качестве альтернативы, привязывая варианты использования к жизненному циклу, вы можете позволить CameraX управлять открытием и закрытием сеанса захвата, а также отвязкой вариантов использования.
Если все функциональные возможности вашей камеры соответствуют жизненному циклу одного компонента, поддерживающего жизненный цикл, такого как AppCompatActivity
или фрагмент AppCompat
, то использование жизненного цикла этого компонента при связывании всех желаемых вариантов использования обеспечит готовность функциональных возможностей камеры, когда компонент, поддерживающий жизненный цикл, активен, и их безопасное удаление без потребления каких-либо ресурсов в противном случае.
Владельцы пользовательских жизненных циклов
В сложных случаях вы можете создать собственный LifecycleOwner
, чтобы ваше приложение могло явно управлять жизненным циклом сеанса CameraX вместо того, чтобы привязывать его к стандартному LifecycleOwner
Android.
В следующем примере кода показано, как создать простой пользовательский LifecycleOwner:
Котлин
class CustomLifecycle : LifecycleOwner { private val lifecycleRegistry: LifecycleRegistry init { lifecycleRegistry = LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED) } ... fun doOnResume() { lifecycleRegistry.markState(State.RESUMED) } ... override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Ява
public class CustomLifecycle implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; public CustomLifecycle() { lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } ... public void doOnResume() { lifecycleRegistry.markState(State.RESUMED); } ... public Lifecycle getLifecycle() { return lifecycleRegistry; } }
Используя LifecycleOwner
, ваше приложение может размещать переходы состояний в нужных точках кода. Подробнее о реализации этой функции в вашем приложении см. в разделе «Реализация пользовательского LifecycleOwner » .
Одновременные варианты использования
Варианты использования могут выполняться одновременно. Хотя варианты использования можно последовательно привязывать к жизненному циклу, лучше всего привязать все варианты использования одним вызовом CameraProcessProvider.bindToLifecycle()
. Подробнее о передовых методах изменения конфигурации см. в разделе Обработка изменений конфигурации .
В следующем примере кода приложение указывает, что два варианта использования должны быть созданы и запущены одновременно. Также оно указывает жизненный цикл для обоих вариантов использования, чтобы они запускались и останавливались в соответствии с жизненным циклом.
Котлин
private lateinit var imageCapture: ImageCapture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { // Camera provider is now guaranteed to be available val cameraProvider = cameraProviderFuture.get() // Set up the preview use case to display camera preview. val preview = Preview.Builder().build() // Set up the capture use case to allow users to take photos. imageCapture = ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build() // Choose the camera by requiring a lens facing val cameraSelector = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_FRONT) .build() // Attach use cases to the camera with the same lifecycle owner val camera = cameraProvider.bindToLifecycle( this as LifecycleOwner, cameraSelector, preview, imageCapture) // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()) }, ContextCompat.getMainExecutor(this)) }
Ява
private ImageCapture imageCapture; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); PreviewView previewView = findViewById(R.id.previewView); ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { // Camera provider is now guaranteed to be available ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); // Set up the view finder use case to display camera preview Preview preview = new Preview.Builder().build(); // Set up the capture use case to allow users to take photos imageCapture = new ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build(); // Choose the camera by requiring a lens facing CameraSelector cameraSelector = new CameraSelector.Builder() .requireLensFacing(lensFacing) .build(); // Attach use cases to the camera with the same lifecycle owner Camera camera = cameraProvider.bindToLifecycle( ((LifecycleOwner) this), cameraSelector, preview, imageCapture); // Connect the preview use case to the previewView preview.setSurfaceProvider( previewView.getSurfaceProvider()); } catch (InterruptedException | ExecutionException e) { // Currently no exceptions thrown. cameraProviderFuture.get() // shouldn't block since the listener is being called, so no need to // handle InterruptedException. } }, ContextCompat.getMainExecutor(this)); }
CameraX позволяет одновременно использовать по одному экземпляру Preview
, VideoCapture
, ImageAnalysis
и ImageCapture
. Кроме того,
- Каждый вариант использования может работать сам по себе. Например, приложение может записывать видео без использования предварительного просмотра.
- При включении расширений гарантированно работает только комбинация
ImageCapture
иPreview
. В зависимости от реализации OEM-производителя, добавлениеImageAnalysis
может быть невозможным; расширения нельзя включить для использованияVideoCapture
. Подробности см. в справочном документе по расширениям . - В зависимости от возможностей камеры, некоторые камеры могут поддерживать комбинацию в режимах с более низким разрешением, но не могут поддерживать ту же комбинацию в режимах с более высоким разрешением.
- На устройствах с аппаратной версией камеры
FULL
или ниже одновременное использование функцийPreview
,VideoCapture
иImageCapture
илиImageAnalysis
может привести к дублированию потокаPRIV
камеры дляPreview
иVideoCapture
. Такое дублирование, называемое совместным использованием потока, позволяет одновременно использовать эти функции, но приводит к повышению требований к вычислительной мощности. В результате может наблюдаться несколько более высокая задержка и сокращение времени работы аккумулятора.
Поддерживаемый уровень оборудования можно узнать из Camera2CameraInfo
. Например, следующий код проверяет, относится ли задняя камера по умолчанию к устройству LEVEL_3
:
Котлин
@androidx.annotation.OptIn(ExperimentalCamera2Interop::class) fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.availableCameraInfos) .firstOrNull() ?.let { Camera2CameraInfo.from(it) } ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 } return false }
Ява
@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class) Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { List\<CameraInfo\> filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA .filter(cameraProvider.getAvailableCameraInfos()); if (!filteredCameraInfos.isEmpty()) { return Objects.equals( Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL), CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3); } } return false; }
Разрешения
Вашему приложению потребуется разрешение CAMERA
. Для сохранения изображений в файлы также потребуется разрешение WRITE_EXTERNAL_STORAGE
, за исключением устройств под управлением Android 10 и более поздних версий.
Дополнительную информацию о настройке разрешений для вашего приложения см. в разделе Запрос разрешений для приложения .
Требования
CameraX имеет следующие минимальные требования к версии:
- API Android уровня 21
- Компоненты архитектуры Android 1.1.1
Для действий, учитывающих жизненный цикл, используйте FragmentActivity
или AppCompatActivity
.
Объявить зависимости
Чтобы добавить зависимость от CameraX, необходимо добавить репозиторий Google Maven в свой проект.
Откройте файл settings.gradle
для вашего проекта и добавьте репозиторий google()
, как показано ниже:
Круто
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Котлин
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } }
Добавьте следующее в конец блока Android:
Круто
android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Котлин
android { compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } // For Kotlin projects kotlinOptions { jvmTarget = "1.8" } }
Добавьте следующее в файл build.gradle
каждого модуля приложения:
Groovy
dependencies { // CameraX core library using the camera2 implementation def camerax_version = "1.5.0-beta01" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation "androidx.camera:camera-core:${camerax_version}" implementation "androidx.camera:camera-camera2:${camerax_version}" // If you want to additionally use the CameraX Lifecycle library implementation "androidx.camera:camera-lifecycle:${camerax_version}" // If you want to additionally use the CameraX VideoCapture library implementation "androidx.camera:camera-video:${camerax_version}" // If you want to additionally use the CameraX View class implementation "androidx.camera:camera-view:${camerax_version}" // If you want to additionally add CameraX ML Kit Vision Integration implementation "androidx.camera:camera-mlkit-vision:${camerax_version}" // If you want to additionally use the CameraX Extensions library implementation "androidx.camera:camera-extensions:${camerax_version}" }
Kotlin
dependencies { // CameraX core library using the camera2 implementation val camerax_version = "1.5.0-beta01" // The following line is optional, as the core library is included indirectly by camera-camera2 implementation("androidx.camera:camera-core:${camerax_version}") implementation("androidx.camera:camera-camera2:${camerax_version}") // If you want to additionally use the CameraX Lifecycle library implementation("androidx.camera:camera-lifecycle:${camerax_version}") // If you want to additionally use the CameraX VideoCapture library implementation("androidx.camera:camera-video:${camerax_version}") // If you want to additionally use the CameraX View class implementation("androidx.camera:camera-view:${camerax_version}") // If you want to additionally add CameraX ML Kit Vision Integration implementation("androidx.camera:camera-mlkit-vision:${camerax_version}") // If you want to additionally use the CameraX Extensions library implementation("androidx.camera:camera-extensions:${camerax_version}") }
Дополнительную информацию о настройке приложения в соответствии с этими требованиями см. в разделе Объявление зависимостей .
Совместимость CameraX с Camera2
CameraX построен на базе Camera2, и CameraX предоставляет способы чтения и даже записи свойств в реализации Camera2. Подробную информацию см. в пакете Interop .
Для получения дополнительной информации о настройке свойств Camera2 с помощью CameraX используйте Camera2CameraInfo
для чтения базовых свойств CameraCharacteristics
. Вы также можете записать базовые свойства Camera2 одним из следующих двух способов:
Используйте
Camera2CameraControl
, который позволяет задавать свойства базовогоCaptureRequest
, например режим автофокусировки.Расширьте
UseCase
CameraX с помощьюCamera2Interop.Extender
. Это позволяет задавать свойства CaptureRequest, как иCamera2CameraControl
. Это также предоставляет дополнительные элементы управления, например, настройку варианта использования потока для оптимизации камеры под ваш сценарий использования. Подробнее см. в разделе «Использование вариантов использования потока для повышения производительности» .
В следующем примере кода используются варианты использования потока для оптимизации видеозвонка. Используйте Camera2CameraInfo
, чтобы узнать, доступен ли вариант использования потока для видеозвонка. Затем используйте Camera2Interop.Extender
, чтобы задать базовый вариант использования потока.
Котлин
// Set underlying Camera2 stream use case to optimize for video calls. val videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong() // Check available CameraInfos to find the first one that supports // the video call stream use case. val frontCameraInfo = cameraProvider.getAvailableCameraInfos() .first { cameraInfo -> val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES )?.contains(videoCallStreamId) val isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT) (isVideoCallStreamingSupported == true) && isFrontFacing } val cameraSelector = frontCameraInfo.cameraSelector // Start with a Preview Builder. val previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation) // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId) // Bind the Preview UseCase and the corresponding CameraSelector. val preview = previewBuilder.build() camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Ява
// Set underlying Camera2 stream use case to optimize for video calls. Long videoCallStreamId = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong(); // Check available CameraInfos to find the first one that supports // the video call stream use case. List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos(); CameraInfo frontCameraInfo = null; for (cameraInfo in cameraInfos) { Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo) .getCameraCharacteristic( CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES ); boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases) .contains(videoCallStreamId); boolean isFrontFacing = (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT); if (isVideoCallStreamingSupported && isFrontFacing) { frontCameraInfo = cameraInfo; } } if (frontCameraInfo == null) { // Handle case where video call streaming is not supported. } CameraSelector cameraSelector = frontCameraInfo.getCameraSelector(); // Start with a Preview Builder. Preview.Builder previewBuilder = Preview.Builder() .setTargetAspectRatio(screenAspectRatio) .setTargetRotation(rotation); // Use Camera2Interop.Extender to set the video call stream use case. Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId); // Bind the Preview UseCase and the corresponding CameraSelector. Preview preview = previewBuilder.build() Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Дополнительные ресурсы
Чтобы узнать больше о CameraX, ознакомьтесь со следующими дополнительными ресурсами.