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. 1 つの YUV CPU ストリーム。現在は常に 640x480
    ARCore はこのストリームをモーション トラッキングに使用します。
  2. 1x GPU ストリーム(通常は 1920x1080
    Session#getCameraConfig() を使用して、現在の GPU ストリームの解像度を特定します。

サポートされているデバイスでは、getSupportedCameraConfigs()setCameraConfig() を使用して GPU ストリームの解像度を変更できます。

おおよその目安として、次のことが想定されます。

デバイスの種類 サポートされている同時ストリーム数
ハイエンド スマートフォン
  • 2 つの YUV CPU ストリーム640x4801920x1080 など)
  • 1 つの GPU ストリーム1920x1080 など)
  • 1x の高解像度静止画像(JPEG)、例: 12MP
ミッドレンジのスマートフォン
  • 2 つの YUV CPU ストリーム640x4801920x1080 など)
  • 1 つの GPU ストリーム1920x1080 など)
–または–
  • 1 つの YUV CPU ストリーム640x480 または 1920x1080 など)
  • 1 つの GPU ストリーム1920x1080 など)
  • 1x の高解像度静止画像(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()