Общий доступ к камере с помощью ARCore

В этом руководстве разработчика рассказывается, как обеспечить плавное переключение вашего приложения между эксклюзивным управлением камерой через Android Camera2 API и общим доступом к камере с помощью ARCore.

Эта тема предполагает, что вы:

Создайте и запустите пример приложения.

Когда вы создаете и запускаете пример Java-приложения «Совместная камера» , он создает сеанс ARCore, который поддерживает доступ к общей камере. Приложение запускается в режиме без AR, а ARCore приостановлен.

Когда приложение работает в режиме без AR, средство просмотра камеры отображает цветовой эффект сепии. При переключении в режим AR эффект сепии отключается, поскольку приложение возвращает управление камерой ARCore, возобновляя приостановленный сеанс.

Вы можете использовать переключатель AR в приложении для изменения режимов. Во время предварительного просмотра в обоих режимах отображается количество непрерывных кадров, снятых камерой Camera2.

Чтобы создать и запустить пример Java-приложения «Совместная камера»:

  1. Загрузите и извлеките Google ARCore SDK для Android .

  2. Откройте проект samples/shared_camera_java .

  3. Убедитесь, что ваше устройство Android подключено к машине разработки через USB. Подробную информацию см. в разделе « Устройства, поддерживаемые ARCore».

  4. В Android Studio нажмите Run .

  5. Выберите свое устройство в качестве цели развертывания и нажмите OK , чтобы запустить пример приложения на своем устройстве.

  6. На устройстве подтвердите, что вы хотите разрешить приложению делать снимки и записывать видео.

  7. Если будет предложено это сделать, обновите или установите последнюю версию ARCore.

  8. Используйте переключатель AR для переключения между режимами без AR и AR.

Обзор возможности предоставления приложению совместного доступа к камере с ARCore

Выполните следующие действия, чтобы реализовать общий доступ к камере с помощью ARCore в вашем приложении. Все фрагменты кода доступны в файле SharedCameraActivity.java в shared_camera_java .

Запросить разрешение CAMERA

Чтобы иметь возможность использовать камеру устройства, пользователь должен предоставить вашему приложению разрешение CAMERA . Образцы ARCore включают CameraPermissionHelper , который предоставляет утилиты для запроса правильного разрешения для вашего приложения.

Джава

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

Котлин

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

Убедитесь, что ARCore установлен и обновлен.

Прежде чем его можно будет использовать, ARCore должен быть установлен и обновлен. В следующем фрагменте показано, как запросить установку ARCore, если он еще не установлен на устройстве.

Джава

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

Котлин

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

Создайте сеанс ARCore, поддерживающий совместное использование камеры.

Это включает в себя создание сеанса и сохранение ссылки и идентификатора общей камеры ARCore:

Джава

// 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();

Котлин

// 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

(Необязательно) Сообщите ARCore о любых нестандартных поверхностях.

Запрос дополнительных пользовательских поверхностей увеличивает требования к производительности устройства. Чтобы убедиться в хорошей работе приложения, протестируйте свое приложение на устройствах, которые будут использовать ваши пользователи.

По умолчанию ARCore запросит два потока:

  1. 1 поток процессора YUV , в настоящее время всегда 640x480 .
    ARCore использует этот поток для отслеживания движения .
  2. 1 поток графического процессора , обычно 1920x1080
    Используйте Session#getCameraConfig() , чтобы определить текущее разрешение потока графического процессора.

Вы можете изменить разрешение потока графического процессора на поддерживаемых устройствах, используя getSupportedCameraConfigs() и setCameraConfig() .

В качестве приблизительного показателя вы можете ожидать:

Тип устройства Поддерживаются одновременные потоки
Высококлассные телефоны
  • 2 потока процессора YUV , например 640x480 и 1920x1080
  • 1 поток графического процессора , например 1920x1080
  • 1 случайное неподвижное изображение высокого разрешения (JPEG), например 12MP
Телефоны среднего уровня
  • 2 потока процессора YUV , например 640x480 и 1920x1080
  • 1 поток графического процессора , например 1920x1080
-или-
  • 1 поток процессора YUV , например 640x480 – или – 1920x1080
  • 1 поток графического процессора , например 1920x1080
  • 1 случайное неподвижное изображение высокого разрешения (JPEG), например 12MP

Чтобы использовать пользовательские поверхности, такие как поверхность чтения изображений ЦП, обязательно добавьте ее в список поверхностей, которые необходимо обновить (например, ImageReader ).

Джава

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

Котлин

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

Открыть камеру

Откройте камеру с помощью обратного вызова, завернутого в ARCore:

Джава

// 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);

Котлин

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

Используйте обратный вызов состояния устройства камеры

В обратном вызове состояния устройства камеры сохраняется ссылка на устройство камеры и начинается новый сеанс захвата.

Джава

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

Котлин

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

Создать новый сеанс захвата

Создайте новый запрос на захват. Используйте TEMPLATE_RECORD , чтобы гарантировать совместимость запроса на захват с ARCore и обеспечить плавное переключение между режимами без AR и AR во время выполнения.

Джава

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

Котлин

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

Запуск в режиме без AR или AR.

Чтобы начать захват кадров, вызовите captureSession.setRepeatingRequest() из обратного вызова состояния onConfigured() сеанса захвата камеры. Возобновите сеанс ARCore в обратном вызове onActive() , чтобы запустить его в режиме AR.

Джава

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

Котлин

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

Плавное переключение между режимами без AR и AR во время выполнения.

Чтобы переключиться из режима без AR в режим AR и возобновить приостановленный сеанс ARCore:

Джава

// Resume the ARCore session.
resumeARCore();

Котлин

// Resume the ARCore session.
resumeARCore()

Чтобы переключиться из режима AR в режим без AR:

Джава

// Pause ARCore.
sharedSession.pause();

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

Котлин

// Pause ARCore.
sharedSession.pause()

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