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, jeśli kod jest uruchamiany 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 } }
Sprawdzanie ważności
Umieść wywołanie createProjectedDeviceContext w ProjectedContext.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 AI.
Zwalnianie miejsca po odłączeniu
Przewidywany kontekst jest powiązany z cyklem życia połączonego urządzenia, więc jest niszczony, gdy urządzenie zostanie odłączone. 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:
- Użyj prognozowanego kontekstu.
- Używaj profilu zestawu głośnomówiącego Bluetooth (HFP).
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 w okularach i ich specjalistycznych funkcji sprzętowych, takich jak:
|
Bluetooth HFP |
Pojedynczy mikrofon |
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 przy użyciu 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 RECORD_AUDIOdla telefonu, które użytkownik przyznał Twojej aplikacji na urządzeniu mobilnym, są niewystarczające.
Aby poprosić o uprawnienia, wykonaj te czynności:
- Zadeklaruj uprawnienia
RECORD_AUDIOw pliku manifestu aplikacji. Poproś o uprawnienia w zakresie urządzenia projekcyjnego w jeden z tych sposobów, w zależności od tego, gdzie jest wykonywany kod:
- Kod wykonywany w ramach wyświetlanej aktywności: użyj
ActivityResultLauncherzProjectedPermissionsResultContract. Więcej informacji o używaniu tej metody znajdziesz w sekcji Rejestrowanie narzędzia do uruchamiania uprawnień i w kolejnych sekcjach przewodnika dotyczącego proszenia o uprawnienia do sprzętu. - Kod wykonywany w ramach aktywności na telefonie hosta: użyj
Activity#requestPermissions(permissions, requestCode, deviceId)i podaj identyfikator urządzenia uzyskany zprojectedDeviceContext, zgodnie z opisem w sekcji Poznaj proces prośby o uprawnienia w przewodniku dotyczącym proszenia o uprawnienia do sprzętu.
- Kod wykonywany w ramach wyświetlanej aktywności: użyj
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 parametr 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_COMMUNICATIONlubUNPROCESSED, 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_RECOGNITIONjest przetwarzany z użyciem akustycznej redukcji echa (AEC). Jeśli potrzebujesz surowego, nieprzetworzonego dźwięku, wybierzUNPROCESSEDlubCAMCORDER.Aby zapewnić zgodność z okularami, obiekt
audioFormatmusi mieć zdefiniowaną częstotliwość próbkowania 16 kHz i konfigurację kanałów mono lub stereo (za pomocąCHANNEL_IN_MONOlubCHANNEL_IN_STEREO).Użyj
AudioRecord.getMinBufferSize(), aby określić minimalny rozmiar bufora do utworzenia obiektuAudioRecord. 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 20-milisekundowych), zamiast czekać na wypełnienie całego bufora.
Zwalniaj miejsce po użyciu
Gdy aplikacja nie potrzebuje już mikrofonu lub gdy aktywność zostanie zatrzymana, wywołaj metody stop i release 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ą 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:
Zadeklaruj te uprawnienia w pliku manifestu aplikacji:
Poproś o uprawnienia
RECORD_AUDIOiBLUETOOTH_CONNECTw 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 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ę
ProcessCameraProviderza pomocą prognozowanego kontekstu urządzenia. - W zakresie prognozowanego kontekstu główny aparat okularów skierowany na zewnątrz jest mapowany na
DEFAULT_BACK_CAMERApodczas 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 Interop z
Camera2CameraInfodo odczytywania podstawowejCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP, co może być przydatne w przypadku zaawansowanych kontroli obsługiwanych rozdzielczości. - Niestandardowy
ResolutionSelectorzostał opracowany z myślą o precyzyjnym kontrolowaniu rozdzielczości obrazu wyjściowego w przypadkuImageCapture. - Tworzy przypadek użycia
ImageCaptureskonfigurowany za pomocą niestandardowegoResolutionSelector. - Wiąże przypadek użycia
ImageCapturez 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
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 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 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:
- Aby uzyskać kontekst telefonu, użyj kontekstu
ActivityzActivitytelefonu lubProjectedContext.createHostDeviceContext. - Nie używaj
getApplicationContext, ponieważ kontekst aplikacji może nieprawidłowo zwrócić kontekst okularów, jeśli ostatnio uruchomionym komponentem była prognozowana aktywność.