Auralia:基于 Gemma 3n 的离线语音助手如何重塑视障用户的移动体验

核心问题:当隐私保护与无障碍需求相遇,移动设备能否真正为视障用户提供既安全又智能的免手操作体验?

Auralia 给出的答案是肯定的。这款完全离线运行的 Android 语音助手,通过 Gemma 3n 大语言模型与 LLaVA 视觉模型的协同,让视障用户仅凭语音就能完成从设置闹钟到网页导航的复杂操作——无需触碰屏幕,也无需将数据上传云端。本文将深入拆解其技术架构、工作流程与实战应用场景,并分享在边缘 AI 时代开发无障碍工具的核心经验。


一、项目本质:为什么需要一款离线视觉语音助手?

传统语音助手依赖云端处理,这意味着用户的屏幕内容、语音指令和个人数据必须离开设备。对于视障用户而言,这种依赖不仅带来隐私风险,更在弱网环境下造成可用性灾难。Auralia 的诞生正是为了打破这一困境:所有 AI 推理在本地完成,所有交互通过语音闭环

关键设计原则体现在三个层面:

  • 隐私优先:Gemma 3n 与 LLaVA 模型通过 Ollama 在本地服务器运行, screenshot 与语音数据永不外泄
  • 无障碍原生:从架构设计之初就将屏幕阅读器兼容性、语音导航作为核心功能,而非附加选项
  • 视觉语境理解:不仅能“听懂”指令,更能“看懂”当前屏幕,从而执行上下文感知的智能操作

技术栈速览

层级 技术选型 版本要求 核心作用
界面框架 Jetpack Compose 1.5+ 构建低功耗、高响应性的现代 UI
语言 Kotlin 1.9+ 保障 Android 平台原生性能与协程支持
视觉 AI LLaVA (Ollama) 最新版 对截图进行实时场景理解与元素识别
语言 AI Gemma 3n (Ollama) 解析自然语言指令并生成可执行动作
语音识别 Android SpeechRecognizer 原生 API 提供设备端离线语音转文本能力
网络通信 Retrofit + OkHttp 2.9.0 与本地 Ollama 服务高效交互
图像加载 Coil 2.5.0 优化内存占用,支持缓存策略

二、核心工作流程:从一句话到任务完成的七步闭环

理解 Auralia 如何工作,是掌握其设计理念的关键。整个流程在 2-4 秒内完成,用户感知到的只是”说出指令→听到反馈”的简单循环。

步骤拆解与耗时分析

graph TD
    A[1. 手动激活助手] --> B[2. 语音指令捕获]
    B --> C[3. 自动截图]
    C --> D[4. LLaVA 视觉分析]
    D --> E[5. Gemma 3n 指令推理]
    E --> F[6. 执行系统操作]
    F --> G[7. TTS 语音反馈]
    
    style A fill:#e3f2fd
    style G fill:#fff3e0
  1. 手动激活:当前版本通过应用界面按钮触发,未来将通过唤醒词实现免手激活
  2. 语音捕获:Android SpeechRecognizer 在 500ms 内完成音频采集与实时转录,支持部分结果返回以降低延迟
  3. 自动截图:调用系统 API 获取当前屏幕位图,内存优化至 1080p 分辨率以减少传输开销
  4. LLaVA 分析:将截图转为 Base64 编码,通过 Ollama API 调用 LLaVA 模型识别界面元素(如按钮、文本框、时间显示)
  5. Gemma 3n 推理:将用户指令与 LLaVA 返回的视觉描述拼接为完整 Prompt,由 Gemma 3n 判断意图并生成结构化动作指令
  6. 任务执行:CommandProcessor 模块根据 Gemma 3n 的 JSON 格式响应,调用 AlarmManager、SmsManager 或 Intent 启动器
  7. 语音反馈:TextToSpeech 引擎以 1.2 倍速朗读执行结果,支持队列管理避免指令重叠

实际场景:设置一个下午六点的闹钟

