Über einen projizierten Kontext auf die Hardware von Audio- und Displaybrillen zugreifen

XR‑Geräte, für die der Leitfaden gilt
Dieser Leitfaden hilft Ihnen dabei, Erlebnisse für die folgenden Arten von XR-Geräten zu entwickeln.
Audio- und
Displaybrillen

Nachdem Sie die erforderlichen Berechtigungen angefordert und erhalten haben, kann Ihre App auf die Hardware der Audio- oder Displaybrille 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 zu erhalten, 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 in einer projizierten Aktivität ausgeführt wird, ist der eigene Aktivitätskontext bereits ein projizierter Kontext. In diesem Fall können Aufrufe innerhalb dieser Aktivität bereits auf die Hardware der Brille zugreifen.

Projizierten Kontext für Code abrufen, der in einer Smartphone-App-Komponente ausgeführt wird

Wenn ein Teil Ihrer App außerhalb der 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 createProjectedDeviceContext Methode:

@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

Umschließen Sie den createProjectedDeviceContext Aufruf mit dem ProjectedContext.isProjectedDeviceConnected. Solange diese Methode true zurückgibt, bleibt der projizierte Kontext für das verbundene Gerät gültig und Ihre Smartphone-App-Aktivität oder Ihr Dienst (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, ProjectedContext.isProjectedDeviceConnected gibt false zurück. Ihre App sollte auf diese Änderung reagieren und alle Systemdienste (z. B. einen CameraManager) oder Ressourcen bereinigen, die mit diesem projizierten Kontext erstellt wurden.

Bei erneuter Verbindung neu initialisieren

Wenn die Verbindung zur Brille wiederhergestellt wird, 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 mit der Brille auf zwei verschiedene Arten aufnehmen:

Aufnahmemethode auswählen

Die gewählte Methode hängt davon ab, ob Sie eine Hi-Fi-Audioverarbeitung, eine XR‑spezifische Audioverarbeitung oder eine Standard-Bluetooth-Audioeingabe benötigen.

Aufzeichnungsmethode Mikrofonzugriff Häufiger Anwendungsfall

Projizierter Kontext

Mehrere Mikrofone

Bei der Aufzeichnung mit einem projizierten Kontext kann Ihre App auf mehrere Mikrofone der Brille und ihre speziellen Hardwarefunktionen zugreifen, z. B.:

  • XR‑spezifische Räumlichkeit
  • Erweiterte Rauschunterdrückung
  • Sprachtrennung, die die Stimme des Trägers isoliert
  • Aufnahmezugriff in Umgebungen mit mehreren Geräten beibehalten, auch wenn die Brille nicht das aktive Bluetooth-Gerät ist

Bluetooth HFP

Einzelnes Mikrofon

Verwendet das Bluetooth Hands-Free Profile (HFP) für sofortige, sofort einsatzbereite Kompatibilität. In diesem Modus stellt die Brille über die Standardprofile für Headsets und Advanced Audio Distribution Profile (A2DP) Profile eine Verbindung zum Smartphone her und fungiert wie ein typisches Bluetooth-Peripheriegerät.

Wenn Ihre App bereits für die Standard-Bluetooth-Aufzeichnung entwickelt wurde, können Sie diese Methode verwenden, um Audio von der Brille aufzunehmen, ohne XR‑spezifische Funktionen zu integrieren.

Audio mit einem projizierten Kontext aufnehmen

Wenn Sie Audio mit einem projizierten Kontext aufnehmen möchten, fordern Sie zuerst die erforderlichen Laufzeit berechtigungen an und nehmen Sie dann das Audio mit der AudioRecord API auf, wie in den folgenden Abschnitten beschrieben.

Laufzeitberechtigungen anfordern

Wenn Sie auf mehrere Mikrofone der Brille zugreifen möchten, müssen Sie Audioberechtigungen speziell für das projizierte Gerät anfordern. Die standardmäßige, auf das Smartphone beschränkte Berechtigung RECORD_AUDIO, die ein Nutzer Ihrer App auf seinem Mobilgerät erteilt hat, reicht nicht aus.

So fordern Sie die Berechtigungen an:

  1. Deklarieren Sie die Berechtigung RECORD_AUDIO in der Manifestdatei Ihrer App.
  2. Fordern Sie die auf das projizierte Gerät beschränkten Berechtigungen auf eine der folgenden Arten an, je nachdem, wo Ihr Code ausgeführt wird:

AudioRecord mit einem projizierten Kontext initialisieren

Damit Audio von der Brille und nicht vom Host-Smartphone aufgenommen wird, müssen Sie das AudioRecord-Objekt dem projizierten Gerätekontext zuordnen.

Der folgende Code verwendet AudioRecord.Builder und übergibt projectedDeviceContext an die Methode 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()

Wichtige Punkte zum Code
  • Sie können die Audioquelle auf CAMCORDER, VOICE_RECOGNITION, VOICE_COMMUNICATION oder UNPROCESSED setzen, um die Audioverarbeitung an Ihren spezifischen Anwendungsfall anzupassen.

    Verwenden Sie beispielsweise VOICE_COMMUNICATION, wenn Ihr Anwendungsfall eine automatische Rauschunterdrückung erfordert oder die Stimme des Trägers isoliert werden soll. VOICE_RECOGNITION wird mit der akustischen Echounterdrückung (AEC) verarbeitet, was nützlich sein kann, wenn während der Aufnahme gleichzeitig Audio wiedergegeben wird. Wenn Sie rohes, unverändertes Audio benötigen, wählen Sie UNPROCESSED oder CAMCORDER aus.

  • Um die Kompatibilität mit der Brille zu gewährleisten, muss das audioFormat Objekt eine Abtastrate von 16 kHz und eine Kanalkonfiguration von Mono oder Stereo definieren (mit CHANNEL_IN_MONO oder CHANNEL_IN_STEREO).

  • Verwenden Sie AudioRecord.getMinBufferSize(), um die Mindestpuffer größe für die Erstellung des AudioRecord Objekts zu bestimmen. Um Audioausfälle von der Brille zu vermeiden, sollten Sie diesen Puffer jedoch in kurzen, häufigen Blöcken (idealerweise 20‑ms-Abschnitten) lesen, anstatt zu warten, bis der gesamte Puffer gefüllt ist.

Nach der Verwendung bereinigen

Wenn Ihre App das Mikrofon nicht mehr benötigt oder die Aktivität beendet wird, rufen Sie stop und release für das AudioRecord Objekt auf.

Vor der Aufnahme Laufzeitberechtigungen prüfen

Bevor Sie startRecording aufrufen, prüfen Sie mit dem projizierten Kontext, ob der Nutzer die Mikrofonberechtigung für die Brille erteilt hat.

Audio mit Bluetooth HFP aufnehmen

Wenn Sie Audio mit Bluetooth HFP aufnehmen möchten, fordern Sie zuerst die erforderlichen Laufzeit berechtigungen 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 RECORD_AUDIO, BLUETOOTH_CONNECT und andere zugehörige Berechtigungen vom Smartphone und nicht vom verbundenen Gerät (z. B. Audio- oder Displaybrille) gesteuert.

So fordern Sie die Berechtigungen an:

  1. Deklarieren Sie die folgenden Berechtigungen in der Manifestdatei Ihrer App:

  2. Fordern Sie sowohl die Berechtigung RECORD_AUDIO als auch BLUETOOTH_CONNECT zur Laufzeit mit dem Standardablauf für Android-Berechtigungen an.

Audio mit AudioManager weiterleiten

Nachdem der Nutzer Ihrer App die erforderlichen Laufzeitberechtigungen erteilt hat, legen Sie mit der AudioManager API das Kommunikationsgerät auf TYPE_BLUETOOTH_SCO fest, um das Audio über Bluetooth HFP weiterzuleiten. Dadurch wird das System angewiesen, Audio vom Bluetooth-Peripheriegerät 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 Brille aufnehmen

Wenn Sie ein Bild mit der Kamera der Brille aufnehmen möchten, richten Sie den CameraX‑ ImageCapture Anwendungsfall ein und binden Sie ihn an die Kamera der Brille. Verwenden Sie dazu den richtigen Kontext für Ihre App:

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 ProcessCameraProvider mit dem projizierten Gerätekontext ab.
  • Im Bereich des projizierten Kontexts wird die primäre, nach außen gerichtete Kamera der Brille beim Auswählen einer Kamera DEFAULT_BACK_CAMERA zugeordnet.
  • 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 liegende CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP zu lesen. Dies kann für erweiterte Prüfungen der unterstützten Auflösungen nützlich sein.
  • Ein benutzerdefiniertes ResolutionSelector wird erstellt, um die Ausgabe bildauflösung für ImageCapture genau zu steuern.
  • Erstellt einen ImageCapture-Anwendungsfall, der mit einem benutzerdefinierten ResolutionSelector konfiguriert ist.
  • Bindet den ImageCapture-Anwendungsfall an den Lebenszyklus der Aktivität. Dadurch wird das Öffnen und Schließen der Kamera automatisch anhand des Aktivitätsstatus verwaltet (z. B. wird die Kamera angehalten, wenn die Aktivität pausiert wird).
  • Für Hardware mit einem einzelnen Stream konfigurieren:Die Kamerapipeline der Brille ist auf einen einzelnen aktiven Stream gleichzeitig beschränkt. Wenn ImageCapture allein gebunden ist, hängt CameraX standardmäßig einen internen wiederholten Stream (MeteringRepeating) für Fokus und Messung an, wodurch die Pipeline mit einem einzelnen Stream ins Stocken gerät. Wenn Sie ImageCapture allein ohne Vorschaustream binden möchten, deaktivieren Sie den erzwungenen wiederholten Stream, indem Sie setRepeatingStreamForced(false) für CameraXConfig.Builder festlegen (z. B. durch Implementieren CameraXConfig.Provider in Ihrer Application Klasse oder Konfigurieren ProcessCameraProvider vor der Initialisierung).

Nachdem die Kamera der Brille eingerichtet wurde, können Sie mit der ImageCapture-Klasse von CameraX ein Bild aufnehmen. In der CameraX's Dokumentation erfahren Sie, wie Sie mit takePicture ein Bild aufnehmen.

Video mit der Kamera der Brille aufnehmen

Wenn Sie mit der Kamera der Brille ein Video anstelle 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 sind die Verwendung eines anderen Anwendungsfalls, das Erstellen einer anderen Ausgabedatei und das Starten der Aufnahme mit der entsprechenden Methode für die Videoaufnahme. Weitere Informationen zur VideoCapture API und ihrer Verwendung finden Sie in der Dokumentation zur Videoaufnahme von CameraX.

In der folgenden Tabelle sind die empfohlene Auflösung und Framerate je nach Anwendungsfall Ihrer App aufgeführt:

Anwendungsfall Auflösung Frame rate
Videokommunikation 1280 x 720 15 FPS
Computer Vision 640 x 480 10 FPS
KI-Videostreaming 640 x 480 1 FPS

Von einer projizierten 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, indem sie mit createHostDeviceContext(context) den Kontext des Hostgeräts (Smartphone) abruft:

@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 Sie in einer Hybrid-App (einer App, die sowohl mobile als auch Brillenfunktionen enthält) auf Hardware oder Ressourcen zugreifen, die spezifisch für das Hostgerät (Smartphone) sind, müssen Sie explizit den richtigen Kontext auswählen, damit Ihre App auf die richtige Hardware zugreifen kann:

  • Verwenden Sie den Activity-Kontext aus dem Smartphone-Activity oder dem ProjectedContext.createHostDeviceContext, um den Kontext des Smartphones abzurufen.
  • Verwenden Sie nicht getApplicationContext, da der Anwendungskontext fälschlicherweise den Kontext der Brille zurückgeben kann, wenn eine projizierte Aktivität die zuletzt gestartete Komponente war.