Używaj prognozowanego kontekstu, aby uzyskać dostęp do sprzętu na okularach audio i okularach z wyświetlaczem

Odpowiednie urządzenia XR
Te wskazówki pomogą Ci tworzyć rozwiązania na te typy urządzeń XR.
Okulary audio i
wyświetlacz

Gdy poprosisz o niezbędne uprawnienia i je uzyskasz, Twoja aplikacja będzie mieć dostęp do sprzętu okularów audio lub okularów z wyświetlaczem. Kluczem do uzyskania dostępu do hardware okularów (zamiast hardware telefonu) jest użycie prognozowanego kontekstu.

Istnieją 2 podstawowe sposoby uzyskiwania prognozowanego kontekstu, w zależności od tego, gdzie jest wykonywany kod:

Jak uzyskać prognozowany kontekst, jeśli kod jest uruchamiany w prognozowanej aktywności

Jeśli kod aplikacji jest uruchamiany w ramach prognozowanej aktywności, własny kontekst aktywności jest już prognozowanym kontekstem. W takim przypadku połączenia wykonywane w ramach tej aktywności mogą już korzystać z hardware okularów.

Jak uzyskać prognozowany kontekst w przypadku kodu uruchamianego w komponencie aplikacji na telefon

Jeśli część aplikacji poza prognozowaną aktywnością (np. aktywność na telefonie lub usługa) musi mieć dostęp do hardware okularów, musi wyraźnie uzyskać prognozowany kontekst. Aby to zrobić, użyj metody createProjectedDeviceContext:

@OptIn(ExperimentalProjectedApi::class)
private fun getGlassesContext(context: Context): Context? {
    return try {
        // From a phone Activity or Service, get a context for the AI glasses.
        ProjectedContext.createProjectedDeviceContext(context)
    } catch (e: IllegalStateException) {
        Log.e(TAG, "Failed to create projected device context", e)
        null
    }
}

Sprawdź ważność

Umieść wywołanie createProjectedDeviceContextProjectedContext.isProjectedDeviceConnected. Chociaż ta metoda zwraca wartość true, przewidywany kontekst pozostaje ważny dla połączonego urządzenia, a aktywność aplikacji na telefonie lub usługi (np. CameraManager) może uzyskać dostęp do sprzętu okularów z AI.

Zwalnianie miejsca po odłączeniu

Przewidywany kontekst jest powiązany z cyklem życia połączonego urządzenia, więc jest usuwany po rozłączeniu urządzenia. Gdy urządzenie zostanie odłączone, ProjectedContext.isProjectedDeviceConnected zwróci wartość false. Aplikacja powinna nasłuchiwać tej zmiany i zwalniać miejsce zajmowane przez wszystkie usługi systemowe (np. CameraManager) lub zasoby, które utworzyła przy użyciu tego kontekstu.

Ponowna inicjalizacja po ponownym połączeniu

Gdy okulary ponownie się połączą, aplikacja może uzyskać kolejną instancję kontekstu projecji za pomocą metody createProjectedDeviceContext, a następnie ponownie zainicjować dowolne usługi systemowe lub zasoby za pomocą nowego kontekstu projecji.

Nagrywanie dźwięku za pomocą mikrofonu okularów

Dźwięk z okularów możesz nagrywać na 2 sposoby:

Wybieranie metod nagrywania

Wybrana metoda zależy od tego, czy potrzebujesz przetwarzania dźwięku o wysokiej wierności, specyficznego dla XR, czy standardowego wejścia audio Bluetooth.

Metoda nagrywania Dostęp do mikrofonu Typowy przypadek użycia

Kontekst prognozowany

Wiele mikrofonów

Nagrywanie z użyciem prognozowanego kontekstu umożliwia aplikacji dostęp do wielu mikrofonów okularów i ich specjalistycznych funkcji sprzętowych, takich jak:

  • przestrzenne dostosowanie dźwięku do XR,
  • Zaawansowane odszumianie.
  • Rozdzielanie głosu, które odróżnia głos użytkownika od głosu osoby postronnej.
  • Utrzymanie dostępu do nagrywania w środowiskach z wieloma urządzeniami, nawet jeśli okulary nie są aktywnym urządzeniem Bluetooth.

Bluetooth HFP

Jeden mikrofon

Korzysta z profilu Bluetooth Hands-Free Profile (HFP), co zapewnia natychmiastową zgodność po wyjęciu z pudełka. W tym trybie okulary łączą się z telefonem za pomocą standardowych profili zestawu słuchawkowego i A2DP (Advanced Audio Distribution Profile), działając jak typowe urządzenie peryferyjne Bluetooth.

