Arquitetura do CameraX

Esta página aborda a arquitetura do CameraX, incluindo a estrutura, como trabalhar com a API e os ciclos de vida e como combinar casos de uso.

Estrutura do CameraX

É possível usar o CameraX para interagir com a câmera de um dispositivo por uma abstração chamada de caso de uso. Os casos de uso abaixo estão disponíveis:

  • Visualização: aceita uma superfície para exibir uma visualização, como uma PreviewView.
  • Análise de imagem: fornece buffers acessíveis por CPU para análise, por exemplo, para aprendizado de máquina.
  • Captura de imagem: captura e salva uma foto.
  • Captura de vídeo: faz uma captura de vídeo e áudio com VideoCapture.

Os casos de uso podem ser combinados e ativados simultaneamente. Por exemplo, um app pode permitir que o usuário visualize a imagem que a câmera vê usando um caso de uso de visualização, ter um caso de uso de análise de imagem que determina se as pessoas na foto estão sorrindo e incluir um caso de uso de captura de imagem para tirar uma foto quando elas sorrirem.

Modelo de API

Para trabalhar com a biblioteca, especifique o seguinte:

  • O caso de uso desejado com opções de configuração.
  • O que fazer com os dados de saída ao anexar listeners.
  • O fluxo pretendido, por exemplo, quando ativar câmeras e produzir dados ao vincular o caso de uso ao Android Architecture Lifecycles.

Há duas maneiras de criar um app do CameraX: um CameraController (excelente se você quiser a maneira mais simples de usar o CameraX) ou um CameraProvider (ótimo se você precisar de mais flexibilidade).

CameraController

Um CameraController fornece a maioria das funcionalidades principais do CameraX em uma única classe. Ele requer pouco código de configuração e processa automaticamente a inicialização da câmera, o gerenciamento de casos de uso, a rotação desejada, o toque para focar, o gesto de pinça para aplicar zoom e muito mais. A classe concreta que estende CameraController é LifecycleCameraController.

Kotlin

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);

Os UseCases padrão do CameraController são Preview, ImageCapture e ImageAnalysis. Para desativar ImageCapture ou ImageAnalysis ou ativar VideoCapture, use o método setEnabledUseCases().

Para conferir mais usos de CameraController, consulte o exemplo do leitor de QR code ou o vídeo de noções básicas do CameraController (links em inglês).

CameraProvider

Um CameraProvider ainda é fácil de usar, mas como o desenvolvedor do app processa mais da configuração, há mais oportunidades para personalizá-la, como ativar a rotação da imagem de saída ou definir o formato da imagem de saída em ImageAnalysis. Você também pode usar uma Surface personalizada para a visualização da câmera, o que proporciona mais flexibilidade. Já com o CameraController é necessário usar uma PreviewView. Usar o código da Surface já existente pode ser útil se ele já for uma entrada para outras partes do app.

Os casos de uso são configurados com métodos set() e finalizados com o método build(). Cada objeto de caso de uso fornece um conjunto de APIs específicas ao casos de uso. Por exemplo, o caso de uso de captura de imagem fornece uma chamada de método takePicture().

Em vez de um aplicativo posicionar chamadas específicas de início e parada em onResume() e onPause(), o aplicativo especifica um ciclo de vida ao qual a câmera será associada, usando cameraProvider.bindToLifecycle(). O ciclo de vida informa o CameraX quando configurar a sessão de captura de câmera, garantindo que o estado da câmera mude de maneira adequada para corresponder às transições do ciclo.

Para conferir as etapas de implementação de cada caso de uso, consulte Implementar uma visualização, Analisar imagens, Tirar uma foto e Captura de vídeo.

O caso de uso da visualização interage com uma Surface para exibição. Os aplicativos usam o seguinte código para criar o caso de uso com opções de configuração:

Kotlin

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();

Para ver mais exemplos de códigos, consulte o app de exemplo oficial do CameraX (link em inglês).

Lifecycles do CameraX

O CameraX leva em conta um ciclo de vida para determinar quando abrir a câmera, quando criar uma sessão de captura e quando parar tudo e encerrar. As APIs de caso de uso fornecem chamadas de método e callbacks para monitorar o progresso.

Conforme explicado em Combinar casos de uso, é possível vincular algumas combinações de casos de uso a um único ciclo de vida. Quando seu app tiver que ser compatível com casos de uso que não podem ser combinados, você poderá seguir um destes procedimentos:

  • Agrupe os casos de uso compatíveis em mais de um fragmento e depois alterne entre os fragmentos.
  • Crie um componente de ciclo de vida personalizado e use-o para controlar manualmente o ciclo de vida da câmera.

Se você desacoplar os proprietários de ciclos de vida dos seus casos de uso de visualização e câmera, por exemplo, ao usar um ciclo de vida personalizado ou um fragmento de retenção, será necessário garantir que todos os casos de uso estejam desvinculados da CameraX usando ProcessCameraProvider.unbindAll() ou desvinculando cada um deles individualmente. Como alternativa, ao vincular casos de uso a um ciclo de vida, você pode deixar que o CameraX gerencie a abertura e o encerramento da sessão de captura e a desvinculação dos casos de uso.

