Nachdem du die erforderlichen Berechtigungen angefordert und erhalten hast, kann deine App auf die Hardware der Audio- oder Display-Brille zugreifen. Der Schlüssel für den Zugriff auf die Hardware der Brille (anstelle der Hardware des Smartphones) ist die Verwendung eines projizierten Kontexts.
Es gibt zwei Möglichkeiten, einen projizierten Kontext abzurufen, je nachdem, wo Ihr Code ausgeführt wird:
Projizierten Kontext abrufen, wenn Ihr Code in einer projizierten Aktivität ausgeführt wird
Wenn der Code Ihrer App innerhalb Ihrer projizierten Aktivität ausgeführt wird, ist der eigene Aktivitätskontext bereits ein projizierter Kontext. In diesem Szenario können Aufrufe, die innerhalb dieser Aktivität erfolgen, bereits auf die Hardware der Brille zugreifen.
Projizierten Kontext für Code abrufen, der in einer Telefon-App-Komponente ausgeführt wird
Wenn ein Teil Ihrer App außerhalb Ihrer projizierten Aktivität (z. B. eine Smartphone-Aktivität oder ein Dienst) auf die Hardware der Brille zugreifen muss, muss er explizit einen projizierten Kontext abrufen. Verwenden Sie dazu die Methode 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...") } } } }
Gültigkeit prüfen
Schließen Sie den Aufruf createProjectedDeviceContext in ProjectedContext.isProjectedDeviceConnected ein. Während diese Methode true zurückgibt, bleibt der projizierte Kontext für das verbundene Gerät gültig und die Aktivität oder der Dienst Ihrer Smartphone-App (z. B. ein CameraManager) kann auf die Hardware der KI-Brille zugreifen.
Beim Trennen der Verbindung bereinigen
Der projizierte Kontext ist an den Lebenszyklus des verbundenen Geräts gebunden und wird daher zerstört, wenn die Verbindung zum Gerät getrennt wird. Wenn die Verbindung zum Gerät getrennt wird, gibt ProjectedContext.isProjectedDeviceConnected den Wert false zurück. Ihre App sollte auf diese Änderung warten und alle Systemdienste (z. B. CameraManager) oder Ressourcen bereinigen, die Ihre App mit diesem projizierten Kontext erstellt hat.
Bei Wiederverbindung neu initialisieren
Wenn die Datenbrille wieder verbunden ist, kann Ihre App mit createProjectedDeviceContext eine weitere projizierte Kontextinstanz abrufen und dann alle Systemdienste oder Ressourcen mit dem neuen projizierten Kontext neu initialisieren.
Audio mit dem Mikrofon der Brille aufnehmen
Sie können Audio von der Brille über das Bluetooth-Hands-Free Profile (HFP) aufnehmen. In diesem Modus wird die Brille über die Standardprofile „Headset“ und „Advanced Audio Distribution Profile“ (A2DP) mit dem Smartphone verbunden und funktioniert wie ein typisches Bluetooth-Peripheriegerät. Wenn deine App bereits für die Standard-Bluetooth-Aufnahme konzipiert ist, kannst du diese Methode verwenden, um Audio von der Brille aufzunehmen, ohne dass zusätzliche Entwicklungsarbeit erforderlich ist.
Audio über Bluetooth-HFP aufnehmen
Wenn Sie Audio mit Bluetooth HFP aufnehmen möchten, fordern Sie zuerst die erforderlichen Laufzeitberechtigungen an und nehmen Sie dann das Audio mit der AudioManager API auf, wie in den folgenden Abschnitten beschrieben.
Berechtigungen anfordern
Wie bei jedem Standard-Bluetooth-Audiogerät werden die Berechtigungen RECORD_AUDIO, BLUETOOTH_CONNECT und andere zugehörige Berechtigungen vom Smartphone und nicht vom verbundenen Gerät (z. B. Audio- oder Display-Brille) gesteuert.
So fordern Sie die Berechtigungen an:
Deklarieren Sie die folgenden Berechtigungen in der Manifestdatei Ihrer App:
Fordern Sie die Berechtigungen
RECORD_AUDIOundBLUETOOTH_CONNECTzur Laufzeit über den Standard-Android-Berechtigungsablauf an.
AudioManager zum Weiterleiten von Audio verwenden
Nachdem der Nutzer Ihrer App die erforderlichen Laufzeitberechtigungen erteilt hat, verwenden Sie die AudioManager API, um das Kommunikationsgerät auf TYPE_BLUETOOTH_SCO festzulegen, damit das Audio über Bluetooth HFP geleitet wird. Dadurch wird das System angewiesen, Audio von der Bluetooth-Peripherie abzurufen.
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()
Bild mit der Kamera der Datenbrille aufnehmen
Wenn du ein Bild mit der Kamera der Datenbrille aufnehmen möchtest, richte den ImageCapture-Anwendungsfall von CameraX ein und binde ihn mit dem richtigen Kontext für deine App an die Kamera der Datenbrille:
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)) } } } }
Wichtige Punkte zum Code
- Ruft eine Instanz von
ProcessCameraProvidermit dem Kontext des Geräts mit der Projektionsfläche ab. - Im Rahmen des projizierten Kontexts wird die primäre, nach außen gerichtete Kamera der Brille bei der Auswahl einer Kamera der
DEFAULT_BACK_CAMERAzugeordnet. - Bei einer Vorabprüfung wird mit
cameraProvider.hasCamera(cameraSelector)geprüft, ob die ausgewählte Kamera auf dem Gerät verfügbar ist, bevor fortgefahren wird. - Verwendet Camera2 Interop mit
Camera2CameraInfo, um die zugrunde liegendeCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAPzu lesen. Das kann für erweiterte Prüfungen der unterstützten Auflösungen nützlich sein. - Ein benutzerdefinierter
ResolutionSelectorwird erstellt, um die Auflösung des Ausgabebilds fürImageCapturegenau zu steuern. - Erstellt einen
ImageCapture-Anwendungsfall, der mit einem benutzerdefiniertenResolutionSelectorkonfiguriert ist. - Bindet den Anwendungsfall
ImageCapturean den Lebenszyklus der Aktivität. Dadurch wird das Öffnen und Schließen der Kamera automatisch basierend auf dem Status der Aktivität verwaltet, z. B. wird die Kamera gestoppt, wenn die Aktivität pausiert wird. - Konfiguration für Single-Stream-Hardware:Die Kamerapipeline der Brille ist auf einen einzelnen aktiven Stream beschränkt. Wenn
ImageCaptureallein gebunden ist, hängt CameraX standardmäßig einen internen sich wiederholenden Stream (MeteringRepeating) für Fokus und Messung an, was dazu führt, dass die Single-Stream-Pipeline ins Stocken gerät. Wenn SieImageCaptureohne Vorschaustream binden möchten, deaktivieren Sie den erzwungenen Wiederholungsstream, indem SiesetRepeatingStreamForced(false)fürCameraXConfig.Builderfestlegen. Implementieren Sie dazu beispielsweiseCameraXConfig.Providerin IhrerApplication-Klasse oder konfigurieren SieProcessCameraProvidervor der Initialisierung.
Nachdem die Kamera der Brille eingerichtet ist, kannst du mit der ImageCapture-Klasse von CameraX ein Bild aufnehmen. Informationen zur Verwendung von takePicture zum Aufnehmen eines Bildes finden Sie in der CameraX-Dokumentation.
Video mit der Kamera der Datenbrille aufnehmen
Wenn Sie mit der Kamera der Brille ein Video statt eines Bildes aufnehmen möchten, ersetzen Sie die ImageCapture-Komponenten durch die entsprechenden VideoCapture-Komponenten und ändern Sie die Logik für die Aufnahmeausführung.
Die wichtigsten Änderungen betreffen die Verwendung eines anderen Anwendungsfalls, die Erstellung einer anderen Ausgabedatei und die Initiierung der Aufnahme mit der entsprechenden Videoaufzeichnungsmethode.
Weitere Informationen zur VideoCapture API und ihrer Verwendung finden Sie in der Dokumentation zur Videoaufnahme mit CameraX.
In der folgenden Tabelle finden Sie die empfohlene Auflösung und Framerate je nach Anwendungsfall Ihrer App:
| Anwendungsfall | Auflösung | Framerate |
|---|---|---|
| Videokommunikation | 1280 × 720 | 15 fps |
| Computer Vision | 640 x 480 | 10 fps |
| KI-Videostreaming | 640 x 480 | 1 fps |
Über eine projizierte Aktivität auf die Hardware eines Smartphones zugreifen
Eine projizierte Aktivität kann auch auf die Hardware des Smartphones (z. B. Kamera oder Mikrofon) zugreifen. Dazu wird createHostDeviceContext(context) verwendet, um den Kontext des Hostgeräts (Smartphone) abzurufen:
@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 } }
Wenn du in einer Hybrid-App (einer App, die sowohl für Smartphones als auch für Datenbrillen geeignet ist) auf Hardware oder Ressourcen zugreifst, die spezifisch für das Hostgerät (Smartphone) sind, musst du explizit den richtigen Kontext auswählen, damit deine App auf die richtige Hardware zugreifen kann:
- Verwenden Sie den Kontext
Activityvom SmartphoneActivityoderProjectedContext.createHostDeviceContext, um den Kontext des Smartphones abzurufen. - Verwende nicht
getApplicationContext, da der Anwendungskontext fälschlicherweise den Kontext der Brille zurückgeben kann, wenn eine projizierte Aktivität die zuletzt gestartete Komponente war.