共计 2017 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点
在移动端实现离线语音识别,开发者通常会遇到三个核心挑战:

- 模型体积过大:主流语音识别模型往往超过 200MB,严重影响 APK 体积和加载速度
- 实时性要求高:流式处理需要平衡延迟与性能,不当的线程管理会导致 UI 卡顿
- 能耗控制困难:持续运行的语音识别会快速消耗电量,特别是低端设备上 CPU 占用率飙升
技术选型对比
| 方案 | 离线支持 | 中文准确率 | 模型体积 | 定制灵活性 |
|---|---|---|---|---|
| Android 原生 SpeechRecognizer | ❌ | 中等 | 无 | 低 |
| MLKit 语音识别 | ✅ | 高 | 80MB+ | 中 |
| Vosk | ✅ | 高 | 可裁剪 | 高 |
Vosk 的核心优势在于:
- 完全离线的 Apache 2.0 授权
- 支持动态加载不同语言模型
- 提供细粒度的音频流处理 API
实现细节
1. 基础集成步骤
// build.gradle 依赖配置
dependencies {
implementation 'net.java.dev.jna:jna:5.12.1'
implementation 'com.alphacephei:vosk-android:0.3.47'
// 模型文件需手动放入 assets 目录
}
2. 模型加载优化
使用内存映射加速模型加载:
fun loadModel(context: Context, modelName: String): Model? {
return try {
// 将模型文件复制到内部存储
val modelFile = File(context.filesDir, modelName)
if (!modelFile.exists()) {context.assets.open(modelName).use { input ->
FileOutputStream(modelFile).use { output ->
input.copyTo(output)
}
}
}
// 使用内存映射加载
Model(modelFile.absolutePath)
} catch (e: IOException) {Log.e("Vosk", "Model loading failed", e)
null
}
}
3. 音频流配置
推荐参数组合(实测 Redmi Note 11 最佳表现):
private fun setupAudioRecord(): AudioRecord {
val SAMPLE_RATE = 16000 // 必须与模型匹配
val BUFFER_SIZE = 1024 * 4 // 4KB 缓冲区
return AudioRecord(
MediaRecorder.AudioSource.MIC,
SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
BUFFER_SIZE
).also {if (it.state != AudioRecord.STATE_INITIALIZED) {throw IllegalStateException("AudioRecord 初始化失败")
}
}
}
生产级优化
模型裁剪技巧
中文模型精简步骤:
- 下载完整模型:
zh-cn约 200MB - 删除
rescore目录(节省 50MB) - 移除
conf/ivector相关文件(再减 30MB) - 使用
vosk-model-small-zh-cn-0.22作为基线(最终约 50MB)
性能实测数据
设备:Redmi Note 11 (骁龙 680)
| 场景 | 平均延迟 | CPU 占用率 |
|---|---|---|
| 完整模型 | 320ms | 18% |
| 裁剪后模型 | 280ms | 15% |
| 启用热词过滤 | 240ms | 12% |
常见问题解决
SO 库冲突:
android {
packagingOptions {
pickFirst 'lib/armeabi-v7a/libvosk.so'
exclude 'lib/x86/libvosk.so' // 不需要 x86 支持可移除
}
}
延伸功能
热词增强示例
val recognizer = Recognizer(model, 16000f).apply {
// 添加业务关键词提升识别率
setWords(true)
addGrammar(listOf("登录", "注册", "支付"))
}
Compose 实时展示
@Composable
fun RecognitionResult(text: String) {Box(modifier = Modifier.fillMaxWidth()) {
Text(
text = text,
modifier = Modifier.padding(16.dp),
style = MaterialTheme.typography.bodyLarge
)
}
}
经验总结
经过三个版本的迭代优化,我们最终将语音识别模块的 APK 增量控制在 12MB 以内,平均响应时间低于 300ms。关键收获:
- 模型加载一定要做异步处理,主线程阻塞会导致 ANR
- AudioRecord 缓冲区不宜过小,否则会出现丢帧
- 低端设备上建议关闭标点符号预测功能(
setMaxAlternatives(0))
建议开发者根据实际业务场景,在识别准确率和性能消耗之间找到平衡点。下一步可以尝试将模型托管在 CDN,实现应用启动后的动态下载更新。
正文完
