一、为什么做这个
经常在YouTube上看英文技术视频,自动字幕经常翻译得乱七八糟。掏手机查单词吧,一会儿就跟不上进度了。试过蓝牙耳机+同传App,但只有语音没文字,听不清就完全懵了。
后来看到Rokid的AR眼镜,想着能不能把翻译内容直接显示在视野里?这样看视频、跟人面对面交流的时候,不用频繁低头看手机,也不会错过什么。正好SDK开放了,就试着做了一个。

本文记录下用Rokid CXR-M SDK开发实时翻译应用的过程,包括代码实现和踩过的坑。
二、认识Rokid CXR-M SDK
Rokid AR生态概览
Rokid是国内AR眼镜领域的主要厂商之一,其产品线覆盖消费级和企业级市场。根据Rokid官网的介绍,主要产品包括Rokid Air、Rokid Max等消费级AR眼镜,以及面向企业应用的Rokid Glasses。这些设备都运行在Rokid自研的YodaOS操作系统上。
对开发者而言,Rokid提供了两套SDK:
- CXR-S SDK:面向眼镜端的原生开发
- CXR-M SDK:面向手机端的协同开发
我们选择CXR-M SDK的原因很简单:它允许开发者在熟悉的Android环境中完成所有开发工作,无需在眼镜端编写原生应用。手机负责复杂的运算和网络通信,眼镜专注于内容展示,这种分工方式对移动端开发者更友好。
CXR-M SDK的核心能力
根据官方SDK文档,当前版本是V1.0.1(2025年8月25日更新),提供了9大核心功能。其中"翻译场景"正是我们实现实时翻译的关键能力。

重要说明 :根据Rokid翻译场景文档,翻译场景提供的是显示框架,而非完整的翻译功能:
- ✅ SDK提供:翻译内容的UI渲染、文本显示、场景控制
- ❌ SDK不提供:语音识别、文本翻译引擎
- 📝 开发者需要:自己对接ASR和翻译API,然后通过SDK推送结果
也就是说,我们需要自己实现"语音→文字→翻译"的完整链路,SDK负责最后一步"在眼镜上显示"。这种分工让开发者可以自由选择ASR和翻译服务商,灵活性更高。
三、技术架构设计
整体架构
系统采用经典的双端协同架构,手机端作为计算中心,眼镜端作为显示终端:

整个流程的关键路径包括:语音采集、ASR识别、翻译API、数据传输。
数据流转过程:

技术选型说明
开发语言:Kotlin,使用协程处理异步网络请求。
网络框架:Retrofit 2.9 + OkHttp 4.12
ASR****服务:选择腾讯云实时语音识别,支持WebSocket流式识别,延迟在200-300ms,适合实时场景。
翻译服务:腾讯云机器翻译API,支持中英日韩等主流语种。
SDK连接方式:推荐使用蓝牙连接,稳定性较好且不受网络切换影响。翻译场景只传输文本数据,蓝牙带宽完全够用。
选ASR服务时,流式识别是关键。批量识别延迟太高(3-5秒)没法用,流式识别虽然中间结果可能不准,但能"边说边显示",体验好很多。SDK不限制服务商,可以自己选。
四、开发环境配置
SDK集成
首先在settings.gradle.kts
中添加Rokid Maven仓库:
Kotlin
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven { url = uri("https://maven.rokid.com/repository/maven-public/") }
google()
mavenCentral()
}
}

