ARCore による共有カメラアクセス

このデベロッパー ガイドでは、アプリで Android Camera2 API を介したカメラの排他制御と、ARCore とのカメラアクセスの共有をシームレスに切り替えられるようにする手順について説明します。

このトピックの前提は次のとおりです。

サンプルアプリをビルドして実行する

Shared Camera Java サンプルアプリをビルドして実行すると、共有カメラアクセスをサポートする ARCore セッションが作成されます。アプリが非 AR モードで起動し、ARCore は一時停止します。

アプリが非 AR モードで動作する場合、カメラビューアにセピアカラー効果が表示されます。AR モードに切り替えると、一時停止したセッションを再開してアプリがカメラ コントロールを ARCore に戻すため、セピア効果がオフになります。

アプリの AR スイッチを使用してモードを変更できます。プレビュー中は、どちらのモードでも Camera2 がキャプチャした連続フレームの数が表示されます。

Shared Camera Java サンプルアプリをビルドして実行するには:

  1. Google ARCore SDK for 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 による共有カメラアクセスをアプリに実装する手順は次のとおりです。すべてのコード スニペットは、shared_camera_java サンプル内の SharedCameraActivity.java で入手できます。

CAMERA 権限をリクエストする

デバイスのカメラを使用できるようにするには、ユーザーがアプリに CAMERA 権限を付与する必要があります。ARCore サンプルには、アプリの正しい権限をリクエストするためのユーティリティを提供する CameraPermissionHelper が含まれています。

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

ARCore がインストールされ、最新であることを確認してください

ARCore を使用するには、インストールして最新の状態にする必要があります。 次のスニペットは、デバイスに ARCore がまだインストールされていない場合に、ARCore のインストールをリクエストする方法を示しています。

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

カメラの共有をサポートする ARCore セッションを作成する

この操作では、セッションを作成し、ARCore 共有カメラの参照と ID を保存します。

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

(省略可)カスタム サーフェスを ARCore に通知する

追加のカスタム サーフェスをリクエストすると、デバイスのパフォーマンスに対する要求が高くなります。パフォーマンスが優れていることを確認するには、ユーザーが使用するデバイスでアプリをテストします。

ARCore はデフォルトで 2 つのストリームをリクエストします。

  1. 1x YUV CPU ストリーム、現在は常に 640x480
    ARCore は、モーション トラッキングにこのストリームを使用します。
  2. 1x GPU ストリーム(通常は 1920x1080
    Session#getCameraConfig() を使用して、現在の GPU ストリーム解像度を確認します。

サポート対象デバイスの GPU ストリームの解像度を変更するには、getSupportedCameraConfigs()setCameraConfig() を使用します。

大まかな目安は次のとおりです。

デバイスの種類 同時ストリーミングのサポート
ハイエンド スマートフォン
  • 2 つの YUV CPU ストリーム(例: 640x4801920x1080
  • 1x GPU ストリーム(例: 1920x1080
  • 1 倍の高解像度の静止画像(JPEG)(例: 12MP
ミッドレンジのスマートフォン
  • 2 つの YUV CPU ストリーム(例: 640x4801920x1080
  • 1x GPU ストリーム(例: 1920x1080
または
  • 1x YUV CPU ストリーム(例: 640x480 – または – 1920x1080
  • 1x GPU ストリーム(例: 1920x1080
  • 1 倍の高解像度の静止画像(JPEG)(例: 12MP

CPU イメージ リーダー サーフェスなどのカスタム サーフェスを使用する場合は、更新する必要があるサーフェスのリスト(ImageReader など)に必ず追加してください。

Java

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

Kotlin

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

カメラを起動する

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)

カメラデバイスの状態コールバックを使用する

カメラデバイス状態のコールバックで、カメラデバイスへの参照を保存し、新しいキャプチャ セッションを開始します。

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

新しいキャプチャ セッションを作成する

新しいキャプチャ リクエストを作成します。TEMPLATE_RECORD を使用して、キャプチャ リクエストが ARCore に対応していることを確認し、実行時に非 AR モードと AR モードをシームレスに切り替えられるようにします。

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

非 AR モードまたは AR モードで開始する

フレームのキャプチャを開始するには、カメラ キャプチャ セッションの onConfigured() 状態コールバックから captureSession.setRepeatingRequest() を呼び出します。onActive() コールバック内で ARCore セッションを再開し、AR モードで開始します。

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

非 AR モードと AR モードを実行時にシームレスに切り替え

AR 以外のモードから AR モードに切り替えて、一時停止した ARCore セッションを再開するには:

Java

// Resume the ARCore session.
resumeARCore();

Kotlin

// Resume the ARCore session.
resumeARCore()

AR モードから非 AR モードに切り替えるには:

Java

// Pause ARCore.
sharedSession.pause();

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

Kotlin

// Pause ARCore.
sharedSession.pause()

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