After you've requested and been granted the necessary permissions , your app can access the hardware on the audio glasses or display glasses. The key to accessing the glasses' hardware (instead of the phone's hardware), is to use a projected context .
Существует два основных способа получения контекста проекции, в зависимости от того, где выполняется ваш код:
Получите контекст проекции, если ваш код выполняется в проецируемой активности.
If your app's code is running from within your projected activity , its own activity context is already a projected context . In this scenario, calls made within that activity can already access the glasses' hardware.
Получите контекст выполнения кода в компоненте мобильного приложения.
If a part of your app outside of your projected activity (such as a phone activity or a service) needs to access the glasses' hardware, it must explicitly obtain a projected context. To do this, use the createProjectedDeviceContext method:
@RequiresApi(Build.VERSION_CODES.BAKLAVA) @OptIn(ExperimentalProjectedApi::class, ExperimentalCoroutinesApi::class) private fun monitorProjectedConnectivity(activity: ComponentActivity) { activity.lifecycleScope.launch { // Before creating a projected context, check to see if the projected device is connected. // While this method returns true, the projected context remains valid. ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext) .collectLatest { isConnected -> if (isConnected) { // From a phone Activity or Service, get a context for the audio and display glasses. // Re-initialize on reconnect: Obtain another context instance. val projectedContext = try { ProjectedContext.createProjectedDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to create projected context", e) return@collectLatest } // Use the projectedContext to initialize system services (e.g., CameraManager). Log.i(TAG, "Projected device connected. Initializing hardware...") } else { // The projected context is destroyed when the device disconnects. // Clean up on disconnect: Listen for 'false' and release resources. Log.i(TAG, "Projected device disconnected. Cleaning up hardware resources...") } } } }
Проверьте достоверность
Wrap the createProjectedDeviceContext call within the ProjectedContext.isProjectedDeviceConnected . While this method returns true , the projected context remains valid to the connected device, and your phone app activity or service (such as a CameraManager ) can access the AI glasses hardware.
Очистка при отключении
The projected context is tied to the lifecycle of the connected device, so it is destroyed when the device disconnects. When the device disconnects, ProjectedContext.isProjectedDeviceConnected returns false . Your app should listen for this change and clean up any system services (such as a CameraManager ) or resources that your app created using that projected context.
Повторная инициализация при повторном подключении.
When the glasses reconnect, your app can obtain another projected context instance using createProjectedDeviceContext , and then re-initialize any system services or resources using the new projected context.
Записывайте звук с помощью микрофона очков.
Запись звука с очков возможна двумя различными способами:
- Используйте проецируемый контекст .
- Используйте профиль громкой связи Bluetooth (HFP).
Выберите способ записи
Выбор метода зависит от того, требуется ли вам высококачественная обработка звука, специально разработанная для XR, или стандартный аудиовход Bluetooth.
| Метод записи | доступ к микрофону | Типичный сценарий использования |
|---|---|---|
Прогнозируемый контекст | Несколько микрофонов | Запись с использованием проецируемого контекста позволяет вашему приложению получать доступ к нескольким микрофонам очков и их специализированным аппаратным функциям, таким как:
|
Bluetooth HFP | Один микрофон | Relies on the Bluetooth Hands-Free Profile (HFP) for immediate, out-of-the-box compatibility. In this mode, the glasses connect to the phone using standard Headset and Advanced Audio Distribution Profile (A2DP) profiles , functioning like a typical Bluetooth peripheral. Если ваше приложение уже разработано для стандартной записи по Bluetooth, вы можете использовать этот метод для записи звука с очков без интеграции каких-либо специфических функций XR. |
Запись звука с использованием проецируемого контекста.
Для записи звука с использованием проецируемого контекста сначала запросите необходимые разрешения во время выполнения, а затем запишите звук с помощью API AudioRecord , как описано в следующих разделах.
Запросить разрешения во время выполнения
To access multiple microphones on the glasses, you must request audio permissions specifically for the projected device. The standard, phone-scoped RECORD_AUDIO permission that a user has granted for your app on their mobile device is insufficient.
Для запроса разрешений выполните следующие действия:
- Укажите разрешение
RECORD_AUDIOв файле манифеста вашего приложения. Запросить разрешения, ограниченные областью действия проектируемого устройства, можно одним из следующих способов, в зависимости от места выполнения вашего кода:
- Code executing from a projected activity : Use the
ActivityResultLauncherwith theProjectedPermissionsResultContract. For more information on using this method, see the register the permissions launcher section and subsequent sections in the guide for requesting hardware permissions. - Code executing from a host phone activity : Use
Activity#requestPermissions(permissions, requestCode, deviceId)and provide the device ID obtained from yourprojectedDeviceContext, as described in the understand the permission request user flow section of the guide for requesting hardware permissions.
- Code executing from a projected activity : Use the
Инициализируйте AudioRecord с помощью проецируемого контекста.
Чтобы гарантировать запись звука с очков, а не с основного телефона, необходимо связать объект AudioRecord с контекстом проецируемого устройства.
В следующем коде используется объект AudioRecord.Builder , и в метод setContext передается объект projectedDeviceContext :
// 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, чтобы настроить обработку звука в соответствии с вашими конкретными потребностями.For example, use
VOICE_COMMUNICATIONif your use case needs automatic noise reduction or to isolate the wearer's voice.VOICE_RECOGNITIONis processed with acoustic echo cancellation (AEC), which can be useful when there's concurrent audio playback while recording. And if you need raw, unaltered audio, selectUNPROCESSEDorCAMCORDER.To ensure compatibility with the glasses, the
audioFormatobject must define a sample rate of 16kHz and a channel configuration of either mono or stereo (usingCHANNEL_IN_MONOorCHANNEL_IN_STEREO).Use
AudioRecord.getMinBufferSize()to determine the minimum buffer size to create theAudioRecordobject. However, to prevent audio drops from the glasses, you should read from this buffer in short, frequent chunks (ideally 20ms slices) rather than waiting for the entire buffer to fill.
После использования необходимо убрать за собой.
Когда вашему приложению больше не нужен микрофон или когда активность остановлена, вызовите методы stop и release для объекта AudioRecord .
Перед началом записи проверьте права доступа во время выполнения.
Перед вызовом startRecording убедитесь, что пользователь предоставил очкам разрешение на использование микрофона, используя контекст проекции.
Запись звука с использованием Bluetooth HFP
Для записи звука с использованием Bluetooth HFP сначала запросите необходимые разрешения во время выполнения, а затем запишите звук с помощью API AudioManager , как описано в следующих разделах.
Запросить разрешения
As with any standard Bluetooth audio device, the RECORD_AUDIO , BLUETOOTH_CONNECT , and other related permissions are controlled by the phone and not the connected device (such as audio glasses or display glasses).
Для запроса разрешений выполните следующие действия:
В файле манифеста вашего приложения укажите следующие разрешения :
Запросите разрешения
RECORD_AUDIOиBLUETOOTH_CONNECTво время выполнения, используя стандартный алгоритм получения разрешений Android .
Используйте AudioManager для маршрутизации звука.
After the user has granted your app the necessary runtime permissions, use the AudioManager API to set the communication device to TYPE_BLUETOOTH_SCO to route the audio through Bluetooth HFP. This directs the system to retrieve audio from the Bluetooth peripheral.
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()
Сделайте снимок с помощью камеры очков.
Чтобы сделать снимок с помощью камеры очков, настройте и свяжите сценарий использования ImageCapture объекта CameraX с камерой очков, используя правильный контекст для вашего приложения:
private fun startCameraOnGlasses(activity: ComponentActivity) { activity.lifecycleScope.launch { // Before creating a projected context, check to see if the projected device is connected. ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext) .collectLatest { isConnected -> if (isConnected) { // 1. Get the CameraProvider using the projected context. // When using the projected context, DEFAULT_BACK_CAMERA maps to the audio and display glasses' camera. val projectedContext = try { ProjectedContext.createProjectedDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Projected context could not be created", e) return@collectLatest } 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, что может быть полезно для расширенной проверки поддерживаемых разрешений. - Для точного управления разрешением выходного изображения в
ImageCaptureсоздан специальныйResolutionSelector. - Создает сценарий использования
ImageCapture, настроенный с использованием пользовательскогоResolutionSelector. - Binds the
ImageCaptureuse case to the activity's lifecycle. This automatically manages the opening and closing of the camera based on the activity's state (for example, stopping the camera when the activity is paused). - Configuring for single-stream hardware: The glasses camera pipeline is constrained to a single active stream at a time. When
ImageCaptureis bound alone, CameraX attaches an internal repeating stream (MeteringRepeating) for focus and metering by default, which causes the single-stream pipeline to stall. To bindImageCaptureby itself without a preview stream, disable the forced repeating stream by settingsetRepeatingStreamForced(false)onCameraXConfig.Builder(for example, by implementingCameraXConfig.Providerin yourApplicationclass or configuringProcessCameraProviderbefore initialization).
После настройки камеры очков вы можете сделать снимок с помощью класса ImageCapture из библиотеки CameraX. Чтобы узнать, как использовать takePicture для захвата изображения , обратитесь к документации CameraX.
Снимите видео с помощью камеры очков.
Чтобы с помощью камеры очков захватывать видео, а не изображение, замените компоненты ImageCapture соответствующими компонентами VideoCapture и измените логику выполнения захвата.
The main changes involve using a different use case, creating a different output file, and initiating the capture using the appropriate video recording method. For more information about the VideoCapture API and how to use it, see the CameraX's video capture documentation .
В таблице ниже приведены рекомендуемые разрешение и частота кадров в зависимости от сценария использования вашего приложения:
| Вариант использования | Разрешение | Частота кадров |
|---|---|---|
| Видеосвязь | 1280 x 720 | 15 кадров в секунду |
| Компьютерное зрение | 640 x 480 | 10 кадров в секунду |
| Потоковое видео с использованием ИИ | 640 x 480 | 1 кадр в секунду |
Получить доступ к аппаратному обеспечению телефона из проецируемой активности
Проектируемое действие также может получить доступ к аппаратному обеспечению телефона (например, к камере или микрофону), используя createHostDeviceContext(context) для получения контекста хост-устройства (телефона):
@OptIn(ExperimentalProjectedApi::class) private fun getPhoneContext(activity: ComponentActivity): Context? { return try { // From a projected Activity, get a context for the phone. ProjectedContext.createHostDeviceContext(activity) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to create host device context", e) null } }
When accessing hardware or resources that are specific to the host device (phone) in a hybrid app (an app containing both mobile and glasses experiences), you must explicitly select the correct context to make sure your app can access the correct hardware:
- Для получения контекста телефона используйте контекст
ActivityизActivityтелефона или методProjectedContext.createHostDeviceContext. - Не используйте
getApplicationContextпоскольку контекст приложения может некорректно возвращать контекст очков, если проецируемая активность была последним запущенным компонентом.