在模块的build.gradle.kts
中添加依赖:
Kotlin
android {
compileSdk = 34
defaultConfig {
applicationId = "com.example.rokid.translator"
minSdk = 28 // Rokid SDK最低要求
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
// Rokid CXR-M SDK
implementation("com.rokid.cxr:client-m:1.0.1")
// 网络请求
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// 协程
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
// ViewModel和LiveData
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
// JSON处理
implementation("com.google.code.gson:gson:2.10.1")
}
权限配置
在AndroidManifest.xml
中声明必要权限:
XML
<!-- 蓝牙权限 (Android 12+) -->
<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_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!-- 音频权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
五、核心功能实现
SDK初始化与设备连接
这是使用SDK的第一步,我们需要在应用启动时初始化SDK,并建立与Rokid眼镜的连接。
首先在Application中初始化Rokid SDK:
Kotlin
class TranslatorApp : Application() {
override fun onCreate() {
super.onCreate()
// 初始化CXR-M SDK
RokidCXRManager.init(this) { success, error ->
if (success) {
Log.d(TAG, "SDK初始化成功")
}
}
}
}
接下来在Activity中扫描并连接Rokid设备:
Kotlin
// 扫描Rokid设备
RokidCXRManager.startScan { device ->
if (device.name.contains("Rokid", ignoreCase = true)) {
deviceList.add(device)
}
}
// 连接选中的设备
RokidCXRManager.connect(selectedDevice) { success, error ->
if (success) {
// 连接成功,可以开始使用
}
}
初次使用扫不到设备?检查一下蓝牙和定位权限。Android 12以上必须同时授予BLUETOOTH_SCAN
和ACCESS_FINE_LOCATION
权限。扫描前最好先检查蓝牙是否开启。
语音识别集成
完成设备连接后,下一步是采集用户的语音并进行实时识别。这里需要配合第三方ASR服务。
首先配置AudioRecord进行音频采集:
Kotlin
val audioRecord = AudioRecord(
MediaRecorder.AudioSource.MIC,
16000, // 采样率
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize
)
然后通过WebSocket连接ASR服务,实时推送音频数据并接收识别结果:
Kotlin
// 建立WebSocket连接
val webSocket = client.newWebSocket(asrUrl, object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
val result = parseASRResponse(text)
if (result.isFinal) {
// 识别完成,进行翻译
translateAndDisplay(result.text)
}
}
})
// 发送音频数据
val base64Audio = Base64.encodeToString(audioData, Base64.NO_WRAP)
webSocket.send(JSONObject().apply {
put("data", base64Audio)
}.toString())
语音识别容易出问题的两个点:
- 音频格式要按ASR服务要求来,我一开始用48kHz,识别全是乱码,改成16kHz才正常
- 不要每次read就发送,累积到一定量(比如40ms音频)再发,不然浪费带宽
示例:说"Hello, how are you?" → ASR识别出文本 → 进入翻译
翻译服务对接
使用Retrofit调用腾讯云翻译API:
Kotlin
suspend fun translate(text: String, fromLang: String, toLang: String): String {
// 先查本地缓存,避免重复翻译
val cacheKey = "$text|$fromLang|$toLang"
translationCache.get(cacheKey)?.let {
Log.d(TAG, "缓存命中: $text")
return it
}
// 调用腾讯云翻译API
val response = api.translate(TranslateRequest(
sourceText = text,
source = fromLang, // 源语言:en
target = toLang // 目标语言:zh
))
val result = response.body()?.targetText ?: ""
Log.d(TAG, "翻译结果: $text → $result")
// 缓存结果,下次直接使用
translationCache.put(cacheKey, result)
return result
}
调用示例:
Kotlin
// 从ASR获得识别结果后
scope.launch {
val originalText = "Hello, how are you?"
val translatedText = translate(originalText, "en", "zh")
// 得到翻译结果后,推送到眼镜显示
displayOnGlasses(originalText, translatedText)
}
建议加个本地缓存。会议场景很多专业术语会重复出现("API"、"SDK"),缓存后响应能从300ms降到几乎0延迟,还能省API费用。我用LruCache实现,500条容量够用了。
示例流程:
- ASR识别:
"Hello, how are you?"
- 翻译API返回:
"你好,你好吗?"
- 推送到眼镜显示
推送到眼镜显示(核心)
这是整个流程的最后一步,也是最关键的一步------通过Rokid SDK将翻译结果显示在眼镜上。根据官方翻译场景文档,需要分三步操作。
SDK调用流程:

第一步:打开翻译场景
Kotlin
// 打开翻译场景(通常在Activity的onCreate或连接成功后调用)
CxrApi.getInstance().controlScene(
ValueUtil.CxrSceneType.Translation,
true, // true表示打开场景
null
)
Log.d(TAG, "翻译场景已打开")
第二步:配置显示参数(可选)
Kotlin
// 配置文本显示参数
CxrApi.getInstance().configTranslationText(
textSize = 32, // 文字大小(推荐28-36)
startPointX = 0, // 起始X坐标
startPointY = 0, // 起始Y坐标
width = 800, // 显示区域宽度
height = 600 // 显示区域高度
)
第三步:发送翻译内容
Kotlin
// 完整示例:发送翻译结果
fun displayOnGlasses(originalText: String, translatedText: String) {
vadId++ // 每次新的翻译,VAD序号递增
// 组织要显示的内容
val content = buildString {
append("原文: $originalText\n")
append("译文: $translatedText")
}
// 推送到眼镜显示
CxrApi.getInstance().sendTranslationContent(
id = vadId, // VAD序号,用于区分不同段落
subId = 0, // 子ID,通常为0
temporary = false, // false=持久显示,true=临时显示
finished = true, // true=翻译完成,false=还在处理中
content = content
)
Log.d(TAG, "已推送到眼镜: $content")
}
实际运行效果:
当用户说 "Hello, how are you?"
时,眼镜上会实时显示:
原文: Hello, how are you?
译文: 你好,你好吗?
然后用户继续说 "I'm fine, thank you!"
时,眼镜会更新显示:
原文: I'm fine, thank you!
译文: 我很好,谢谢你!
几个要注意的点:
-
内容格式自己控制:SDK只负责显示,具体显示啥、怎么排版都是你说了算。我用"原文+译文"格式,你也可以只显示译文。
-
VAD****序号要递增 :
vadId
用来区分不同段落,不递增的话新内容会覆盖旧的。每次识别完成后vadId++
就行。 -
更新别太快:更新太频繁(100ms一次)会闪,建议500ms以上,或者只在识别完成时更新。
-
超长文本截断:眼镜显示区域有限,超过3行(约60字符/行)就显示不全了,记得截断。
-
用完关场景:退出时记得关闭翻译场景释放资源。
完整运行效果演示:
假设在一个国际会议上,外籍嘉宾用英文发言:
Plain
嘉宾说:"Welcome to our conference today."
→ 眼镜显示:
原文: Welcome to our conference today.
译文: 欢迎来到我们今天的会议。
嘉宾继续说:"Let's discuss the new features."
→ 眼镜更新显示:
原文: Let's discuss the new features.
译文: 让我们讨论一下新功能。
嘉宾说:"Any questions?"
→ 眼镜再次更新:
原文: Any questions?
译文: 有什么问题吗?
整个过程流畅自然,延迟控制在1-2秒内,可以边看眼镜边保持眼神交流。

