Acceso compartido a la cámara con ARCore

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

En este tema, se supone que ya hiciste lo siguiente:

Cómo compilar y ejecutar la app de muestra

Cuando compilas y ejecutas la app de ejemplo de 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 en pausa.

Cuando la app funciona en modo no 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, ya que la app reanuda la sesión pausada y devuelve el control de la cámara a ARCore.

Puedes usar 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 Shared Camera en Java, 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 a través de un cable USB. Consulta la lista de 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 de 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 cambiar entre los modos de RA y no RA.

Descripción general para habilitar una app para que comparta 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 de shared_camera_java.

Solicita permiso de CAMERA

Para poder usar la cámara del dispositivo, el usuario debe otorgarle a tu app el permiso CAMERA. Los ejemplos 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 usarse. En el siguiente fragmento, se muestra cómo solicitar la instalación de ARCore si aún no se instaló 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

Solicitar superficies personalizadas adicionales aumenta las exigencias de rendimiento del dispositivo. Para asegurarte de que funcione bien, prueba tu app en los dispositivos que usarán tus usuarios.

De forma predeterminada, ARCore solicitará dos transmisiones:

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

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

Como indicador aproximado, puedes esperar lo siguiente:

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

Para usar superficies personalizadas, como una superficie de lector de imágenes de CPU, asegúrate de agregarla a la lista de superficies que deben actualizarse (por ejemplo, un 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 encapsulada en 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 del estado del dispositivo de la cámara

En el almacenamiento de devolución de llamada del 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()
}

Cómo crear una sesión de captura nueva

Crea una nueva solicitud de captura. Usa TEMPLATE_RECORD para garantizar que la solicitud de captura sea compatible con ARCore y permitir el cambio fluido entre el modo AR y el modo no AR en 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 iniciar en modo RA o no 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 dentro de la devolución de llamada onActive() para iniciar en el 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 los modos de RA y sin RA en el tiempo de ejecución

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

Java

// Resume the ARCore session.
resumeARCore();

Kotlin

// Resume the ARCore session.
resumeARCore()

Para cambiar del modo RA al modo sin 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()