Une fois que vous avez demandé et obtenu les autorisations nécessaires, votre application peut accéder au matériel des lunettes audio ou des lunettes avec affichage. Pour accéder au matériel des lunettes (au lieu de celui du téléphone), vous devez utiliser un contexte projeté.
Il existe deux méthodes principales pour obtenir un contexte projeté, selon l'endroit où votre code est exécuté :
Obtenir un contexte projeté si votre code s'exécute dans une activité projetée
Si le code de votre application s'exécute à partir de votre activité projetée, son propre contexte d'activité est déjà un contexte projeté. Dans ce scénario, les appels effectués dans cette activité peuvent déjà accéder au matériel des lunettes.
Obtenir un contexte projeté pour le code exécuté dans un composant d'application pour téléphone
Si une partie de votre application en dehors de votre activité projetée (comme une activité téléphonique ou un service) doit accéder au matériel des lunettes, elle doit explicitement obtenir un contexte projeté. Pour ce faire, utilisez la méthode 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...") } } } }
Vérifier la validité
Encapsulez l'appel createProjectedDeviceContext dans ProjectedContext.isProjectedDeviceConnected. Bien que cette méthode renvoie true, le contexte projeté reste valide pour l'appareil connecté, et l'activité dans l'application ou le service de votre application Téléphone (comme un CameraManager) peut accéder au matériel des lunettes IA.
Nettoyer à la déconnexion
Le contexte projeté est lié au cycle de vie de l'appareil connecté. Il est donc détruit lorsque l'appareil se déconnecte. Lorsque l'appareil se déconnecte, ProjectedContext.isProjectedDeviceConnected renvoie false. Votre application doit écouter cette modification et nettoyer tous les services système (comme un CameraManager) ou les ressources qu'elle a créés à l'aide de ce contexte projeté.
Réinitialiser lors de la reconnexion
Lorsque les lunettes se reconnectent, votre application peut obtenir une autre instance de contexte projeté à l'aide de createProjectedDeviceContext, puis réinitialiser les services ou ressources système à l'aide du nouveau contexte projeté.
Enregistrer du contenu audio avec le micro des lunettes
Vous pouvez enregistrer du contenu audio à partir des lunettes à l'aide du profil mains libres (HFP) Bluetooth. Dans ce mode, les lunettes se connectent au téléphone à l'aide des profils standard Headset et Advanced Audio Distribution Profile (A2DP), et fonctionnent comme un périphérique Bluetooth classique. Si votre application est déjà conçue pour l'enregistrement Bluetooth standard, vous pouvez utiliser cette méthode pour enregistrer l'audio des lunettes sans développement supplémentaire.
Enregistrer du contenu audio à l'aide du profil mains libres Bluetooth
Pour enregistrer du contenu audio à l'aide du profil Bluetooth HFP, demandez d'abord les autorisations d'exécution requises, puis enregistrez le contenu audio à l'aide de l'API AudioManager, comme décrit dans les sections suivantes.
Demander des autorisations
Comme pour tout appareil audio Bluetooth standard, les autorisations RECORD_AUDIO, BLUETOOTH_CONNECT et autres autorisations associées sont contrôlées par le téléphone, et non par l'appareil connecté (comme des lunettes audio ou des lunettes avec affichage).
Pour demander les autorisations, procédez comme suit :
Déclarez les autorisations suivantes dans le fichier manifeste de votre application :
Demandez les autorisations
RECORD_AUDIOetBLUETOOTH_CONNECTau moment de l'exécution à l'aide du processus d'autorisation Android standard.
Utiliser AudioManager pour acheminer l'audio
Une fois que l'utilisateur a accordé à votre application les autorisations d'exécution nécessaires, utilisez l'API AudioManager pour définir l'appareil de communication sur TYPE_BLUETOOTH_SCO afin de router l'audio via le profil mains libres Bluetooth. Cela indique au système de récupérer l'audio à partir du périphérique 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()
Prendre une photo avec la caméra des lunettes
Pour capturer une image avec la caméra des lunettes, configurez et associez le cas d'utilisation ImageCapture de CameraX à la caméra des lunettes en utilisant le contexte approprié pour votre application :
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)) } } } }
Points clés concernant le code
- Obtient une instance de
ProcessCameraProviderà l'aide du contexte de l'appareil projeté. - Dans le champ d'application du contexte projeté, la caméra principale des lunettes, orientée vers l'extérieur, correspond à
DEFAULT_BACK_CAMERAlors de la sélection d'une caméra. - Une vérification de pré-liaison utilise
cameraProvider.hasCamera(cameraSelector)pour vérifier que la caméra sélectionnée est disponible sur l'appareil avant de continuer. - Utilise l'interopérabilité Camera2 avec
Camera2CameraInfopour lire l'élémentCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAPsous-jacent, ce qui peut être utile pour les vérifications avancées des résolutions compatibles. - Un
ResolutionSelectorpersonnalisé est conçu pour contrôler précisément la résolution de l'image de sortie pourImageCapture. - Crée un cas d'utilisation
ImageCaptureconfiguré avec unResolutionSelectorpersonnalisé. - Lie le cas d'utilisation
ImageCaptureau cycle de vie de l'activité. Cela gère automatiquement l'ouverture et la fermeture de l'appareil photo en fonction de l'état de l'activité (par exemple, l'arrêt de l'appareil photo lorsque l'activité est suspendue). - Configuration pour le matériel à flux unique : le pipeline de la caméra des lunettes est limité à un seul flux actif à la fois. Lorsque
ImageCaptureest lié seul, CameraX associe par défaut un flux de répétition interne (MeteringRepeating) pour la mise au point et la mesure, ce qui entraîne le blocage du pipeline à flux unique. Pour lierImageCaptureseul sans flux d'aperçu, désactivez le flux de répétition forcé en définissantsetRepeatingStreamForced(false)surCameraXConfig.Builder(par exemple, en implémentantCameraXConfig.Providerdans votre classeApplicationou en configurantProcessCameraProvideravant l'initialisation).
Une fois la caméra des lunettes configurée, vous pouvez capturer une image avec la classe ImageCapture de CameraX. Consultez la documentation de CameraX pour savoir comment utiliser takePicture pour capturer une image.
Enregistrer une vidéo avec la caméra des lunettes
Pour capturer une vidéo au lieu d'une image avec la caméra des lunettes, remplacez les composants ImageCapture par les composants VideoCapture correspondants et modifiez la logique d'exécution de la capture.
Les principaux changements consistent à utiliser un cas d'utilisation différent, à créer un fichier de sortie différent et à lancer la capture à l'aide de la méthode d'enregistrement vidéo appropriée.
Pour en savoir plus sur l'API VideoCapture et son utilisation, consultez la documentation sur la capture vidéo de CameraX.
Le tableau suivant indique la résolution et la fréquence d'images recommandées en fonction du cas d'utilisation de votre application :
| Cas d'utilisation | Solution | Fréquence d'images |
|---|---|---|
| Communication vidéo | 1 280 x 720 | 15 FPS |
| Vision par ordinateur | 640 x 480 | 10 FPS |
| Streaming vidéo IA | 640 x 480 | 1 FPS |
Accéder au matériel d'un téléphone depuis une activité projetée
Une activité projetée peut également accéder au matériel du téléphone (comme l'appareil photo ou le micro) en utilisant createHostDeviceContext(context) pour obtenir le contexte de l'appareil hôte (téléphone) :
@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 } }
Lorsque vous accédez à du matériel ou à des ressources spécifiques à l'appareil hôte (téléphone) dans une application hybride (une application contenant à la fois des expériences mobiles et des expériences sur lunettes), vous devez sélectionner explicitement le contexte approprié pour vous assurer que votre application peut accéder au matériel approprié :
- Utilisez le contexte
Activitydu téléphoneActivityouProjectedContext.createHostDeviceContextpour obtenir le contexte du téléphone. - N'utilisez pas
getApplicationContext, car le contexte de l'application peut renvoyer de manière incorrecte le contexte des lunettes si une activité projetée était le composant lancé le plus récemment.