ใช้บริบทที่คาดการณ์ไว้เพื่อเข้าถึงฮาร์ดแวร์ในแว่นตาเสียงและแว่นตาแสดงผล

อุปกรณ์ XR ที่รองรับ
คำแนะนำนี้จะช่วยคุณสร้างประสบการณ์การใช้งานสำหรับอุปกรณ์ XR ประเภทต่อไปนี้
แว่นตาสำหรับเสียงและ
การแสดงผล

หลังจากที่คุณขอและได้รับสิทธิ์ที่จำเป็นแล้ว แอป จะเข้าถึงฮาร์ดแวร์ในแว่นตาสำหรับเสียงหรือแว่นตาสำหรับการแสดงผลได้ เคล็ดลับในการ เข้าถึงฮาร์ดแวร์ของแว่นตา (แทนที่จะเป็นฮาร์ดแวร์ของโทรศัพท์) คือการใช้ บริบทที่คาดการณ์ไว้

มี 2 วิธีหลักๆ ในการรับบริบทที่คาดการณ์ไว้ โดยขึ้นอยู่กับตำแหน่งที่โค้ดของคุณทำงาน

รับบริบทที่คาดการณ์ไว้หากโค้ดของคุณทำงานในกิจกรรมที่คาดการณ์ไว้

หากโค้ดของแอปทำงานจากภายในกิจกรรมที่คาดการณ์ไว้ บริบทกิจกรรม ของโค้ดเองจะเป็นบริบทที่คาดการณ์ไว้แล้ว ในสถานการณ์นี้ การเรียกใช้ภายในกิจกรรมนั้นจะเข้าถึงฮาร์ดแวร์ของแว่นตาได้อยู่แล้ว

รับบริบทที่คาดการณ์ไว้สำหรับโค้ดที่ทำงานในคอมโพเนนต์แอปโทรศัพท์

หากส่วนหนึ่งของแอปที่อยู่นอกกิจกรรมที่คาดการณ์ไว้ (เช่น กิจกรรมโทรศัพท์หรือบริการ) จำเป็นต้องเข้าถึงฮาร์ดแวร์ของแว่นตา ส่วนนั้นจะต้องรับบริบทที่คาดการณ์ไว้อย่างชัดเจน โดยใช้เมธอด 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
    }
}

ตรวจสอบความถูกต้อง

ใส่การเรียกใช้ createProjectedDeviceContext ไว้ใน ProjectedContext.isProjectedDeviceConnected ในขณะที่เมธอดนี้แสดงผลเป็น true บริบทที่คาดการณ์ไว้จะยังคงถูกต้องสำหรับอุปกรณ์ที่เชื่อมต่อ และกิจกรรมหรือบริการของแอปโทรศัพท์ (เช่น CameraManager) จะเข้าถึงฮาร์ดแวร์ของแว่นตา AI ได้

ล้างข้อมูลเมื่อยกเลิกการเชื่อมต่อ

บริบทที่คาดการณ์ไว้จะเชื่อมโยงกับวงจรชีวิตของอุปกรณ์ที่เชื่อมต่อ ดังนั้นระบบจะทำลายบริบทนี้เมื่ออุปกรณ์ยกเลิกการเชื่อมต่อ เมื่ออุปกรณ์ยกเลิกการเชื่อมต่อ ProjectedContext.isProjectedDeviceConnected จะแสดงผลเป็น false แอปควรตรวจสอบการเปลี่ยนแปลงนี้และล้างบริการระบบ (เช่น CameraManager) หรือทรัพยากรที่แอปสร้างขึ้นโดยใช้บริบทที่คาดการณ์ไว้นั้น

เริ่มต้นใหม่เมื่อเชื่อมต่ออีกครั้ง

เมื่อแว่นตาเชื่อมต่ออีกครั้ง แอปจะรับอินสแตนซ์บริบทที่คาดการณ์ไว้ได้อีกรายการโดยใช้ createProjectedDeviceContext จากนั้น เริ่มต้นบริการระบบหรือทรัพยากรใหม่โดยใช้บริบทที่คาดการณ์ไว้ใหม่

บันทึกเสียงด้วยไมโครโฟนของแว่นตา

คุณบันทึกเสียงจากแว่นตาได้ 2 วิธีที่แตกต่างกันดังนี้

เลือกวิธีการบันทึก

วิธีการที่คุณเลือกจะขึ้นอยู่กับว่าคุณต้องการการประมวลผลเสียงที่มีความเที่ยงตรงสูงและเฉพาะเจาะจงสำหรับ XR หรืออินพุตเสียงบลูทูธมาตรฐาน

