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 Audio &
Tampilan

Setelah Anda meminta dan diberikan izin yang diperlukan, aplikasi Anda dapat mengakses hardware di kacamata audio atau kacamata tampilan. 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 sendiri sudah merupakan 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 dalam komponen aplikasi ponsel

Jika bagian 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:

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

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.

Membersihkan saat koneksi terputus

Konteks yang diproyeksikan terikat dengan siklus proses perangkat yang terhubung, sehingga 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.

Menginisialisasi ulang saat terhubung kembali

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

Merekam audio dengan mikrofon kacamata

Anda dapat merekam audio dari kacamata menggunakan dua metode berbeda:

  • Menggunakan konteks yang diproyeksikan.
  • Menggunakan Profil Handsfree (HFP) Bluetooth.

Memilih metode perekaman

Metode yang Anda pilih bergantung pada apakah Anda memerlukan pemrosesan audio dengan fidelitas tinggi dan khusus XR, atau input audio Bluetooth standar.

Metode perekaman Akses mikrofon Kasus penggunaan umum

Konteks yang Diproyeksikan

Beberapa mikrofon

Perekaman menggunakan konteks yang diproyeksikan memungkinkan aplikasi Anda mengakses beberapa mikrofon dari kacamata dan fitur hardware khususnya, seperti:

  • Spasialisasi khusus XR.
  • Pengurangan noise lanjutan.
  • Pemisahan suara yang membedakan antara suara pengguna dan suara orang lain.
  • Mempertahankan akses perekaman di lingkungan multi-perangkat meskipun kacamata bukan perangkat Bluetooth aktif.

HFP Bluetooth

Mikrofon tunggal

Mengandalkan Profil Handsfree (HFP) Bluetooth untuk kompatibilitas langsung dan siap pakai. Dalam mode ini, kacamata terhubung ke ponsel menggunakan profil Headset dan Profil Distribusi Audio Lanjutan (A2DP) standar, yang berfungsi seperti periferal Bluetooth biasa.

Jika aplikasi Anda sudah didesain untuk perekaman Bluetooth standar, Anda dapat menggunakan metode ini untuk merekam audio dari kacamata tanpa mengintegrasikan kemampuan khusus XR.

Merekam audio menggunakan konteks yang diproyeksikan

Untuk merekam audio menggunakan konteks yang diproyeksikan, pertama-tama minta izin runtime yang diperlukan, lalu rekam audio menggunakan AudioRecord API, seperti dijelaskan di bagian berikut.

Meminta izin runtime

Untuk mengakses beberapa mikrofon di kacamata, Anda harus meminta izin audio khusus untuk perangkat yang diproyeksikan. Izin RECORD_AUDIO standar dan cakupan ponsel yang telah diberikan pengguna untuk aplikasi Anda di perangkat selulernya tidak cukup.

Ikuti langkah-langkah berikut untuk meminta izin:

  1. Deklarasikan izin RECORD_AUDIO di file manifes aplikasi Anda.
  2. Minta izin cakupan perangkat yang diproyeksikan dengan salah satu cara berikut, bergantung pada tempat kode Anda dieksekusi:

Menginisialisasi AudioRecord dengan konteks yang diproyeksikan

Untuk memastikan bahwa audio direkam dari kacamata, bukan ponsel host, Anda harus mengaitkan objek AudioRecord dengan konteks perangkat yang diproyeksikan.

Kode berikut menggunakan AudioRecord.Builder dan meneruskan projectedDeviceContext ke metode 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()

Poin-poin penting tentang kode
  • Anda dapat menetapkan sumber audio ke CAMCORDER, VOICE_RECOGNITION, VOICE_COMMUNICATION, atau UNPROCESSED untuk menyesuaikan pemrosesan audio dengan kasus penggunaan tertentu.

    Misalnya, gunakan VOICE_COMMUNICATION jika kasus penggunaan Anda memerlukan pengurangan noise otomatis. VOICE_RECOGNITION diproses dengan peredam gema akustik (AEC). Dan jika Anda memerlukan audio mentah yang tidak diubah, pilih UNPROCESSED atau CAMCORDER.

  • Untuk memastikan kompatibilitas dengan kacamata, objek audioFormat harus menentukan frekuensi sampel 16 kHz dan konfigurasi saluran mono atau stereo (menggunakan CHANNEL_IN_MONO atau CHANNEL_IN_STEREO).

  • Meskipun tidak ada persyaratan tetap untuk ukuran buffer, dapatkan ukuran buffer minimum untuk meminimalkan latensi yang dirasakan.

Membersihkan setelah digunakan

Jika aplikasi Anda tidak lagi memerlukan mikrofon, atau saat aktivitas dihentikan, panggil stop dan release pada objek AudioRecord.

Memeriksa izin runtime sebelum merekam

Sebelum memanggil startRecording, pastikan pengguna telah memberikan izin mikrofon untuk kacamata menggunakan konteks yang diproyeksikan.

Merekam audio menggunakan HFP Bluetooth

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

Meminta izin

Seperti halnya perangkat audio Bluetooth standar, RECORD_AUDIO, BLUETOOTH_CONNECT, dan izin terkait lainnya dikontrol oleh ponsel, bukan perangkat yang terhubung (seperti kacamata audio atau kacamata tampilan).

Ikuti langkah-langkah berikut untuk meminta izin:

  1. Deklarasikan izin berikut di file manifes aplikasi Anda:

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

Menggunakan AudioManager untuk merutekan audio

Setelah pengguna memberikan izin runtime yang diperlukan untuk aplikasi Anda, gunakan API AudioManager untuk menetapkan 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

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

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

Poin-poin penting tentang kode

  • Mendapatkan instance ProcessCameraProvider menggunakan konteks perangkat yang diproyeksikan.
  • Dalam cakupan konteks yang diproyeksikan, kamera utama kacamata yang menghadap 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. Tindakan ini secara otomatis mengelola pembukaan dan penutupan kamera berdasarkan status aktivitas (misalnya, menghentikan kamera saat aktivitas dijeda).

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 merekam video, bukan gambar, dengan kamera kacamata, ganti komponen ImageCapture dengan komponen VideoCapture yang sesuai dan ubah logika eksekusi pengambilan.

Perubahan utama melibatkan 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 pengambilan 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 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
    }
}

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