После того как вы запросите и получите необходимые разрешения, ваше приложение сможет получить доступ к аппаратным компонентам аудио- или видеоочков. Чтобы получить доступ к аппаратным компонентам очков, а не телефона, необходимо использовать проецируемый контекст.
Получить контекст проекции можно двумя основными способами в зависимости от того, где выполняется ваш код:
Получение контекста, если код выполняется в спроецированном действии
Если код приложения выполняется в рамках проецируемого действия, то контекст действия уже является проецируемым. В этом случае вызовы, сделанные в рамках этого действия, уже могут получить доступ к оборудованию очков.
Получение прогнозируемого контекста для кода, выполняемого в компоненте приложения "Телефон"
Если части приложения, не связанной с проецируемым действием (например, действию или сервису телефона), требуется доступ к аппаратным компонентам очков, она должна явным образом получить проецируемый контекст. Для этого используйте метод 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 (HFP). В этом режиме очки подключаются к телефону с помощью стандартных профилей Headset и Advanced Audio Distribution Profile (A2DP) и работают как обычное периферийное устройство Bluetooth. Если ваше приложение уже поддерживает стандартную запись по Bluetooth, вы можете использовать этот метод для записи аудио с очков без дополнительной разработки.
Записывать аудио с помощью Bluetooth HFP
Чтобы записать аудио с помощью Bluetooth HFP, сначала запросите необходимые динамические разрешения, а затем запишите аудио с помощью AudioManager API, как описано в следующих разделах.
Запросить разрешения
Как и в случае с любым стандартным аудиоустройством 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к жизненному циклу действия. Это позволяет автоматически управлять открытием и закрытием камеры в зависимости от состояния действия (например, останавливать камеру, когда действие приостановлено). - Настройка для оборудования с одним потоком. Конвейер камеры очков ограничен одним активным потоком за раз. Если привязать только
ImageCapture, CameraX по умолчанию подключит внутренний повторяющийся поток (MeteringRepeating) для фокусировки и замера, что приведет к остановке конвейера с одним потоком. Чтобы привязатьImageCaptureбез потока предварительного просмотра, отключите принудительный повторяющийся поток, установив дляsetRepeatingStreamForced(false)значениеCameraXConfig.Builder(например, реализовавCameraXConfig.Providerв классеApplicationили настроивProcessCameraProviderперед инициализацией).
После настройки камеры очков вы можете сделать снимок с помощью класса 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 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 } }
При обращении к оборудованию или ресурсам, которые относятся к хост-устройству (телефону), в гибридном приложении (приложении, которое работает и на мобильном устройстве, и на очках) необходимо явно выбрать правильный контекст, чтобы приложение могло получить доступ к нужному оборудованию.
- Используйте контекст
Activityиз телефонаActivityилиProjectedContext.createHostDeviceContext, чтобы получить контекст телефона. - Не используйте
getApplicationContext, поскольку контекст приложения может неправильно возвращать контекст очков, если проецируемое действие было последним запущенным компонентом.