שימוש בהקשר מוקרן כדי לגשת לציוד במשקפי שמע ובמשקפי תצוגה

מכשירי XR שמתאימים לשימוש
ההנחיות האלה יעזרו לכם ליצור חוויות למכשירי XR מהסוגים הבאים.
משקפי אודיו ותצוגה

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

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

לקבל הקשר משוער אם הקוד פועל בפעילות משוערת

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

קבלת הקשר משוער להרצת קוד ברכיב של אפליקציה לטלפון

אם חלק מהאפליקציה שלכם מחוץ לפעילות המוקרנת (כמו פעילות בטלפון או שירות) צריך לגשת לחומרה של המשקפיים, הוא צריך לקבל הקשר מוקרן באופן מפורש. כדי לעשות את זה, משתמשים בשיטה createProjectedDeviceContext:

@OptIn(ExperimentalProjectedApi::class)
private fun getGlassesContext(context: Context): Context? {
    return try {
        // From a phone Activity or Service, get a context for the AI glasses.
        ProjectedContext.createProjectedDeviceContext(context)
    } catch (e: IllegalStateException) {
        Log.e(TAG, "Failed to create projected device context", e)
        null
    }
}

בדיקת התוקף

עוטפים את הקריאה של createProjectedDeviceContext בתוך התג ProjectedContext.isProjectedDeviceConnected. למרות שהשיטה הזו מחזירה את הערך true, ההקשר המוקרן נשאר תקף למכשיר המחובר, והפעילות או השירות של אפליקציית הטלפון (כמו CameraManager) יכולים לגשת לחומרה של משקפי ה-AI.

ניקוי לאחר ניתוק

ההקשר המוקרן קשור למחזור החיים של המכשיר המחובר, ולכן הוא נמחק כשהמכשיר מתנתק. כשהמכשיר מתנתק, הפונקציה ProjectedContext.isProjectedDeviceConnected מחזירה את הערך false. האפליקציה שלכם צריכה להאזין לשינוי הזה ולנקות את כל שירותי המערכת (כמו CameraManager) או המשאבים שהאפליקציה יצרה באמצעות ההקשר המוקרן הזה.

הפעלה מחדש של האתחול בחיבור מחדש

כשהמשקפיים מתחברים מחדש, האפליקציה יכולה לקבל עוד מופע של הקשר המוקרן באמצעות createProjectedDeviceContext, ואז לאתחל מחדש את כל שירותי המערכת או המשאבים באמצעות ההקשר המוקרן החדש.

הקלטת אודיו באמצעות המיקרופון של המשקפיים

יש שתי דרכים שונות להקליט אודיו מהמשקפיים:

  • להשתמש בהקשר משוער.
  • משתמשים בפרופיל דיבורית (HFP) של Bluetooth.

בחירת שיטת הקלטה

השיטה שתבחרו תלויה בשאלה אם אתם צריכים עיבוד אודיו באיכות גבוהה, עיבוד אודיו ספציפי ל-XR או קלט אודיו רגיל ב-Bluetooth.

שיטת ההקלטה גישה למיקרופון תרחיש נפוץ לדוגמה

הקשר מוקרן

כמה מיקרופונים

הקלטה באמצעות הקשר מוקרן מאפשרת לאפליקציה לגשת לכמה מיקרופונים מהמשקפיים ולתכונות החומרה המיוחדות שלהם, כמו:

  • התאמה מרחבית ספציפית ל-XR.
  • הסרת רעשים מתקדמת.
  • הפרדת קולות שמבחינה בין הקול של מי שעונד את המשקפיים לבין הקול של מי שעומד לידו.
  • שמירה על גישה להקלטה בסביבות מרובות מכשירים, גם כשהמשקפיים לא מוגדרים כמכשיר ה-Bluetooth הפעיל.

Bluetooth HFP

מיקרופון יחיד

התאימות המיידית מבוססת על פרופיל הדיבורית (HFP) של Bluetooth. במצב הזה, המשקפיים מתחברים לטלפון באמצעות פרופילים סטנדרטיים של אוזניות ופרופיל מתקדם להפצת אודיו (A2DP), ופועלים כמו ציוד היקפי רגיל של Bluetooth.

אם האפליקציה שלכם כבר מיועדת להקלטה רגילה באמצעות Bluetooth, אתם יכולים להשתמש בשיטה הזו כדי להקליט אודיו מהמשקפיים בלי לשלב יכולות ספציפיות ל-XR.

הקלטת אודיו באמצעות הקשר מוקרן

כדי להקליט אודיו באמצעות הקשר מוקרן, קודם צריך לבקש את הרשאות זמן הריצה הנדרשות, ואז להקליט את האודיו באמצעות AudioRecord API, כמו שמתואר בקטעים הבאים.

שליחת בקשה להרשאות בזמן ריצה

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

כדי לבקש את ההרשאות:

  1. מצהירים על ההרשאה RECORD_AUDIO בקובץ המניפסט של האפליקציה.
  2. אפשר לבקש את ההרשאות בהיקף של מכשיר להקרנה באחת מהדרכים הבאות, בהתאם למקום שבו הקוד מופעל:

הפעלת AudioRecord עם הקשר מתוכנן

כדי לוודא שהאודיו מוקלט מהמשקפיים ולא מהטלפון המארח, צריך לשייך את האובייקט AudioRecord להקשר של המכשיר המוקרן.

בדוגמת הקוד הבאה נעשה שימוש ב-AudioRecord.Builder והפרמטר projectedDeviceContext מועבר ל-method‏ setContext:

// Initialize AudioRecord with projected device context
val audioRecord = AudioRecord.Builder()
    .setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
    .setAudioFormat(audioFormat)
    .setBufferSizeInBytes(bufferSize)
    // pass in the projected device context
    .setContext(projectedDeviceContext)
    .build()

audioRecord.startRecording()

מידע חשוב על הקוד
  • אתם יכולים להגדיר את מקור האודיו ל-CAMCORDER,‏ VOICE_RECOGNITION,‏ VOICE_COMMUNICATION או UNPROCESSED כדי להתאים את עיבוד האודיו לתרחיש השימוש הספציפי שלכם.

    לדוגמה, אפשר להשתמש ב-VOICE_COMMUNICATION אם בתרחיש השימוש שלכם נדרשת הפחתת רעשים אוטומטית. האודיו של VOICE_RECOGNITION עובר עיבוד באמצעות ביטול הד אקוסטי (AEC). אם אתם צריכים אודיו גולמי ללא שינויים, בוחרים באפשרות UNPROCESSED או CAMCORDER.

  • כדי להבטיח תאימות למשקפיים, באובייקט audioFormat צריך להגדיר תדירות דגימה של 16kHz ותצורת ערוץ של מונו או סטריאו (באמצעות CHANNEL_IN_MONO או CHANNEL_IN_STEREO).

  • אין דרישה קבועה לגודל שטח אחסון זמני, אבל מומלץ לקבל את גודל שטח אחסון זמני המינימלי כדי לצמצם את זמן הטעינה.

ניקוי אחרי השימוש

כשהאפליקציה כבר לא צריכה את המיקרופון, או כשהפעילות מופסקת, צריך להפעיל את stop ואת release באובייקט AudioRecord.

בדיקה של הרשאות בזמן ריצה לפני הקלטה

לפני שמתקשרים אל startRecording, צריך לוודא שהמשתמש העניק את הרשאת גישה למיקרופון למשקפיים באמצעות ההקשר המוקרן.

הקלטת אודיו באמצעות Bluetooth HFP

כדי להקליט אודיו באמצעות Bluetooth HFP, צריך קודם לבקש את הרשאות זמן הריצה הנדרשות, ואז להקליט את האודיו באמצעות API‏ AudioManager, כמו שמתואר בקטעים הבאים.

שליחת בקשה להרשאות

בדומה לכל מכשיר אודיו רגיל ב-Bluetooth, ההרשאות RECORD_AUDIO,‏ BLUETOOTH_CONNECT והרשאות קשורות אחרות נשלטות על ידי הטלפון ולא על ידי המכשיר המחובר (כמו משקפי אודיו או משקפיים עם תצוגה).

כדי לבקש את ההרשאות:

  1. מצהירים על ההרשאות הבאות בקובץ המניפסט של האפליקציה:

  2. צריך לבקש את ההרשאות RECORD_AUDIO ו-BLUETOOTH_CONNECT בזמן הריצה באמצעות תהליך ההרשאות הרגיל של Android.

שימוש ב-AudioManager כדי לנתב אודיו

אחרי שהמשתמש מעניק לאפליקציה את הרשאות זמן הריצה הנדרשות, משתמשים ב-API‏ AudioManager כדי להגדיר את מכשיר התקשורת ל-TYPE_BLUETOOTH_SCO ולנתב את האודיו דרך Bluetooth HFP. ההגדרה הזו מכוונת את המערכת לאחזר אודיו מהציוד ההיקפי של Bluetooth.

val audioManager = context.getSystemService(AudioManager::class.java) ?: return
val devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
val hfpDevice = devices.find { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }

hfpDevice?.let { device ->
    val audioRecord = AudioRecord.Builder()
        .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
        .setAudioFormat(audioFormat)
        .setBufferSizeInBytes(bufferSize)
        .build()

    // Route recording to the Bluetooth device
    audioRecord.setPreferredDevice(device)
    audioManager.setCommunicationDevice(device)

    audioRecord.startRecording()

צילום תמונה עם המצלמה של המשקפיים

כדי לצלם תמונה באמצעות המצלמה של המשקפיים, צריך להגדיר ולקשור את תרחיש השימוש של CameraX‏ ImageCapture למצלמה של המשקפיים באמצעות ההקשר הנכון לאפליקציה:

private fun startCameraOnGlasses(activity: ComponentActivity) {
    // 1. Get the CameraProvider using the projected context.
    // When using the projected context, DEFAULT_BACK_CAMERA maps to the AI glasses' camera.
    val projectedContext = try {
        ProjectedContext.createProjectedDeviceContext(activity)
    } catch (e: IllegalStateException) {
        Log.e(TAG, "AI Glasses context could not be created", e)
        return
    }

    val cameraProviderFuture = ProcessCameraProvider.getInstance(projectedContext)

    cameraProviderFuture.addListener({
        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

        // 2. Check for the presence of a camera.
        if (!cameraProvider.hasCamera(cameraSelector)) {
            Log.w(TAG, "The selected camera is not available.")
            return@addListener
        }

        // 3. Query supported streaming resolutions using Camera2 Interop.
        val cameraInfo = cameraProvider.getCameraInfo(cameraSelector)
        val camera2CameraInfo = Camera2CameraInfo.from(cameraInfo)
        val cameraCharacteristics = camera2CameraInfo.getCameraCharacteristic(
            CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
        )

        // 4. Define the resolution strategy.
        val targetResolution = Size(1920, 1080)
        val resolutionStrategy = ResolutionStrategy(
            targetResolution,
            ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER
        )
        val resolutionSelector = ResolutionSelector.Builder()
            .setResolutionStrategy(resolutionStrategy)
            .build()

        // 5. If you have other continuous use cases bound, such as Preview or ImageAnalysis,
        // you can use  Camera2 Interop's CaptureRequestOptions to set the FPS
        val fpsRange = Range(30, 60)
        val captureRequestOptions = CaptureRequestOptions.Builder()
            .setCaptureRequestOption(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange)
            .build()

        // 6. Initialize the ImageCapture use case with options.
        val imageCapture = ImageCapture.Builder()
            // Optional: Configure resolution, format, etc.
            .setResolutionSelector(resolutionSelector)
            .build()

        try {
            // Unbind use cases before rebinding.
            cameraProvider.unbindAll()

            // Bind use cases to camera using the Activity as the LifecycleOwner.
            cameraProvider.bindToLifecycle(
                activity,
                cameraSelector,
                imageCapture
            )
        } catch (exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }
    }, ContextCompat.getMainExecutor(activity))
}

מידע חשוב על הקוד

  • מקבלים מופע של ProcessCameraProvider באמצעות הקשר המכשיר המוקרן.
  • במסגרת ההקשר המוקרן, המצלמה הראשית של המשקפיים, שמפנה החוצה, ממופה ל-DEFAULT_BACK_CAMERA כשבוחרים מצלמה.
  • בדיקה לפני קישור משתמשת ב-cameraProvider.hasCamera(cameraSelector) כדי לוודא שהמצלמה שנבחרה זמינה במכשיר לפני שממשיכים.
  • הוא משתמש ב-Camera2 Interop עם Camera2CameraInfo כדי לקרוא את CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP הבסיסי, שיכול להיות שימושי לבדיקות מתקדמות של רזולוציות נתמכות.
  • ResolutionSelector בהתאמה אישית נוצר כדי לשלוט בדיוק ברזולוציית התמונה של הפלט ב-ImageCapture.
  • יוצרת ImageCapture תרחיש שימוש שמוגדר עם ResolutionSelector מותאם אישית.
  • קושר את תרחיש השימוש ImageCapture למחזור החיים של הפעילות. הפעולה הזו מנהלת באופן אוטומטי את הפתיחה והסגירה של המצלמה על סמך מצב הפעילות (לדוגמה, עצירת המצלמה כשהפעילות מושהית).

אחרי שמגדירים את המצלמה של המשקפיים, אפשר לצלם תמונה באמצעות המחלקה ImageCapture של CameraX. במאמרי העזרה של CameraX מוסבר איך משתמשים ב-takePicture כדי לצלם תמונה.

צילום סרטון באמצעות המצלמה של המשקפיים

כדי לצלם סרטון במקום תמונה באמצעות המצלמה של המשקפיים, מחליפים את הרכיבים ImageCapture ברכיבים התואמים VideoCapture ומשנים את הלוגיקה של ביצוע הצילום.

השינויים העיקריים כוללים שימוש בתרחיש שימוש אחר, יצירת קובץ פלט אחר והתחלת הצילום באמצעות שיטת הקלטת הווידאו המתאימה. מידע נוסף על VideoCapture API ועל אופן השימוש בו זמין במאמרי העזרה בנושא צילום וידאו ב-CameraX.

בטבלה הבאה מוצגים הרזולוציה וקצב הפריימים המומלצים בהתאם לתרחיש השימוש באפליקציה:

תרחיש שימוש רזולוציה קצב פריימים
תקשורת בווידאו ‫1280 x 720 ‫15 פריימים לשנייה (FPS)
ראייה ממוחשבת ‫640 x 480 ‫10 פריימים לשנייה (FPS)
סטרימינג של סרטונים מ-AI ‫640 x 480 ‫1 FPS

גישה לחומרה של הטלפון מפעילות חזויה

פעילות חזויה יכולה גם לגשת לחומרה של הטלפון (כמו המצלמה או המיקרופון) באמצעות createHostDeviceContext(context) כדי לקבל את ההקשר של המכשיר המארח (הטלפון):

@OptIn(ExperimentalProjectedApi::class)
private fun getPhoneContext(activity: ComponentActivity): Context? {
    return try {
        // From an AI glasses Activity, get a context for the phone.
        ProjectedContext.createHostDeviceContext(activity)
    } catch (e: IllegalStateException) {
        Log.e(TAG, "Failed to create host device context", e)
        null
    }
}

כשניגשים לחומרה או למשאבים שספציפיים למכשיר המארח (הטלפון) באפליקציה היברידית (אפליקציה שמכילה גם חוויות לנייד וגם חוויות למשקפיים), צריך לבחור במפורש את ההקשר הנכון כדי לוודא שהאפליקציה יכולה לגשת לחומרה הנכונה:

  • משתמשים בהקשר Activity מהטלפון Activity או ב-ProjectedContext.createHostDeviceContext כדי לקבל את ההקשר של הטלפון.
  • אל תשתמשו ב-getApplicationContext כי הקשר של האפליקציה עלול להחזיר באופן שגוי את הקשר של המשקפיים אם רכיב של פעילות מוקרנת היה הרכיב שהופעל לאחרונה.