几个关键点:
- 翻译API是耗时操作,用协程异步处理,不然会卡UI
- 网络请求可能失败,catch后至少显示原文,别啥都不显示
- onDestroy里记得清理资源,不然退出后眼镜还在显示
- 每一步都加日志,方便调试
六、扩展思路
基于Rokid SDK的灵活性,翻译应用还有很多改进空间:
可扩展功能架构:

1. 语言自动检测
当前需要手动设置翻译方向(中译英或英译中),但实际对话中常常是混合语言。可以加入语言检测,自动判断说的是哪种语言,然后翻译成目标语言。
Kotlin
// 简单的语言检测示例
fun detectLanguage(text: String): String {
val chineseRatio = text.count { it in '\u4e00'..'\u9fa5' }.toFloat() / text.length
return when {
chineseRatio > 0.3 -> "zh" // 中文占比超过30%
text.matches(Regex(".*[a-zA-Z]{3,}.*")) -> "en" // 包含英文单词
else -> "auto" // 自动检测
}
}
2. 翻译历史管理
把每次翻译记录保存到本地数据库,可以实现:
- 会后导出完整对话记录(双语对照)
- 常用语句快速查询
- 个人专属术语库积累
3. 多人会议支持
利用Rokid SDK的多内容显示能力,可以同时显示多人的发言翻译:
Plain
[张三]: 我们的产品进展如何?
[John]: The progress is good.
(进展很好。)
4. 专业领域适配
针对医疗、法律等专业领域,可以加载专业词典。Rokid SDK只负责显示,翻译引擎可以根据场景自由切换:
Kotlin
// 根据场景加载不同词典
fun loadDictionary(scene: String) {
val dict = when(scene) {
"medical" -> loadMedicalTerms()
"legal" -> loadLegalTerms()
"tech" -> loadTechTerms()
else -> emptyMap()
}
translationService.updateDictionary(dict)
}
扩展功能时别贪多。我最初想加离线翻译、多语种、语音合成一堆功能,结果应用变得又慢又臃肿。后来砍掉大半,只留核心功能,反而体验更好。SDK灵活性是很高,但不代表都要用上,够用就行。
七、几个坑
开发过程中踩过几个坑,记录一下:
1. 生命周期管理别忘了
用完记得关闭翻译场景,不然切换页面后眼镜还在显示内容。在onDestroy
里调用:
Kotlin
CxrApi.getInstance().controlScene(
ValueUtil.CxrSceneType.Translation,
false,
null
)
2. VAD****序号要递增
vadId
必须每次识别完成后递增,不然新内容会覆盖旧的。正确做法:
Kotlin
private var vadId = 0
fun display(text: String) {
vadId++ // 先递增
CxrApi.getInstance().sendTranslationContent(id = vadId, ...)
}
3. 更新别太频繁
ASR中间结果实时显示会导致闪烁。建议中间结果用temporary=true
,500ms更新一次;最终结果用temporary=false
,立即显示。
4. 长文本要截断
眼镜显示区域有限,超过60字符左右就显示不全了。可以在标点处截断,或者干脆只显示最后一句。
5. 加日志方便调试
每个关键步骤都加日志,出问题时好定位:
Kotlin
Log.d(TAG, "ASR识别: $text")
Log.d(TAG, "翻译完成: $original → $translated")
Log.d(TAG, "推送VAD ID: $vadId")
八、总结
用Rokid SDK开发AR翻译应用,核心就这几步:初始化SDK、连接设备、打开翻译场景、接入ASR和翻译API、把结果推送到眼镜显示。SDK只负责显示部分,语音识别和翻译要自己对接第三方服务。
几个关键点:
- VAD序号记得递增
- 更新频率别太高,会闪
- 长文本要截断
- 用完关闭场景释放资源
整体开发体验还可以,在Android环境下开发比较顺手。除了翻译,SDK还支持字幕、导航这些场景,后面有空可以试试。
更多细节看Rokid开发者论坛。