Se toda a funcionalidade da sua câmera corresponder ao ciclo de vida de um único componente com reconhecimento de ciclo de vida, como um fragmento AppCompatActivity ou AppCompat, usar o ciclo de vida desse componente ao vincular todos os casos de uso garantirá que a funcionalidade de câmera esteja pronta quando o componente com reconhecimento de ciclo de vida estiver ativo e que, caso contrário, seja descartada com segurança, sem consumir recursos.

LifecycleOwners personalizados

Em casos avançados, é possível criar um LifecycleOwner personalizado para permitir que seu app controle explicitamente o ciclo de vida da sessão do CameraX, em vez de vinculá-lo a um LifecycleOwner padrão do Android.

O exemplo de código a seguir mostra como criar um LifecycleOwner personalizado simples:

Kotlin

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;
    }
}

Com esse LifecycleOwner, seu app poderá posicionar transições de estado em pontos específicos do código. Para ter mais informações sobre como implementar essa funcionalidade no seu app, consulte Implementar um LifecycleOwner personalizado.

Casos de usos simultâneos

Casos de uso podem ser executados simultaneamente. Embora eles possam ser vinculados de modo sequencial a um ciclo de vida, é melhor vincular todos os casos de uso com uma única chamada a CameraProcessProvider.bindToLifecycle(). Para ter mais informações sobre práticas recomendadas em mudanças de configuração, consulte Gerenciar alterações de configuração.

No exemplo de código a seguir, o app especifica os dois casos de uso que serão criados e executados simultaneamente. Também determina o ciclo de vida a ser usado nos dois casos de uso, para que ambos possam ser iniciados e encerrados de acordo com esse ciclo.

Kotlin

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));
}

O CameraX permite o uso simultâneo de uma instância de Preview, VideoCapture, ImageAnalysis e ImageCapture. Além disso:

  • Cada caso de uso funciona sozinho. Por exemplo, um app pode gravar vídeos sem usar uma visualização.
  • Quando as extensões estão ativadas, apenas a combinação ImageCapture e Preview tem garantia de funcionar. Dependendo da implementação do OEM, talvez não seja possível adicionar ImageAnalysis. As extensões não podem ser ativadas no caso de uso de VideoCapture. Consulte o documento de referência de extensão para saber mais.
  • Dependendo da capacidade, algumas câmeras oferecem suporte para a combinação em modos de resolução mais baixa, mas não em uma resolução mais alta.
  • Em dispositivos com nível de hardware de câmera FULL ou inferior, combinando Preview, VideoCapture, e ImageCapture ou ImageAnalysis podem forçar o CameraX para duplicar o stream de PRIV da câmera para Preview e VideoCapture. Isso chamada compartilhamento de stream, permite o uso simultâneo desses mas à custa do aumento das demandas de processamento. Como resultado, a latência pode ser um pouco maior e a duração da bateria pode ser reduzida.

O nível de hardware compatível pode ser recuperado de Camera2CameraInfo. Por exemplo, o código a seguir verifica se a câmera traseira padrão é um dispositivo LEVEL_3:

Kotlin

@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;
}

Permissões

Seu app precisará da permissão CAMERA. Para salvar imagens em arquivos, ele também precisa da permissão WRITE_EXTERNAL_STORAGE, exceto em dispositivos com Android 10 ou mais recente.

Para ter mais informações sobre como configurar permissões para seu app, leia Solicitar permissões do app.

Requisitos

O CameraX possui os seguintes requisitos mínimos de versão:

  • Nível 21 da API do Android
  • Android Architecture Components 1.1.1

Para atividades que envolvam ciclo de vida, use FragmentActivity ou AppCompatActivity.

Declarar dependências

Para adicionar uma dependência ao CameraX, é preciso adicionar o repositório Maven do Google ao projeto.

Abra o arquivo settings.gradle do projeto e adicione o repositório google(), conforme mostrado a seguir.

Groovy

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Kotlin

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Adicione o código abaixo ao fim do bloco do Android:

Groovy

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Kotlin

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Adicione o código a seguir ao arquivo build.gradle de cada módulo para um app:

Groovy

dependencies {
  // CameraX core library using the camera2 implementation
  def camerax_version = "1.5.0-alpha01"
  // 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-alpha01"
    // 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}")
}

Para ter mais informações sobre como configurar seu app para atender a esses requisitos, consulte Declarar dependências.

Interoperabilidade do CameraX com o Camera2

O CameraX foi desenvolvido com base no Camera2. Ele permite a leitura e até mesmo a gravação de propriedades na implementação do Camera2. Para mais detalhes, consulte o pacote de interoperabilidade.

Para mais informações sobre como o CameraX configurou as propriedades do Camera2, use Camera2CameraInfo para ler o CameraCharacteristics subjacente. Também é possível gravar as propriedades subjacentes do Camera2 de uma das duas formas a seguir:

O exemplo de código a seguir usa casos de uso de stream para otimizar uma videochamada. Use Camera2CameraInfo para buscar se o caso de uso de stream de videochamada está disponível. Em seguida, use um Camera2Interop.Extender para definir o caso de uso do stream subjacente.

Kotlin

// 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)

Outros recursos

Para saber mais sobre o CameraX, consulte os recursos a seguir.

Codelab

  • Introdução ao CameraX
  • Exemplo de código

  • Apps de exemplo do CameraX (link em inglês)