На этой странице рассказывается об архитектуре CameraX, включая ее структуру, работу с API, работу с жизненными циклами и объединение вариантов использования.
Структура CameraX
Вы можете использовать CameraX для взаимодействия с камерой устройства через абстракцию, называемую вариантом использования. Доступны следующие варианты использования:
- Preview : принимает поверхность для отображения предварительного просмотра, например
PreviewView
. - Анализ изображений : предоставляет доступные ЦП буферы для анализа, например, для машинного обучения.
- Захват изображения : захватывает и сохраняет фотографию.
- Захват видео : захватывайте видео и аудио с помощью
VideoCapture
Варианты использования можно комбинировать и активировать одновременно. Например, приложение может позволить пользователю просматривать изображение, которое видит камера, используя вариант использования предварительного просмотра, иметь вариант использования для анализа изображения, который определяет, улыбаются ли люди на фотографии, и включать вариант использования для захвата изображения, чтобы сделать снимок. раз они есть.
Модель API
Для работы с библиотекой вы указываете следующие вещи:
- Желаемый вариант использования с опциями конфигурации.
- Что делать с выходными данными, подключив прослушиватели.
- Предполагаемый поток, например, когда включать камеры и когда создавать данные, путем привязки варианта использования к жизненным циклам архитектуры Android .
Есть два способа написать приложение CameraX: CameraController
(отлично, если вам нужен самый простой способ использования CameraX) или CameraProvider
(отлично, если вам нужна большая гибкость).
КамераКонтроллер
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
по-прежнему прост в использовании, но поскольку большую часть настройки берет на себя разработчик приложения, появляется больше возможностей для настройки конфигурации, например включение вращения выходного изображения или установка формата выходного изображения в 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
может заставить 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 }
Ява
@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 имеет следующие минимальные требования к версии:
- Android API уровня 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.5.0-alpha02" // 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.5.0-alpha02" // 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
, такие как режим автофокусировки.Расширьте
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, обратитесь к следующим дополнительным ресурсам.