Acesso compartilhado à câmera com o ARCore

Este guia para desenvolvedores explica as etapas para ativar o app para alternar facilmente entre o controle exclusivo da câmera pela API Camera2 do Android e o compartilhamento do acesso à câmera com o ARCore.

Este tópico pressupõe que você:

Criar e executar o app de exemplo

Ao criar e executar o app de exemplo Shared Camera Java, ele cria uma sessão do ARCore que oferece suporte ao acesso compartilhado à câmera. O app é iniciado no modo não RA, com o ARCore pausado.

Quando o app opera no modo não RA, o visualizador da câmera mostra um efeito de cor sépia. Ao mudar para o modo RA, o efeito sépia é desativado à medida que o app retorna o controle da câmera para o ARCore, retomando a sessão pausada.

Você pode usar o botão de RA no app para mudar os modos. Durante a prévia, os dois modos mostram o número de frames contínuos capturados pelo Camera2.

Para criar e executar o app de exemplo Shared Camera Java:

  1. Faça o download e extraia o SDK do Google ARCore para Android.

  2. Abra o samples/shared_camera_java projeto.

  3. Confira se o dispositivo Android está conectado à máquina de desenvolvimento por USB. Consulte Dispositivos compatíveis com o ARCore para mais detalhes.

  4. No Android Studio, clique em Run .

  5. Escolha o dispositivo como destino de implantação e clique em OK para iniciar o app de exemplo no dispositivo.

  6. No dispositivo, confirme que você quer permitir que o app tire fotos e grave vídeos.

  7. Se solicitado, atualize ou instale a versão mais recente do ARCore.

  8. Use o botão AR para alternar entre os modos não RA e RA.

Visão geral da ativação de um app para compartilhar o acesso à câmera com o ARCore

Siga estas etapas para implementar o acesso compartilhado à câmera com o ARCore no seu app. Todos os snippets de código estão disponíveis no SharedCameraActivity.java no exemplo shared_camera_java.

Solicitar permissão CAMERA

Para usar a câmera do dispositivo, o usuário precisa conceder ao app a CAMERA permissão. Os exemplos do ARCore incluem um CameraPermissionHelper, que fornece utilitários para solicitar a permissão correta para seu app.

Java

protected void onResume() {
  // Request the camera permission, if necessary.
  if (!CameraPermissionHelper.hasCameraPermission(this)) {
      CameraPermissionHelper.requestCameraPermission(this);
  }
}

Kotlin

override fun onResume() {
  // Request the camera permission, if necessary.
  if (!CameraPermissionHelper.hasCameraPermission(this)) {
    CameraPermissionHelper.requestCameraPermission(this)
  }
}

Verificar se o ARCore está instalado e atualizado

O ARCore precisa estar instalado e atualizado para ser usado. O snippet a seguir mostra como solicitar uma instalação do ARCore se ele ainda não tiver sido instalado no dispositivo.

Java

boolean isARCoreSupportedAndUpToDate() {
  // Make sure that ARCore is installed and supported on this device.
  ArCoreApk.Availability availability = ArCoreApk.getInstance().checkAvailability(this);
  switch (availability) {
    case SUPPORTED_INSTALLED:
      return true;

    case SUPPORTED_APK_TOO_OLD:
    case SUPPORTED_NOT_INSTALLED:
        // Requests an ARCore installation or updates ARCore if needed.
        ArCoreApk.InstallStatus installStatus = ArCoreApk.getInstance().requestInstall(this, userRequestedInstall);
        switch (installStatus) {
          case INSTALL_REQUESTED:
            return false;
          case INSTALLED:
            return true;
        }
      return false;

    default:
      // Handle the error. For example, show the user a snackbar that tells them
      // ARCore is not supported on their device.
      return false;
  }
}

Kotlin

// Determine ARCore installation status.
// Requests an ARCore installation or updates ARCore if needed.
fun isARCoreSupportedAndUpToDate(): Boolean {
  when (ArCoreApk.getInstance().checkAvailability(this)) {
    Availability.SUPPORTED_INSTALLED -> return true

    Availability.SUPPORTED_APK_TOO_OLD,
    Availability.SUPPORTED_NOT_INSTALLED -> {
      when(ArCoreApk.getInstance().requestInstall(this, userRequestedInstall)) {
        InstallStatus.INSTALLED -> return true
        else -> return false
      }
    }

    else -> {
      // Handle the error. For example, show the user a snackbar that tells them
      // ARCore is not supported on their device.
      return false
    }
  }
}

Criar uma sessão do ARCore que ofereça suporte ao compartilhamento de câmera

Isso envolve a criação da sessão e o armazenamento da referência e do ID da câmera compartilhada do ARCore:

Java

// Create an ARCore session that supports camera sharing.
sharedSession = new Session(this, EnumSet.of(Session.Feature.SHARED_CAMERA))

// Store the ARCore shared camera reference.
sharedCamera = sharedSession.getSharedCamera();

// Store the ID of the camera that ARCore uses.
cameraId = sharedSession.getCameraConfig().getCameraId();

Kotlin

// Create an ARCore session that supports camera sharing.
sharedSession = Session(this, EnumSet.of(Session.Feature.SHARED_CAMERA))

// Store the ARCore shared camera reference.
sharedCamera = sharedSession.sharedCamera

// Store the ID of the camera that ARCore uses.
cameraId = sharedSession.cameraConfig.cameraId

(Opcional) Informar o ARCore sobre superfícies personalizadas

Solicitar mais superfícies personalizadas aumenta as demandas de desempenho do dispositivo. Para garantir que ele funcione bem, teste o app nos dispositivos que seus usuários vão usar.

O ARCore vai solicitar dois streams por padrão:

  1. Stream de CPU YUV 1x, atualmente sempre 640x480.
    O ARCore usa esse stream para rastreamento de movimento.
  2. Um stream de GPU 1x, normalmente 1920x1080
    Use Session#getCameraConfig() para determinar a resolução atual do stream da GPU.

Você pode mudar a resolução do stream da GPU em dispositivos compatíveis usando getSupportedCameraConfigs() e setCameraConfig().

Como indicador aproximado, você pode esperar:

Tipo de dispositivo Streams simultâneos com suporte
Smartphones sofisticados
  • Streams de CPU YUV 2x, por exemplo, 640x480 e 1920x1080
  • Stream de GPU 1x, por exemplo, 1920x1080
  • 1x imagem estática de alta resolução ocasional (JPEG), por exemplo, 12MP
Smartphones de nível médio
  • Streams de CPU YUV 2x, por exemplo, 640x480 e 1920x1080
  • Stream de GPU 1x, por exemplo, 1920x1080
–ou–
  • Streams de CPU YUV 1x, por exemplo, 640x480 –ou– 1920x1080
  • Stream de GPU 1x, por exemplo, 1920x1080
  • 1x imagem estática de alta resolução ocasional (JPEG), por exemplo, 12MP

Para usar superfícies personalizadas, como uma superfície de leitor de imagens da CPU, adicione-a à lista de superfícies que precisam ser atualizadas (por exemplo, um ImageReader).

Java

sharedCamera.">setAppSurfaces(this.cameraId, Arrays.asList(imageReader.getSurface()));

Kotlin

sharedCamera.">setAppSurfaces(this.cameraId, listOf(imageReader.surface))

Abrir a câmera

Abra a câmera usando um callback encapsulado do ARCore:

Java

// Wrap the callback in a shared camera callback.
CameraDevice.StateCallback wrappedCallback =
    sharedCamera.createARDeviceStateCallback(cameraDeviceCallback, backgroundHandler);

// Store a reference to the camera system service.
cameraManager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);

// Open the camera device using the ARCore wrapped callback.
cameraManager.openCamera(cameraId, wrappedCallback, backgroundHandler);

Kotlin

// Wrap the callback in a shared camera callback.
val wrappedCallback = sharedCamera.createARDeviceStateCallback(cameraDeviceCallback, backgroundHandler)

// Store a reference to the camera system service.
val cameraManager = this.getSystemService(Context.CAMERA_SERVICE) as CameraManager

// Open the camera device using the ARCore wrapped callback.
cameraManager.openCamera(cameraId, wrappedCallback, backgroundHandler)

Usar o callback de estado do dispositivo da câmera

No callback de estado do dispositivo da câmera, armazene uma referência ao dispositivo da câmera e inicie uma nova sessão de captura.

Java

public void onOpened(@NonNull CameraDevice cameraDevice) {
    Log.d(TAG, "Camera device ID " + cameraDevice.getId() + " opened.");
    SharedCameraActivity.this.cameraDevice = cameraDevice;
    createCameraPreviewSession();
}

Kotlin

fun onOpened(cameraDevice: CameraDevice) {
  Log.d(TAG, "Camera device ID " + cameraDevice.id + " opened.")
  this.cameraDevice = cameraDevice
  createCameraPreviewSession()
}

Criar uma nova sessão de captura

Crie uma nova solicitação de captura. Use TEMPLATE_RECORD para garantir que a solicitação de captura seja compatível com o ARCore e para permitir a alternância perfeita entre o modo não RA e RA no momento da execução.

Java

void createCameraPreviewSession() {
  try {
    // Create an ARCore-compatible capture request using `TEMPLATE_RECORD`.
    previewCaptureRequestBuilder =
        cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);

    // Build a list of surfaces, starting with ARCore provided surfaces.
    List<Surface> surfaceList = sharedCamera.getArCoreSurfaces();

    // (Optional) Add a CPU image reader surface.
    surfaceList.add(cpuImageReader.getSurface());

    // The list should now contain three surfaces:
    // 0. sharedCamera.getSurfaceTexture()
    // 1. …
    // 2. cpuImageReader.getSurface()

    // Add ARCore surfaces and CPU image surface targets.
    for (Surface surface : surfaceList) {
      previewCaptureRequestBuilder.addTarget(surface);
    }

    // Wrap our callback in a shared camera callback.
    CameraCaptureSession.StateCallback wrappedCallback =
        sharedCamera.createARSessionStateCallback(cameraSessionStateCallback, backgroundHandler);

    // Create a camera capture session for camera preview using an ARCore wrapped callback.
    cameraDevice.createCaptureSession(surfaceList, wrappedCallback, backgroundHandler);
  } catch (CameraAccessException e) {
    Log.e(TAG, "CameraAccessException", e);
  }
}

Kotlin

fun createCameraPreviewSession() {
  try {
    // Create an ARCore-compatible capture request using `TEMPLATE_RECORD`.
    previewCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)

    // Build a list of surfaces, starting with ARCore provided surfaces.
    val surfaceList: MutableList<Surface> = sharedCamera.arCoreSurfaces

    // (Optional) Add a CPU image reader surface.
    surfaceList.add(cpuImageReader.getSurface())

    // The list should now contain three surfaces:
    // 0. sharedCamera.getSurfaceTexture()
    // 1. …
    // 2. cpuImageReader.getSurface()

    // Add ARCore surfaces and CPU image surface targets.
    for (surface in surfaceList) {
      previewCaptureRequestBuilder.addTarget(surface)
    }

    // Wrap the callback in a shared camera callback.
    val wrappedCallback = sharedCamera.createARSessionStateCallback(cameraSessionStateCallback, backgroundHandler)

    // Create a camera capture session for camera preview using an ARCore wrapped callback.
    cameraDevice.createCaptureSession(surfaceList, wrappedCallback, backgroundHandler)
  } catch (e: CameraAccessException) {
    Log.e(TAG, "CameraAccessException", e)
  }
}

Iniciar no modo não RA ou RA

Para começar a capturar frames, chame captureSession.setRepeatingRequest() do callback de estado onConfigured() da sessão de captura da câmera. Retome a sessão do ARCore no onActive() callback para iniciar no modo RA.

Java

// Repeating camera capture session state callback.
CameraCaptureSession.StateCallback cameraSessionStateCallback =
    new CameraCaptureSession.StateCallback() {

      // Called when ARCore first configures the camera capture session after
      // initializing the app, and again each time the activity resumes.
      @Override
      public void onConfigured(@NonNull CameraCaptureSession session) {
        captureSession = session;
        setRepeatingCaptureRequest();
      }

      @Override
      public void onActive(@NonNull CameraCaptureSession session) {
        if (arMode && !arcoreActive) {
          resumeARCore();
        }
      }
    };

// A repeating camera capture session capture callback.
CameraCaptureSession.CaptureCallback cameraCaptureCallback =
    new CameraCaptureSession.CaptureCallback() {
      @Override
      public void onCaptureCompleted() {
        shouldUpdateSurfaceTexture.set(true);
      }
    };

void setRepeatingCaptureRequest() {
    captureSession.setRepeatingRequest(
        previewCaptureRequestBuilder.build(), cameraCaptureCallback, backgroundHandler);
}

void resumeARCore() {
    // Resume ARCore.
    sharedSession.resume();
    arcoreActive = true;

    // Set the capture session callback while in AR mode.
    sharedCamera.setCaptureCallback(cameraCaptureCallback, backgroundHandler);
}

Kotlin

val cameraSessionStateCallback = object : CameraCaptureSession.StateCallback() {
      // Called when ARCore first configures the camera capture session after
      // initializing the app, and again each time the activity resumes.
  override fun onConfigured(session: CameraCaptureSession) {
    captureSession = session
    setRepeatingCaptureRequest()
  }

  override fun onActive(session: CameraCaptureSession) {
    if (arMode && !arcoreActive) {
      resumeARCore()
    }
  }
}

val cameraCaptureCallback = object : CameraCaptureSession.CaptureCallback() {
  override fun onCaptureCompleted(
    session: CameraCaptureSession,
    request: CaptureRequest,
    result: TotalCaptureResult
  ) {
    shouldUpdateSurfaceTexture.set(true);
  }
}

fun setRepeatingCaptureRequest() {
  captureSession.setRepeatingRequest(
    previewCaptureRequestBuilder.build(), cameraCaptureCallback, backgroundHandler
  )
}

fun resumeARCore() {
    // Resume ARCore.
    sharedSession.resume()
    arcoreActive = true

    // Set the capture session callback while in AR mode.
    sharedCamera.setCaptureCallback(cameraCaptureCallback, backgroundHandler)
}

Alternar facilmente entre os modos não RA ou RA no momento da execução

Para mudar do modo não RA para o modo RA e retomar uma sessão pausada do ARCore:

Java

// Resume the ARCore session.
resumeARCore();

Kotlin

// Resume the ARCore session.
resumeARCore()

Para mudar do modo RA para o modo não RA:

Java

// Pause ARCore.
sharedSession.pause();

// Create the Camera2 repeating capture request.
setRepeatingCaptureRequest();

Kotlin

// Pause ARCore.
sharedSession.pause()

// Create the Camera2 repeating capture request.
setRepeatingCaptureRequest()