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

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

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

Codelab

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

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