วิธีการบันทึก สิทธิ์เข้าถึงไมโครโฟน กรณีการใช้งานทั่วไป

บริบทที่คาดการณ์ไว้

ไมโครโฟนหลายตัว

การบันทึกโดยใช้บริบทที่คาดการณ์ไว้จะช่วยให้แอปเข้าถึงไมโครโฟนหลายตัวจากแว่นตาและฟีเจอร์ฮาร์ดแวร์เฉพาะทางของแว่นตาได้ เช่น

  • การสร้างเสียง 3 มิติเฉพาะสำหรับ XR
  • การลดเสียงรบกวนขั้นสูง
  • การแยกเสียงที่แยกความแตกต่างระหว่างเสียงของผู้สวมใส่กับเสียงของผู้ที่อยู่รอบข้าง
  • รักษาการเข้าถึงการบันทึกในสภาพแวดล้อมแบบหลายอุปกรณ์ แม้ว่าแว่นตาจะไม่ใช่อุปกรณ์บลูทูธที่ใช้งานอยู่

HFP ของบลูทูธ

ไมโครโฟนตัวเดียว

ใช้โปรไฟล์แฮนด์ฟรี (HFP) ของบลูทูธเพื่อความเข้ากันได้ทันทีโดยไม่ต้องตั้งค่า ในโหมดนี้ แว่นตาจะเชื่อมต่อกับโทรศัพท์โดยใช้โปรไฟล์ชุดหูฟังมาตรฐานและโปรไฟล์การกระจายเสียงขั้นสูง (A2DP) โปรไฟล์ ซึ่งทำงานเหมือนอุปกรณ์ต่อพ่วงบลูทูธทั่วไป

หากแอปได้รับการออกแบบมาสำหรับการบันทึกบลูทูธมาตรฐานอยู่แล้ว คุณสามารถใช้วิธีนี้เพื่อบันทึกเสียงจากแว่นตาโดยไม่ต้องผสานรวมความสามารถเฉพาะสำหรับ XR

บันทึกเสียงโดยใช้บริบทที่คาดการณ์ไว้

หากต้องการบันทึกเสียงโดยใช้บริบทที่คาดการณ์ไว้ ให้ขอสิทธิ์รันไทม์ที่จำเป็น ก่อน จากนั้นบันทึกเสียงโดยใช้ AudioRecord API ตามที่ อธิบายไว้ในส่วนต่อไปนี้

ขอสิทธิ์รันไทม์

หากต้องการเข้าถึงไมโครโฟนหลายตัวในแว่นตา คุณต้องขอสิทธิ์เสียงสำหรับอุปกรณ์ที่คาดการณ์ไว้โดยเฉพาะ สิทธิ์ RECORD_AUDIO มาตรฐานที่มีขอบเขตเป็นโทรศัพท์ซึ่งผู้ใช้ให้ไว้สำหรับแอปในอุปกรณ์เคลื่อนที่นั้นไม่เพียงพอ

ทำตามขั้นตอนต่อไปนี้เพื่อขอสิทธิ์

  1. ประกาศสิทธิ์ RECORD_AUDIO permission ในไฟล์ Manifest ของแอป
  2. ขอสิทธิ์ที่มีขอบเขตเป็นอุปกรณ์ที่คาดการณ์ไว้ด้วยวิธีใดวิธีหนึ่งต่อไปนี้ โดยขึ้นอยู่กับตำแหน่งที่โค้ดของคุณทำงาน

เริ่มต้น AudioRecord ด้วยบริบทที่คาดการณ์ไว้

หากต้องการให้บันทึกเสียงจากแว่นตาแทนที่จะเป็นโทรศัพท์โฮสต์ คุณต้องเชื่อมโยงออบเจ็กต์ AudioRecord กับบริบทอุปกรณ์ที่คาดการณ์ไว้

โค้ดต่อไปนี้ใช้ AudioRecord.Builder และส่ง projectedDeviceContext ไปยังเมธอด 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()

