גישה משותפת למצלמה באמצעות ARCore

במדריך למפתחים מוסבר איך לאפשר לאפליקציה לעבור באופן חלק בין שליטה בלעדית במצלמה באמצעות Android Camera2 API לשיתוף הגישה למצלמה באמצעות ARCore.

נושא זה מניח ש:

יצירה והפעלה של האפליקציה לדוגמה

כשיוצרים ומפעילים את האפליקציה לדוגמה של מצלמה משותפת ב-Java, נוצר סשן ARCore שתומך בגישה משותפת למצלמה. האפליקציה מתחילה במצב שאינו AR כשה-ARCore מושהה.

כשהאפליקציה פועלת במצב שאינו AR, מוצגת במציג המצלמה אפקט של צבע חום-ספיה. כשעוברים למצב AR, אפקט החום-ספיה מושבת בזמן שהאפליקציה מחזירה את בקרת המצלמה ל-ARCore על ידי המשך הסשן שהושהה.

אפשר להשתמש במתג ה-AR באפליקציה כדי לשנות מצבים. במהלך התצוגה המקדימה, שני המצבים מציגים את מספר הפריימים הרציפים שצולמו על ידי Camera2.

כדי ליצור ולהפעיל את האפליקציה לדוגמה של מצלמת Java משותפת:

  1. מורידים ומחלצים את ה-SDK של Google ARCore ל-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, שמספק כלי תחזוקה לבקשת ההרשאה המתאימה לאפליקציה שלכם.

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 אם הוא עדיין לא הותקן במכשיר.

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:

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. זרם CPU אחד של YUV, כרגע תמיד 640x480.
    ARCore משתמש בשידור הזה למעקב אחר תנועה.
  2. זרם 1x GPU, בדרך כלל 1920x1080
    שימוש ב-Session#getCameraConfig() כדי לקבוע את רזולוציית הסטרימינג הנוכחית של ה-GPU.

אפשר לשנות את הרזולוציה של השידור ב-GPU במכשירים נתמכים באמצעות getSupportedCameraConfigs() ו-setCameraConfig().

כמדד כללי, אפשר לצפות לדברים הבאים:

סוג המכשיר תמיכה בזרמים סימולטניים
טלפונים מתקדמים
  • שני שידורים של YUV CPU, לדוגמה 640x480 ו-1920x1080
  • סטרימינג של 1x GPU, לדוגמה 1920x1080
  • 1x תמונה ברזולוציה גבוהה מדי פעם (JPEG), למשל 12MP
טלפונים ברמה בינונית
  • שני שידורים של YUV CPU, לדוגמה 640x480 ו-1920x1080
  • סטרימינג של 1x GPU, לדוגמה 1920x1080
–או–
  • שידורים של 1x YUV באמצעות מעבד (CPU), לדוגמה: 640x480 – או 1920x1080
  • סטרימינג של 1x 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)

שימוש בקריאה חוזרת (callback) של מצב המצלמה

בקריאה החוזרת (callback) של מצב המצלמה, מאוחסנים הפניה למכשיר המצלמה ומתחילים סשן צילום חדש.

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

כדי להתחיל לצלם פריימים, צריך להפעיל את הקריאה החוזרת captureSession.setRepeatingRequest() מתוך סשן הצילום של המצלמה onConfigured() במצב קריאה חוזרת (callback). צריך להמשיך את הסשן של ARCore במהלך הקריאה החוזרת (callback) ב-onActive() כדי להתחיל במצב 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()