共计 4071 个字符,预计需要花费 11 分钟才能阅读完成。
背景痛点
语音识别功能在移动应用中越来越常见,但开发者在集成过程中经常会遇到各种问题。根据我的经验,最常见的痛点包括:

- SDK 版本兼容性问题,特别是 Android 系统版本差异导致的兼容性问题
- 权限配置遗漏,尤其是运行时权限申请容易被忽略
- 网络环境不稳定导致识别失败
- 识别延迟高,影响用户体验
- API Key 管理不当导致安全风险
这些问题如果不处理好,轻则影响功能使用,重则导致应用崩溃或数据泄露。
技术选型
目前主流的语音识别方案主要有以下几种:
- 百度语音识别
- 优点:识别准确率高、支持离线识别、中文支持好
-
缺点:免费调用次数有限制
-
科大讯飞
- 优点:语音技术成熟、支持多种方言
-
缺点:商业授权费用较高
-
Google Speech API
- 优点:国际化支持好、与 Android 系统集成度高
-
缺点:国内访问不稳定
-
阿里云智能语音
- 优点:阿里云生态整合好
- 缺点:文档相对复杂
综合比较后,百度语音识别在中文场景下具有明显优势,特别是对中小开发者比较友好。
实现步骤
获取 API Key
- 访问百度 AI 开放平台 (ai.baidu.com)
- 注册 / 登录百度账号
- 进入控制台创建新应用
- 记录下 AppID、API Key 和 Secret Key
build.gradle 配置
在 app 模块的 build.gradle 中添加依赖:
dependencies {
implementation 'com.baidu.aip:java-sdk:4.16.11'
// 最低支持 Android 5.0(API 21)
minSdkVersion 21
}
AndroidManifest.xml 配置
添加必要的权限声明:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
SDK 初始化
最佳实践是在 Application 类中初始化:
class MyApp : Application() {override fun onCreate() {super.onCreate()
// 初始化语音识别
SpeechUtility.getInstance().setAppid("你的 AppID")
// 设置开发模式 (上线时改为 false)
SpeechUtility.getInstance().setParam(SpeechConstant.DEBUG_MODE, "true")
}
}
核心代码实现
动态权限申请
private fun checkRecordPermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.RECORD_AUDIO),
REQUEST_RECORD_AUDIO_PERMISSION
)
} else {startRecognizing()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {when (requestCode) {
REQUEST_RECORD_AUDIO_PERMISSION -> {if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {startRecognizing()
} else {Toast.makeText(this, "需要录音权限", Toast.LENGTH_SHORT).show()}
}
}
}
语音识别回调处理
private val listener = object : EventListener() {
override fun onEvent(
name: String,
params: String,
data: ByteArray,
offset: Int,
length: Int
) {when (name) {
SpeechConstant.CALLBACK_EVENT_ASR_READY -> {// 识别器准备就绪}
SpeechConstant.CALLBACK_EVENT_ASR_BEGIN -> {// 检测到用户说话}
SpeechConstant.CALLBACK_EVENT_ASR_END -> {// 检测到用户停止说话}
SpeechConstant.CALLBACK_EVENT_ASR_PARTIAL -> {
// 临时识别结果
val result = JSONObject(params).getString("best_result")
runOnUiThread {updateResultUI(result) }
}
SpeechConstant.CALLBACK_EVENT_ASR_FINISH -> {
// 识别结束,最终结果
if (params.contains("success")) {val result = JSONObject(params).getString("best_result")
runOnUiThread {updateFinalResult(result) }
} else {
// 识别失败,可在此处实现重试逻辑
handleError(params)
}
}
}
}
}
音频流实时处理
private fun setupAudioStream() {
val asr = SpeechRecognizer.createRecognizer(
this,
"您的 appid"
).apply {setEventListener(listener)
}
// 设置识别参数
val params = HashMap<String, String>().apply {put(SpeechConstant.PID, "1536") // 普通话输入法模型
put(SpeechConstant.VAD, SpeechConstant.VAD_DNN) // 使用深度学习 VAD
put(SpeechConstant.ACCEPT_AUDIO_VOLUME, "false") // 不需要音量回调
}
asr.setParameter(params)
asr.startListening(params)
// 实现 AudioRecord 读取音频数据并发送给 asr.feed
audioThread = Thread {val buffer = ByteArray(3200) // 16k 采样率,16bit,100ms 数据
while (isRecording) {val len = audioRecord.read(buffer, 0, buffer.size)
if (len > 0) {asr.feedAudioBuffer(buffer, 0, len)
}
}
}.apply {start() }
}
性能优化
降低识别延迟
- 使用 VAD_DNN 语音活动检测
- 适当降低采样率 (16kHz 足够)
- 开启本地端点检测
params.apply {put(SpeechConstant.VAD, SpeechConstant.VAD_DNN)
put(SpeechConstant.SAMPLE_RATE, "16000")
put(SpeechConstant.ACCEPT_AUDIO_VOLUME, "false")
}
离线语音包加载
- 提前下载离线资源包
- 设置离线语音识别模式
// 检查离线资源是否已下载
if (!SpeechUtility.getInstance().checkResourceExist("asr")) {
// 下载离线资源
SpeechUtility.getInstance().loadResource(
"您的 appid",
"asr",
object : IResourceLoadListener {override fun onLoadSuccess() {// 离线资源加载成功}
override fun onLoadError(errorCode: Int) {// 加载失败}
}
)
}
// 设置离线识别参数
params.put(SpeechConstant.DECODER, "2") // 2 表示离线识别
预防内存泄漏
- 在 Activity 销毁时释放资源
- 使用弱引用持有 Activity
- 停止录音线程
override fun onDestroy() {super.onDestroy()
speechRecognizer?.let {it.stopListening()
it.cancel()
it.destroy()}
isRecording = false
audioThread?.interrupt()
audioRecord?.release()}
避坑指南
- ERROR_NO_NETWORK 错误
- 检查网络权限是否声明
-
添加网络状态监听,提示用户检查网络
-
录音失败
- 检查 RECORD_AUDIO 权限是否授予
-
检查是否有其他应用占用了麦克风
-
识别准确率低
- 确保采样率设置正确 (16000)
- 在安静环境下测试
- 调整 VAD 参数
安全建议
- 不要将 API Key 硬编码在客户端
- 建议通过后端服务中转 API 调用
- 使用 ProGuard 混淆代码
- 定期轮换 API Key
动手实验
尝试修改采样率参数,观察识别效果变化:
- 将采样率从 16000 改为 8000,测试识别准确率
- 改为 44100,观察延迟变化
- 记录不同设置下的识别准确率和响应时间
通过这个实验,你可以找到最适合你应用场景的参数配置。
正文完
