Sau khi bạn yêu cầu và được cấp các quyền cần thiết, ứng dụng của bạn có thể truy cập vào phần cứng trên kính phát âm thanh hoặc kính có màn hình. Để truy cập vào phần cứng của kính (thay vì phần cứng của điện thoại), bạn cần sử dụng ngữ cảnh được chiếu.
Có 2 cách chính để lấy ngữ cảnh được chiếu, tuỳ thuộc vào vị trí thực thi mã của bạn:
Lấy ngữ cảnh được chiếu nếu mã của bạn đang chạy trong một hoạt động được chiếu
Nếu mã của ứng dụng đang chạy trong hoạt động được chiếu, thì ngữ cảnh hoạt động riêng của mã đó đã là ngữ cảnh được chiếu. Trong trường hợp này, các lệnh gọi được thực hiện trong hoạt động đó đã có thể truy cập vào phần cứng của kính.
Lấy ngữ cảnh được chiếu cho mã đang chạy trong một thành phần ứng dụng điện thoại
Nếu một phần của ứng dụng bên ngoài hoạt động được chiếu (chẳng hạn như hoạt động trên điện thoại hoặc dịch vụ) cần truy cập vào phần cứng của kính, thì phần đó phải lấy ngữ cảnh được chiếu một cách rõ ràng. Để thực hiện việc này, hãy sử dụng phương thức
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 } }
Kiểm tra tính hợp lệ
Gói lệnh gọi createProjectedDeviceContext trong
ProjectedContext.isProjectedDeviceConnected. Trong khi phương thức này trả về true, ngữ cảnh được chiếu vẫn hợp lệ đối với thiết bị thông minh được kết nối và hoạt động hoặc dịch vụ của ứng dụng điện thoại (chẳng hạn như CameraManager) có thể truy cập vào phần cứng của kính AI.
Dọn dẹp khi ngắt kết nối
Ngữ cảnh được chiếu được liên kết với vòng đời của thiết bị thông minh, vì vậy, ngữ cảnh này sẽ bị huỷ khi thiết bị ngắt kết nối. Khi thiết bị ngắt kết nối,
ProjectedContext.isProjectedDeviceConnected sẽ trả về false. Ứng dụng của bạn sẽ theo dõi sự thay đổi này và dọn dẹp mọi dịch vụ hệ thống (chẳng hạn như CameraManager) hoặc tài nguyên mà ứng dụng của bạn đã tạo bằng ngữ cảnh được chiếu đó.
Khởi động lại khi kết nối lại
Khi kính kết nối lại, ứng dụng của bạn có thể lấy một thực thể ngữ cảnh được chiếu khác bằng cách sử dụng createProjectedDeviceContext, sau đó khởi động lại mọi dịch vụ hoặc tài nguyên hệ thống bằng ngữ cảnh được chiếu mới.
Ghi âm bằng micrô của kính
Bạn có thể ghi âm từ kính bằng 2 phương thức riêng biệt:
- Sử dụng ngữ cảnh được chiếu.
- Sử dụng Cấu hình rảnh tay (HFP) qua Bluetooth.
Chọn phương thức ghi
Phương thức bạn chọn phụ thuộc vào việc bạn cần xử lý âm thanh có độ trung thực cao, dành riêng cho XR hay dữ liệu đầu vào âm thanh Bluetooth tiêu chuẩn.
| Phương thức ghi | Quyền truy cập micrô | Trường hợp sử dụng phổ biến |
|---|---|---|
Ngữ cảnh được chiếu |
Nhiều micrô |
Việc ghi bằng ngữ cảnh được chiếu cho phép ứng dụng của bạn truy cập vào nhiều micrô từ kính và các tính năng phần cứng chuyên biệt của kính, chẳng hạn như:
|
HFP qua Bluetooth |
Một micrô |
Dựa vào Cấu hình rảnh tay (HFP) qua Bluetooth để có khả năng tương thích ngay lập tức. Ở chế độ này, kính kết nối với điện thoại bằng cách sử dụng các cấu hình Tai nghe và Cấu hình phân phối âm thanh nâng cao (A2DP) tiêu chuẩn, hoạt động giống như một thiết bị ngoại vi Bluetooth thông thường. Nếu ứng dụng của bạn đã được thiết kế để ghi âm qua Bluetooth tiêu chuẩn, thì bạn có thể sử dụng phương thức này để ghi âm từ kính mà không cần tích hợp bất kỳ tính năng nào dành riêng cho XR. |
Ghi âm bằng ngữ cảnh được chiếu
Để ghi âm bằng ngữ cảnh được chiếu, trước tiên, hãy yêu cầu các quyền cần thiết khi bắt đầu chạy
sau đó ghi âm bằng API AudioRecord, như
mô tả trong các phần sau.
Yêu cầu quyền khi bắt đầu chạy
Để truy cập vào nhiều micrô trên kính, bạn phải yêu cầu quyền âm thanh dành riêng cho thiết bị được chiếu. Quyền RECORD_AUDIO tiêu chuẩn, có phạm vi là điện thoại mà người dùng đã cấp cho ứng dụng của bạn trên thiết bị di động của họ là không đủ.
Hãy làm theo các bước sau để yêu cầu quyền:
- Khai báo quyền
RECORD_AUDIOtrong tệp kê khai của ứng dụng. Yêu cầu quyền có phạm vi là thiết bị được chiếu theo một trong các cách sau, tuỳ thuộc vào vị trí thực thi mã của bạn:
- Mã thực thi từ một hoạt động được chiếu: Sử dụng
ActivityResultLaunchervớiProjectedPermissionsResultContract. Để biết thêm thông tin về cách sử dụng phương thức này, hãy xem phần đăng ký trình chạy quyền và các phần tiếp theo trong hướng dẫn yêu cầu quyền phần cứng. - Mã thực thi từ một hoạt động trên điện thoại lưu trữ: Sử dụng
Activity#requestPermissions(permissions, requestCode, deviceId)và cung cấp mã thiết bị lấy được từprojectedDeviceContext, như mô tả trong phần tìm hiểu luồng người dùng yêu cầu cấp quyền trong hướng dẫn yêu cầu quyền phần cứng.
- Mã thực thi từ một hoạt động được chiếu: Sử dụng
Khởi chạy AudioRecord bằng ngữ cảnh được chiếu
Để đảm bảo rằng âm thanh được ghi từ kính chứ không phải từ điện thoại lưu trữ, bạn phải liên kết đối tượng AudioRecord với ngữ cảnh thiết bị được chiếu.
Đoạn mã sau đây sử dụng AudioRecord.Builder và truyền
projectedDeviceContext đến phương thức 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()
Các điểm chính về mã
Bạn có thể đặt nguồn âm thanh thành
CAMCORDER,VOICE_RECOGNITION,VOICE_COMMUNICATIONhoặcUNPROCESSEDđể điều chỉnh quy trình xử lý âm thanh cho trường hợp sử dụng cụ thể của bạn.Ví dụ: hãy sử dụng
VOICE_COMMUNICATIONnếu trường hợp sử dụng của bạn cần giảm tiếng ồn tự động.VOICE_RECOGNITIONđược xử lý bằng tính năng khử tiếng vọng âm thanh (AEC). Và nếu bạn cần âm thanh thô, không thay đổi, hãy chọnUNPROCESSEDhoặcCAMCORDER.Để đảm bảo khả năng tương thích với kính, đối tượng
audioFormatphải xác định tốc độ lấy mẫu là 16 kHz và cấu hình kênh là đơn âm hoặc âm thanh nổi (sử dụngCHANNEL_IN_MONOhoặcCHANNEL_IN_STEREO).Sử dụng
AudioRecord.getMinBufferSize()để xác định kích thước bộ đệm tối thiểu nhằm tạo đối tượngAudioRecord. Tuy nhiên, để ngăn âm thanh bị gián đoạn từ kính, bạn nên đọc từ bộ đệm này theo các phần ngắn và thường xuyên (tốt nhất là các phần 20 ms) thay vì đợi toàn bộ bộ đệm được lấp đầy.
Dọn dẹp sau khi sử dụng
Khi ứng dụng của bạn không còn cần micrô hoặc khi hoạt động bị dừng,
hãy gọi stop và release trên đối tượng AudioRecord.
Kiểm tra quyền khi bắt đầu chạy trước khi ghi
Trước khi gọi startRecording, xác minh rằng người dùng đã cấp quyền
micrô cho kínhbằng ngữ cảnh được chiếu.
Ghi âm bằng HFP qua Bluetooth
Để ghi âm bằng HFP qua Bluetooth, trước tiên, hãy yêu cầu các quyền cần thiết khi bắt đầu chạy
, sau đó ghi âm bằng API AudioManager, như
mô tả trong các phần sau.
Yêu cầu cấp quyền
Giống như mọi thiết bị âm thanh Bluetooth tiêu chuẩn, RECORD_AUDIO,
BLUETOOTH_CONNECT và các quyền liên quan khác đều do
điện thoại kiểm soát chứ không phải thiết bị thông minh được kết nối (chẳng hạn như kính phát âm thanh hoặc kính có màn hình).
Hãy làm theo các bước sau để yêu cầu quyền:
Khai báo các quyền sau trong tệp kê khai của ứng dụng:
Yêu cầu cả quyền
RECORD_AUDIOvàBLUETOOTH_CONNECTkhi bắt đầu chạy bằng quy trình cấp quyền Android tiêu chuẩn.
Sử dụng AudioManager để định tuyến âm thanh
Sau khi người dùng cấp cho ứng dụng của bạn các quyền cần thiết khi bắt đầu chạy, hãy sử dụng API
AudioManager để đặt thiết bị giao tiếp thành
TYPE_BLUETOOTH_SCO nhằm định tuyến âm thanh thông qua HFP qua Bluetooth. Thao tác này sẽ hướng hệ thống truy xuất âm thanh từ thiết bị ngoại vi 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()
Chụp ảnh bằng camera của kính
Để chụp ảnh bằng camera của kính, hãy thiết lập và liên kết trường hợp sử dụng
ImageCapture với camera của kính bằng ngữ cảnh chính xác
cho ứng dụng của bạn:
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)) }
Các điểm chính về mã
- Lấy một thực thể của
ProcessCameraProviderbằng ngữ cảnh thiết bị được chiếu. - Trong phạm vi ngữ cảnh được chiếu, camera chính hướng ra ngoài của kính sẽ ánh xạ đến
DEFAULT_BACK_CAMERAkhi chọn camera. - Quá trình kiểm tra trước khi liên kết sử dụng
cameraProvider.hasCamera(cameraSelector)để xác minh rằng camera đã chọn có trên thiết bị trước khi tiếp tục. - Sử dụng Camera2 Interop với
Camera2CameraInfođể đọcCameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAPcơ bản, có thể hữu ích cho các hoạt động kiểm tra nâng cao về độ phân giải được hỗ trợ. - `
ResolutionSelector` tuỳ chỉnh được tạo để kiểm soát chính xác độ phân giải hình ảnh đầu ra cho `ImageCapture`. - Tạo trường hợp sử dụng
ImageCaptuređược định cấu hình bằngResolutionSelectortuỳ chỉnh. - Liên kết trường hợp sử dụng
ImageCapturevới vòng đời của hoạt động. Thao tác này sẽ tự động quản lý việc mở và đóng camera dựa trên trạng thái của hoạt động (ví dụ: dừng camera khi hoạt động bị tạm dừng).
Sau khi thiết lập camera của kính, bạn có thể chụp ảnh bằng lớp ImageCapture của CameraX. Hãy tham khảo tài liệu của CameraX để tìm hiểu
về cách sử dụng takePicture để chụp ảnh.
Quay video bằng camera của kính
Để quay video thay vì chụp ảnh bằng camera của kính, hãy thay thế các thành phần
ImageCapture tương ứng với các thành phần VideoCapture và sửa đổi logic thực thi quá trình quay.
Các thay đổi chính liên quan đến việc sử dụng một trường hợp sử dụng khác, tạo một tệp đầu ra khác và bắt đầu quá trình quay bằng phương thức quay video thích hợp.
Để biết thêm thông tin về API VideoCapture và cách sử dụng API này, hãy xem
tài liệu về tính năng quay video của CameraX.
Bảng sau đây cho thấy độ phân giải và tốc độ khung hình được đề xuất tuỳ thuộc vào trường hợp sử dụng của ứng dụng:
| Trường hợp sử dụng | Độ phân giải | Tốc độ khung hình |
|---|---|---|
| Giao tiếp bằng video | 1280 x 720 | 15 FPS |
| Thị giác máy tính | 640 x 480 | 10 FPS |
| Truyền video AI | 640 x 480 | 1 FPS |
Truy cập vào phần cứng của điện thoại từ một hoạt động được chiếu
Hoạt động được chiếu cũng có thể truy cập vào phần cứng của điện thoại (chẳng hạn như
camera hoặc micrô) bằng cách sử dụng createHostDeviceContext(context) để lấy
ngữ cảnh của thiết bị lưu trữ (điện thoại):
@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 } }
Khi truy cập vào phần cứng hoặc tài nguyên dành riêng cho thiết bị lưu trữ (điện thoại) trong một ứng dụng kết hợp (ứng dụng chứa cả trải nghiệm trên thiết bị di động và kính), bạn phải chọn ngữ cảnh chính xác một cách rõ ràng để đảm bảo ứng dụng của bạn có thể truy cập vào phần cứng chính xác:
- Sử dụng ngữ cảnh
ActivitytừActivitycủa điện thoại hoặcProjectedContext.createHostDeviceContextđể lấy ngữ cảnh của điện thoại. - Đừng sử dụng
getApplicationContextvì ngữ cảnh ứng dụng có thể trả về ngữ cảnh của mắt kính không chính xác nếu hoạt động được chiếu là thành phần được chạy gần đây nhất.