Архитектура CameraX

На этой странице рассматривается архитектура 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 одним из следующих двух способов:

В приведенном ниже примере кода используются сценарии потоковой передачи для оптимизации видеозвонка. Используйте 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, ознакомьтесь со следующими дополнительными ресурсами.

Кодлаб

  • Начало работы с CameraX
  • Пример кода

  • Примеры приложений CameraX