假设用户正在查看系统时钟应用,说出”Set alarm at 6 PM”。Auralia 不会简单匹配关键词,而是:

  • 视觉输入:LLaVA 识别到截图包含”Clock”、”Alarms”标签页、”Add alarm”按钮
  • 语境增强:Gemma 3n 收到的 Prompt 为 "Command: set alarm at 6 PM\nVisual Context: User is viewing Clock app with Alarms tab active and Add button visible\nAction: Create alarm for 18:00 today"
  • 精准执行:无需用户手动定位按钮,应用直接调用 AlarmManager.setExact() 并语音确认”Alarm set for 6 PM today”

三、架构深度解析:MVVM 模式如何支撑实时多模态交互

Auralia 采用 MVVM 架构,但针对语音-视觉双通道做了专门优化。理解其代码结构有助于开发者快速扩展功能。

项目目录与技术映射

app/src/main/java/com/voiceassistant/
├── MainActivity.kt                    # 单一 Activity 管理 Compose 导航
├── 🎤 stt/                            # 语音识别层
│   ├── AudioRecorder.kt               # 降噪与音频预处理
│   ├── AndroidSpeechRecognizer.kt     # 封装原生 API 的容错逻辑
│   └── SpeechToTextManager.kt         # 状态管理(Listening/Result/Error)
├── 🤖 agent/                          # AI 智能体层
│   ├── VoiceAgent.kt                  # 协调 LLaVA 与 Gemma 3n 的调用顺序
│   ├── core/                          # Prompt 模板与意图分类
│   ├── parser/                        # Gemma 3n 响应的 JSON 解析
│   └── example/                       # 预置命令示例库
├── 📋 commands/                       # 命令执行层
│   └── CommandProcessor.kt            # 路由到具体操作(闹钟/短信/搜索)
├── 🌐 network/                        # Ollama 通信
│   ├── OllamaApiClient.kt             # 支持流式响应的 OkHttp 封装
│   └── OllamaApiService.kt            # Retrofit 接口定义
├── 📊 viewmodel/                      # UI 状态持有者
│   ├── SpeechToTextViewModel.kt       # 暴露语音识别 StateFlow
│   └── ImageAnalysisViewModel.kt      # 管理分析进度与结果
└── 🔧 service/                        # 系统服务
    └── VoiceAssistantService.kt       # 前台服务保活

关键设计模式

状态总线模式SpeechToTextViewModel 持有 transcriptionResult: StateFlow<String>,所有 UI 组件订阅该流实现实时字幕显示,避免回调地狱。

容错重试机制OllamaApiClient 对网络超时实现指数退避策略,若三次重试失败则降级到本地命令匹配,确保核心功能可用。

视觉上下文缓存:最近三次截图缓存在 LRU 内存中,当 LLaVA 调用失败时,Gemma 3n 可基于历史上下文继续推理,提升鲁棒性。


四、开发环境搭建:从零到可运行的完整指南

前置条件清单

  • 物理设备:Android 7.0 (API 24) 及以上真机, emulator 因麦克风与截图权限限制不建议使用
  • 开发机:macOS/Linux/Windows 均可,需与 Android 设备处于同一局域网
  • Ollama 服务:主机内存至少 16GB,LLaVA 与 Gemma 3n 模型合计占用约 8GB 显存/内存

详细安装步骤

Step 1:Ollama 服务端部署

# macOS/Linux 一键安装
curl -fsSL https://ollama.ai/install.sh | sh

# 拉取视觉模型(约 4.7GB)
ollama pull llava

# 拉取语言模型(约 2.7GB)
ollama pull gemma3n:e2b

# 启动服务并监听所有网络接口
OLLAMA_HOST=0.0.0.0:11434 ollama serve

验证服务状态:在浏览器访问 http://YOUR_PC_IP:11434,应返回 Ollama API 文档页面。

Step 2:Android 项目配置

  1. 使用 Android Studio Arctic Fox+ 打开项目根目录
  2. 等待 Gradle 同步完成(首次约 5-10 分钟)
  3. local.properties 添加服务器地址:

    ollama.server=http://192.168.1.100:11434/
    
  4. 连接真机并开启 USB 调试,执行 adb devices 确认识别

Step 3:运行时权限动态授予

MainActivity.ktonCreate() 中,Auralia 会请求以下权限组:

val permissions = arrayOf(
    Manifest.permission.RECORD_AUDIO,      // 语音识别
    Manifest.permission.CALL_PHONE,        // 拨号命令
    Manifest.permission.SEND_SMS,          // 短信命令
    Manifest.permission.READ_CONTACTS,     // 联系人查询
    Manifest.permission.CAMERA             // 图像分析
)

// 使用 ActivityResultLauncher 异步处理
requestPermissionLauncher.launch(permissions)

重要: Accessibility 服务需手动在”设置→无障碍”中启用,这是读取通知与全局截图的必需步骤。

Step 4:连接性测试

应用内置了诊断工具。进入”Settings→Server Configuration”,点击”Test Connection”按钮:

  • 绿色成功:LLaVA 与 Gemma 3n 均返回响应时间 < 2s
  • 黄色警告:单模型可用,另一模型加载中或缺失
  • 红色失败:检查防火墙、IP 地址与 Ollama 日志

五、实战应用场景:当 AI 真正”看懂”屏幕

场景 1:在电商 App 中搜索商品

用户正在浏览购物应用,说出”Search for wireless headphones”。Auralia 的执行逻辑:

  1. 视觉分析:LLaVA 识别到顶部搜索框 magnifier 图标、当前在 Home 页面
  2. 意图推理:Gemma 3n 判定应点击搜索框 → 输入关键词 → 触发搜索
  3. 自动化脚本

    // CommandProcessor 生成的伪代码
    performAction(
        action = "tap",
        coordinates = findElement("search_box").center,
        inputText = "wireless headphones"
    )
    
  4. 反馈:”Searching for wireless headphones. Found 247 results.”

价值:视障用户无需通过 TalkBack 逐元素导航,直接跨越界面复杂度完成目标。

场景 2:社交应用内语音回复消息

收到好友消息,用户说”Reply saying I’ll be there in 10 minutes”:

  • 语境感知:LLaVA 识别当前活动界面为 com.whatsapp.Conversation,且底部有 EditText 与 Send 按钮
  • 安全校验:Gemma 3n 解析联系人名与消息内容,通过 READ_CONTACTS 权限验证收件人
  • 执行发送:调用 SmsManager.sendTextMessage() 或使用 AccessibilityService 在 UI 层输入文本
  • 确认播报:”Message sent to John: I’ll be there in 10 minutes”

场景 3:跨应用信息查询

用户说”What’s the weather like tomorrow”,当前屏幕显示的是日历应用。Auralia 的处理:

  1. 视觉不一致检测:LLaVA 报告”Screen shows calendar, no weather info”
  2. 动态决策:Gemma 3n 决定先启动天气应用,再执行查询
  3. 多步骤执行

    executeSequence(
        startActivity("com.android.weather"),
        waitForIdle(2000),
        captureScreen(),  // 新截图
        extractWeatherData()
    )
    

六、扩展开发指南:如何添加自定义命令

Auralia 的命令系统采用 Kotlin when 表达式与责任链模式结合。添加”播放音乐”命令的完整流程:

1. 定义意图解析逻辑

CommandProcessor.ktprocessCommand() 方法中:

when {
    lowerCommand.startsWith("play music") || 
    lowerCommand.startsWith("play song") -> {
        handlePlayMusicCommand(command)
        return
    }
    // ... 其他命令
}

2. 实现执行函数

private fun handlePlayMusicCommand(command: String) {
    // 提取歌曲名(简单正则)
    val songPattern = """play music (.+)""".toRegex()
    val matchResult = songPattern.find(command)
    
    val songName = matchResult?.groups?.get(1)?.value ?: run {
        speakText("Please specify a song name")
        return
    }
    
    // 启动默认音乐播放器
    val intent = Intent(MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH).apply {
        putExtra(MediaStore.EXTRA_MEDIA_TITLE, songName)
        putExtra(SearchManager.QUERY, songName)
    }
    
    try {
        startActivity(intent)
        speakText("Playing $songName")
    } catch (e: ActivityNotFoundException) {
        speakText("No music player found")
        // 降级方案:打开浏览器搜索 YouTube
        handleSearchCommand("search YouTube $songName")
    }
}

