На этой странице рассматривается архитектура CameraX, включая её структуру, способы работы с API, работу с жизненными циклами и комбинирование вариантов использования.
Структура CameraX
С помощью CameraX можно взаимодействовать с камерой устройства через абстракцию, называемую вариантом использования. Доступны следующие варианты использования:
- Preview : принимает поверхность для отображения предварительного просмотра, например,
PreviewView. - Анализ изображений : предоставляет доступные для ЦП буферы для анализа, например, для машинного обучения.
- Функция захвата изображения : делает снимок и сохраняет его.
- Захват видео : захват видео и аудио с помощью
VideoCapture
Варианты использования могут комбинироваться и быть активными одновременно. Например, приложение может позволять пользователю просматривать изображение, полученное с камеры, используя сценарий предварительного просмотра, иметь сценарий анализа изображения, определяющий, улыбаются ли люди на фотографии, и включать сценарий захвата изображения для съемки фотографии, когда они улыбаются.
модель API
Для работы с библиотекой необходимо указать следующие параметры:
- Желаемый сценарий использования с параметрами конфигурации.
- Что делать с выходными данными, полученными при добавлении слушателей?
- Предполагаемый алгоритм действий, например, когда включать камеры и когда генерировать данные, определяется путем привязки сценария использования к жизненным циклам архитектуры Android .
Существует два способа написания приложения 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
Java
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 по-прежнему прост в использовании, но поскольку разработчик приложения берет на себя большую часть настройки, появляется больше возможностей для персонализации конфигурации, например, включение поворота выходного изображения или установка формата выходного изображения в 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())
Java
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, вместо того чтобы привязывать его к стандартному Android LifecycleOwner .
В следующем примере кода показано, как создать простой пользовательский объект 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 } }
Java
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)) }
Java
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может привести к тому, что CameraX будет дублировать поток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 }
Java
@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 каждого модуля приложения:
Круто
dependencies { // CameraX core library using the camera2 implementation def camerax_version = "1.6.0-beta02" // 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}" }
Котлин
dependencies { // CameraX core library using the camera2 implementation val camerax_version = "1.6.0-beta02" // 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 .
Для получения дополнительной информации о том, как CameraX настроила свойства Camera2, используйте Camera2CameraInfo для чтения базовых характеристик CameraCharacteristics . Вы также можете записать базовые свойства Camera2 одним из следующих двух способов:
Используйте
Camera2CameraControl, которая позволяет задавать свойства базового объектаCaptureRequest, например, режим автофокусировки.Расширьте
UseCaseCameraX с помощью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)
Java
// 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, ознакомьтесь со следующими дополнительными ресурсами.