ประเด็นสำคัญเกี่ยวกับโค้ด
  • คุณสามารถตั้งค่าแหล่งที่มาของเสียงเป็น CAMCORDER, VOICE_RECOGNITION, VOICE_COMMUNICATION, หรือ UNPROCESSED เพื่อปรับแต่งการประมวลผลเสียงให้เหมาะกับกรณีการใช้งานที่เฉพาะเจาะจง

    เช่น ใช้ VOICE_COMMUNICATION หากกรณีการใช้งานของคุณต้องมีการลดเสียงรบกวนอัตโนมัติ ระบบจะประมวลผล VOICE_RECOGNITION ด้วยการตัดเสียงก้อง (AEC) และหากต้องการเสียงดิบที่ไม่มีการเปลี่ยนแปลง ให้เลือก UNPROCESSED หรือ CAMCORDER

  • ออบเจ็กต์ audioFormat ต้อง กำหนดอัตราการสุ่มตัวอย่างที่ 16kHz และการกำหนดค่าช่องสัญญาณเป็นโมโนหรือ สเตอริโอ (โดยใช้ CHANNEL_IN_MONO หรือ CHANNEL_IN_STEREO) เพื่อให้มั่นใจว่าเข้ากันได้กับแว่นตา

  • แม้ว่าจะไม่มีข้อกำหนดที่ตายตัวสำหรับขนาดบัฟเฟอร์ แต่ให้ รับขนาดบัฟเฟอร์ขั้นต่ำ เพื่อลดเวลาในการตอบสนองที่รับรู้ได้

ล้างข้อมูลหลังการใช้งาน

เมื่อแอปไม่ต้องการไมโครโฟนอีกต่อไปหรือเมื่อกิจกรรมหยุดลง ให้เรียกใช้ stop และ release ในออบเจ็กต์ AudioRecord

ตรวจสอบสิทธิ์รันไทม์ก่อนบันทึก

ก่อนเรียกใช้ startRecording, ตรวจสอบว่าผู้ใช้ได้ให้สิทธิ์เข้าถึง ไมโครโฟนสำหรับแว่นตาโดยใช้บริบทที่คาดการณ์ไว้แล้ว

บันทึกเสียงโดยใช้ HFP ของบลูทูธ

หากต้องการบันทึกเสียงโดยใช้ HFP ของบลูทูธ ให้ขอสิทธิ์รันไทม์ที่จำเป็น ก่อน จากนั้นบันทึกเสียงโดยใช้ AudioManager API ตามที่ อธิบายไว้ในส่วนต่อไปนี้

ขอสิทธิ์

เช่นเดียวกับอุปกรณ์เสียงบลูทูธมาตรฐาน สิทธิ์ RECORD_AUDIO, BLUETOOTH_CONNECT และสิทธิ์อื่นๆ ที่เกี่ยวข้องจะควบคุมโดย โทรศัพท์ ไม่ใช่อุปกรณ์ที่เชื่อมต่อ (เช่น แว่นตาสำหรับเสียงหรือแว่นตาสำหรับการแสดงผล)

ทำตามขั้นตอนต่อไปนี้เพื่อขอสิทธิ์

  1. ประกาศสิทธิ์ต่อไปนี้ในไฟล์ Manifest ของแอป

  2. ขอสิทธิ์ RECORD_AUDIO และ BLUETOOTH_CONNECT ทั้ง 2 รายการในขณะรันไทม์โดยใช้โฟลว์สิทธิ์มาตรฐานของ Android

ใช้ AudioManager เพื่อกำหนดเส้นทางเสียง

หลังจากที่ผู้ใช้ให้สิทธิ์รันไทม์ที่จำเป็นแก่แอปแล้ว ให้ใช้ AudioManager API เพื่อตั้งค่าอุปกรณ์สื่อสารเป็น TYPE_BLUETOOTH_SCO เพื่อกำหนดเส้นทางเสียงผ่าน HFP ของบลูทูธ ซึ่งจะสั่งให้ระบบดึงเสียงจากอุปกรณ์ต่อพ่วงบลูทูธ

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

ถ่ายภาพด้วยกล้องของแว่นตา

หากต้องการถ่ายภาพด้วยกล้องของแว่นตา ให้ตั้งค่าและผูกกรณีการใช้งาน ImageCapture ของ CameraX กับกล้องของแว่นตาโดยใช้บริบทที่ถูกต้อง สำหรับแอป

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

