אחרי שמבקשים ומקבלים את ההרשאות הנדרשות, האפליקציה יכולה לגשת לחומרה במשקפי השמע או במשקפי התצוגה. כדי לגשת לחומרה של המשקפיים (במקום לחומרה של הטלפון), צריך להשתמש בהקשר מוקרן.
יש שתי דרכים עיקריות לקבל הקשר משוער, בהתאם למקום שבו הקוד מופעל:
לקבל הקשר משוער אם הקוד פועל בפעילות משוערת
אם הקוד של האפליקציה פועל מתוך הפעילות המשוערת, הקשר של הפעילות שלה הוא כבר קשר משוער. בתרחיש הזה, לשיחות שמתבצעות במסגרת הפעילות הזו כבר יש גישה לחומרה של המשקפיים.
קבלת הקשר משוער להרצת קוד ברכיב של אפליקציה לטלפון
אם חלק מהאפליקציה שלכם מחוץ לפעילות המוקרנת (כמו פעילות בטלפון או שירות) צריך לגשת לחומרה של המשקפיים, הוא צריך לקבל הקשר מוקרן באופן מפורש. כדי לעשות את זה, משתמשים בשיטה 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.
| שיטת ההקלטה | גישה למיקרופון | תרחיש נפוץ לדוגמה |
|---|---|---|
הקשר מוקרן |
כמה מיקרופונים |
הקלטה באמצעות הקשר מוקרן מאפשרת לאפליקציה לגשת לכמה מיקרופונים מהמשקפיים ולתכונות החומרה המיוחדות שלהם, כמו:
|
Bluetooth HFP |
מיקרופון יחיד |
התאימות המיידית מבוססת על פרופיל הדיבורית (HFP) של Bluetooth. במצב הזה, המשקפיים מתחברים לטלפון באמצעות פרופילים סטנדרטיים של אוזניות ופרופיל מתקדם להפצת אודיו (A2DP), ופועלים כמו ציוד היקפי רגיל של Bluetooth. אם האפליקציה שלכם כבר מיועדת להקלטה רגילה באמצעות Bluetooth, אתם יכולים להשתמש בשיטה הזו כדי להקליט אודיו מהמשקפיים בלי לשלב יכולות ספציפיות ל-XR. |
הקלטת אודיו באמצעות הקשר מוקרן
כדי להקליט אודיו באמצעות הקשר מוקרן, קודם צריך לבקש את הרשאות זמן הריצה הנדרשות, ואז להקליט את האודיו באמצעות AudioRecord API, כמו שמתואר בקטעים הבאים.
שליחת בקשה להרשאות בזמן ריצה
כדי לגשת לכמה מיקרופונים במשקפיים, צריך לבקש הרשאות אודיו באופן ספציפי למכשיר המוקרן. ההרשאה הרגילה שניתנה לאפליקציה על ידי המשתמש בטלפון הנייד שלו, RECORD_AUDIOלא מספיקה.
כדי לבקש את ההרשאות:
- מצהירים על ההרשאה
RECORD_AUDIOבקובץ המניפסט של האפליקציה. אפשר לבקש את ההרשאות בהיקף של מכשיר להקרנה באחת מהדרכים הבאות, בהתאם למקום שבו הקוד מופעל:
- קוד שמופעל מפעילות מתוכננת: משתמשים ב-
ActivityResultLauncherעםProjectedPermissionsResultContract. מידע נוסף על השיטה הזו מופיע בקטע רישום של כלי ההפעלה של ההרשאות ובקטעים הבאים במדריך לבקשת הרשאות לשימוש בחומרה. - קוד שמופעל מפעילות של טלפון מארח: משתמשים ב-
Activity#requestPermissions(permissions, requestCode, deviceId)ומספקים את מזהה המכשיר שהתקבל מ-projectedDeviceContext, כמו שמתואר בקטע הסבר על תהליך בקשת ההרשאה במדריך לבקשת הרשאות חומרה.
- קוד שמופעל מפעילות מתוכננת: משתמשים ב-
הפעלת 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 והרשאות קשורות אחרות נשלטות על ידי הטלפון ולא על ידי המכשיר המחובר (כמו משקפי אודיו או משקפיים עם תצוגה).
כדי לבקש את ההרשאות:
מצהירים על ההרשאות הבאות בקובץ המניפסט של האפליקציה:
צריך לבקש את ההרשאות
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כי הקשר של האפליקציה עלול להחזיר באופן שגוי את הקשר של המשקפיים אם רכיב של פעילות מוקרנת היה הרכיב שהופעל לאחרונה.