Jeśli Twoja aplikacja jest już przeznaczona do nagrywania za pomocą standardowego Bluetootha, możesz użyć tej metody do nagrywania dźwięku z okularów bez integrowania żadnych funkcji specyficznych dla XR.

Nagrywanie dźwięku z użyciem kontekstu prognozowanego

Aby nagrywać dźwięk przy użyciu kontekstu projekcji, najpierw poproś o wymagane uprawnienia w czasie działania, a potem nagraj dźwięk za pomocą interfejsu AudioRecord API, jak opisano w kolejnych sekcjach.

Wysyłanie prośby o uprawnienia w czasie działania

Aby uzyskać dostęp do wielu mikrofonów w okularach, musisz poprosić o uprawnienia do dźwięku w przypadku projektowanego urządzenia. Standardowe uprawnienia w zakresie telefonuRECORD_AUDIO, które użytkownik przyznał Twojej aplikacji na urządzeniu mobilnym, są niewystarczające.

Aby poprosić o uprawnienia, wykonaj te czynności:

  1. Zadeklaruj uprawnienie RECORD_AUDIO w pliku manifestu aplikacji.
  2. Poproś o uprawnienia w zakresie urządzenia docelowego w jeden z tych sposobów, w zależności od tego, gdzie jest wykonywany kod:

Inicjowanie AudioRecord za pomocą kontekstu prognozowanego

Aby dźwięk był nagrywany z okularów, a nie z telefonu hosta, musisz powiązać obiekt AudioRecord z kontekstem projektowanego urządzenia.

Poniższy kod używa metody AudioRecord.Builder i przekazuje wartość projectedDeviceContext do metody 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()

Najważniejsze informacje o kodzie
  • Możesz ustawić źródło dźwięku na CAMCORDER, VOICE_RECOGNITION, VOICE_COMMUNICATION lub UNPROCESSED, aby dostosować przetwarzanie dźwięku do konkretnego zastosowania.

    Jeśli na przykład w Twoim przypadku użycia wymagana jest automatyczna redukcja szumów, użyj VOICE_COMMUNICATION. VOICE_RECOGNITION jest przetwarzany z użyciem akustycznego usuwania echa (AEC). Jeśli potrzebujesz surowego, nieprzetworzonego dźwięku, wybierz UNPROCESSED lub CAMCORDER.

  • Aby zapewnić zgodność z okularami, obiekt audioFormat musi mieć zdefiniowaną częstotliwość próbkowania 16 kHz i konfigurację kanałów mono lub stereo (za pomocą CHANNEL_IN_MONO lub CHANNEL_IN_STEREO).

  • Nie ma stałego wymogu dotyczącego rozmiaru bufora, ale uzyskaj minimalny rozmiar bufora, aby zminimalizować odczuwalne opóźnienie.

Zwalniaj miejsce po użyciu

Gdy aplikacja nie potrzebuje już mikrofonu lub gdy aktywność zostanie zatrzymana, wywołaj metody stoprelease na obiekcie AudioRecord.

Sprawdzanie uprawnień w czasie działania przed rozpoczęciem nagrywania

Przed wywołaniem funkcji startRecording sprawdź, czy użytkownik przyznał okularom dostęp do mikrofonu, korzystając z przewidywanego kontekstu.

Nagrywanie dźwięku za pomocą Bluetooth HFP

Aby nagrywać dźwięk za pomocą profilu Bluetooth HFP, najpierw poproś o wymagane uprawnienia w czasie działania, a następnie nagraj dźwięk za pomocą interfejsu AudioManager API, jak opisano w kolejnych sekcjach.

Wyślij prośbę o uprawnienia

Podobnie jak w przypadku każdego standardowego urządzenia audio Bluetooth, uprawnienia RECORD_AUDIO,BLUETOOTH_CONNECT i inne powiązane uprawnienia są kontrolowane przez telefon, a nie przez połączone urządzenie (np. okulary audio lub okulary z wyświetlaczem).

Aby poprosić o uprawnienia, wykonaj te czynności:

  1. Zadeklaruj te uprawnienia w pliku manifestu aplikacji:

  2. Poproś o uprawnienia RECORD_AUDIOBLUETOOTH_CONNECT w czasie działania aplikacji, korzystając ze standardowego przepływu uprawnień Androida.

Używanie klasy AudioManager do kierowania dźwięku

