ARCore를 통한 공유 카메라 액세스

이 개발자 가이드에서는 앱이 Android Camera2 API를 통한 독점 카메라 제어와 ARCore와의 카메라 액세스 공유 간에 원활하게 전환할 수 있도록 하는 단계를 안내합니다.

이 주제에서는 다음을 가정합니다.

샘플 앱 빌드 및 실행

공유 카메라 자바 샘플 앱을 빌드하고 실행하면 공유 카메라 액세스를 지원하는 ARCore 세션이 생성됩니다. 앱이 AR이 아닌 모드에서 시작되고 ARCore가 일시중지됩니다.

앱이 AR이 아닌 모드에서 작동하면 카메라 뷰어에 세피아 색상 효과가 표시됩니다. AR 모드로 전환하면 앱이 일시중지된 세션을 재개하여 ARCore로 카메라 컨트롤을 반환할 때 세피아 효과가 중지됩니다.

앱의 AR 스위치를 사용하여 모드를 변경할 수 있습니다. 미리보기 중에는 두 모드 모두 Camera2에서 캡처한 연속 프레임 수를 표시합니다.

공유 카메라 자바 샘플 앱을 빌드하고 실행하려면 다음 단계를 따르세요.

  1. Android용 Google ARCore SDK를 다운로드하고 압축을 풉니다.

  2. samples/shared_camera_java 프로젝트를 엽니다.

  3. USB를 통해 Android 기기가 개발 머신에 연결되어 있는지 확인합니다. 자세한 내용은 ARCore 지원되는 기기를 참고하세요.

  4. Android 스튜디오에서 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는 기본적으로 다음과 같이 두 개의 스트림을 요청합니다.

  1. YUV CPU 스트림 1개, 현재 항상 640x480입니다.
    ARCore는 이 스트림을 모션 추적에 사용합니다.
  2. 1x GPU 스트림, 일반적으로 1920x1080
    Session#getCameraConfig()를 사용하여 현재 GPU 스트림 해상도를 확인합니다.

getSupportedCameraConfigs()setCameraConfig()를 사용하여 지원되는 기기에서 GPU 스트림 해상도를 변경할 수 있습니다.

대략적인 지표로 예상할 수 있습니다.

기기 유형 동시 스트림 지원됨
고급형 휴대전화
  • 2배 YUV CPU 스트림(예: 640x4801920x1080)
  • 1x GPU 스트림(예: 1920x1080)
  • 가끔씩 고해상도 정지 이미지 1배(JPEG)(예: 12MP)
중간 등급 휴대전화
  • 2배 YUV CPU 스트림(예: 640x4801920x1080)
  • 1x GPU 스트림(예: 1920x1080)
–또는
  • YUV CPU 스트림 1개(예: 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()를 호출합니다. AR 모드에서 시작하려면 onActive() 콜백 내에서 ARCore 세션을 다시 시작합니다.

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