หลังจากที่คุณขอและได้รับสิทธิ์ที่จำเป็นแล้ว แอปจะเข้าถึงฮาร์ดแวร์ในแว่นตาที่มีระบบเสียงหรือแว่นตาที่มีจอแสดงผลได้ กุญแจสำคัญในการเข้าถึงฮาร์ดแวร์ของแว่นตา (แทนฮาร์ดแวร์ของโทรศัพท์) คือการใช้บริบทที่ฉายภาพ
คุณรับบริบทที่คาดการณ์ได้ 2 วิธีหลักๆ โดยขึ้นอยู่กับตำแหน่งที่โค้ดทำงานอยู่ ดังนี้
รับบริบทที่คาดการณ์ไว้หากโค้ดของคุณทำงานในกิจกรรมที่คาดการณ์ไว้
หากโค้ดของแอปทำงานจากภายในกิจกรรมที่ฉายภาพ บริบทกิจกรรมของแอปเองจะเป็นบริบทที่ฉายภาพอยู่แล้ว ในกรณีนี้ การเรียกใช้ที่ทำภายในกิจกรรมนั้นจะเข้าถึงฮาร์ดแวร์ของแว่นตาได้อยู่แล้ว
รับบริบทที่คาดการณ์ไว้สำหรับโค้ดที่ทำงานในคอมโพเนนต์ของแอปโทรศัพท์
หากส่วนหนึ่งของแอปที่อยู่นอกกิจกรรมที่ฉายภาพ (เช่น กิจกรรมหรือบริการของโทรศัพท์) จำเป็นต้องเข้าถึงฮาร์ดแวร์ของแว่นตา แอปจะต้องได้รับบริบทที่ฉายภาพอย่างชัดเจน โดยใช้วิธีการ 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...") } } } }
ตรวจสอบความถูกต้อง
รวมการเรียก createProjectedDeviceContext ไว้ใน ProjectedContext.isProjectedDeviceConnected แม้ว่าเมธอดนี้จะแสดงผล true แต่บริบทที่คาดการณ์ไว้จะยังคงใช้ได้กับอุปกรณ์ที่เชื่อมต่อ และกิจกรรมหรือบริการของแอปโทรศัพท์ (เช่น CameraManager) จะเข้าถึงฮาร์ดแวร์แว่นตา AI ได้
ล้างข้อมูลเมื่อยกเลิกการเชื่อมต่อ
บริบทที่ฉายจะเชื่อมโยงกับวงจรของอุปกรณ์ที่เชื่อมต่อ ดังนั้นระบบจะทำลายบริบทดังกล่าวเมื่ออุปกรณ์ยกเลิกการเชื่อมต่อ เมื่ออุปกรณ์ยกเลิกการเชื่อมต่อ
ProjectedContext.isProjectedDeviceConnected จะแสดงผลเป็น false แอปของคุณควรคอยฟังการเปลี่ยนแปลงนี้และล้างบริการของระบบ (เช่น CameraManager) หรือทรัพยากรที่แอปสร้างขึ้นโดยใช้บริบทที่ฉายภาพนั้น
เริ่มต้นใหม่เมื่อเชื่อมต่ออีกครั้ง
เมื่อแว่นตาเชื่อมต่ออีกครั้ง แอปของคุณจะรับอินสแตนซ์บริบทที่ฉายอีกรายการได้โดยใช้ createProjectedDeviceContext จากนั้นจึงเริ่มต้นบริการหรือทรัพยากรระบบอีกครั้งโดยใช้บริบทที่ฉายใหม่
บันทึกเสียงด้วยไมโครโฟนของแว่นตา
คุณสามารถบันทึกเสียงจากแว่นตาโดยใช้โปรไฟล์แฮนด์ฟรี (HFP) ของบลูทูธได้ ในโหมดนี้ แว่นตาจะเชื่อมต่อกับโทรศัพท์โดยใช้โปรไฟล์ Headset และ Advanced Audio Distribution Profile (A2DP) มาตรฐาน ซึ่งทำงานเหมือนกับอุปกรณ์ต่อพ่วงบลูทูธทั่วไป หากแอปของคุณออกแบบมาสำหรับการบันทึกเสียงผ่านบลูทูธมาตรฐานอยู่แล้ว คุณสามารถใช้วิธีนี้เพื่อบันทึกเสียงจากแว่นตาได้โดยไม่ต้องพัฒนาเพิ่มเติม
บันทึกเสียงโดยใช้บลูทูธ HFP
หากต้องการบันทึกเสียงโดยใช้ Bluetooth HFP ให้ขอสิทธิ์รันไทม์ที่จำเป็นก่อน แล้วจึงบันทึกเสียงโดยใช้ AudioManager API ตามที่อธิบายไว้ในส่วนต่อไปนี้
ขอสิทธิ์
เช่นเดียวกับอุปกรณ์เสียงบลูทูธมาตรฐาน สิทธิ์ RECORD_AUDIO, BLUETOOTH_CONNECT และสิทธิ์อื่นๆ ที่เกี่ยวข้องจะควบคุมโดยโทรศัพท์ ไม่ใช่อุปกรณ์ที่เชื่อมต่อ (เช่น แว่นตาที่มีระบบเสียง หรือแว่นตาที่มีจอแสดงผล)
ทำตามขั้นตอนต่อไปนี้เพื่อขอสิทธิ์
ประกาศสิทธิ์ต่อไปนี้ในไฟล์ Manifest ของแอป
ขอทั้งสิทธิ์
RECORD_AUDIOและBLUETOOTH_CONNECTขณะรันไทม์โดยใช้ขั้นตอนการขอสิทธิ์ Android มาตรฐาน
ใช้ AudioManager เพื่อกำหนดเส้นทางเสียง
หลังจากที่ผู้ใช้ให้สิทธิ์รันไทม์ที่จำเป็นแก่แอปแล้ว ให้ใช้ API AudioManager เพื่อตั้งค่าอุปกรณ์สื่อสารเป็น TYPE_BLUETOOTH_SCO เพื่อกำหนดเส้นทางเสียงผ่าน Bluetooth 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) { 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)) } } } }
ประเด็นสำคัญเกี่ยวกับโค้ด
- รับอินสแตนซ์ของ
ProcessCameraProviderโดยใช้บริบทอุปกรณ์ที่คาดการณ์ไว้ - ภายในขอบเขตของบริบทที่ฉาย กล้องหลักที่หันออกด้านนอกของแว่นตาจะแมปกับ
DEFAULT_BACK_CAMERAเมื่อเลือกกล้อง - การตรวจสอบก่อนการเชื่อมโยงจะใช้
cameraProvider.hasCamera(cameraSelector)เพื่อยืนยันว่ากล้องที่เลือกพร้อมใช้งานในอุปกรณ์ก่อนดำเนินการต่อ - ใช้ Camera2 Interop กับ
Camera2CameraInfoเพื่ออ่านCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAPพื้นฐาน ซึ่งอาจเป็นประโยชน์สำหรับการตรวจสอบขั้นสูงเกี่ยวกับความละเอียดที่รองรับ ResolutionSelectorที่กำหนดเองสร้างขึ้นเพื่อควบคุมความละเอียดของรูปภาพเอาต์พุตสำหรับImageCaptureอย่างแม่นยำ- สร้างกรณีการใช้งาน
ImageCaptureที่กำหนดค่าด้วยResolutionSelectorที่กำหนดเอง - เชื่อมโยง Use Case
ImageCaptureกับวงจรของกิจกรรม ซึ่งจะจัดการการเปิดและปิดกล้องโดยอัตโนมัติตามสถานะของกิจกรรม (เช่น การหยุดกล้องเมื่อกิจกรรมหยุดชั่วคราว) - การกำหนดค่าสำหรับฮาร์ดแวร์สตรีมเดียว: ไปป์ไลน์กล้องของแว่นตาถูกจำกัดให้มีสตรีมที่ใช้งานอยู่ได้ครั้งละ 1 สตรีมเท่านั้น เมื่อเชื่อมโยง
ImageCaptureเพียงอย่างเดียว CameraX จะแนบสตรีมที่ทำซ้ำภายใน (MeteringRepeating) สำหรับการโฟกัสและการวัดแสงโดยค่าเริ่มต้น ซึ่งทำให้ไปป์ไลน์สตรีมเดียวหยุดชะงัก หากต้องการเชื่อมโยงImageCaptureด้วยตัวเองโดยไม่มีสตรีมตัวอย่าง ให้ปิดใช้สตรีมที่บังคับให้ทำซ้ำโดยตั้งค่าsetRepeatingStreamForced(false)เป็นCameraXConfig.Builder(เช่น โดยการใช้CameraXConfig.ProviderในคลาสApplicationหรือกำหนดค่าProcessCameraProviderก่อนการเริ่มต้น)
หลังจากตั้งค่ากล้องของแว่นตาแล้ว คุณจะจับภาพด้วยคลาส ImageCapture ของ CameraX ได้ ดูข้อมูลเกี่ยวกับการใช้ takePicture เพื่อจับภาพได้ในเอกสารประกอบของ CameraX
จับภาพวิดีโอด้วยกล้องของแว่นตา
หากต้องการจับภาพวิดีโอแทนรูปภาพด้วยกล้องของแว่นตา ให้แทนที่คอมโพเนนต์ ImageCapture ด้วยคอมโพเนนต์ VideoCapture ที่เกี่ยวข้อง และแก้ไขตรรกะการดำเนินการจับภาพ
การเปลี่ยนแปลงหลักๆ ได้แก่ การใช้ Use Case อื่น การสร้างไฟล์เอาต์พุตอื่น และการเริ่มจับภาพโดยใช้วิธีการบันทึกวิดีโอที่เหมาะสม
ดูข้อมูลเพิ่มเติมเกี่ยวกับ 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 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 } }
เมื่อเข้าถึงฮาร์ดแวร์หรือทรัพยากรที่เฉพาะเจาะจงสำหรับอุปกรณ์โฮสต์ (โทรศัพท์) ในแอปแบบไฮบริด (แอปที่มีทั้งประสบการณ์การใช้งานบนอุปกรณ์เคลื่อนที่และแว่นตา) คุณต้องเลือกบริบทที่ถูกต้องอย่างชัดเจนเพื่อให้แอปเข้าถึงฮาร์ดแวร์ที่ถูกต้องได้ โดยทำดังนี้
- ใช้บริบท
Activityจากโทรศัพท์ActivityหรือProjectedContext.createHostDeviceContextเพื่อรับบริบทของโทรศัพท์ - อย่าใช้
getApplicationContextเนื่องจากบริบทของแอปพลิเคชันอาจแสดงผลบริบทของแว่นตาอย่างไม่ถูกต้องหากกิจกรรมที่ฉายภาพเป็นคอมโพเนนต์ที่เปิดล่าสุด