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

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

После того, как вы запросили и получили необходимые разрешения , ваше приложение сможет получить доступ к аппаратному обеспечению аудио- или дисплейных очков. Ключ к доступу к аппаратному обеспечению очков (а не телефона) — использование проецируемого контекста .

Существует два основных способа получения контекста проекции, в зависимости от того, где выполняется ваш код:

Получите контекст проекции, если ваш код выполняется в проецируемой активности.

Если код вашего приложения выполняется внутри вашей проецируемой активности , то контекст её собственной активности уже является проецируемым контекстом . В этом сценарии вызовы, совершаемые внутри этой активности, уже могут получить доступ к аппаратному обеспечению очков.

Получите контекст выполнения кода в компоненте мобильного приложения.

Если части вашего приложения, находящейся за пределами проектируемой активности (например, активности телефона или сервиса), необходимо получить доступ к аппаратному обеспечению очков, она должна явно получить проектируемый контекст. Для этого используйте метод 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 ) может получить доступ к аппаратному обеспечению очков с искусственным интеллектом.

Очистка при отключении

Проецируемый контекст привязан к жизненному циклу подключенного устройства, поэтому он уничтожается при отключении устройства. При отключении устройства ProjectedContext.isProjectedDeviceConnected возвращает false . Ваше приложение должно отслеживать это изменение и очищать все системные службы (например, CameraManager ) или ресурсы, созданные вашим приложением с использованием этого проецируемого контекста.

Повторная инициализация при повторном подключении.

Когда очки снова подключатся, ваше приложение сможет получить еще один экземпляр проецируемого контекста с помощью createProjectedDeviceContext , а затем повторно инициализировать любые системные службы или ресурсы, используя новый проецируемый контекст.

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

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

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

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

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

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

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

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

  • Пространственная ориентация, специфичная для XR.
  • Расширенные возможности шумоподавления.
  • Функция разделения голосов, позволяющая различать голоса пользователя и окружающих.
  • Сохраняйте доступ к записи в многоустройственных средах, даже если очки не являются активным устройством Bluetooth.

Bluetooth HFP

Один микрофон

Для обеспечения мгновенной совместимости «из коробки» используются профили Bluetooth Hands-Free Profile (HFP). В этом режиме очки подключаются к телефону, используя стандартные профили Headset и Advanced Audio Distribution Profile (A2DP), функционируя как обычное Bluetooth-устройство.

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

Запись звука с использованием проецируемого контекста.

Для записи звука с использованием проецируемого контекста сначала запросите необходимые разрешения во время выполнения, а затем запишите звук с помощью API AudioRecord , как описано в следующих разделах.

Запросить разрешения во время выполнения

Для доступа к нескольким микрофонам на очках необходимо запросить разрешения на воспроизведение звука специально для проецируемого устройства. Стандартного разрешения RECORD_AUDIO , предоставленного пользователем вашему приложению на своем мобильном устройстве и ограничивающего его использование на телефоне, недостаточно.

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

  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 , чтобы настроить обработку звука в соответствии с вашими конкретными потребностями.

    Например, используйте VOICE_COMMUNICATION , если для вашего сценария требуется автоматическое шумоподавление. VOICE_RECOGNITION обрабатывается с помощью подавления акустического эха (AEC). А если вам нужен необработанный, неизмененный звук, выберите UNPROCESSED или CAMCORDER .

  • Для обеспечения совместимости с очками объект audioFormat должен определять частоту дискретизации 16 кГц и конфигурацию каналов либо моно, либо стерео (используя CHANNEL_IN_MONO или CHANNEL_IN_STEREO ).

  • Хотя строгих требований к размеру буфера нет, выберите минимальный размер буфера , чтобы минимизировать воспринимаемую задержку.

После использования необходимо убрать за собой.

Когда вашему приложению больше не нужен микрофон или когда активность остановлена, вызовите методы stop и release для объекта AudioRecord .

Перед началом записи проверьте права доступа во время выполнения.

Перед вызовом startRecording убедитесь, что пользователь предоставил очкам разрешение на использование микрофона, используя контекст проекции.

Запись звука с использованием Bluetooth HFP

Для записи звука с использованием Bluetooth HFP сначала запросите необходимые разрешения во время выполнения, а затем запишите звук с помощью API AudioManager , как описано в следующих разделах.

Запросить разрешения

Как и в случае с любым стандартным аудиоустройством Bluetooth, права доступа RECORD_AUDIO , BLUETOOTH_CONNECT и другие связанные с ними разрешения контролируются телефоном, а не подключенным устройством (например, аудиоочками или очками с дисплеем).

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

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

  2. Запросите разрешения 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()

Сделайте снимок с помощью камеры очков.

Чтобы сделать снимок с помощью камеры очков, настройте и свяжите сценарий использования ImageCapture объекта CameraX с камерой очков, используя правильный контекст для вашего приложения:

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 , что может быть полезно для расширенной проверки поддерживаемых разрешений.
  • Для точного управления разрешением выходного изображения в ImageCapture создан специальный ResolutionSelector .
  • Создает сценарий использования ImageCapture , настроенный с использованием пользовательского ResolutionSelector .
  • Привязывает сценарий использования ImageCapture к жизненному циклу активности. Это автоматически управляет открытием и закрытием камеры в зависимости от состояния активности (например, останавливает камеру, когда активность приостановлена).

После настройки камеры очков вы можете сделать снимок с помощью класса ImageCapture из библиотеки CameraX. Чтобы узнать, как использовать takePicture для захвата изображения , обратитесь к документации CameraX.

Снимите видео с помощью камеры очков.

Чтобы с помощью камеры очков захватывать видео, а не изображение, замените компоненты ImageCapture соответствующими компонентами VideoCapture и измените логику выполнения захвата.

Основные изменения касаются использования другого сценария применения, создания другого выходного файла и запуска захвата с помощью соответствующего метода видеозаписи. Для получения дополнительной информации об API VideoCapture и способах его использования см. документацию по захвату видео CameraX .

В таблице ниже приведены рекомендуемые разрешение и частота кадров в зависимости от сценария использования вашего приложения:

Вариант использования Разрешение Частота кадров
Видеосвязь 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 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 поскольку контекст приложения может некорректно возвращать контекст очков, если проецируемая активность была последним запущенным компонентом.