使用投影的上下文来访问 AI 眼镜硬件

适用的 XR 设备
本指南可帮助您为以下类型的 XR 设备打造优质体验。
AI 眼镜

在您请求并获得必要的权限后,您的应用 即可访问 AI 眼镜硬件。访问眼镜硬件 (而不是手机硬件)的关键是使用投影上下文

根据代码的执行位置,您可以通过以下两种主要方式获取投影上下文:

如果代码在 AI 眼镜 activity 中运行,请获取投影上下文

如果应用的代码在 AI 眼镜 activity 中运行,则其自身的 activity 上下文已是投影上下文。在这种情况下,在该 activity 中进行的调用已可以访问眼镜的硬件。

为在手机应用组件中运行的代码获取投影上下文

如果 AI 眼镜 activity 之外的应用部分(例如手机 activity 或服务)需要访问眼镜的硬件,则必须明确获取投影上下文。为此,请使用 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
    }
}

检查有效性

创建投影上下文后,请监控 ProjectedContext.isProjectedDeviceConnected。当此方法返回 true 时,投影上下文对已连接的设备仍然有效,并且您的手机应用活动或服务(例如 CameraManager)可以访问 AI 眼镜硬件。

断开连接时清理

投影上下文与已连接的设备的生命周期相关联,因此会在设备断开连接时被销毁。当设备断开连接时, ProjectedContext.isProjectedDeviceConnected 会返回 false。您的应用应监听此更改,并清理应用使用该投影上下文创建的任何系统服务(例如 CameraManager)或资源。

重新连接时重新初始化

当 AI 眼镜设备重新连接时,您的应用可以使用 createProjectedDeviceContext() 获取另一个投影 上下文实例,然后 使用新的投影上下文重新初始化任何系统服务或资源。

使用蓝牙访问音频

目前,AI 眼镜作为标准蓝牙音频设备连接到您的手机。耳机和 A2DP(高级音频分发配置文件) 配置文件均受支持。使用此方法,任何支持音频输入或输出的 Android 应用都可以在眼镜上运行,即使这些应用并非专门为支持眼镜而构建也是如此。在某些情况下,使用蓝牙可能比使用投影上下文访问眼镜硬件更适合您的应用用例。

与任何标准蓝牙音频设备一样,授予 RECORD_AUDIO权限的权限由手机控制,而不是由眼镜控制。

使用 AI 眼镜的相机拍摄图片

如需使用 AI 眼镜的相机拍摄图片,请使用应用的正确 上下文设置 CameraX 的 ImageCapture 用例并将其绑定到眼镜的相机:

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

代码要点

设置 AI 眼镜的相机后,您可以使用 CameraX 的 ImageCapture 类拍摄图片。如需了解 如何使用 takePicture()拍摄图片,请参阅 CameraX 的文档。

使用 AI 眼镜的相机拍摄视频

如需使用 AI 眼镜的相机拍摄视频而不是图片,请将 ImageCapture 组件替换为相应的 VideoCapture 组件 并修改拍摄执行逻辑。

主要更改包括使用不同的用例、创建不同的输出文件,以及使用适当的视频录制方法启动拍摄。 如需详细了解 VideoCapture API 及其使用方法,请参阅 CameraX 的视频拍摄文档

下表显示了根据应用用例推荐的分辨率和帧速率:

用例 分辨率 帧速率
视频通信 1280 x 720 15 FPS
计算机视觉 640 x 480 10 FPS
AI 视频流式传输 640 x 480 1 FPS

从 AI 眼镜 activity 访问手机的硬件

AI 眼镜 activity 还可以使用 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
    }
}

在混合应用(同时包含移动设备和 AI 眼镜体验的应用)中访问特定于主机设备(手机)的硬件或资源时,您必须明确选择正确的上下文,以确保应用可以访问正确的硬件: