Visualização da câmera

Observação: esta página se refere ao pacote Camera2. A menos que seu app exija recursos específicos de baixo nível do Camera2, recomendamos o uso do CameraX. CameraX e Camera2 oferecem suporte ao Android 5.0 (nível 21 da API) e versões mais recentes.

As câmeras e as visualizações de câmera nem sempre estão na mesma orientação em dispositivos Android.

A câmera fica em uma posição fixa em um dispositivo, seja ele um smartphone, tablet ou computador. Quando a orientação do dispositivo muda, a orientação da câmera também muda.

Como resultado, os apps de câmera geralmente presumem uma relação fixa entre a orientação do dispositivo e a proporção da visualização da câmera. Quando um smartphone está na orientação retrato, presume-se que a visualização da câmera seja mais alta do que larga. Quando o smartphone (e a câmera) são girados para o modo paisagem, a visualização da câmera deve ser mais larga do que alta.

No entanto, essas suposições são desafiadas por novos formatos, como dispositivos dobráveis, e modos de exibição, como várias janelas e várias telas. Os dispositivos dobráveis mudam o tamanho e a proporção da tela sem mudar a orientação. O modo de várias janelas restringe os apps de câmera a uma parte da tela, dimensionando a visualização da câmera, independente da orientação do dispositivo. O modo de várias telas permite o uso de telas secundárias que podem não estar na mesma orientação que a principal.

Orientação da câmera

A Definição de compatibilidade do Android especifica que um sensor de imagem de câmera "PRECISA estar orientado de forma que a dimensão longa da câmera se alinhe à dimensão longa da tela". Ou seja, quando o dispositivo é segurado na orientação paisagem, as câmeras PRECISAM capturar imagens na orientação paisagem. Isso se aplica independentemente da orientação natural do dispositivo, ou seja, vale para dispositivos com orientação principal em paisagem e em retrato."

A disposição da câmera em relação à tela maximiza a área de exibição do visor em um app de câmera. Além disso, os sensores de imagem normalmente mostram os dados em proporções de paisagem, sendo 4:3 a mais comum.

Smartphone e sensor da câmera na orientação retrato.
Figura 1. Relação típica entre a orientação do smartphone e do sensor da câmera.

A orientação natural do sensor da câmera é paisagem. Na figura 1, o sensor da câmera frontal (a câmera apontando na mesma direção que a tela) é girado em 270 graus em relação ao smartphone para obedecer à Definição de compatibilidade do Android.

Para expor a rotação do sensor aos apps, a API camera2 inclui uma constante SENSOR_ORIENTATION. Para a maioria dos smartphones e tablets, o dispositivo informa uma orientação do sensor de 270 graus para câmeras frontais e 90 graus (ponto de vista da parte de trás do dispositivo) para câmeras traseiras, o que alinha a borda longa do sensor com a borda longa do dispositivo. As câmeras de laptops geralmente informam uma orientação de sensor de 0 ou 180 graus.

Como os sensores de imagem da câmera mostram os dados (um buffer de imagem) na orientação natural do sensor (paisagem), o buffer de imagem precisa ser girado o número de graus especificado por SENSOR_ORIENTATION para que a visualização da câmera apareça na vertical na orientação natural do dispositivo. Para câmeras frontais, a rotação é no sentido anti-horário. Para câmeras traseiras, no sentido horário.

Por exemplo, para a câmera frontal na figura 1, o buffer de imagem produzido pelo sensor da câmera é assim:

Sensor da câmera girado para a orientação paisagem com a imagem
            lateral, no canto superior esquerdo.
Figura 2. Buffer de imagem bruta de um sensor de câmera frontal na orientação paisagem natural.

A imagem precisa ser girada 270 graus no sentido anti-horário para que a orientação da prévia corresponda à do dispositivo:

Sensor da câmera na orientação retrato com a imagem na vertical.
Figura 3. Buffer de imagem da câmera frontal girado 270 graus no sentido anti-horário para corresponder à orientação retrato do dispositivo.

Uma câmera traseira produziria um buffer de imagem com a mesma orientação do buffer anterior, mas SENSOR_ORIENTATION é de 90 graus. Como resultado, o buffer é girado 90 graus no sentido horário.

Rotação do dispositivo

A rotação do dispositivo é o número de graus em que um dispositivo é girado em relação à orientação natural. Por exemplo, um smartphone na orientação paisagem tem uma rotação de 90 ou 270 graus, dependendo da direção da rotação.

Um buffer de imagem do sensor da câmera precisa ser girado o mesmo número de graus que a rotação do dispositivo (além dos graus de orientação do sensor) para que a visualização da câmera apareça na posição vertical.

Cálculo de orientação

A orientação adequada da visualização da câmera considera a orientação do sensor e a rotação do dispositivo.

A rotação geral do buffer de imagem do sensor pode ser calculada usando a seguinte fórmula:

rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360

em que sign é 1 para câmeras frontais e -1 para câmeras traseiras.

Para câmeras frontais, o buffer de imagem é girado no sentido anti-horário (da orientação natural do sensor). Para câmeras traseiras, o buffer de imagem do sensor é girado no sentido horário.

A expressão deviceOrientationDegrees * sign + 360 converte a rotação do dispositivo de sentido anti-horário para horário em câmeras traseiras (por exemplo, convertendo 270 graus no sentido anti-horário para 90 graus no sentido horário). A operação de módulo dimensiona o resultado para menos de 360 graus (por exemplo, dimensionando 540 graus de rotação para 180).

APIs diferentes informam a rotação do dispositivo de maneiras distintas:

  • Display#getRotation() fornece a rotação no sentido anti-horário do dispositivo (do ponto de vista do usuário). Esse valor é inserido na fórmula anterior como está.
  • OrientationEventListener#onOrientationChanged() retorna a rotação no sentido horário do dispositivo (do ponto de vista do usuário). Negue o valor para usar na fórmula anterior.

Câmeras frontais

A visualização da câmera e o sensor estão na orientação paisagem, e o sensor
            está na posição correta.
Figura 4. Visualização da câmera e sensor com o smartphone girado em 90 graus para a orientação paisagem.

Este é o buffer de imagem produzido pelo sensor da câmera na figura 4:

Sensor da câmera na orientação paisagem com a imagem na vertical.
Figura 5. Buffer de imagem produzido pelo sensor da câmera frontal quando o smartphone é girado 90 graus no sentido anti-horário.

O buffer precisa ser girado 270 graus no sentido anti-horário para se ajustar à orientação do sensor (consulte Orientação da câmera acima):

Sensor da câmera girado para a orientação retrato com a imagem de lado,
            no canto superior direito.
Figura 6. Buffer de imagem girado 270 graus no sentido anti-horário para ajustar a orientação do sensor.

Em seguida, o buffer é girado mais 90 graus no sentido anti-horário para considerar a rotação do dispositivo, resultando na orientação correta da visualização da câmera na figura 4:

Sensor da câmera girado para a orientação paisagem com a imagem
            na vertical.
Figura 7. Buffer de imagem após uma rotação adicional de 90 graus no sentido anti-horário para a orientação do dispositivo.

Aqui, a câmera está virada para a direita na orientação paisagem:

A visualização da câmera e o sensor estão na orientação paisagem, mas o
            sensor está de cabeça para baixo.
Figura 8. Visualização da câmera e sensor com o smartphone girado 270 graus (ou -90 graus) para a orientação paisagem.

Este é o buffer de imagem:

Sensor da câmera girado para a orientação paisagem com a imagem de cabeça para
            baixo.
Figura 9. Buffer de imagem produzido pelo sensor da câmera frontal quando o smartphone é girado 270 graus no sentido anti-horário.

O buffer precisa ser girado 270 graus no sentido anti-horário para se ajustar à orientação do sensor:

Sensor da câmera girado para a orientação retrato com a imagem de lado,
            no canto superior esquerdo.
Figura 10. Buffer de imagem girado 270 graus no sentido anti-horário para ajustar a orientação do sensor.

Em seguida, o buffer é girado mais 270 graus no sentido anti-horário para compensar a rotação do dispositivo:

