Menggunakan konteks yang diproyeksikan untuk mengakses hardware di kacamata audio dan kacamata tampilan

Perangkat XR yang kompatibel
Panduan ini membantu Anda membangun pengalaman untuk jenis perangkat XR ini.
Kacamata Pintar dengan Layar
& Audio

Setelah Anda meminta dan diberi izin yang diperlukan, aplikasi Anda dapat mengakses hardware di kacamata pintar audio atau kacamata pintar dengan layar. Kunci untuk mengakses hardware kacamata (bukan hardware ponsel) adalah menggunakan konteks yang diproyeksikan.

Ada dua cara utama untuk mendapatkan konteks yang diproyeksikan, bergantung pada tempat kode Anda dieksekusi:

Mendapatkan konteks yang diproyeksikan jika kode Anda berjalan dalam aktivitas yang diproyeksikan

Jika kode aplikasi Anda berjalan dari dalam aktivitas yang diproyeksikan, konteks aktivitasnya sudah menjadi konteks yang diproyeksikan. Dalam skenario ini, panggilan yang dilakukan dalam aktivitas tersebut sudah dapat mengakses hardware kacamata.

Mendapatkan konteks yang diproyeksikan untuk kode yang berjalan di komponen aplikasi Telepon

Jika bagian dari aplikasi Anda di luar aktivitas yang diproyeksikan (seperti aktivitas ponsel atau layanan) perlu mengakses hardware kacamata, bagian tersebut harus secara eksplisit mendapatkan konteks yang diproyeksikan. Untuk melakukannya, gunakan metode 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...")
                }
            }
    }
}

Memeriksa validitas

Gabungkan panggilan createProjectedDeviceContext dalam ProjectedContext.isProjectedDeviceConnected. Meskipun metode ini menampilkan true, konteks yang diproyeksikan tetap valid untuk perangkat yang terhubung, dan aktivitas atau layanan aplikasi ponsel Anda (seperti CameraManager) dapat mengakses hardware kacamata AI.

Pembersihan saat koneksi terputus

Konteks yang diproyeksikan terkait dengan siklus proses perangkat terhubung, sehingga konteks tersebut akan dihancurkan saat perangkat terputus. Saat perangkat terputus, ProjectedContext.isProjectedDeviceConnected akan menampilkan false. Aplikasi Anda harus memproses perubahan ini dan membersihkan layanan sistem (seperti CameraManager) atau resource yang dibuat aplikasi Anda menggunakan konteks yang diproyeksikan tersebut.

Lakukan inisialisasi ulang saat menghubungkan kembali

Saat kacamata terhubung kembali, aplikasi Anda dapat memperoleh instance konteks yang diproyeksikan lainnya menggunakan createProjectedDeviceContext, lalu melakukan inisialisasi ulang layanan atau resource sistem apa pun menggunakan konteks yang diproyeksikan baru.

Merekam audio dengan mikrofon kacamata

Anda dapat merekam audio dari kacamata menggunakan Profil Hands-Free (HFP) Bluetooth. Dalam mode ini, kacamata terhubung ke ponsel menggunakan profil Headset dan Advanced Audio Distribution Profile (A2DP) standar, yang berfungsi seperti periferal Bluetooth biasa. Jika aplikasi Anda sudah dirancang untuk perekaman Bluetooth standar, Anda dapat menggunakan metode ini untuk merekam audio dari kacamata tanpa pengembangan tambahan.

Merekam audio menggunakan HFP Bluetooth

Untuk merekam audio menggunakan Bluetooth HFP, pertama-tama minta izin runtime yang diperlukan, lalu rekam audio menggunakan API AudioManager, seperti yang dijelaskan di bagian berikut.

Meminta izin

Seperti perangkat audio Bluetooth standar, RECORD_AUDIO, BLUETOOTH_CONNECT, dan izin terkait lainnya dikontrol oleh ponsel, bukan perangkat terhubung (seperti kacamata pintar audio atau kacamata pintar dengan layar).

Ikuti langkah-langkah berikut untuk meminta izin:

  1. Deklarasikan izin berikut dalam file manifes aplikasi Anda:

  2. Minta izin RECORD_AUDIO dan BLUETOOTH_CONNECT saat runtime menggunakan alur izin Android standar.

Menggunakan AudioManager untuk mengarahkan audio

