Acceso compartido a la cámara con ARCore

En esta guía para desarrolladores, se explican los pasos para permitir que tu app cambie sin problemas entre el control exclusivo de la cámara mediante la API de Camera2 de Android y el uso compartido del acceso a la cámara con ARCore.

En este tema, se supone que:

Cómo compilar y ejecutar la app de muestra

Cuando compilas y ejecutas la app de ejemplo Shared Camera Java, se crea una sesión de ARCore que admite el acceso compartido a la cámara. La app se inicia en modo no RA, con ARCore pausado.

Cuando la app funciona en un modo que no es de RA, el visor de la cámara muestra un efecto de color sepia. Cuando se cambia al modo de RA, el efecto sepia se desactiva cuando la app muestra el control de la cámara a ARCore reanudando la sesión de pausa.

Usa el interruptor de RA en la app para cambiar de modo. Durante la vista previa, ambos modos muestran la cantidad de fotogramas continuos capturados por Camera2.

Para compilar y ejecutar la app de ejemplo de Java de cámara compartida, haz lo siguiente:

  1. Descarga y extrae el SDK de Google ARCore para Android.

  2. Abre el proyecto samples/shared_camera_java.

  3. Asegúrate de que tu dispositivo Android esté conectado a la máquina de desarrollo por USB. Consulta Dispositivos compatibles con ARCore para obtener información detallada.

  4. En Android Studio, haz clic en Run .

  5. Elige tu dispositivo como destino de implementación y haz clic en OK para iniciar la app del ejemplo en tu dispositivo.

  6. En el dispositivo, confirma que quieres permitir que la app tome fotos y grabe videos.

  7. Si se te solicita, actualiza o instala la versión más reciente de ARCore.

  8. Usa el interruptor AR para alternar entre los modos de RA y no.

Descripción general de cómo habilitar una app para compartir el acceso a la cámara con ARCore

Sigue estos pasos para implementar el acceso compartido a la cámara con ARCore en tu app. Todos los fragmentos de código están disponibles en SharedCameraActivity.java dentro de la muestra shared_camera_java.

Solicita permiso CAMERA

Para poder usar la cámara del dispositivo, el usuario debe otorgar a tu app el permiso CAMERA. Las muestras de ARCore incluyen un CameraPermissionHelper, que proporciona utilidades para solicitar el permiso correcto para tu 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)
  }
}

Asegúrate de que ARCore esté instalado y actualizado

ARCore debe estar instalado y actualizado para poder usarlo. En el siguiente fragmento, se muestra cómo solicitar la instalación de ARCore si aún no está instalado en el 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
    }
  }
}

Crea una sesión de ARCore que admita el uso compartido de la cámara

Esto implica crear la sesión y almacenar la referencia y el ID de la cámara compartida de 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) Informa a ARCore sobre cualquier superficie personalizada

La solicitud de plataformas personalizadas adicionales aumenta las demandas de rendimiento del dispositivo. Para asegurarte de que funcione bien, prueba tu app en los dispositivos que usarán los usuarios.

ARCore solicitará dos transmisiones de forma predeterminada:

  1. Transmisión de CPU YUV 1x, actualmente siempre 640x480
    ARCore usa esta transmisión para el seguimiento de movimiento.
  2. Una transmisión de GPU de 1x, por lo general, 1920x1080
    Usa Session#getCameraConfig() para determinar la resolución de transmisión de GPU actual.

Puedes cambiar la resolución de la transmisión de GPU en dispositivos compatibles mediante getSupportedCameraConfigs() y setCameraConfig().

Como indicador aproximado, puedes esperar lo siguiente:

Tipo de dispositivo Se admiten transmisiones simultáneas
Teléfonos de alta gama
  • 2x de transmisiones YUV de CPU, p.ej., 640x480 y 1920x1080
  • Transmisión de GPU de 1x, p.ej., 1920x1080
  • 1x de imagen estática de alta resolución ocasional (JPEG), p.ej., 12MP
Teléfonos de gama media
  • 2x de transmisiones YUV de CPU, p.ej., 640x480 y 1920x1080
  • Transmisión de GPU de 1x, p.ej., 1920x1080
–o–
  • 1x transmisiones YUV de CPU, p.ej., 640x480 –o– 1920x1080
  • Transmisión de GPU de 1x, p.ej., 1920x1080
  • 1x de imagen estática de alta resolución ocasional (JPEG), p.ej., 12MP

Para usar superficies personalizadas, como una plataforma de lector de imágenes de CPU, asegúrate de agregarla a la lista de superficies que se deben actualizar (por ejemplo, ImageReader).

Java

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

Kotlin

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

Abre la cámara

Abre la cámara con una devolución de llamada unida de 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)

Cómo usar la devolución de llamada de estado del dispositivo de cámara

En la devolución de llamada de estado del dispositivo de cámara, almacena una referencia al dispositivo de cámara y comienza una nueva sesión 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()
}

Crear una nueva sesión de captura

Compila una solicitud de captura nueva. Usa TEMPLATE_RECORD para asegurarte de que la solicitud de captura sea compatible con ARCore y permitir un cambio fluido entre los modos no RA y de RA durante el tiempo de ejecución.

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

Cómo comenzar en modo no RA o RA

Para comenzar a capturar fotogramas, llama a captureSession.setRepeatingRequest() desde la devolución de llamada de estado onConfigured() de la sesión de captura de la cámara. Reanuda la sesión de ARCore en la devolución de llamada onActive() para comenzar en modo de 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)
}

Cambia sin problemas entre modos que no son de RA y de RA durante el tiempo de ejecución

Para cambiar del modo no basado en RA al de RA y reanudar una sesión de ARCore pausada, haz lo siguiente:

Java

// Resume the ARCore session.
resumeARCore();

Kotlin

// Resume the ARCore session.
resumeARCore()

Para cambiar del modo de RA al modo no RA, haz lo siguiente:

Java

// Pause ARCore.
sharedSession.pause();

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

Kotlin

// Pause ARCore.
sharedSession.pause()

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