在这篇文章里,我想分享一个基于 Rokid CXR-M SDK + 大模型(ASR + LLM) 打造的实验项目------「会议透镜(Meeting Lens)」。
佩戴 Rokid AI 眼镜进入会议室,你只需要正常发言、讨论、举手、点头;
会议结束时:
- 结构化纪要(议题、结论、行动项)已经生成
- 关键决策片段自动高亮
- 下一步 To-do 直接推送到你的手机 / 协作工具
甚至在会议进行中,眼镜已经实时把「当前议题」「待确认事项」浮现在你眼前。
下面我会围绕 CXR-M SDK 的真实能力与接口*展开,从环境配置、设备连接、音频流采集,到端云协同的 AI 工作流,以及如何利用 提词器场景 + 自定义页面(Custom View),把实时要点和最终纪要回传到眼镜端显示。
从"做笔记"到"只开口":会议纪要的重构
1.1 传统会议纪要的瓶颈
大多数人的会议体验大概是这样的:
- 一边听,一边记,错过别人说了什么
- 会议结束堆满语音、照片、零散文档
- 真正有用的是那几条:决议 & 行动项 ------ 却偏偏最难完整记录
- 远程 / 现场混合开会时,多人同时说话,回放和标注十分痛苦
「会议透镜」想改变的是:
让"会议纪要"不再是人的负担,而是 AI 的职责。 人只负责 表达 和 决策,记录、整理、分发全部自动完成。
1.2 为什么选择 Rokid + CXR-M SDK 来做会议助手
要做一个「只要戴上就能自动做纪要」的系统,需要同时满足:
-
第一视角 & 免手持
- 眼镜自带麦克风,天然贴近发言者
- 屏幕就在视野里,适合实时展示要点、提醒「还有一个行动项没确认」
-
端云协同能力
- 眼镜通过 蓝牙 / Wi-Fi 与手机建立稳定链路
- 手机有充足算力跑本地 ASR / 噪声处理
- 云端大模型负责长期记忆、多轮推理、结构化输出
Rokid 的 CXR-M(Mobile SDK) 正好提供了这一整套基础设施:
- 蓝牙扫描与连接、状态管理
- Wi-Fi P2P 高速链路(适合同步录音文件、会议多媒体)
- 眼镜端音频流采集(
openAudioRecord/setAudioStreamListener) - AI 场景事件监听(
AiEventListener) - 提词器 / 翻译 / 自定义页面等场景,用于在眼镜上展示文字与 UI
对「会议透镜」这样的项目来说,最自然的架构是:
- 眼镜: 采集 & 显示
- 手机: 通信 + AI 工作流(ASR + LLM)
- 云端: 大规模模型推理 & 长期存储
整体架构:端 -- 云协同的「会议透镜」
先看一眼 Meeting Lens 的数据流(加一张简易流程图方便读者理解整体链路):
scss
[用户佩戴 Rokid 眼镜]
│ 1. 讲话 / 讨论
▼
[眼镜麦克风]
│ 2. 音频流(蓝牙)
▼
[CXR-M SDK @ 手机]
│ 3. AudioStreamListener 接收音频
│ 分段 / 预处理
▼
[MeetingLensManager @ 手机]
│ 4. 调用后端 /asr/stream 做实时转写
│ 累积 transcript
│ 5. 定时调用 /meeting/summarize 做中间 & 最终纪要
▼
[后端 AI 服务]
│ ASR(Whisper/讯飞...) + LLM(大模型)
│ 输出:summary / 决策 / To-do
▼
[MeetingLensManager]
│ 6. 通过 Custom View / 提词器
│ 把纪要 & TODO 推回眼镜
▼
[眼镜显示层:Meeting View]
→ 实时展示议题 / 结论 / 行动项
2.1 会议现场采集
- 佩戴 Rokid 眼镜,启动「会议助手」AI 场景
- 眼镜端麦克风启动录音,音频流通过 CXR-M 回传手机
2.2 端侧预处理
- 手机端对音频做降噪、VAD(分段)、可能的说话人分离
- 分段音频送往本地 / 云端 ASR 服务(英文、中文或多语种)
2.3 云端 AI 纪要引擎
-
基于 ASR 文本 + 说话人标签
-
使用 LLM 做:逐段总结 + 会议中期小结 + 会后全局总结
-
输出结构化结果:
- 议题列表
- 讨论过程摘要
- 决策
- 待办(Owner + 截止时间)
2.4 结果回传 & 实时提示
-
会议进行中:
- 当前议题 / 刚刚说的关键句被高亮为「候选决议」
- 经手动确认后固化为决议
- 通过 提词器 / Custom View 回传到眼镜显示
-
会议结束:
- 最终纪要发送手机 App / 邮件 / IM 群
- 眼镜屏幕展示「本次会议 X 项关键决策 + Y 个行动项」
2.5 循环刷新
- 每当检测到一个「新的议题块」开始(通过主持人语句、关键词或 UI 交互),重启一轮 ASR + 摘要,保证结构清晰。
开发环境与 CXR-M 集成
3.1 基础环境
- Android Studio 2023.1+
- Android 手机(Android 9.0+)
- Rokid AR 眼镜(开启开发者模式)
- JDK 8+
- Kotlin 1.8+/1.9+(本文代码以 Kotlin 为主)
3.2 添加 Rokid Maven 仓库与 CXR-M 依赖
根据官方文档,CXR-M SDK 发布在 Rokid 自有 Maven 仓库,需要在 settings.gradle.kts 里添加:
scss
// settings.gradle.kts
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven { url = uri("https://maven.rokid.com/repository/maven-public/") }
google()
mavenCentral()
}
}
在 app/build.gradle.kts 中引入 SDK:
ini
android {
compileSdk = 34
defaultConfig {
applicationId = "com.example.meetinglens"
minSdk = 28 // CXR-M 要求的最小版本
targetSdk = 34
versionCode = 1
versionName = "1.0"
}
}
dependencies {
implementation("com.rokid.cxr:client-m:1.0.1-20250812.080117-2")
// Retrofit / OkHttp / Gson 等网络依赖按需添加
}
3.3 权限与动态申请
CXR-M 涉及的最小权限集包括:蓝牙、Wi-Fi、网络等,典型配置:
xml
<!-- AndroidManifest.xml(节选) -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<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_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<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" />
<!-- 录音权限,用于会议音频采集 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
运行时建议一次性申请所有必要权限(包括 Android 12+ 的 BLUETOOTH_SCAN / BLUETOOTH_CONNECT),再初始化 CXR,否则 SDK 会直接不可用。
设备接入:用 CXR-M 打通眼镜与手机
4.1 蓝牙扫描与连接
官方通常会提供一个较完整的 BluetoothHelper 示例,用于:
- 动态申请蓝牙相关权限
- 检测蓝牙开关状态
- 扫描 Rokid 眼镜(通过特定 Service UUID 过滤)
- 维护已配对 / 已连接设备列表
在 Meeting Lens 中,我们可以复用这一套逻辑,扫描中通过 Service UUID 过滤 Rokid 设备:
scss
// 扫描时使用 Rokid Glasses 的 Service UUID 过滤
ScanFilter.Builder()
.setServiceUuid(
ParcelUuid.fromString("00009100-0000-1000-8000-00805f9b34fb")
)
.build()
找到目标设备后,调用:
initBluetooth(context, device, callback)完成初始化- 在
onConnectionInfo获得socketUuid和macAddress,再调用connectBluetooth()建立通信
4.2 Wi-Fi P2P:为录音文件和附件留出通道
会议中如果需要同步完整录音文件、演示资料、截图等,可以使用 Wi-Fi P2P 模块:
- 初始化:
initWifiP2P(callback) - 获取状态:
isWifiP2PConnected - 反初始化:
deinitWifiP2P()
策略上:
- 实时 ASR:走蓝牙音频流
- 会后上传完整录音 / 媒体:走 Wi-Fi P2P + 文件同步接口(如
startSync/syncSingleFile)
核心链路:从音频流到结构化会议纪要
5.1 获取眼镜端音频流:AudioStreamListener
CXR-M 提供了一套完整的录音接口:
- 开启录音:
openAudioRecord(codecType, streamType) - 关闭录音:
closeAudioRecord(streamType) - 设置监听:
setAudioStreamListener(callback)
示例代码:
kotlin
// 1. 设置音频流监听
private val audioStreamListener = object : AudioStreamListener {
override fun onStartAudioStream(codecType: Int, streamType: String?) {
// 例如 streamType = "meeting"
// 可以在这里重置缓冲区、时间戳等
}
override fun onAudioStream(data: ByteArray?, offset: Int, length: Int) {
if (data == null || length <= 0) return
// 将 PCM / Opus 数据推入本地 ASR Engine / 发送到云端
asrEngine.feed(data, offset, length)
}
}
fun startMeetingAudioStream() {
// 注册监听
CxrApi.getInstance().setAudioStreamListener(audioStreamListener)
// 开启录音:codecType: 1=PCM, 2=OPUS(按文档约定)
CxrApi.getInstance().openAudioRecord(
codecType = 2,
streamType = "meeting_assistant"
)
}
fun stopMeetingAudioStream() {
CxrApi.getInstance().closeAudioRecord("meeting_assistant")
CxrApi.getInstance().setAudioStreamListener(null)
}
这一步完成后,我们就把「眼镜上的麦克风」变成了「AI 会议纪要系统的耳朵」。
5.2 ASR + 说话人分离:把"声"变成"谁说了什么"
在端 / 云侧,可以使用你熟悉的 ASR 引擎(如 Whisper、讯飞等):
- 将
onAudioStream中的音频切成 1~2 秒片段 - 送入流式 ASR
- 对每段打上时间戳,并结合麦克风阵列 / 外部麦 / 会议室麦克风做说话人分离
得到的数据结构大致是:
json
{
"segments": [
{
"speaker": "Alice",
"start": 12.3,
"end": 18.7,
"text": "我们这季度的核心目标是把留存提升到 35%。"
}
]
}
5.3 LLM 纪要引擎:从文本到结构化结论
基于上述 segments,你可以设计多层次总结流程:
-
分段摘要
- 每 2~3 分钟做一次小节,得到"当前议题的小结"
-
会议中期小结
- 当主持人切换议题时("那下面讨论第二个问题"),触发一次自然段总结
-
会后总总结
-
聚合所有文本 + 中间小结
-
按如下结构输出:
- 会议时间 / 地点 / 参与者
- 议题列表
- 每个议题的:背景 / 讨论过程 / 结论
- 待办事项(Owner + Deadline + 状态)
-
5.4 把关键要点回传到眼镜:提词器 + 自定义页面
方式一:用提词器场景做「会议 TODO 滚动条」
CXR-M 提供了 提词器(Word Tips)场景,可以:
- 打开 / 关闭场景:
controlScene(ValueUtil.CxrSceneType.WORD_TIPS, toOpen, null) - 用
sendStream(ValueUtil.CxrStreamType.WORD_TIPS, ...)发送提词内容
例如,把已经确认的行动项转成一段清单文本推送到眼镜:
kotlin
fun openTodosOnGlasses(text: String, fileName: String = "meeting_todos.txt") {
// 打开提词器场景
CxrApi.getInstance().controlScene(
ValueUtil.CxrSceneType.WORD_TIPS,
true,
null
)
// 发送文字内容
CxrApi.getInstance().sendStream(
ValueUtil.CxrStreamType.WORD_TIPS,
text.toByteArray(),
fileName,
object : SendStatusCallback {
override fun onSendSucceed() { }
override fun onSendFailed(code: ValueUtil.CxrSendErrorCode?) { }
}
)
}
再配合 configWordTipsText 调整字号、行距、显示区域,可以把「关键 TODO」像字幕一样滚动在视野边缘。
方式二:用 Custom View 做「实时会议状态卡片」
自定义页面(Custom View)允许用 JSON 描述一个界面,包含 TextView / ImageView / LinearLayout / RelativeLayout 等控件,眼镜端会根据 JSON 渲染。
典型用法:
- 打开界面:
openCustomView(content) - 更新:
updateCustomView(content) - 监听状态:
setCustomViewListener(listener)
例如构建一个简单的「会议状态卡片」:
json
{
"type": "LinearLayout",
"props": {
"layout_width": "match_parent",
"layout_height": "wrap_content",
"orientation": "vertical",
"backgroundColor": "#80000000",
"paddingTop": "8dp",
"paddingBottom": "8dp"
},
"children": [
{
"type": "TextView",
"props": {
"id": "tv_topic",
"text": "当前议题:Q3 留存目标",
"textSize": "14sp",
"textColor": "#FFFFFFFF",
"textStyle": "bold"
}
},
{
"type": "TextView",
"props": {
"id": "tv_highlight",
"text": "Alice:我们需要把产品内推荐体系重构一遍。",
"textSize": "12sp",
"textColor": "#FFDDDDDD",
"marginTop": "4dp"
}
},
{
"type": "TextView",
"props": {
"id": "tv_todos",
"text": "行动项:3 条(点头确认可记为已认领)",
"textSize": "12sp",
"textColor": "#FF00FF00",
"marginTop": "4dp"
}
}
]
}
用 openCustomView() 推送一次,后续只需用 updateCustomView() 改 tv_topic / tv_highlight / tv_todos 的 text 即可。
实战:基于 CXR-M 的 Meeting Lens 开发实践(含完整代码)
6.1 实战目标
实现一个 MeetingLensManager:
-
startMeeting():- 开启眼镜端录音(
openAudioRecord) - 注册
AudioStreamListener接收音频流 - 打开 Custom View,在眼镜上显示「会议纪要生成中」
- 启动一个定时任务,每隔 N 秒把当前文本发给后端做中间总结
- 开启眼镜端录音(
-
stopMeeting():- 关闭录音
- 停止定时任务
- 把完整 transcript 发送给后端,生成最终纪要
- 把最终纪要回传眼镜显示
6.2 MeetingLensManager 核心类(完整代码)
下面这段 Kotlin 代码可以直接复制使用,记得把
https://your.backend.com/...换成你自己的服务地址。
scss
package com.example.meetinglens
import android.content.Context
import android.util.Base64
import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.rokid.cxr.client.m.CxrApi
import com.rokid.cxr.client.m.callback.AudioStreamListener
import com.rokid.cxr.client.m.callback.CustomViewListener
import com.rokid.cxr.client.m.utils.ValueUtil
import kotlinx.coroutines.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
/**
* MeetingLensManager:会议透镜核心控制类
* - 控制录音
* - 调用后端 ASR / LLM
* - 将纪要结果通过 Custom View 显示在眼镜上
*/
class MeetingLensManager(
private val context: Context,
private val owner: LifecycleOwner
) {
companion object {
private const val TAG = "MeetingLens"
private const val STREAM_TYPE = "meeting_assistant"
}
private val httpClient = OkHttpClient()
private val transcriptBuffer = StringBuilder()
private var running = false
private var summaryJob: Job? = null
// 1. 音频流监听器
private val audioStreamListener = object : AudioStreamListener {
override fun onStartAudioStream(codecType: Int, streamType: String?) {
Log.d(TAG, "Audio stream started: codec=$codecType, type=$streamType")
transcriptBuffer.clear()
}
override fun onAudioStream(data: ByteArray?, offset: Int, length: Int) {
if (data == null || length <= 0) return
val chunk = data.copyOfRange(offset, offset + length)
owner.lifecycleScope.launch(Dispatchers.IO) {
sendAudioChunkToAsr(chunk)
}
}
}
// 2. Custom View 状态监听(可选)
private val customViewListener = object : CustomViewListener {
override fun onOpened() {
Log.d(TAG, "CustomView opened")
}
override fun onClosed() {
Log.d(TAG, "CustomView closed")
}
override fun onOpenFailed(code: Int) {
Log.e(TAG, "CustomView open failed: $code")
}
override fun onIconsSent() {}
override fun onUpdated() {}
}
// ====================== 外部调用接口 ======================
/** 开始一场会议 */
fun startMeeting() {
if (running) return
running = true
Log.i(TAG, "startMeeting()")
// 1. 设置音频监听
CxrApi.getInstance().setAudioStreamListener(audioStreamListener)
// 2. 开启录音:2 = OPUS 编码
CxrApi.getInstance().openAudioRecord(2, STREAM_TYPE)
// 3. 打开眼镜端会议页面
openMeetingView()
// 4. 设置 Custom View 监听(调试用)
CxrApi.getInstance().setCustomViewListener(customViewListener)
// 5. 定时进行中间总结
summaryJob = owner.lifecycleScope.launch(Dispatchers.IO) {
while (running) {
delay(15_000L) // 每 15 秒总结一次
val text = transcriptBuffer.toString()
if (text.isBlank()) continue
val midSummary = summarizeOnBackend(text, final = false)
if (midSummary.isNotBlank()) {
updateSummaryOnGlasses("中间小结:\n$midSummary")
}
}
}
updateSummaryOnGlasses("纪要生成中,请自然发言...")
}
/** 结束会议 */
fun stopMeeting() {
if (!running) return
running = false
Log.i(TAG, "stopMeeting()")
// 1. 停止录音
CxrApi.getInstance().closeAudioRecord(STREAM_TYPE)
CxrApi.getInstance().setAudioStreamListener(null)
// 2. 停止中间总结任务
summaryJob?.cancel()
summaryJob = null
// 3. 调用后端生成最终纪要
owner.lifecycleScope.launch(Dispatchers.IO) {
val finalText = transcriptBuffer.toString()
val finalSummary = summarizeOnBackend(finalText, final = true)
if (finalSummary.isNotBlank()) {
updateSummaryOnGlasses("【本次会议最终纪要】\n$finalSummary")
} else {
updateSummaryOnGlasses("本次会议纪要生成失败,请检查网络或后端服务")
}
}
}
// ====================== 与后端交互 ======================
/** 将音频切片发送到 ASR 后端,获取 partial_text */
private fun sendAudioChunkToAsr(chunk: ByteArray) {
try {
val base64 = Base64.encodeToString(chunk, Base64.NO_WRAP)
val json = JSONObject().apply {
put("audio_base64", base64)
put("stream_type", STREAM_TYPE)
}.toString()
val request = Request.Builder()
.url("https://your.backend.com/asr/stream") // TODO: 替换为你的 ASR 接口
.post(json.toRequestBody("application/json".toMediaType()))
.build()
httpClient.newCall(request).execute().use { resp ->
if (!resp.isSuccessful) return
val body = resp.body?.string().orEmpty()
if (body.isBlank()) return
val obj = JSONObject(body)
val partial = obj.optString("partial_text", "")
if (partial.isNotBlank()) {
transcriptBuffer.append(partial).append('\n')
}
}
} catch (e: Exception) {
Log.e(TAG, "sendAudioChunkToAsr error: ${e.message}", e)
}
}
/** 调用 LLM 后端进行纪要总结 */
private fun summarizeOnBackend(text: String, final: Boolean): String {
if (text.isBlank()) return ""
return try {
val json = JSONObject().apply {
put("transcript", text)
put("final", final) // final=true 使用更重模型
}.toString()
val request = Request.Builder()
.url("https://your.backend.com/meeting/summarize") // TODO: 替换为你的 Summary 接口
.post(json.toRequestBody("application/json".toMediaType()))
.build()
httpClient.newCall(request).execute().use { resp ->
if (!resp.isSuccessful) return ""
val body = resp.body?.string().orEmpty()
if (body.isBlank()) return ""
val obj = JSONObject(body)
obj.optString("summary", "")
}
} catch (e: Exception) {
Log.e(TAG, "summarizeOnBackend error: ${e.message}", e)
""
}
}
// ====================== 眼镜端 UI:Custom View ======================
/** 打开会议透镜的基础页面 */
private fun openMeetingView() {
val pageJson = """
{
"type": "LinearLayout",
"props": {
"id": "root_layout",
"layout_width": "match_parent",
"layout_height": "match_parent",
"orientation": "vertical",
"gravity": "top",
"paddingTop": "40dp",
"paddingStart": "12dp",
"paddingEnd": "12dp",
"backgroundColor": "#80000000"
},
"children": [
{
"type": "TextView",
"props": {
"id": "tv_title",
"layout_width": "wrap_content",
"layout_height": "wrap_content",
"text": "会议透镜 · Meeting Lens",
"textSize": "16sp",
"textColor": "#FFFFFFFF",
"textStyle": "bold",
"marginBottom": "8dp"
}
},
{
"type": "TextView",
"props": {
"id": "tv_summary",
"layout_width": "match_parent",
"layout_height": "wrap_content",
"text": "纪要生成中,请自然发言...",
"textSize": "12sp",
"textColor": "#FFDDDDDD"
}
},
{
"type": "TextView",
"props": {
"id": "tv_hint",
"layout_width": "wrap_content",
"layout_height": "wrap_content",
"text": "提示:结束前可口头确认关键决策",
"textSize": "10sp",
"textColor": "#FF00FF00",
"marginTop": "8dp"
}
}
]
}
""".trimIndent()
CxrApi.getInstance().openCustomView(pageJson)
}
/** 更新眼镜上的纪要内容(只改 tv_summary) */
private fun updateSummaryOnGlasses(summary: String) {
val display = if (summary.length > 400) {
summary.substring(0, 400) + "..."
} else summary
val updateJson = """
[ { "action": "update", "id": "tv_summary", "props": { "text": ${JSONObject.quote(display)} } } ]
""".trimIndent()
CxrApi.getInstance().updateCustomView(updateJson)
}
}
6.3 在 Activity 中调用
在你的 Activity 里,只需要实例化并调用即可:
kotlin
class MeetingActivity : AppCompatActivity() {
private lateinit var meetingLensManager: MeetingLensManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_meeting)
meetingLensManager = MeetingLensManager(this, this)
findViewById<Button>(R.id.btnStart).setOnClickListener {
meetingLensManager.startMeeting()
}
findViewById<Button>(R.id.btnStop).setOnClickListener {
meetingLensManager.stopMeeting()
}
}
}
前提:
- 已经用 CXR-M 连接上眼镜(蓝牙已连)
- 权限已经申请完成
- 后端的
/asr/stream和/meeting/summarize接口可用
6.4 后端接口的最小约定(方便你自己实现)
你可以先定义一个非常简单的协议:
-
POST /asr/stream- 请求:
{ "audio_base64": "..." } - 响应:
{ "partial_text": "我们这季度的核心目标是..." }
- 请求:
-
POST /meeting/summarize- 请求:
{ "transcript": "...长文本...", "final": true/false } - 响应:
{ "summary": "本次会议围绕 X 展开,形成 3 个决策,分别是..." }
- 请求:
后端可以用:
- Python + FastAPI + Whisper + 任意大模型
- 或者直接接云上的 ASR / LLM 服务(省事但花钱)
踩坑与工程经验
7.1 权限不全导致 SDK 静默失败
- 蓝牙相关权限必须一次性申请:
BLUETOOTH/BLUETOOTH_ADMIN/BLUETOOTH_SCAN/BLUETOOTH_CONNECT/FINE_LOCATION - 未授权时 CXR-M 很多接口直接不可用,建议在初始化前加一层完整检查
7.2 音频流与 ASR 的边界对齐
AudioStreamListener推来的数据是连续流,需要自己切片 + 时间戳管理- 记得在
onStartAudioStream重置缓冲区 - 建议使用 10~20ms 帧长的内部队列,对接大多数 ASR 引擎更友好
7.3 Wi-Fi P2P 兼容与降级策略
-
部分机型对 Wi-Fi Direct 支持不好,CXR 回调里可能返回
WIFI_CONNECT_FAILED -
建议:
- 实时纪要全部走音频流 + 文本,不依赖 Wi-Fi
- 会后录音备份 / 多媒体附件再尝试 Wi-Fi,同步失败则提示用户改为手机本地保存
7.4 Custom View 复杂度与性能
- 图片不超过 128×128 px,数量不要太多(< 10 张)
- 布局树不要太深,多用
LinearLayout/RelativeLayout做简单排版 - 更新时尽量用
updateCustomView做局部更新,避免频繁 reopen
会议助手场景下,最好只在视野边缘放一个小卡片,不要铺满屏幕,以免影响用户看人 / 看 PPT。
7.5 电量与亮度、音量管理
CXR-M 提供了:
- 亮度监听与设置:
setGlassBrightness/BrightnessUpdateListener - 音量监听与设置:
setGlassVolume/VolumeUpdateListener - 电量监听:
setBatteryLevelUpdateListener
可以做一些自动优化,例如:
- 进入会议后自动把亮度调到中档,减少干扰
- 电量低于 20% 时,在眼镜端弹出小提示
小结:从「记录会议」到「为决策服务」
「会议透镜(Meeting Lens)」本质上做了三件事:
- 用 CXR-M SDK 把 眼镜的麦克风 + 显示屏 接入到手机的 AI 工作流中;
- 用 ASR + LLM,把"谁在什么时间说了什么"变成"有哪些议题、结论和行动项";
- 用 提词器 / 自定义页面,把真正重要的信息适时地浮现到用户眼前。
从工程角度看,它验证了:
- 手机 + 眼镜 可以像一个统一的「会议终端」:眼镜负责感知 & 显示,手机负责计算与联网。
- CXR 提供的场景能力(AI 场景、提词器、翻译、自定义页面)可以很好地承载「实时会议状态」这类信息。
从体验角度看,它把开会这件事从:
「我得边听边记,生怕漏掉一个数字」
变成:
「我只需要好好讨论,剩下的交给 AI 和眼镜。」
如果你已经有一台 Rokid 眼镜和一台 Android 手机,那么你离自己的「会议透镜」其实只差几步:
- 用 CXR-M 把眼镜连到手机;
- 打开音频流,接入你熟悉的 ASR 服务;
- 设计一套适合你团队的 LLM 纪要 Prompt;
- 用提词器或 Custom View,把实时要点和最后纪要推回眼镜。