После того как вы запросите и получите необходимые разрешения, ваше приложение сможет получить доступ к оборудованию аудио- или видеоочков. Чтобы получить доступ к аппаратной части очков (а не телефона), нужно использовать проецируемый контекст.
Получить прогнозируемый контекст можно двумя основными способами в зависимости от того, где выполняется ваш код:
Получение прогнозируемого контекста, если код выполняется в прогнозируемом действии
Если код приложения выполняется в рамках прогнозируемой активности, его контекст активности уже является прогнозируемым контекстом. В этом случае вызовы, сделанные в рамках этого действия, уже могут получить доступ к оборудованию очков.
Получение прогнозируемого контекста для кода, выполняемого в компоненте приложения для телефона
Если части приложения, не относящиеся к запланированному действию (например, телефон или сервис), должны получить доступ к оборудованию очков, они должны явно запросить контекст запланированного действия. Для этого используйте метод createProjectedDeviceContext:
@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...") } } } }
Проверка на действительность
Оберните вызов createProjectedDeviceContext в ProjectedContext.isProjectedDeviceConnected. Хотя этот метод возвращает значение true, спроецированный контекст остается действительным для подключенного устройства, и приложение или сервис на вашем телефоне (например, CameraManager) может получить доступ к аппаратной части ИИ-очков.
Удаление данных при отключении
Проецируемый контекст связан с жизненным циклом подключенного устройства, поэтому он уничтожается при отключении устройства. Когда устройство отключается, ProjectedContext.isProjectedDeviceConnected возвращает значение false. Приложение должно отслеживать это изменение и очищать все системные сервисы (например, CameraManager) или ресурсы, созданные приложением с использованием этого прогнозируемого контекста.
Повторная инициализация при повторном подключении
Когда очки подключатся снова, ваше приложение сможет получить ещё один экземпляр проецируемого контекста с помощью createProjectedDeviceContext, а затем повторно инициализировать любые системные сервисы или ресурсы, используя новый проецируемый контекст.
Как записывать аудио с помощью микрофона очков
Записывать аудио с помощью очков можно двумя способами:
- Используйте прогнозируемый контекст.
- Используйте профиль Bluetooth Hands-Free Profile (HFP).
Как выбрать способ записи
Выбор метода зависит от того, нужна ли вам высококачественная обработка звука, предназначенная для XR, или стандартный аудиовход Bluetooth.
| Способ записи | Доступ к микрофону | Распространенный вариант использования |
|---|---|---|
Прогнозируемый контекст |
Несколько микрофонов |
Запись с использованием проецируемого контекста позволяет приложению получать доступ к нескольким микрофонам очков и их специализированным аппаратным функциям, таким как:
|
Bluetooth HFP |
Один микрофон |
Для немедленной совместимости используется профиль Bluetooth Hands-Free Profile (HFP). В этом режиме очки подключаются к телефону с помощью стандартных профилей гарнитуры и расширенного профиля распространения аудио (A2DP) и работают как обычное периферийное устройство Bluetooth. Если ваше приложение уже поддерживает запись через стандартное подключение Bluetooth, вы можете использовать этот метод для записи аудио с очков, не добавляя никаких функций, предназначенных специально для XR. |
Запись аудио с использованием спроецированного контекста
Чтобы записать аудио с помощью проецируемого контекста, сначала запросите необходимые разрешения во время выполнения, а затем запишите аудио с помощью API AudioRecord, как описано в следующих разделах.
Как запросить динамические разрешения
Чтобы получить доступ к нескольким микрофонам на очках, необходимо запросить разрешения на аудио специально для проецируемого устройства. Стандартного разрешения RECORD_AUDIO, предоставленного пользователем вашему приложению на мобильном устройстве, недостаточно.
Чтобы запросить разрешения, выполните следующие действия:
- Объявите разрешение
RECORD_AUDIOв файле манифеста приложения. Запросите разрешения, действующие в рамках проецируемого устройства, одним из следующих способов в зависимости от того, где выполняется ваш код:
- Код, выполняемый из спроецированного действия. Используйте
ActivityResultLauncherсProjectedPermissionsResultContract. Подробную информацию об этом методе можно найти в разделе Регистрация средства запуска разрешений и последующих разделах руководства по запросу разрешений на доступ к оборудованию. - Код, выполняемый из активности хост-телефона. Используйте
Activity#requestPermissions(permissions, requestCode, deviceId)и укажите идентификатор устройства, полученный отprojectedDeviceContext, как описано в разделе Как запрашивать разрешения на доступ к оборудованию.
- Код, выполняемый из спроецированного действия. Используйте
Инициализация 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обрабатывается с помощью функции эхоподавления, которая может быть полезна при одновременном воспроизведении аудио и записи. Если вам нужен необработанный звук, выберитеUNPROCESSEDилиCAMCORDER.Чтобы обеспечить совместимость с очками, в объекте
audioFormatдолжна быть задана частота дискретизации 16 кГц и конфигурация каналов "моно" или "стерео" (с помощьюCHANNEL_IN_MONOилиCHANNEL_IN_STEREO).Используйте
AudioRecord.getMinBufferSize(), чтобы определить минимальный размер буфера для создания объектаAudioRecord. Однако, чтобы избежать пропусков звука, считывайте данные из буфера небольшими фрагментами (идеально – по 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()
Как сделать снимок с помощью камеры очков
Чтобы сделать снимок с помощью камеры очков, настройте и привяжите 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, что может быть полезно для расширенных проверок поддерживаемых разрешений. - Специальный
ResolutionSelectorпозволяет точно контролировать разрешение выходного изображения дляImageCapture. - Создает
ImageCaptureвариант использования, настроенный с помощью специальногоResolutionSelector. - Привязывает вариант использования
ImageCaptureк жизненному циклу объекта Activity. Это позволяет автоматически управлять открытием и закрытием камеры в зависимости от состояния активности (например, останавливать камеру, когда активность приостановлена). - Настройка для оборудования с одним потоком. Конвейер камеры очков ограничен одним активным потоком за раз. Если
ImageCaptureиспользуется отдельно, CameraX по умолчанию подключает внутренний повторяющийся поток (MeteringRepeating) для фокусировки и замера, из-за чего однопоточный конвейер останавливается. Чтобы привязатьImageCaptureбез предварительного просмотра, отключите принудительное повторение потока, задавsetRepeatingStreamForced(false)наCameraXConfig.Builder(например, реализовавCameraXConfig.Providerв классеApplicationили настроивProcessCameraProviderперед инициализацией).
После настройки камеры очков вы можете сделать снимок с помощью класса ImageCapture CameraX. Чтобы узнать, как использовать takePicture для создания снимков, ознакомьтесь с документацией CameraX.
Как снять видео с помощью камеры очков
Чтобы вместо изображения снять видео с помощью камеры очков, замените компоненты ImageCapture на соответствующие компоненты VideoCapture и измените логику выполнения съемки.
Основные изменения связаны с использованием другого варианта использования, созданием другого выходного файла и запуском захвата с помощью подходящего метода записи видео.
Дополнительную информацию о VideoCapture API и о том, как его использовать, можно найти в документации по видеосъемке с помощью 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 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 } }
При доступе к оборудованию или ресурсам, которые относятся к хост-устройству (телефону) в гибридном приложении (содержащем как мобильный, так и Glass-контент), необходимо явно выбрать правильный контекст, чтобы приложение могло получить доступ к нужному оборудованию:
- Используйте контекст
Activityс телефонаActivityилиProjectedContext.createHostDeviceContext, чтобы получить контекст телефона. - Не используйте
getApplicationContext, поскольку контекст приложения может неправильно вернуть контекст очков, если последним запущенным компонентом было проецируемое действие.