Acceso compartido a la cámara con ARCore

En esta guía para desarrolladores, se explican los pasos que debes seguir para habilitar el cambio de tu app sin interrupciones entre el control exclusivo de la cámara mediante el API de Camera2 de Android y compartir el acceso a la cámara con ARCore.

En este tema, se supone que hiciste lo siguiente:

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 a la cámara compartida. La app se inicia en un entorno que no es de RA con ARCore en pausa.

Cuando la app funciona en modo que no es de RA, el visor de la cámara muestra un color sepia efecto. Al cambiar al modo RA, el efecto sepia se desactiva cuando la app devuelve el control de la cámara a ARCore mediante la reanudación de la sesión pausada.

Puedes usar el interruptor de RA de la app para cambiar de modo. Durante la vista previa, ambos modos mostrar la cantidad de fotogramas continuos capturados por Camera2.

Para compilar y ejecutar la app de ejemplo de Shared Camera de Java, haz lo siguiente:

  1. Descarga y extrae el archivo 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 los dispositivos compatibles de 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 grabar video.

  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 que no son de RA y RA.

Descripción general de la habilitación de 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 la SharedCameraActivity.java en el shared_camera_java muestra.

Solicita permiso CAMERA

Para poder usar la cámara del dispositivo, el usuario debes 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 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 cámaras

Esto implica crear la sesión y almacenar la referencia y el ID de ARCore cámara compartida:

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

Informa a ARCore sobre cualquier plataforma personalizada (opcional)

La solicitud de plataformas personalizadas adicionales aumenta las exigencias de rendimiento de la dispositivo. Para asegurarte de que funcione bien, prueba la 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, generalmente 1920x1080
    Usar Session#getCameraConfig() para determinar la resolución actual de transmisión de GPU.

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

Como indicador aproximado, puede esperar lo siguiente:

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

Para usar superficies personalizadas, como una de lector de imágenes de la CPU, asegúrate de agregarla a la lista de plataformas 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 unida a 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 la devolución de llamada de estado del dispositivo de la cámara, almacena una referencia al dispositivo de la cámara. inicia 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()
}

Crea una nueva sesión de captura

Crea una nueva solicitud de captura. Usa TEMPLATE_RECORD. para asegurarte de que la solicitud de captura sea compatible con ARCore alternando entre los modos que no son de RA y 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 iniciar en modo que no sea 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 dentro de la devolución de llamada onActive() para iniciarla 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 los modos que no sean de RA o RA durante el tiempo de ejecución

Sigue estos pasos para cambiar del modo que no es de RA al modo de RA y reanudar una sesión de ARCore en pausa:

Java

// Resume the ARCore session.
resumeARCore();

Kotlin

// Resume the ARCore session.
resumeARCore()

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