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

Obsługiwane urządzenia XR
Ten przewodnik pomoże Ci tworzyć aplikacje na te typy urządzeń XR.
Okulary z dźwiękiem i
wyświetlaczem

Gdy poprosisz o niezbędne uprawnienia i je uzyskasz, Twoja aplikacja będzie mogła uzyskać 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 uzyskania prognozowanego kontekstu, w zależności od tego, gdzie jest wykonywany Twój 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 wywołania wykonywane w ramach tej aktywności mogą już uzyskiwać dostęp do 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:

@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...")
                }
            }
    }
}

Sprawdzanie ważności

Umieść wywołanie createProjectedDeviceContext w ProjectedContext.isProjectedDeviceConnected. Gdy ta metoda zwraca wartość true, prognozowany kontekst pozostaje ważny dla połączonego urządzenia, a aktywność lub usługa aplikacji na telefon (np. CameraManager) może uzyskiwać dostęp do hardware okularów AI.

Zwalnianie miejsca po odłączeniu

Prognozowany kontekst jest powiązany z cyklem życia połączonego urządzenia, więc jest niszczony po odłączeniu urządzenia. Gdy urządzenie się odłączy, ProjectedContext.isProjectedDeviceConnected zwróci wartość false. Twoja aplikacja powinna nasłuchiwać tej zmiany i zwalniać miejsce zajmowane przez wszystkie usługi systemowe (np. CameraManager) lub zasoby utworzone przez aplikację za pomocą tego prognozowanego kontekstu.

Ponowne inicjowanie po ponownym połączeniu

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

Nagrywanie dźwięku za pomocą mikrofonu w okularach

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 jakości, specyficznego dla XR, czy standardowego wejścia audio Bluetooth.

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

Prognozowany kontekst

Wiele mikrofonów

Nagrywanie za pomocą prognozowanego kontekstu umożliwia aplikacji dostęp do wielu mikrofonów w okularach i ich specjalistycznych funkcji sprzętowych, takich jak:

  • aspekt przestrzenny specyficzny dla XR,
  • zaawansowane usuwanie szumów,
  • rozdzielanie głosu, które izoluje głos osoby noszącej okulary,
  • utrzymywanie dostępu do nagrywania w środowiskach z wieloma urządzeniami, nawet gdy okulary nie są aktywnym urządzeniem Bluetooth.

Bluetooth HFP

Pojedynczy mikrofon

Opiera się na profilu zestawu głośnomówiącego (HFP) Bluetooth, co zapewnia natychmiastową zgodność. W tym trybie okulary łączą się z telefonem za pomocą standardowych profili zestawu słuchawkowego i profilu zaawansowanej dystrybucji dźwięku (A2DP) profili, działając jak typowe urządzenie peryferyjne Bluetooth.

Jeśli Twoja aplikacja jest już przeznaczona do standardowego nagrywania przez Bluetooth, 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 za pomocą prognozowanego kontekstu

Aby nagrywać dźwięk za pomocą prognozowanego kontekstu, najpierw poproś o wymagane uprawnienia czasu działania, a następnie nagraj dźwięk za pomocą interfejsu AudioRecord API, jak opisano w kolejnych sekcjach.

Prośba o uprawnienia czasu działania

Aby uzyskać dostęp do wielu mikrofonów w okularach, musisz poprosić o uprawnienia do nagrywania dźwięku specjalnie dla prognozowanego urządzenia. Standardowe uprawnienie RECORD_AUDIO w zakresie telefonu, które użytkownik przyznał Twojej aplikacji na urządzeniu mobilnym, jest 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 prognozowanego urządzenia na jeden z tych sposobów, w zależności od tego, gdzie jest wykonywany Twój kod:

Inicjowanie AudioRecord za pomocą prognozowanego kontekstu

Aby mieć pewność, że dźwięk jest nagrywany z okularów, a nie z telefonu hosta, musisz powiązać obiekt AudioRecord z prognozowanym kontekstem urządzenia.

