Используйте контекст проекции для доступа к аппаратному обеспечению на аудиоочках и очках с дисплеем.

Применимые устройства XR
Данное руководство поможет вам создавать приложения для устройств XR такого типа.
Аудио и
Очки для демонстрации

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.

Записывайте звук с помощью микрофона очков.

Запись звука с очков возможна двумя различными способами:

Выберите способ записи

Выбор метода зависит от того, требуется ли вам высококачественная обработка звука, специально разработанная для XR, или стандартный аудиовход Bluetooth.

Метод записи доступ к микрофону Типичный сценарий использования

Прогнозируемый контекст

Несколько микрофонов

Запись с использованием проецируемого контекста позволяет вашему приложению получать доступ к нескольким микрофонам очков и их специализированным аппаратным функциям, таким как:

  • Пространственная ориентация, специфичная для 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.

Для запроса разрешений выполните следующие действия:

  1. Укажите разрешение RECORD_AUDIO в файле манифеста вашего приложения.
  2. Запросить разрешения, ограниченные областью действия проектируемого устройства, можно одним из следующих способов, в зависимости от места выполнения вашего кода:

Инициализируйте 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_COMMUNICATION if your use case needs automatic noise reduction or to isolate the wearer's voice. VOICE_RECOGNITION is 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, select UNPROCESSED or CAMCORDER .

  • To ensure compatibility with the glasses, the audioFormat object must define a sample rate of 16kHz and a channel configuration of either mono or stereo (using CHANNEL_IN_MONO or CHANNEL_IN_STEREO ).

  • Use AudioRecord.getMinBufferSize() to determine the minimum buffer size to create the AudioRecord object. 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).

Для запроса разрешений выполните следующие действия:

  1. В файле манифеста вашего приложения укажите следующие разрешения :

  2. Запросите разрешения 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 ImageCapture use 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 ImageCapture is bound alone, CameraX attaches an internal repeating stream ( MeteringRepeating ) for focus and metering by default, which causes the single-stream pipeline to stall. To bind ImageCapture by itself without a preview stream, disable the forced repeating stream by setting setRepeatingStreamForced(false) on CameraXConfig.Builder (for example, by implementing CameraXConfig.Provider in your Application class or configuring ProcessCameraProvider before 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 поскольку контекст приложения может некорректно возвращать контекст очков, если проецируемая активность была последним запущенным компонентом.