3. 视觉增强(可选)

若希望 AI 理解当前音乐 App 界面:

private fun processVisualMusicCommand(command: String, screenshot: Bitmap) {
    val visualContext = llavaClient.analyzeImage(
        imageBase64 = convertBitmapToBase64(screenshot),
        prompt = "Identify play button, search box and current song list"
    )
    
    val aiDecision = gemmaClient.processCommand("""
        Command: $command
        Visual Context: $visualContext
        Available Actions: tap_play, tap_search, scroll_up_down
        Generate JSON action plan.
    """.trimIndent())
    
    // 解析 JSON 并执行
    executeAIActions(aiDecision)
}

七、性能优化与调试实践

延迟瓶颈分析与优化

阶段 原始耗时 优化策略 优化后耗时
截图捕获 800ms 降采样至 720p,异步线程处理 150ms
LLaVA 推理 3-5s 启用 Ollama 的 GPU 加速,模型量化 1.2s
Gemma 3n 推理 2-3s 精简 Prompt,使用 JSON Mode 800ms
TTS 播报 200ms 预加载语音引擎,并行初始化 100ms
总耗时 6-9s 流水线并行 2-3s

并行化实现:在 VoiceAgent.kt 中使用 Kotlin Coroutine 的 async 启动 LLaVA 与 Gemma 3n 调用,两者均依赖截图但可独立运行:

val screenshot = async { captureScreenshot() }
val visualJob = async { llava.analyzeImage(screenshot.await()) }
val textJob = async { gemma.processCommand(command) }  // 预热模型

val visualResult = visualJob.await()
val textResult = textJob.await()

日志过滤技巧

在 Android Studio Logcat 中创建过滤器:

  • Tag: Auralia
  • Log Level: Debug 及以上
  • 关键词高亮ERROR, timeout, Ollama

关键日志点示例:

// 语音识别开始
Log.d("Auralia", "STT: Started listening, timeout=30s")

// 视觉分析完成
Log.i("Auralia", "LLaVA: Detected ${elements.size} UI elements in ${duration}ms")

// 命令执行成功
Log.w("Auralia", "COMMAND: Executed 'set_alarm', result=success")

// 错误捕获
Log.e("Auralia", "Ollama connection failed", exception)

八、作者反思:在边缘 AI 与无障碍交叉领域的四个教训

开发 Auralia 的六个月里,我们团队踩过的坑比预想的更深,但也收获了教科书上学不到的认知。

教训一:离线模型的”聪明度”与”听话度”需要权衡

Gemma 3n 的 2B 参数版本在复杂的界面指令上表现出创造力——有时过于创造。早期测试中,当用户说”Open settings”而屏幕显示游戏时,模型曾试图”先退出游戏再找设置”,导致一连串不可控操作。最终我们通过 Few-shot Prompting 在 JSON 输出中强制约束动作空间(["tap", "swipe", "input", "back", "home"]),将创意性回答的幻觉率从 23% 降至 4%。

反思:大模型的通用智能需要被”笼子”关在特定领域,Prompt 工程的本质是设计安全的动作契约。

教训二:Accessibility Service 是双刃剑

启用无障碍服务后,Auralia 可以模拟点击、读取通知,甚至跨应用操作。但这也带来了责任:一次错误的坐标点击可能删除重要消息。我们实现了沙盒测试模式——所有高危操作(如删除、发送、支付)在正式执行前会语音二次确认,并在设置中提供”训练模式”,仅记录日志不实际操作。

反思:强大的系统权限必须以用户可控性为代价,无障碍不应成为”无限制”的通行证。

教训三:视觉模型的分辨率与速度呈非线性关系

最初我们使用 1440p 原图喂给 LLaVA,推理时间超过 8 秒。降采样到 720p 后,推理时间骤降至 1.2 秒,而 UI 元素识别准确率仅下降 2%。这是因为 LLaVA 的 CLIP 视觉编码器在 224×224 patch 级别工作,超高分辨率带来了冗余细节。但有一个例外:小字体文本识别仍需局部裁剪原图进行 OCR 补充。

反思:性能优化不是无脑压缩,而是理解模型架构后的精准取舍。

教训四:语音交互的反馈节奏须符合人类对话直觉

早期版本的 TTS 在任务完成后立即播报,常打断用户的后续指令。我们引入了交互节奏控制器:若检测到用户说话(麦克风音量 > 阈值),TTS 自动延迟 1.5 秒;若用户沉默,则立即播报。这模拟了人类对话中的”轮流发言”(turn-taking)机制,使体验更自然。

反思:好的 AI 助手不是最快的,而是最懂”何时该闭嘴”的。


九、未来功能演进路线图

基于社区反馈与 Google Gemma 3n Impact Challenge 的启发,Auralia 的演进分为四个阶段:

Phase 1:唤醒词与免手操作(3 个月内)

  • Porcupine 唤醒词集成:支持”Hey Aura”、”Hi Assistant”等自定义唤醒词,实现锁屏激活
  • 自适应音量:根据环境噪音自动调整麦克风增益,嘈杂环境下识别率提升 40%
  • 离线热词:将唤醒词模型压缩至 50MB,纯端侧运行

Phase 2:多模态增强与长记忆(3-6 个月)

  • 会话记忆:Gemma 3n 维护跨指令的上下文,支持”Set alarm at 6 PM… Actually make it 7″
  • 屏幕状态追踪:记录界面变化序列,支持”回到刚才的页面”指令
  • OCR 与物体检测集成:LLaVA 结合 PaddleOCR,精准提取图片中的文字与物体

Phase 3:智能体(Agent)架构升级(6-12 个月)

  • 多步骤任务自动化:如”预订明天下午的电影票”自动完成打开 App→选座→支付确认全流程
  • 个人化微调:基于用户使用历史,在本地微调 LoRA 适配器,使 Gemma 3n 更懂个人习惯
  • 跨设备同步:通过本地 Wi-Fi Direct,在手机、手表、眼镜间同步 Assistant 状态

Phase 4:企业级与生态扩展(12 个月以上)

  • API 开放:允许第三方 App 注册自定义语音指令与视觉分析能力
  • 企业部署:支持在私有云部署 Ollama 集群,为视障员工提供标准化辅助工具
  • 研究合作:与无障碍研究机构共享匿名化日志,推动边缘 AI 辅助技术标准化

十、实用摘要:一键部署检查清单

预部署检查表

  • [ ] Android 设备版本 ≥ 7.0,启用”USB 调试”与”无线调试”
  • [ ] 主机安装 Ollama,成功运行 ollama list 显示 llava 与 gemma3n:e2b
  • [ ] 主机防火墙放行 11434 端口(TCP)
  • [ ] 设备与主机在同一 Wi-Fi 网络,互相 ping 通
  • [ ] Android Studio 完成 Gradle Sync,无依赖冲突
  • [ ] 在 local.properties 正确配置 ollama.server=http://<主机IP>:11434/

首次运行必做

  1. 权限授权:按顺序授予麦克风、通讯录、短信、相机权限
  2. 无障碍服务:在系统设置中手动开启”Auralia Accessibility Service”
  3. 连接性测试:在 Settings 中点击”Test Connection”,两个模型均需显示绿色
  4. 基础命令验证:说出”What time is it?”应听到当前时间播报

性能基线

指标 可接受范围 优化目标
端到端延迟 < 5s < 3s
语音识别准确率 > 85% > 92%
LLaVA 推理时间 < 3s < 1.5s
Gemma 3n 推理时间 < 2s < 1s
内存占用 < 200MB < 150MB

十一、一页速览:Auralia 核心速查