Setelah pengguna memberikan izin runtime yang diperlukan ke aplikasi Anda, gunakan API AudioManager untuk menyetel perangkat komunikasi ke TYPE_BLUETOOTH_SCO guna merutekan audio melalui HFP Bluetooth. Tindakan ini mengarahkan sistem untuk mengambil audio dari periferal 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()

Mengambil gambar dengan kamera kacamata pintar

Untuk mengambil gambar dengan kamera kacamata, siapkan dan ikat ImageCapture kasus penggunaan CameraX ke kamera kacamata menggunakan konteks yang benar untuk aplikasi Anda:

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

Poin penting tentang kode

  • Mendapatkan instance ProcessCameraProvider menggunakan konteks perangkat yang diproyeksikan.
  • Dalam cakupan konteks yang diproyeksikan, kamera utama kacamata pintar yang mengarah ke luar dipetakan ke DEFAULT_BACK_CAMERA saat memilih kamera.
  • Pemeriksaan pra-pengikatan menggunakan cameraProvider.hasCamera(cameraSelector) untuk memverifikasi bahwa kamera yang dipilih tersedia di perangkat sebelum melanjutkan.
  • Menggunakan Camera2 Interop dengan Camera2CameraInfo untuk membaca CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP yang mendasarinya, yang dapat berguna untuk pemeriksaan lanjutan pada resolusi yang didukung.
  • ResolutionSelector kustom dibuat untuk mengontrol resolusi gambar output secara tepat untuk ImageCapture.
  • Membuat kasus penggunaan ImageCapture yang dikonfigurasi dengan ResolutionSelector kustom.
  • Mengikat kasus penggunaan ImageCapture ke siklus proses aktivitas. Hal ini secara otomatis mengelola pembukaan dan penutupan kamera berdasarkan status aktivitas (misalnya, menghentikan kamera saat aktivitas dijeda).
  • Mengonfigurasi untuk hardware streaming tunggal: Pipeline kamera kacamata dibatasi untuk satu streaming aktif dalam satu waktu. Saat ImageCapture terikat sendiri, CameraX akan melampirkan streaming berulang internal (MeteringRepeating) untuk fokus dan pengukuran secara default, yang menyebabkan pipeline streaming tunggal terhenti. Untuk mengikat ImageCapture sendiri tanpa streaming pratinjau, nonaktifkan streaming pengulangan paksa dengan menetapkan setRepeatingStreamForced(false) di CameraXConfig.Builder (misalnya, dengan menerapkan CameraXConfig.Provider di class Application atau mengonfigurasi ProcessCameraProvider sebelum inisialisasi).

Setelah kamera kacamata disiapkan, Anda dapat mengambil gambar dengan class ImageCapture CameraX. Lihat dokumentasi CameraX untuk mempelajari cara menggunakan takePicture untuk mengambil gambar.

Merekam video dengan kamera kacamata

Untuk mengambil video, bukan gambar, dengan kamera kacamata, ganti komponen ImageCapture dengan komponen VideoCapture yang sesuai dan ubah logika eksekusi pengambilan.

Perubahan utamanya mencakup penggunaan kasus penggunaan yang berbeda, pembuatan file output yang berbeda, dan memulai pengambilan menggunakan metode perekaman video yang sesuai. Untuk mengetahui informasi selengkapnya tentang VideoCapture API dan cara menggunakannya, lihat dokumentasi perekaman video CameraX.

Tabel berikut menunjukkan resolusi dan kecepatan frame yang direkomendasikan, bergantung pada kasus penggunaan aplikasi Anda:

Kasus penggunaan Resolusi Kecepatan frame
Komunikasi Video 1280 x 720 15 FPS
Computer Vision 640 x 480 10 FPS
Streaming Video AI 640 x 480 1 FPS

Mengakses hardware ponsel dari aktivitas yang diproyeksikan

Aktivitas yang diproyeksikan juga dapat mengakses hardware ponsel (seperti kamera atau mikrofon) dengan menggunakan createHostDeviceContext(context) untuk mendapatkan konteks perangkat host (ponsel):

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

Saat mengakses hardware atau resource yang khusus untuk perangkat host (ponsel) di aplikasi hybrid (aplikasi yang berisi pengalaman seluler dan kacamata), Anda harus secara eksplisit memilih konteks yang benar untuk memastikan aplikasi dapat mengakses hardware yang benar: