本文详细介绍如何在 Android 设备端实现离线语音识别与自然语言理解,通过语音指令直接控制 TMS(终端管理系统)APK 的设备管理功能。全程无需云端服务,完全在设备本地处理。
📋 目录
项目背景与需求
1.1 业务场景
在 终端管理系统应用中,用户需要通过语音指令控制设备,例如:
- 设备控制:锁定设备、重启设备、重置密码
- 网络管理:打开/关闭 WiFi、蓝牙
- 系统设置:调整音量、亮度
- 信息查询:查询设备型号、电池电量、系统版本
1.2 核心需求
- ✅ 完全离线:不依赖云端服务,保护隐私
- ✅ 实时响应:语音识别延迟低,用户体验好
- ✅ 资源占用小:适合移动设备,不影响系统性能
- ✅ 直接调用:绕过云端,直接调用 TMS APK 内部能力
1.3 技术挑战
- STT(语音转文本):需要离线、轻量级的语音识别引擎
- NLU(自然语言理解):需要将自然语言转换为结构化命令
- 设备控制:需要直接调用 TMS APK 内部的 MDMService 能力
技术方案选型
2.1 STT(语音转文本)方案对比
经过深入调研,我们对比了多个开源方案:
| 方案 | 开源 | 离线 | 中文支持 | 资源占用 | 推荐度 |
|---|---|---|---|---|---|
| Vosk | ✅ | ✅ | ✅ | 低(~50MB) | ⭐⭐⭐⭐⭐ |
| Whisper (whisper.cpp) | ✅ | ✅ | ✅ | 中(~100MB+) | ⭐⭐⭐⭐ |
| Android 系统语音服务 | ❌ | ✅ | ✅ | 低 | ⭐⭐⭐ |
| Google Cloud STT | ❌ | ❌ | ✅ | - | ⭐⭐ |
最终选择:Vosk
选择理由:
- ✅ 完全开源,Apache 2.0 许可证
- ✅ 专为移动端优化,轻量级模型仅 49MB
- ✅ 支持中文,识别准确率高
- ✅ 社区活跃,文档完善
- ✅ 提供 Android Demo,可直接运行
2.2 NLU(自然语言理解)方案对比
| 方案 | 开源 | 离线 | 易用性 | 推荐度 |
|---|---|---|---|---|
| 基于规则的解析 | ✅ | ✅ | 极高 | ⭐⭐⭐⭐⭐ |
| TensorFlow Lite | ✅ | ✅ | 中(需训练模型) | ⭐⭐⭐ |
| Snips NLU | ✅ | ✅ | 高(已停止维护) | ⭐⭐ |
| Rasa | ✅ | ❌ | 高 | ⭐ |
最终选择:基于规则的解析(Rule-based Parsing)
选择理由:
- ✅ 对于固定命令集(如设备控制),规则解析 100% 准确
- ✅ 零资源占用,响应速度快
- ✅ 易于维护和扩展
- ✅ 无需训练数据,开发成本低
2.3 模型选择
重要提醒:必须使用移动端模型!
Vosk 提供了多种中文模型,但只有轻量级模型适合移动端:
| 模型 | 大小 | 用途 | 是否推荐 |
|---|---|---|---|
| vosk-model-small-cn-0.3 | ~49MB | 移动端应用 | ✅ 强烈推荐 |
| vosk-model-cn-0.22 | 1.3GB | 服务器处理 | ❌ 不适合移动端 |
| vosk-model-cn-kaldi-multicn-0.15 | 1.5GB | 服务器处理 | ❌ 不适合移动端 |
为什么不能使用大型模型?
通过 adb 命令评估设备性能:
bash
# 查看设备内存
adb shell "cat /proc/meminfo | grep 'MemTotal\|MemAvailable'"
# 实际测试结果(2.9GB RAM 设备)
Mem: 2902868K total, 2848700K used, 54168K free
结论:
- 大型模型(1.3GB)需要至少 1.8GB 可用内存
- 移动设备通常只有 2-4GB 总内存
- 强行加载会导致 OOM(内存溢出)崩溃
核心架构设计
3.1 系统架构
┌─────────────────────────────────────────────────────────┐
│ 用户语音输入 │
│ "请帮我锁定这台设备" │
└────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 阶段一:语音识别 (STT) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Vosk 引擎 │ │
│ │ - 捕获麦克风音频 │ │
│ │ - 离线语音识别 │ │
│ │ - 输出文本:"请帮我锁定这台设备" │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 阶段二:自然语言理解 (NLU) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 规则解析器 (TmsCommandParser) │ │
│ │ - 文本规范化 │ │
│ │ - 关键词匹配 │ │
│ │ - 意图识别:LOCK_DEVICE │ │
│ │ - 参数提取:无 │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 阶段三:设备控制执行 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ TMS APK 内部能力 │ │
│ │ - 发送广播:android.intent.action.lockdevice │ │
│ │ - MDMService 接收并执行 │ │
│ │ - 设备锁定完成 │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
3.2 数据流
用户语音 → Vosk STT → 文本字符串 → 规则解析器 → TmsIntent → TMS 广播 → 设备执行
3.3 核心组件
- VoskActivity:主活动,管理语音识别生命周期
- TmsCommandParser:NLU 解析器,将文本转换为命令
- TmsIntent:结构化命令数据
- DeviceInfoHelper:设备信息获取工具
- RecognitionLogAdapter:识别日志显示适配器
详细实现步骤
4.1 项目初始化
4.1.1 添加依赖
在 app/build.gradle 中添加:
gradle
dependencies {
// Vosk 语音识别库
implementation 'com.alphacephei:vosk-android:0.3.47@aar'
implementation 'net.java.dev.jna:jna:5.13.0@aar'
// AndroidX 支持
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.cardview:cardview:1.0.0'
}
4.1.2 配置 AAPT 选项
防止模型文件被压缩:
gradle
android {
aaptOptions {
noCompress 'fst', 'mdl', 'conf', 'int', 'txt',
'carpa', 'mat', 'raw', 'dubm', 'ie', 'stats'
}
}
4.1.3 添加权限
在 AndroidManifest.xml 中:
xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />
4.2 模型集成
4.2.1 下载模型
从 Vosk 模型下载页面 下载:
- 模型名称 :
vosk-model-small-cn-0.3 - 大小:约 49MB
- 语言:中文
4.2.2 放置模型文件
将解压后的模型文件夹放到:
app/src/main/assets/vosk-model-small-cn-0.3/
├── am/
├── conf/
├── graph/
├── ivector/
├── rescore/
└── rnnlm/
4.2.3 自动生成 UUID
在 build.gradle 中添加:
gradle
tasks.register('genModelUUID') {
def uuid = UUID.randomUUID().toString()
def odir = file("$projectDir/src/main/assets/vosk-model-small-cn-0.3")
def ofile = file("$odir/uuid")
doLast {
mkdir odir
ofile.text = uuid
}
}
preBuild.dependsOn(genModelUUID)
4.3 核心代码实现
4.3.1 模型初始化
java
import org.vosk.Model;
import org.vosk.android.StorageService;
import org.vosk.android.SpeechService;
import org.vosk.android.RecognitionListener;
public class VoskActivity extends Activity implements RecognitionListener {
private Model model;
private SpeechService speechService;
private TmsCommandParser nluParser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 初始化 NLU 解析器
nluParser = new TmsCommandParser();
// 检查权限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
initModel();
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO},
PERMISSIONS_REQUEST_RECORD_AUDIO);
}
}
private void initModel() {
Log.d(TAG, "开始加载模型: vosk-model-small-cn-0.3");
StorageService.unpack(this, "vosk-model-small-cn-0.3", "model",
(model) -> {
this.model = model;
Log.d(TAG, "模型加载成功");
runOnUiThread(() -> {
setUiState(STATE_READY);
});
},
(exception) -> {
Log.e(TAG, "模型加载失败", exception);
setErrorState("模型加载失败: " + exception.getMessage());
});
}
}
4.3.2 语音识别实现
java
private void recognizeMicrophone() {
if (model == null) {
setErrorState("模型未加载,请等待模型加载完成");
return;
}
try {
// 创建识别器,采样率 16000 Hz
Recognizer rec = new Recognizer(model, 16000.0f);
// 创建语音服务
speechService = new SpeechService(rec, 16000.0f);
// 开始监听
speechService.startListening(this);
Log.d(TAG, "录音监听已启动");
} catch (IOException e) {
Log.e(TAG, "启动录音失败", e);
setErrorState(e.getMessage());
}
}
// 实现 RecognitionListener 接口
@Override
public void onResult(String hypothesis) {
// 最终识别结果
String text = "";
try {
JSONObject json = new JSONObject(hypothesis);
if (json.has("text")) {
text = json.getString("text").replace(" ", "");
Log.d(TAG, "识别文本: " + text);
}
} catch (JSONException e) {
Log.w(TAG, "JSON 解析失败", e);
}
if (text.isEmpty()) {
return;
}
// 将文本送入 NLU 解析器
TmsIntent tmsIntent = nluParser.parse(text);
// 显示识别结果
displayResult(text, tmsIntent, false);
// 执行 TMS 命令
executeTmsCommand(tmsIntent, text);
}
@Override
public void onPartialResult(String hypothesis) {
// 部分识别结果(实时更新)
String text = "";
try {
JSONObject json = new JSONObject(hypothesis);
if (json.has("partial")) {
text = json.getString("partial");
}
} catch (JSONException e) {
text = hypothesis;
}
if (!text.isEmpty()) {
updateRecognitionLog("[部分] " + text, false);
}
}
4.3.3 NLU 规则解析器
java
public class TmsCommandParser {
public TmsIntent parse(String text) {
if (text == null || text.isEmpty()) {
return new TmsIntent(TmsIntent.Command.UNKNOWN, null);
}
// 文本规范化:转小写,移除空格和标点符号
String normalizedText = text.toLowerCase()
.replace(" ", "")
.replace(",", "")
.replace(",", "")
.replace("。", "")
.replace(".", "")
.trim();
// 规则 1:锁定设备
if (normalizedText.contains("锁定设备") || normalizedText.contains("锁屏")) {
return new TmsIntent(TmsIntent.Command.LOCK_DEVICE, null);
}
// 规则 2:WiFi 控制
if (normalizedText.contains("wifi") || normalizedText.contains("wi-fi") ||
normalizedText.contains("网络") || normalizedText.contains("无线")) {
Map<String, String> params = new HashMap<>();
if (normalizedText.contains("关闭") || normalizedText.contains("关掉")) {
params.put("state", "off");
return new TmsIntent(TmsIntent.Command.WIFI_SWITCH, params);
}
if (normalizedText.contains("打开") || normalizedText.contains("开启")) {
params.put("state", "on");
return new TmsIntent(TmsIntent.Command.WIFI_SWITCH, params);
}
}
// 规则 3:音量控制
if (normalizedText.contains("音量") || normalizedText.contains("声音")) {
Map<String, String> params = new HashMap<>();
if (normalizedText.contains("增加") || normalizedText.contains("提高") ||
normalizedText.contains("调大")) {
params.put("action", "increase");
return new TmsIntent(TmsIntent.Command.VOLUME_CONTROL, params);
}
if (normalizedText.contains("减少") || normalizedText.contains("降低") ||
normalizedText.contains("调小")) {
params.put("action", "decrease");
return new TmsIntent(TmsIntent.Command.VOLUME_CONTROL, params);
}
if (normalizedText.contains("最大") || normalizedText.contains("满")) {
params.put("action", "max");
return new TmsIntent(TmsIntent.Command.VOLUME_CONTROL, params);
}
if (normalizedText.contains("最小") || normalizedText.contains("静音")) {
params.put("action", "min");
return new TmsIntent(TmsIntent.Command.VOLUME_CONTROL, params);
}
}
// 规则 4:亮度控制
if (normalizedText.contains("亮度") || normalizedText.contains("屏幕亮度")) {
Map<String, String> params = new HashMap<>();
if (normalizedText.contains("增加") || normalizedText.contains("提高") ||
normalizedText.contains("调大") || normalizedText.contains("亮")) {
params.put("action", "increase");
return new TmsIntent(TmsIntent.Command.BRIGHTNESS_CONTROL, params);
}
if (normalizedText.contains("减少") || normalizedText.contains("降低") ||
normalizedText.contains("调小") || normalizedText.contains("暗")) {
params.put("action", "decrease");
return new TmsIntent(TmsIntent.Command.BRIGHTNESS_CONTROL, params);
}
if (normalizedText.contains("最大") || normalizedText.contains("满")) {
params.put("action", "max");
return new TmsIntent(TmsIntent.Command.BRIGHTNESS_CONTROL, params);
}
if (normalizedText.contains("最小")) {
params.put("action", "min");
return new TmsIntent(TmsIntent.Command.BRIGHTNESS_CONTROL, params);
}
}
// 规则 5:设备信息查询
if (normalizedText.contains("读取设备信息") || normalizedText.contains("查看设备信息")) {
return new TmsIntent(TmsIntent.Command.GET_DEVICE_INFO, null);
}
// 规则 6:单独信息项查询
if (normalizedText.contains("设备型号") ||
(normalizedText.contains("型号") && !normalizedText.contains("设备信息"))) {
Map<String, String> params = new HashMap<>();
params.put("item", "设备型号");
return new TmsIntent(TmsIntent.Command.GET_DEVICE_INFO_ITEM, params);
}
// 更多规则...
return new TmsIntent(TmsIntent.Command.UNKNOWN, null);
}
}
4.3.4 命令执行
java
private void executeTmsCommand(TmsIntent intent, String originalText) {
String result = "";
switch (intent.command) {
case LOCK_DEVICE:
// 发送锁定设备广播
Intent lockIntent = new Intent("android.intent.action.lockdevice");
lockIntent.putExtra("strState", "0");
sendBroadcast(lockIntent);
result = "设备已锁定";
break;
case WIFI_SWITCH:
String wifiState = intent.parameters != null ?
intent.parameters.get("state") : "unknown";
boolean wifiEnable = "on".equals(wifiState);
result = controlWifi(wifiEnable);
break;
case VOLUME_CONTROL:
String volumeAction = intent.parameters != null ?
intent.parameters.get("action") : "unknown";
result = controlVolume(volumeAction);
break;
case BRIGHTNESS_CONTROL:
String brightnessAction = intent.parameters != null ?
intent.parameters.get("action") : "unknown";
result = controlBrightness(brightnessAction);
break;
case GET_DEVICE_INFO:
result = deviceInfoHelper.getFormattedDeviceInfo();
break;
case GET_DEVICE_INFO_ITEM:
String itemName = intent.parameters != null ?
intent.parameters.get("item") : "未知";
String itemValue = deviceInfoHelper.getDeviceInfoItem(itemName);
result = itemName + ": " + itemValue;
break;
case UNKNOWN:
result = "未识别的命令";
break;
}
// 显示执行结果
showToast(result);
appendLogMessage(result);
}
4.4 设备控制实现
4.4.1 WiFi 控制
java
private String controlWifi(boolean enable) {
try {
WifiManager wifiManager = (WifiManager)
getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager == null) {
return "WiFi控制失败: 无法获取WiFi服务";
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+ 需要用户手动操作
try {
Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
startActivity(intent);
return enable ? "请手动开启WiFi" : "请手动关闭WiFi";
} catch (Exception e) {
return "WiFi控制失败: " + e.getMessage();
}
} else {
// Android 10以下可以直接控制
boolean success = wifiManager.setWifiEnabled(enable);
if (success) {
return enable ? "WiFi已开启" : "WiFi已关闭";
} else {
return "WiFi控制失败";
}
}
} catch (Exception e) {
Log.e(TAG, "WiFi控制失败", e);
return "WiFi控制失败: " + e.getMessage();
}
}
4.4.2 音量控制
java
private String controlVolume(String action) {
try {
AudioManager audioManager = (AudioManager)
getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) {
return "音量控制失败: 无法获取音频服务";
}
int streamType = AudioManager.STREAM_MUSIC;
int currentVolume = audioManager.getStreamVolume(streamType);
int maxVolume = audioManager.getStreamMaxVolume(streamType);
int newVolume = currentVolume;
switch (action) {
case "increase":
newVolume = Math.min(currentVolume + (maxVolume / 10), maxVolume);
audioManager.setStreamVolume(streamType, newVolume, 0);
break;
case "decrease":
newVolume = Math.max(currentVolume - (maxVolume / 10), 0);
audioManager.setStreamVolume(streamType, newVolume, 0);
break;
case "max":
newVolume = maxVolume;
audioManager.setStreamVolume(streamType, maxVolume, 0);
break;
case "min":
newVolume = 0;
audioManager.setStreamVolume(streamType, 0, 0);
break;
default:
return "音量控制失败: 未知操作";
}
int volumePercent = (int) ((newVolume / (float) maxVolume) * 100);
return "音量已调整到 " + volumePercent + "%";
} catch (Exception e) {
Log.e(TAG, "音量控制失败", e);
return "音量控制失败: " + e.getMessage();
}
}
性能评估与优化
5.1 设备性能评估
使用 adb 命令评估设备性能:
bash
# 查看存储空间
adb shell df -h
# 查看内存信息
adb shell "cat /proc/meminfo | grep 'MemTotal\|MemAvailable'"
# 查看 CPU 使用情况
adb shell top -n 1
实际测试结果(2.9GB RAM 设备):
存储空间: 19GB 可用 ✅
总内存: 2.9GB
可用内存: 54MB(运行 Vosk 后)
Vosk 占用: 549MB RAM
结论:
- ✅ 轻量模型(49MB)完全可行
- ❌ 大型模型(1.3GB)会导致 OOM 崩溃
5.2 性能优化建议
- 模型选择 :必须使用
vosk-model-small-cn-0.3(49MB) - 内存管理:及时释放模型资源
- 识别优化:使用部分结果减少延迟
- UI 优化:使用 RecyclerView 显示日志,避免内存泄漏
使用场景示例
6.1 驾驶场景
场景描述:驾驶时,双手需要控制方向盘,无法操作手机。
语音指令示例:
- "打开 WiFi" - 连接车载 WiFi
- "增加音量" - 调高音乐音量
- "读取设备信息" - 查看手机状态
- "设备型号" - 快速查询设备型号
优势:
- ✅ 无需分心操作手机
- ✅ 提高驾驶安全性
- ✅ 快速响应需求
6.2 厨房场景
场景描述:做饭时,双手沾满水或油,无法触摸手机。
语音指令示例:
- "增加亮度" - 调亮屏幕查看菜谱
- "减少音量" - 降低视频音量
- "电池电量" - 查看手机电量
- "关闭 WiFi" - 节省电量
优势:
- ✅ 保持双手清洁
- ✅ 无需中断烹饪
- ✅ 快速调整设置
6.3 运动场景
场景描述:运动时,手机放在口袋或运动包中,不方便取出。
语音指令示例:
- "最大音量" - 调高音乐音量
- "打开蓝牙" - 连接蓝牙耳机
- "WiFi 状态" - 检查网络连接
- "锁定设备" - 防止误触
优势:
- ✅ 无需停止运动
- ✅ 快速响应需求
- ✅ 保持运动节奏
6.4 办公场景
场景描述:开会或演示时,需要快速调整设备设置。
语音指令示例:
- "增加亮度" - 提高屏幕亮度便于演示
- "静音" - 快速静音避免打扰
- "关闭蓝牙" - 断开不必要的连接
- "读取设备信息" - 查看设备配置
优势:
- ✅ 快速响应
- ✅ 不影响会议流程
- ✅ 专业高效
6.5 无障碍场景
场景描述:视障或行动不便的用户,无法方便地操作手机。
语音指令示例:
- "设备型号" - 查询设备信息
- "电池电量" - 检查电量
- "WiFi 状态" - 检查网络
- "增加音量" - 调整音量
优势:
- ✅ 提高可访问性
- ✅ 降低操作难度
- ✅ 增强用户体验
常见问题与解决方案
7.1 模型加载失败
问题:模型加载时出现异常
解决方案:
- 检查模型文件是否完整
- 确保模型文件在
assets目录下 - 检查
aaptOptions配置是否正确 - 查看日志获取详细错误信息
7.2 识别准确率低
问题:语音识别结果不准确
解决方案:
- 确保在安静环境中使用
- 说话清晰,语速适中
- 距离麦克风适当距离(30-50cm)
- 可以尝试使用更大的模型(如果设备性能允许)
7.3 内存占用过高
问题:应用占用内存过多
解决方案:
- 使用轻量级模型(vosk-model-small-cn-0.3)
- 及时释放模型资源
- 优化 UI 显示,使用 RecyclerView
- 避免内存泄漏
7.4 权限问题
问题:无法访问麦克风或系统设置
解决方案:
- 检查
AndroidManifest.xml中的权限声明 - 运行时动态请求权限
- 对于系统级控制,需要系统权限或设备管理员权限
总结与展望
8.1 技术总结
本项目成功实现了:
- ✅ 离线语音识别:使用 Vosk 轻量级模型,完全离线运行
- ✅ 自然语言理解:基于规则的解析器,准确率高
- ✅ 设备控制:直接调用 TMS APK 内部能力
- ✅ 性能优化:资源占用小,响应速度快
8.2 技术优势
- 完全离线:不依赖网络,保护隐私
- 轻量级:模型仅 49MB,适合移动设备
- 高准确率:规则解析 100% 准确
- 易于维护:代码简洁,易于扩展
8.5 参考资料
附录:完整代码示例
A.1 TmsIntent.java
java
package org.vosk.demo;
import java.util.Map;
public class TmsIntent {
public enum Command {
LOCK_DEVICE,
WIFI_SWITCH,
BLUETOOTH_SWITCH,
VOLUME_CONTROL,
BRIGHTNESS_CONTROL,
RESET_PASSWORD,
GET_DEVICE_INFO,
GET_DEVICE_INFO_ITEM,
UNKNOWN
}
public final Command command;
public final Map<String, String> parameters;
public TmsIntent(Command command, Map<String, String> parameters) {
this.command = command;
this.parameters = parameters;
}
}
A.2 项目结构
app/src/main/java/org/vosk/demo/
├── VoskActivity.java # 主活动
├── TmsCommandParser.java # NLU 解析器
├── TmsIntent.java # 命令数据结构
├── DeviceInfoHelper.java # 设备信息工具
└── RecognitionLogAdapter.java # 日志适配器
如果本文对您有帮助,欢迎点赞、收藏、转发!如有问题,欢迎在评论区讨论。