Poniższy kod używa AudioRecord.Builder i przekazuje 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
  • Aby dostosować przetwarzanie dźwięku do konkretnego przypadku użycia, możesz ustawić źródło dźwięku na CAMCORDER, VOICE_RECOGNITION, VOICE_COMMUNICATION, or UNPROCESSED to tailor the audio processing to your specific use case.

    Na przykład użyj VOICE_COMMUNICATION, jeśli Twój przypadek użycia wymaga automatycznej redukcji szumów lub izolowania głosu osoby noszącej okulary. VOICE_RECOGNITION jest przetwarzany za pomocą akustycznego usuwania echa (AEC), co może być przydatne, gdy podczas nagrywania odtwarzany jest dźwięk. Jeśli potrzebujesz surowego, nieprzetworzonego dźwięku, wybierz UNPROCESSED lub CAMCORDER.

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

  • Aby określić minimalny rozmiar bufora do utworzenia obiektu AudioRecord, użyj AudioRecord.getMinBufferSize(). Aby jednak zapobiec przerwom w dźwięku z okularów, należy odczytywać dane z tego bufora w krótkich, częstych fragmentach (najlepiej co 20 ms), zamiast czekać na wypełnienie całego bufora.

Zwalnianie miejsca po użyciu

Gdy Twoja aplikacja nie będzie już potrzebować mikrofonu lub gdy aktywność zostanie zatrzymana, wywołaj stop i release na obiekcie AudioRecord.

Sprawdzanie uprawnień czasu działania przed nagrywaniem

Zanim wywołasz startRecording, sprawdź, czy użytkownik przyznał dostęp do mikrofonu w okularach za pomocą prognozowanego kontekstu.

Nagrywanie dźwięku za pomocą Bluetooth HFP

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

Prośba 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_AUDIO i BLUETOOTH_CONNECT w czasie działania za pomocą standardowego procesu przyznawania uprawnień w Androidzie.

Używanie AudioManager do routingu dźwięku

Gdy użytkownik przyzna Twojej aplikacji niezbędne uprawnienia czasu działania, użyj interfejsu AudioManager API, aby ustawić urządzenie komunikacyjne na TYPE_BLUETOOTH_SCO i przekierować dźwięk przez Bluetooth HFP. Spowoduje to, że 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ęć za pomocą aparatu w okularach

Aby zrobić zdjęcie za pomocą aparatu w okularach, skonfiguruj i powiąż przypadek użycia CameraX's ImageCapture z aparatem w okularach, używając odpowiedniego kontekstu dla swojej aplikacji:

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

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 używa cameraProvider.hasCamera(cameraSelector) aby przed kontynuowaniem sprawdzić, czy wybrany aparat jest dostępny na urządzeniu.
  • Używa Camera2 Interop z Camera2CameraInfo do odczytywania podstawowej CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP, co może być przydatne w przypadku zaawansowanych kontroli obsługiwanych rozdzielczości.
  • Tworzy niestandardowy ResolutionSelector, aby precyzyjnie kontrolować rozdzielczość obrazu wyjściowego dla 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).
  • Konfigurowanie hardware z jednym strumieniem: potok aparatu w okularach jest ograniczony do jednego aktywnego strumienia naraz. Gdy ImageCapture jest powiązany samodzielnie, CameraX domyślnie dołącza wewnętrzny powtarzający się strumień (MeteringRepeating) do ustawiania ostrości i pomiaru, co powoduje zatrzymanie potoku z jednym strumieniem. Aby powiązać ImageCapture samodzielnie bez strumienia podglądu, wyłącz wymuszony powtarzający się strumień, ustawiając setRepeatingStreamForced(false) w CameraXConfig.Builder (na przykład implementując CameraXConfig.Provider w klasie Application lub konfigurując ProcessCameraProvider przed inicjacją).

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 za pomocą aparatu w okularach

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 hardware telefonu z poziomu prognozowanej aktywności

Prognozowana aktywność może też uzyskać dostęp do hardware 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 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
    }
}

Gdy w aplikacji hybrydowej (zawierającej zarówno funkcje mobilne, jak i funkcje na okulary) uzyskujesz dostęp do hardware 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 hardware: