要求並取得必要權限後,應用程式就能存取音訊眼鏡或螢幕眼鏡的硬體。如要存取智慧眼鏡的硬體 (而非手機的硬體),請使用投影環境。
視程式碼的執行位置而定,取得預測內容的方式主要有兩種:
如果程式碼在投影活動中執行,則取得投影內容
如果應用程式的程式碼是從投影活動內執行,其活動環境定義本身就是投影環境定義。在這種情況下,該活動內發出的呼叫已可存取眼鏡的硬體。
取得在手機應用程式元件中執行的程式碼預計環境
如果應用程式中預計活動以外的部分 (例如電話活動或服務) 需要存取智慧眼鏡的硬體,就必須明確取得投影環境。如要這麼做,請使用 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)。
選擇錄製方法
選擇的方法取決於您是否需要高保真度、XR 專屬音訊處理,或是標準藍牙音訊輸入。
| 錄製方法 | 麥克風存取權 | 常見用途 |
|---|---|---|
預測脈絡 |
多個麥克風 |
使用投影環境錄音時,應用程式可以存取眼鏡的多個麥克風和專用硬體功能,例如:
|
藍牙 HFP |
單一麥克風 |
透過藍牙免持聽筒設定檔 (HFP) 立即相容。在此模式下,眼鏡會使用標準耳機和進階音訊散布設定檔 (A2DP) 設定檔連線至手機,運作方式與一般藍牙周邊裝置相同。 如果您的應用程式已支援標準藍牙錄音功能,即可使用這項方法錄製眼鏡的音訊,不必整合任何 XR 專用功能。 |
使用投影的脈絡錄製音訊
如要使用投影環境錄製音訊,請先要求必要的執行階段權限,然後使用 AudioRecord API 錄製音訊,詳情請參閱下列章節。
要求執行階段權限
如要存取眼鏡上的多個麥克風,必須為投影裝置特別要求音訊權限。使用者在行動裝置上授予應用程式的標準手機範圍 RECORD_AUDIO 權限不足。
請按照下列步驟要求權限:
- 在應用程式的資訊清單檔案中宣告
RECORD_AUDIO權限。 視程式碼的執行位置而定,請透過下列其中一種方式要求投影裝置範圍權限:
- 從投影活動執行的程式碼:使用
ActivityResultLauncher和ProjectedPermissionsResultContract。如要進一步瞭解如何使用這個方法,請參閱「註冊權限啟動器」一節,以及要求硬體權限指南中的後續章節。 - 從主機手機活動執行的程式碼:使用
Activity#requestPermissions(permissions, requestCode, deviceId),並提供從projectedDeviceContext取得的裝置 ID,如要求硬體權限指南的「瞭解權限要求使用者流程」一節所述。
- 從投影活動執行的程式碼:使用
使用投影的內容初始化 AudioRecord
如要確保錄音來源是智慧眼鏡而非主機手機,請務必將 AudioRecord 物件與投影裝置內容建立關聯。
下列程式碼會使用 AudioRecord.Builder,並將 projectedDeviceContext 傳遞至 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物件必須定義 16 kHz 的取樣率,以及單聲道或立體聲的聲道設定 (使用CHANNEL_IN_MONO或CHANNEL_IN_STEREO)。緩衝區大小沒有固定要求,但建議取得最小緩衝區大小,盡量縮短感知延遲時間。
使用後請自行清潔
當應用程式不再需要麥克風,或活動停止時,請在 AudioRecord 物件上呼叫 stop 和 release。
錄製前檢查執行階段權限
呼叫 startRecording 前,請先確認使用者已透過投影內容授予眼鏡麥克風權限。
透過藍牙 HFP 錄音
如要使用藍牙 HFP 錄製音訊,請先要求必要的執行階段權限,然後使用 AudioManager API 錄製音訊,如下節所述。
要求權限
與任何標準藍牙音訊裝置一樣,RECORD_AUDIO、BLUETOOTH_CONNECT 和其他相關權限是由手機控管,而非連結裝置 (例如音訊眼鏡或螢幕眼鏡)。
請按照下列步驟要求權限:
在應用程式的資訊清單檔案中宣告下列權限:
使用標準 Android 權限流程,在執行階段要求
RECORD_AUDIO和BLUETOOTH_CONNECT權限。
使用 AudioManager 轉送音訊
使用者授予應用程式必要的執行階段權限後,請使用 AudioManager API 將通訊裝置設為 TYPE_BLUETOOTH_SCO,透過藍牙 HFP 傳送音訊。這會指示系統從藍牙周邊裝置擷取音訊。
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 use case 繫結至眼鏡的相機,並為應用程式使用正確的內容:
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的輸出圖片解析度。 - 建立已設定自訂
ResolutionSelector的ImageCapture用途。 - 將
ImageCapture用例繫結至活動的生命週期。系統會根據活動狀態自動管理攝影機的開啟和關閉 (例如在活動暫停時停止攝影機)。
設定好智慧眼鏡的相機後,即可使用 CameraX 的 ImageCapture 類別擷取圖片。請參閱 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,因為如果投影活動是最近啟動的元件,應用程式環境可能會錯誤地傳回眼鏡的環境。