ประเด็นสำคัญเกี่ยวกับโค้ด

  • รับอินสแตนซ์ของ ProcessCameraProvider โดยใช้ บริบทอุปกรณ์ที่คาดการณ์ไว้
  • ภายในขอบเขตของบริบทที่คาดการณ์ไว้ กล้องหลักของแว่นตาที่หันออกด้านนอกจะแมปกับ DEFAULT_BACK_CAMERA เมื่อเลือกกล้อง
  • การตรวจสอบก่อนการผูกใช้ cameraProvider.hasCamera(cameraSelector) เพื่อ ยืนยันว่ากล้องที่เลือกพร้อมใช้งานในอุปกรณ์ก่อน ดำเนินการต่อ
  • ใช้ Camera2 Interop กับ Camera2CameraInfo เพื่ออ่าน CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP ที่อยู่เบื้องหลัง ซึ่งอาจมีประโยชน์สำหรับการตรวจสอบขั้นสูงเกี่ยวกับความละเอียดที่รองรับ
  • สร้าง ResolutionSelector ที่กำหนดเองเพื่อควบคุมความละเอียดของรูปภาพเอาต์พุต สำหรับ ImageCapture ได้อย่างแม่นยำ
  • สร้างกรณีการใช้งาน ImageCapture ที่กำหนดค่าด้วย ResolutionSelector ที่กำหนดเอง
  • ผูกกรณีการใช้งาน ImageCapture กับวงจรชีวิตของกิจกรรม ซึ่งจะจัดการการเปิดและปิดกล้องโดยอัตโนมัติตามสถานะของกิจกรรม (เช่น หยุดกล้องเมื่อกิจกรรมหยุดชั่วคราว)

หลังจากตั้งค่ากล้องของแว่นตาแล้ว คุณจะถ่ายภาพด้วยคลาส ImageCapture ของ CameraX ได้ โปรดดูเอกสารประกอบของ CameraX เพื่อเรียนรู้ เกี่ยวกับวิธีใช้ takePicture เพื่อถ่ายภาพ

บันทึกวิดีโอด้วยกล้องของแว่นตา

หากต้องการบันทึกวิดีโอแทนที่จะเป็นรูปภาพด้วยกล้องของแว่นตา ให้แทนที่ ImageCapture คอมโพเนนต์ด้วยคอมโพเนนต์ VideoCapture ที่เกี่ยวข้อง และแก้ไขตรรกะการดำเนินการบันทึก

การเปลี่ยนแปลงหลักๆ ได้แก่ การใช้กรณีการใช้งานที่แตกต่างกัน การสร้างไฟล์เอาต์พุตที่แตกต่างกัน และการเริ่มต้นการบันทึกโดยใช้วิธีการบันทึกวิดีโอที่เหมาะสม ดูข้อมูลเพิ่มเติมเกี่ยวกับ VideoCapture API และวิธีใช้ได้ใน เอกสารประกอบการบันทึกวิดีโอของ CameraX

ตารางต่อไปนี้แสดงความละเอียดและอัตราเฟรมที่แนะนำโดยขึ้นอยู่กับกรณีการใช้งานของแอป

กรณีการใช้งาน ความละเอียด อัตราเฟรม
การสื่อสารผ่านวิดีโอ 1280 x 720 15 FPS
คอมพิวเตอร์วิทัศน์ 640 x 480 10 FPS
การสตรีมวิดีโอ AI 640 x 480 1 FPS

เข้าถึงฮาร์ดแวร์ของโทรศัพท์จากกิจกรรมที่คาดการณ์ไว้

กิจกรรมที่คาดการณ์ไว้ยังเข้าถึงฮาร์ดแวร์ของโทรศัพท์ (เช่น กล้องหรือไมโครโฟน) ได้ด้วยการใช้ createHostDeviceContext(context) เพื่อรับ บริบทของอุปกรณ์โฮสต์ (โทรศัพท์)

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

เมื่อเข้าถึงฮาร์ดแวร์หรือทรัพยากรที่เฉพาะเจาะจงสำหรับอุปกรณ์โฮสต์ (โทรศัพท์) ในแอปแบบไฮบริด (แอปที่มีทั้งประสบการณ์การใช้งานบนอุปกรณ์เคลื่อนที่และแว่นตา) คุณต้องเลือกบริบทที่ถูกต้องอย่างชัดเจนเพื่อให้แน่ใจว่าแอปจะเข้าถึงฮาร์ดแวร์ที่ถูกต้องได้

  • ใช้บริบท Activity จาก Activity ของโทรศัพท์หรือ ProjectedContext.createHostDeviceContext เพื่อรับบริบท ของโทรศัพท์
  • อย่าใช้ getApplicationContext เนื่องจากบริบทของแอปพลิเคชัน อาจแสดงผลบริบทของแว่นตาอย่างไม่ถูกต้องหากกิจกรรมที่คาดการณ์ไว้เป็น คอมโพเนนต์ที่เปิดตัวล่าสุด