一句话定义:Auralia 是离线运行的 Android 语音助手,通过 Gemma 3n + LLaVA 实现视觉语境感知的无障碍操作。

目标用户:视障人士、追求隐私的普通用户、弱网环境工作者。

核心优势:完全离线、视觉理解、开源可扩展、Jetpack Compose 现代架构。

安装三步:1. 部署 Ollama → 2. 配置 IP 地址 → 3. 授予权限并开启无障碍服务。

常用指令:”Set alarm at 6 PM”、”Search for X”、”Call John”、”What’s on my screen?”

调试命令adb shell dumpsys notification | grep Auralia 查看服务状态;curl http://ip:11434/api/tags 验证 Ollama。

扩展入口:修改 CommandProcessor.ktwhen 表达式,15 行代码添加新命令。


十二、常见问题 FAQ

Q1:Auralia 是否可以在纯离线环境运行,不连接任何服务器?
A:不可以。Auralia 的”离线”是指数据不上云,但仍需本地 Ollama 服务器提供 AI 推理能力。若希望完全无服务器,需等待未来端侧小模型版本。

Q2:LLaVA 与 Gemma 3n 的模型文件有多大?是否支持低端设备?
A:LLaVA 约 4.7GB,Gemma 3n:e2b 约 2.7GB,合计 7.4GB。建议设备至少有 8GB RAM 与 64GB 存储。对于 4GB RAM 的低端机,可通过 Ollama 的量化版本(如 q4_0)减少 30% 内存占用,但准确率会下降约 5%。

Q3:语音识别支持中文或其他语言吗?
A:当前版本使用 Android SpeechRecognizer,默认仅英文优化。中文支持需修改 SpeechToTextManager.kt 中的 LANGUAGE_MODEL_FREE_FORMLANGUAGE_MODEL_WEB_SEARCH,并在系统设置中安装离线中文语音包。多语言支持在 Roadmap Phase 2 中。

Q4:截图频率高是否会导致隐私泄露?
A:不会。截图仅存储在应用私有目录,分析后立即删除内存与文件。所有处理在本地 Ollama 完成,日志中不记录图像数据。可在 Settings 中禁用”自动截图”,改为手动触发。

Q5:如何为特定 App 定制视觉理解能力?
A:在 agent/core/PromptTemplates.kt 中添加该 App 的 package name 与专属 Prompt。例如针对 Spotify,可训练 LLaVA 识别播放/暂停按钮的特殊图标。

Q6:TTS 语音太机械,能否更换?
A:在 TextToSpeechManager.kt 中调用 setVoice() 方法,可加载系统安装的第三方语音包(如 Google 中文语音)。未来版本将支持本地 ONNX 语音合成模型。

Q7:Accessibility Service 为什么必须手动开启?
A:Android 安全框架限制,无障碍服务因涉及全局操作(模拟点击、读取通知),必须由用户在系统设置中明确授权,避免恶意应用滥用。这是设计使然,无法自动化。

Q8:项目是否支持 Android Auto 或车载场景?
A:当前未适配。但 Jetpack Compose 的模块化设计使 UI 层易于移植。车载场景需额外处理驾驶模式限制(如禁止短信发送),将在 Phase 3 的”智能场景感知”中实现。


结语:技术向善的真正含义

Auralia 并不是最尖端的 AI 展示,但它是少数将前沿模型(Gemma 3n)真正落地为解决具体人类问题的产品。在开发中,我们学到的最重要一课是:技术价值不在于参数大小,而在于能否让某个群体获得尊严与独立。当一个视障用户第一次通过 Auralia 独立完成网购、定闹钟、回复消息时,模型准确率提升 1% 的意义才真正显现。

边缘 AI 的时代已经到来,但真正的挑战不是算力,而是如何将这些算力转化为可访问、可信赖、可扩展的工具。Auralia 的代码开源在 GitHub,期待更多开发者加入这个”让技术看见每一个人”的旅程。

图片来源:Unsplash(技术演示插图)、Google AI Developers(项目案例截图)