Sensor da câmera girado para a orientação paisagem com a imagem
            na vertical.
Figura 11. Buffer de imagem após uma rotação adicional de 270 graus no sentido anti-horário para a orientação do dispositivo.

Câmeras traseiras

As câmeras traseiras normalmente têm uma orientação de sensor de 90 graus (quando vistas da parte de trás do dispositivo). Ao orientar a visualização da câmera, o buffer de imagem do sensor é girado no sentido horário de acordo com a rotação do sensor (em vez de no sentido anti-horário, como as câmeras frontais). Em seguida, o buffer de imagem é girado no sentido anti-horário de acordo com a rotação do dispositivo.

A visualização da câmera e o sensor estão na orientação paisagem, mas o
            sensor está de cabeça para baixo.
Figura 12. Smartphone com câmera traseira na orientação paisagem (girado 270 ou -90 graus).

Este é o buffer de imagem do sensor da câmera na figura 12:

Sensor da câmera girado para a orientação paisagem com a imagem de cabeça para
            baixo.
Figura 13. Buffer de imagem produzido pelo sensor da câmera traseira quando o smartphone está na orientação paisagem.

O buffer precisa ser girado 90 graus no sentido horário para se ajustar à orientação do sensor:

Sensor da câmera girado para a orientação retrato com a imagem de lado,
            no canto superior esquerdo.
Figura 14. Buffer de imagem girado 90 graus no sentido horário para se ajustar à orientação do sensor traseiro.

Em seguida, o buffer é girado 270 graus no sentido anti-horário para considerar a rotação do dispositivo:

Sensor da câmera girado para a orientação paisagem com a imagem
            na vertical.
Figura 15. Buffer de imagem após uma rotação de 270 graus no sentido anti-horário para orientação do dispositivo.

Proporção

A proporção da tela muda quando a orientação do dispositivo muda, mas também quando dispositivos dobráveis são dobrados e desdobrados, quando as janelas são redimensionadas em ambientes de várias janelas e quando os apps são abertos em telas secundárias.

O buffer de imagem do sensor da câmera precisa ser orientado e dimensionado para corresponder à orientação e à proporção do elemento da interface do visor, conforme a interface muda dinamicamente de orientação, com ou sem a mudança de orientação do dispositivo.

Em novos formatos ou em ambientes de várias janelas ou telas, se o app presumir que a visualização da câmera tem a mesma orientação do dispositivo (retrato ou paisagem), a visualização pode ser orientada incorretamente, dimensionada incorretamente ou ambos.

Dispositivo dobrável aberto com a visualização da câmera no modo retrato virada
            para o lado.
Figura 16. O dispositivo dobrável muda da proporção retrato para paisagem, mas o sensor da câmera permanece na orientação retrato.

Na figura 16, o aplicativo assumiu incorretamente que o dispositivo foi girado 90 graus no sentido anti-horário. Por isso, o app girou a visualização na mesma proporção.

Dispositivo dobrável aberto com a visualização da câmera na vertical, mas achatada
            devido ao dimensionamento incorreto.
Figura 17. O dispositivo dobrável faz a transição da proporção de retrato para paisagem com uma visualização da câmera dimensionada incorretamente.

Na figura 17, o app não ajustou a proporção do buffer de imagem para que ele fosse dimensionado corretamente para se ajustar às novas dimensões do elemento de interface de visualização da câmera.

Os apps de câmera com orientação fixa geralmente têm problemas em dispositivos dobráveis e outros dispositivos de tela grande, como laptops:

A visualização da câmera no laptop está na vertical, mas a interface do app está na horizontal.
Figura 18. App com orientação retrato fixa em um laptop.

Na figura 18, a interface do app de câmera está de lado porque a orientação do app está restrita ao modo retrato. A imagem do visor está orientada corretamente em relação ao sensor da câmera.

Modo retrato inserido

Os apps de câmera que não são compatíveis com o modo de várias janelas (resizeableActivity="false") e restringem a orientação (screenOrientation="portrait" ou screenOrientation="landscape") podem ser colocados no modo retrato inserido em dispositivos de tela grande para orientar corretamente a visualização da câmera.

O modo retrato inserido coloca apps somente retrato em letterbox (inserções) na orientação retrato, mesmo que a proporção da tela seja paisagem. Os apps somente paisagem são mostrados com efeito letterbox na orientação paisagem, mesmo que a proporção da tela seja retrato. A imagem da câmera é girada para se alinhar à interface do app, cortada para corresponder à proporção da visualização da câmera e, em seguida, dimensionada para preencher a visualização.

O modo retrato inserido é acionado quando a proporção do sensor de imagem da câmera e a proporção da atividade principal do aplicativo não correspondem.

Visualização da câmera e interface do app na orientação retrato adequada em um laptop.
            A imagem de visualização ampla é dimensionada e cortada para se ajustar à orientação
            retrato.
Figura 19. App de retrato com orientação fixa no modo retrato inserido em um laptop.

Na figura 19, o app de câmera somente para modo retrato foi girado para mostrar a IU na vertical na tela do laptop. O app é exibido em formato letterbox devido à diferença na proporção entre o app em modo retrato e a tela em modo paisagem. A imagem de visualização da câmera foi girada para compensar a rotação da interface do app (devido ao modo retrato inserido) e foi cortada e dimensionada para se ajustar à orientação retrato, reduzindo o campo de visão.

Girar, cortar, dimensionar

O modo retrato inserido é invocado para um app de câmera somente retrato em uma tela com proporção de paisagem:

A visualização da câmera no laptop está na vertical, mas a interface do app está na horizontal.
Figura 20. App de orientação de retrato fixo em um laptop.

O app é mostrado com efeito letterbox na orientação retrato:

App girado para a orientação retrato e com efeito letterbox. A imagem está
            de lado, com a parte de cima para a direita.
Figura 21. App somente retrato girado e com efeito letterbox em uma tela paisagem, antes da rotação da imagem do sensor.

A imagem da câmera é girada em 90 graus para se ajustar à reorientação do app:

Imagem do sensor girada em 90 graus para ficar na vertical.
Figura 22. Imagem da câmera girada em 90 graus para se alinhar à interface reorientada do app.

A imagem é cortada de acordo com a proporção da visualização da câmera e, em seguida, dimensionada para preencher a visualização (o campo de visão é reduzido):

Imagem cortada da câmera dimensionada para preencher a visualização da câmera.
Figura 23. Imagem da câmera cortada e dimensionada para preencher a visualização da câmera em retrato.

Em dispositivos dobráveis, a orientação do sensor da câmera pode ser retrato, enquanto a proporção da tela é paisagem:

Visualização da câmera e interface do app viradas para o lado em uma tela ampla e desdobrada.
Figura 24. Dispositivo aberto com app de câmera somente em modo retrato e proporções diferentes do sensor da câmera e da tela.

Como a visualização da câmera é girada para se ajustar à orientação do sensor, a imagem fica orientada corretamente no visor, mas o app que só funciona no modo retrato fica de lado.

O modo retrato inserido só precisa aplicar o efeito letterbox ao app na orientação retrato para orientar corretamente o app e a visualização da câmera:

App em formato letterbox na orientação retrato com visualização da câmera
            na vertical em um dispositivo dobrável.
Figura 25. Um app restrito à orientação de retrato é mostrado com efeito letterbox no modo retrato inserido em um dispositivo dobrável aberto.

API

No Android 12 (nível 31 da API) e versões mais recentes, os apps também podem controlar explicitamente o modo retrato inserido usando a propriedade SCALER_ROTATE_AND_CROP da classe CaptureRequest.

O valor padrão é SCALER_ROTATE_AND_CROP_AUTO, que permite que o sistema invoque o modo retrato inserido. SCALER_ROTATE_AND_CROP_90 é o comportamento do modo retrato inserido, conforme descrito anteriormente.

Nem todos os dispositivos são compatíveis com todos os valores de SCALER_ROTATE_AND_CROP. Para ver uma lista de valores compatíveis, consulte CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES.

CameraX

A biblioteca Jetpack CameraX facilita a criação de um visor de câmera que se adapta à orientação do sensor e à rotação do dispositivo.

O elemento de layout PreviewView cria uma prévia da câmera, ajustando automaticamente a orientação do sensor, a rotação do dispositivo e o dimensionamento. PreviewView mantém a proporção da imagem da câmera aplicando o tipo de escala FILL_CENTER, que centraliza a imagem, mas pode cortá-la para corresponder às dimensões de PreviewView. Para aplicar o letterbox à imagem da câmera, defina o tipo de escala como FIT_CENTER.

Para aprender os princípios básicos da criação de uma prévia de câmera com o PreviewView, consulte Implementar uma prévia.

Para ver um exemplo completo de implementação, consulte o repositório CameraXBasic no GitHub.

CameraViewfinder

Semelhante ao caso de uso de visualização, a biblioteca CameraViewfinder oferece um conjunto de ferramentas para simplificar a criação de uma prévia da câmera. Ela não depende do CameraX Core, então você pode integrá-la facilmente à sua base de código atual da Camera2.

Em vez de usar o Surface diretamente, use o widget CameraViewfinder para mostrar a transmissão da câmera para a Camera2.

O CameraViewfinder usa internamente uma TextureView ou uma SurfaceView para mostrar a transmissão da câmera e aplica as transformações necessárias para mostrar o visor corretamente. Isso envolve corrigir a proporção, a escala e a rotação.

Para solicitar a superfície do objeto CameraViewfinder, é necessário criar um ViewfinderSurfaceRequest.

Essa solicitação contém requisitos para a resolução da superfície e as informações do dispositivo de câmera de CameraCharacteristics.

Chamar requestSurfaceAsync() envia a solicitação ao provedor de superfície, que é um TextureView ou SurfaceView, e recebe um ListenableFuture de Surface.

Chamar markSurfaceSafeToRelease() notifica o provedor de que a plataforma não é necessária e que os recursos relacionados podem ser liberados.

fun startCamera() {
    val previewResolution = Size(width, height)
    val viewfinderSurfaceRequest =
        ViewfinderSurfaceRequest(previewResolution, characteristics)
    val surfaceListenableFuture =
        cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest)

    Futures.addCallback(surfaceListenableFuture, object : FutureCallback<Surface> {
        override fun onSuccess(surface: Surface) {
            /* create a CaptureSession using this surface as usual */
        }
        override fun onFailure(t: Throwable) { /* something went wrong */ }
    }, ContextCompat.getMainExecutor(context))
}

SurfaceView

SurfaceView é uma abordagem simples para criar uma prévia de câmera se ela não exigir processamento e não for animada.

O SurfaceView gira automaticamente o buffer de imagem do sensor da câmera para corresponder à orientação da tela, considerando a orientação do sensor e a rotação do dispositivo. No entanto, o buffer de imagem é dimensionado para se ajustar às dimensões de SurfaceView sem considerar a proporção.

É preciso garantir que a proporção do buffer de imagem corresponda à proporção do SurfaceView, o que pode ser feito dimensionando o conteúdo do SurfaceView no método onMeasure() do componente:

O código-fonte de computeRelativeRotation() está em Rotação relativa abaixo.

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val width = MeasureSpec.getSize(widthMeasureSpec)
    val height = MeasureSpec.getSize(heightMeasureSpec)

    val relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees)

    if (previewWidth > 0f && previewHeight > 0f) {
        /* Scale factor required to scale the preview to its original size on the x-axis. */
        val scaleX =
            if (relativeRotation % 180 == 0) {
                width.toFloat() / previewWidth
            } else {
                width.toFloat() / previewHeight
            }
        /* Scale factor required to scale the preview to its original size on the y-axis. */
        val scaleY =
            if (relativeRotation % 180 == 0) {
                height.toFloat() / previewHeight
            } else {
                height.toFloat() / previewWidth
            }

        /* Scale factor required to fit the preview to the SurfaceView size. */
        val finalScale = min(scaleX, scaleY)

        setScaleX(1 / scaleX * finalScale)
        setScaleY(1 / scaleY * finalScale)
    }
    setMeasuredDimension(width, height)
}

