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

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

בנושא הזה אנחנו יוצאים מנקודת הנחה ש:

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

כשמריצים את אפליקציית הדוגמה Shared Camera 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.

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

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

כאינדיקציה כללית, אפשר לצפות ל:

סוג המכשיר שידורים חיים בו-זמנית שנתמכים
טלפונים מתקדמים
  • 2x YUV CPU streams, e.g. 640x480 and 1920x1080
  • 1x GPU stream, e.g. 1920x1080
  • תמונה סטילס אחת ברזולוציה גבוהה מדי פעם (JPEG), למשל 12MP
טלפונים ברמה בינונית
  • 2x YUV CPU streams, e.g. 640x480 and 1920x1080
  • 1x GPU stream, e.g. 1920x1080
–או–
  • 1x YUV CPU streams, e.g. 640x480 –or– 1920x1080
  • 1x GPU stream, e.g. 1920x1080
  • תמונה סטילס אחת ברזולוציה גבוהה מדי פעם (JPEG), למשל 12MP

כדי להשתמש בפלטפורמות מותאמות אישית, כמו פלטפורמה לקריאת תמונות של CPU, צריך להוסיף אותה לרשימת הפלטפורמות שצריך לעדכן (לדוגמה, ImageReader).

Java

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

Kotlin

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

פתיחת המצלמה

פותחים את המצלמה באמצעות קריאה חוזרת (callback) שעטופה ב-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

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

Java

// Pause ARCore.
sharedSession.pause();

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

Kotlin

// Pause ARCore.
sharedSession.pause()

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