אחרי שמבקשים ומקבלים את ההרשאות הנדרשות, האפליקציה יכולה לגשת לחומרה במשקפיים עם ממשק קולי או במשקפיים עם תצוגה פנימית. כדי לגשת לחומרה של המשקפיים (במקום לחומרה של הטלפון), צריך להשתמש בהקשר מוקרן.
יש שתי דרכים עיקריות לקבל הקשר משוער, בהתאם למקום שבו הקוד מופעל:
לקבל הקשר משוער אם הקוד פועל בפעילות משוערת
אם הקוד של האפליקציה פועל מתוך הפעילות המשוערת, הקשר של הפעילות שלו הוא כבר הקשר המשוער. בתרחיש הזה, לשיחות שמתבצעות במסגרת הפעילות הזו כבר יש גישה לחומרה של המשקפיים.
קבלת הקשר משוער להרצת קוד ברכיב של אפליקציית טלפון
אם חלק מהאפליקציה שלכם שלא כלול בפעילות המוקרנת (למשל פעילות של טלפון או שירות) צריך לגשת לחומרה של המשקפיים, הוא צריך לקבל הקשר מוקרן באופן מפורש. כדי לעשות את זה, צריך להשתמש בשיטה 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).כדי לקבוע את גודל המאגר המינימלי ליצירת האובייקט
AudioRecord, צריך להשתמש ב-AudioRecord.getMinBufferSize(). עם זאת, כדי למנוע נפילות של אודיו מהמשקפיים, מומלץ לקרוא מהמאגר הזה במקטעים קצרים ותכופים (אידיאלית, חלקיקים של 20 אלפיות השנייה) במקום לחכות עד שהמאגר יתמלא.
ניקוי אחרי השימוש
כשהאפליקציה כבר לא צריכה את המיקרופון, או כשהפעילות מופסקת, צריך להפעיל את 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כי הקשר של האפליקציה עלול להחזיר בטעות את הקשר של המשקפיים אם רכיב של פעילות מוקרנת היה הרכיב האחרון שהופעל.