Para mais detalhes sobre como implementar SurfaceView como uma visualização de câmera, consulte Orientações da câmera.

TextureView

TextureView tem um desempenho inferior a SurfaceView e dá mais trabalho, mas TextureView oferece o máximo de controle da visualização da câmera.

TextureView gira o buffer de imagem do sensor com base na orientação do sensor, mas não processa a rotação do dispositivo ou o dimensionamento da visualização.

O dimensionamento e a rotação podem ser codificados em uma transformação de Matrix. Para saber como dimensionar e girar corretamente uma TextureView, consulte Suporte a superfícies redimensionáveis no seu app de câmera.

Rotação relativa

A rotação relativa do sensor da câmera é o quanto é necessário girar para alinhar a saída do sensor da câmera com a orientação do dispositivo.

A rotação relativa é usada por componentes como SurfaceView e TextureView para determinar os fatores de escalonamento x e y da imagem de visualização. Ele também é usado para especificar a rotação do buffer de imagem do sensor.

As classes CameraCharacteristics e Surface permitem o cálculo da rotação relativa do sensor da câmera:

/**
 * Computes rotation required to transform the camera sensor output orientation to the
 * device's current orientation in degrees.
 *
 * @param characteristics The CameraCharacteristics to query for the sensor orientation.
 * @param surfaceRotationDegrees The current device orientation as a Surface constant.
 * @return Relative rotation of the camera sensor output.
 */
public fun computeRelativeRotation(
    characteristics: CameraCharacteristics,
    surfaceRotationDegrees: Int
): Int {
    val sensorOrientationDegrees =
        characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0

    // Reverse device orientation for back-facing cameras.
    val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) ==
        CameraCharacteristics.LENS_FACING_FRONT
    ) 1 else -1

    // Calculate desired orientation relative to camera orientation to make
    // the image upright relative to the device orientation.
    return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360
}

Métricas de janela

O tamanho da tela não deve ser usado para determinar as dimensões do visor da câmera. O app de câmera pode estar sendo executado em uma parte da tela, seja no modo de várias janelas em dispositivos móveis ou no modo de formato livre no ChromeOS.

WindowManager#getCurrentWindowMetrics() (adicionado no nível 30 da API) retorna o tamanho da janela do aplicativo, em vez do tamanho da tela. Os métodos WindowMetricsCalculator#computeCurrentWindowMetrics() e WindowInfoTracker#currentWindowMetrics() da biblioteca WindowManager do Jetpack oferecem suporte semelhante com compatibilidade com versões anteriores até o nível 14 da API.

Rotação de 180 graus

Uma rotação de 180 graus de um dispositivo (por exemplo, da orientação natural para a orientação natural de cabeça para baixo) não aciona o callback onConfigurationChanged(). Como resultado, a visualização da câmera pode ficar de cabeça para baixo.

Para detectar uma rotação de 180 graus, implemente um DisplayListener e verifique a rotação do dispositivo com uma chamada para Display#getRotation() no callback onDisplayChanged().

Recursos exclusivos

Antes do Android 10, apenas a atividade visível mais importante em um ambiente de várias janelas ficava no estado RESUMED. Isso era confuso para os usuários porque o sistema não indicava qual atividade foi retomada.

O Android 10 (nível 29 da API) introduziu a retomada múltipla, em que todas as atividades visíveis estão no estado RESUMED. As atividades visíveis ainda podem entrar no estado PAUSED se, por exemplo, uma atividade transparente estiver sobre ela ou se ela não for focalizável, como no modo picture-in-picture (consulte Suporte ao modo picture-in-picture).

Um aplicativo que usa a câmera, o microfone ou qualquer recurso exclusivo ou singleton no nível 29 da API ou mais recente precisa oferecer suporte à retomada múltipla. Por exemplo, se três atividades retomadas quiserem usar a câmera, apenas uma poderá acessar esse recurso exclusivo. Cada atividade precisa implementar um callback onDisconnected() para ficar ciente do acesso preventivo à câmera por uma atividade de maior prioridade.

Para mais informações, consulte Retomada múltipla.

Outros recursos