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

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

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

Кодлаб

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

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