Gdy użytkownik przyzna Twojej aplikacji niezbędne uprawnienia w czasie działania, użyj interfejsu AudioManager API, aby ustawić urządzenie komunikacyjne na TYPE_BLUETOOTH_SCO i przekierować dźwięk przez Bluetooth HFP. Dzięki temu system będzie pobierać dźwięk z urządzenia peryferyjnego 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()

Robienie zdjęć aparatem okularów

Aby zrobić zdjęcie aparatem okularów, skonfiguruj i powiąż przypadek użycia ImageCapture CameraX z aparatem okularów, używając kontekstu odpowiedniego dla aplikacji:

private fun startCameraOnGlasses(activity: ComponentActivity) {
    // 1. Get the CameraProvider using the projected context.
    // When using the projected context, DEFAULT_BACK_CAMERA maps to the AI glasses' camera.
    val projectedContext = try {
        ProjectedContext.createProjectedDeviceContext(activity)
    } catch (e: IllegalStateException) {
        Log.e(TAG, "AI Glasses context could not be created", e)
        return
    }

    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))
}

Najważniejsze informacje o kodzie

  • Pobiera instancję ProcessCameraProvider za pomocą prognozowanego kontekstu urządzenia.
  • W zakresie prognozowanego kontekstu główny aparat okularów skierowany na zewnątrz jest mapowany na DEFAULT_BACK_CAMERA podczas wybierania aparatu.
  • Sprawdzanie przed powiązaniem wykorzystuje cameraProvider.hasCamera(cameraSelector), aby zanim przejdziesz dalej, było wiadomo, czy wybrany aparat jest dostępny na urządzeniu.
  • Używa Camera2 InteropCamera2CameraInfo do odczytywania podstawowej CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP, co może być przydatne w przypadku zaawansowanych kontroli obsługiwanych rozdzielczości.
  • Niestandardowy ResolutionSelector został opracowany z myślą o precyzyjnym kontrolowaniu rozdzielczości obrazu wyjściowego w przypadku ImageCapture.
  • Tworzy przypadek użycia ImageCapture skonfigurowany za pomocą niestandardowego ResolutionSelector.
  • Wiąże przypadek użycia ImageCapture z cyklem życia aktywności. Ta funkcja automatycznie zarządza otwieraniem i zamykaniem aparatu na podstawie stanu aktywności (np. zatrzymuje aparat, gdy aktywność jest wstrzymana).

Po skonfigurowaniu aparatu w okularach możesz zrobić zdjęcie za pomocą klasy ImageCapture CameraX. Więcej informacji o używaniu takePicture do robienia zdjęć znajdziesz w dokumentacji CameraX.

Nagrywanie filmów aparatem okularów

Aby nagrać film zamiast zrobić zdjęcie za pomocą aparatu w okularach, zastąp komponenty ImageCapture odpowiednimi komponentami VideoCapture i zmodyfikuj logikę wykonywania przechwytywania.

Główne zmiany polegają na zastosowaniu innego przypadku użycia, utworzeniu innego pliku wyjściowego i rozpoczęciu przechwytywania za pomocą odpowiedniej metody nagrywania wideo. Więcej informacji o interfejsie VideoCapture API i sposobie jego używania znajdziesz w dokumentacji CameraX dotyczącej nagrywania filmów.

W tabeli poniżej znajdziesz zalecaną rozdzielczość i liczbę klatek w zależności od przypadku użycia aplikacji:

Przypadek użycia Rozdzielczość Liczba klatek
Komunikacja wideo 1280 x 720 15 kl./s
Rozpoznawanie obrazów 640 x 480 10 kl./s
Strumieniowe przesyłanie filmów wygenerowanych przez AI 640 x 480 1 kl./s

Dostęp do sprzętu telefonu z poziomu wyświetlanej aktywności

Projektowana aktywność może też uzyskać dostęp do sprzętu telefonu (np. aparatu lub mikrofonu) za pomocą createHostDeviceContext(context), aby zdobyć kontekst urządzenia hosta (telefonu):

@OptIn(ExperimentalProjectedApi::class)
private fun getPhoneContext(activity: ComponentActivity): Context? {
    return try {
        // From an AI glasses Activity, get a context for the phone.
        ProjectedContext.createHostDeviceContext(activity)
    } catch (e: IllegalStateException) {
        Log.e(TAG, "Failed to create host device context", e)
        null
    }
}

Gdy w aplikacji hybrydowej (zawierającej zarówno funkcje mobilne, jak i funkcje na okulary) uzyskujesz dostęp do sprzętu lub zasobów, które są specyficzne dla urządzenia hosta (telefonu), musisz wyraźnie wybrać odpowiedni kontekst, aby aplikacja mogła mieć dostęp do właściwego sprzętu: