技术选型与整体架构
核心技术栈
- 移动端:Kotlin + Android Jetpack(ViewModel, LiveData)
- SDK:Rokid CXR-M SDK v1.0.1-Preview(仅支持 Android)
- 通信协议:蓝牙(控制信令) + Wi-Fi P2P(媒体同步)
- UI 渲染:眼镜端使用 SDK 内置的「自定义页面场景」(JSON 驱动)
系统架构图
整个系统以 手机为控制中枢,眼镜为 显示与交互终端。CXR-M SDK 作为桥梁,封装了底层通信细节,让我们能专注于业务逻辑。
项目初始化:集成 CXR-M SDK
配置 Maven 仓库
在 settings.gradle.kts 中添加 Rokid 私有仓库:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven { url = uri("https://maven.rokid.com/repository/maven-public/") }
google()
mavenCentral()
}
}
添加依赖与权限
在 build.gradle.kts 中:
android {
defaultConfig {
minSdk = 28 // 必须 ≥28
}
}
dependencies {
implementation("com.rokid.cxr:client-m:1.0.1-20250812.080117-2")
// 其他 transitive 依赖(如 Retrofit, OkHttp)SDK 已声明,无需重复添加
}
在 AndroidManifest.xml 中声明权限(缺一不可):
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
⚠️ 注意:从 Android 12(API 31)起,蓝牙扫描/连接需动态申请 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT,且必须搭配 ACCESS_FINE_LOCATION。
权限动态申请封装
我封装了一个 PermissionHelper,在 MainActivity 启动时调用:
class PermissionHelper(private val activity: AppCompatActivity) {
fun requestRequiredPermissions(onGranted: () -> Unit) {
val permissions = mutableListOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
permissions += Manifest.permission.BLUETOOTH_SCAN
permissions += Manifest.permission.BLUETOOTH_CONNECT
}
if (permissions.all { activity.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED }) {
onGranted()
} else {
activity.requestPermissions(permissions.toTypedArray(), REQUEST_CODE)
}
}
}
只有权限全部授予,才进入设备连接流程。
设备连接:蓝牙 + Wi-Fi 双通道建立
蓝牙连接流程
CXR-M SDK 的蓝牙连接分为两步:初始化 → 连接。
// 1. 初始化蓝牙(传入扫描到的 BluetoothDevice)
CxrApi.getInstance().initBluetooth(context, device, object : BluetoothStatusCallback {
override fun onConnectionInfo(socketUuid, macAddress, rokidAccount, glassesType) {
// 保存 socketUuid 和 macAddress,用于后续连接
connectBluetooth(socketUuid!!, macAddress!!)
}
override fun onConnected() { /* 连接成功 */ }
override fun onDisconnected() { /* 断开处理 */ }
override fun onFailed(errorCode) { /* 错误处理 */ }
})
// 2. 正式连接
private fun connectBluetooth(uuid: String, mac: String) {
CxrApi.getInstance().connectBluetooth(context, uuid, mac, callback)
}
💡 提示:socketUuid 是 Rokid Glasses 在 BLE 广播中携带的自定义服务 UUID(00009100-...),用于建立 RFCOMM 通道。
Wi-Fi P2P 初始化(用于后续文件同步)
提词内容虽小,但若未来扩展图片/视频提词,则需 Wi-Fi 通道。初始化如下:
val wifiStatus = CxrApi.getInstance().initWifiP2P(object : WifiP2PStatusCallback {
override fun onConnected() {
Log.d("WiFi", "P2P connected")
}
override fun onDisconnected() { /* ... */ }
override fun onFailed(errorCode) { /* ... */ }
})
核心功能实现:提词器场景构建
打开提词器场景
fun openWordTips() {
CxrApi.getInstance().controlScene(
ValueUtil.CxrSceneType.WORD_TIPS,
true, // true = 打开
null
)
}
发送提词内容
提词内容以 ByteArray 形式通过 sendStream 发送:
fun sendScript(text: String, fileName: String = "script.txt") {
CxrApi.getInstance().sendStream(
ValueUtil.CxrStreamType.WORD_TIPS,
text.toByteArray(),
fileName,
object : SendStatusCallback {
override fun onSendSucceed() {
Log.d("Script", "Sent successfully")
}
override fun onSendFailed(errorCode) {
Log.e("Script", "Send failed: $errorCode")
}
}
)
}
配置提词器显示参数
支持字体大小、行距、显示区域、滚动模式等:
fun configureWordTips() {
CxrApi.getInstance().configWordTipsText(
textSize = 18f, // 字体大小(sp)
lineSpace = 1.5f, // 行距
mode = "ai", // "ai" 模式支持 ASR 自动滚动
startPointX = 100, // X 起始坐标(像素)
startPointY = 200, // Y 起始坐标
width = 800, // 显示区域宽
height = 600 // 显示区域高
)
}
AI 模式下的 ASR 联动
当用户说话时,手机端 ASR 引擎识别结果,实时发送给眼镜,触发自动滚动:
fun onAsrResult(text: String) {
CxrApi.getInstance().sendAsrContent(text) // 注意:此处复用 AI 场景的 ASR 接口
}
关键点:提词器的 "ai" 模式会监听 ASR 内容,当识别文本接近当前显示末尾时,自动向上滚动,确保用户始终看到下一句。
增强体验:设备状态控制与监听
远程调节亮度/音量
演讲环境光线多变,允许用户在手机端滑动调节眼镜亮度:
// 设置监听器(获取当前亮度)
CxrApi.getInstance().setBrightnessUpdateListener { brightness ->
viewModel.updateBrightnessUI(brightness)
}
// 设置亮度(0-15)
CxrApi.getInstance().setGlassBrightness(10)
同理,音量控制也支持双向同步。
电量与充电状态监听
CxrApi.getInstance().setBatteryLevelUpdateListener { level, charging ->
Log.d("Battery", "Level: $level%, Charging: $charging")
if (level < 20 && !charging) {
showLowBatteryWarning()
}
}
自动关机与熄屏策略
为节省电量,设置 10 分钟无操作自动关机:
CxrApi.getInstance().setPowerOffTimeout(10) // 单位:分钟
CxrApi.getInstance().setScreenOffTimeout(60) // 60秒无操作熄屏
异常处理与健壮性设计
连接断开重连机制
监听 onDisconnected(),尝试自动重连:
override fun onDisconnected() {
viewModel.setConnectionStatus(false)
// 延迟 3 秒后重试
handler.postDelayed({
if (lastMac != null && lastUuid != null) {
connectBluetooth(lastUuid!!, lastMac!!)
}
}, 3000)
}
权限缺失友好提示
若用户拒绝关键权限,引导其手动开启:
if (!allGranted) {
AlertDialog.Builder(this)
.setTitle("权限不足")
.setMessage("请授予位置和蓝牙权限,否则无法连接眼镜")
.setPositiveButton("去设置") { _, _ ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:$packageName")
startActivity(intent)
}
.show()
}
SDK 错误码解析
CXR-M SDK 定义了详细的错误码(如 CxrBluetoothErrorCode.SOCKET_CONNECT_FAILED),我在日志中统一映射为用户可读信息:
when (errorCode) {
ValueUtil.CxrBluetoothErrorCode.BLE_CONNECT_FAILED -> "蓝牙连接失败,请靠近眼镜重试"
ValueUtil.CxrWifiErrorCode.WIFI_DISABLED -> "请先打开手机 Wi-Fi"
else -> "未知错误,请重启应用"
}
性能优化与用户体验细节
避免频繁发送相同内容
在发送提词脚本前,做内容哈希比对,避免重复传输:
if (currentScriptHash != newScript.hashCode()) {
sendScript(newScript)
currentScriptHash = newScript.hashCode()
}
Wi-Fi P2P 按需开启
仅在用户点击“同步历史图片”时才初始化 Wi-Fi,用完立即 deinitWifiP2P()。
自定义页面作为备选方案
若未来提词需求复杂(如图文混排),可使用「自定义页面场景」:
{
"type": "LinearLayout",
"props": { "orientation": "vertical" },
"children": [
{ "type": "TextView", "props": { "text": "标题", "textSize": "20sp" } },
{ "type": "ImageView", "props": { "name": "icon1" } }
]
}
先调用 sendCustomViewIcons() 上传图标,再 openCustomView(json) 显示。
总结
本项目成功基于Rokid CXR-M SDK,构建了一个以手机为控制中枢、AR眼镜为显示终端的提词器应用。通过蓝牙与Wi-Fi P2P的双通道稳定通信,我们不仅实现了提词内容的实时推送与AI语音联动滚动,还完成了设备状态监控、远程调节等增强功能。整个架构通过模块化设计与全面的异常处理机制,确保了应用的流畅性、稳定性与良好的用户体验,为AR在演讲、直播等场景的应用提供了可靠的实践方案。