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:
- Użyj prognozowanego kontekstu.
- Użyj profilu zestawu głośnomówiącego (HFP) Bluetooth.
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:
|
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:
- Zadeklaruj uprawnienie
RECORD_AUDIOw pliku manifestu aplikacji. 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:
- Kod wykonywany w prognozowanej aktywności: użyj
ActivityResultLauncherzProjectedPermissionsResultContract. Więcej informacji o korzystaniu z tej metody znajdziesz w sekcji Rejestrowanie narzędzia do uruchamiania uprawnień i w kolejnych sekcjach przewodnika dotyczącego proszenia o uprawnienia do hardware. - Kod wykonywany w aktywności telefonu hosta: użyj
Activity#requestPermissions(permissions, requestCode, deviceId)i podaj identyfikator urządzenia uzyskany zprojectedDeviceContext, jak opisano w sekcji Omówienie procesu proszenia o uprawnienia w przewodniku dotyczącym proszenia o uprawnienia do hardware.
- Kod wykonywany w prognozowanej aktywności: użyj
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, orUNPROCESSEDto 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_RECOGNITIONjest 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, wybierzUNPROCESSEDlubCAMCORDER.Aby zapewnić zgodność z okularami, obiekt
audioFormatmusi definiować częstotliwość próbkowania 16 kHz i konfigurację kanałów mono lub stereo (za pomocąCHANNEL_IN_MONOlubCHANNEL_IN_STEREO).Aby określić minimalny rozmiar bufora do utworzenia obiektu
AudioRecord, użyjAudioRecord.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:
Zadeklaruj te uprawnienia w pliku manifestu aplikacji:
Poproś o uprawnienia
RECORD_AUDIOiBLUETOOTH_CONNECTw 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ę
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 używa
cameraProvider.hasCamera(cameraSelector)aby przed kontynuowaniem sprawdzić, 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. - Tworzy niestandardowy
ResolutionSelector, aby precyzyjnie kontrolować rozdzielczość obrazu wyjściowego dlaImageCapture. - 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). - Konfigurowanie hardware z jednym strumieniem: potok aparatu w okularach jest ograniczony do jednego aktywnego strumienia naraz. Gdy
ImageCapturejest 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ćImageCapturesamodzielnie bez strumienia podglądu, wyłącz wymuszony powtarzający się strumień, ustawiającsetRepeatingStreamForced(false)wCameraXConfig.Builder(na przykład implementującCameraXConfig.Providerw klasieApplicationlub konfigurującProcessCameraProviderprzed 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:
- 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 prognozowana aktywność była ostatnio uruchomionym komponentem.