共计 3318 个字符,预计需要花费 9 分钟才能阅读完成。
语音识别已成为移动端人机交互的重要入口,阿里云 SDK 提供了高效的识别能力,但官方文档中关于动态权限申请的时序处理(如必须在初始化前完成权限检查)和 Android 11 文件作用域(Scoped Storage)的适配说明存在明显缺失,容易导致初次集成时出现运行时错误。

环境配置与基础接入
-
权限处理 :在 AndroidManifest.xml 中声明录音和网络权限后,必须使用 ActivityResult API 处理运行时权限申请。注意将权限检查放在 SDK 初始化之前,否则在部分厂商 ROM 上会出现静默失败。
-
Gradle 依赖 :除了添加阿里云 SDK,还需要显式引入 okhttp 和 gson 依赖,因为 SDK 内部使用 websocket 协议传输音频数据。
// build.gradle
implementation 'com.aliyun:aliyun-voice-recognizer:2.3.6'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
音频采集方案对比
- MediaRecorder:系统级封装,支持直接输出 AAC 格式,但延迟较高(约 500ms),适合录制场景
- AudioRecord:底层 API,可获取原始 PCM 数据,延迟可控制在 100ms 内,但需要自行处理音频格式转换
推荐实时识别场景使用 AudioRecord,配合 16kHz 采样率和单声道配置(CHANNEL_IN_MONO):
val bufferSize = AudioRecord.getMinBufferSize(
16000,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
val recorder = AudioRecord(
MediaRecorder.AudioSource.MIC,
16000,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
bufferSize
)
网络层封装关键点
- 带重试机制的 WebSocket:阿里云识别服务要求通过 websocket 传输音频流,需要处理网络抖动导致的连接中断。建议设置 10 秒超时和最多 3 次重试:
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
val request = Request.Builder()
.url("wss://smartvoice-shanghai.aliyuncs.com/ws/v1")
.addHeader("Authorization", "Bearer $token")
.build()
val ws = client.newWebSocket(request, object : WebSocketListener() {override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {if(retryCount < 3) {Thread.sleep(1000)
retryCount++
startStreaming() // 重新连接}
}
})
- 环形缓冲区实现 :创建固定大小的 ByteArray 循环队列,避免频繁 GC。当写入指针追上读取指针时,丢弃最旧数据并记录 overflow 次数:
class AudioBuffer(capacity: Int) {private val buffer = ByteArray(capacity)
private var readPos = 0
private var writePos = 0
@Synchronized
fun put(data: ByteArray) {if(data.size > buffer.size) {
System.arraycopy(data, data.size - buffer.size,
buffer, 0, buffer.size)
writePos = 0
return
}
val remaining = buffer.size - writePos
if(data.size <= remaining) {System.arraycopy(data, 0, buffer, writePos, data.size)
writePos += data.size
} else {System.arraycopy(data, 0, buffer, writePos, remaining)
System.arraycopy(data, remaining, buffer, 0, data.size - remaining)
writePos = data.size - remaining
}
}
}
核心封装类实现
class AliSpeechRecognizer(context: Context) {
@Volatile private var isRecording = false
private val audioThread by lazy {HandlerThread("AudioThread").apply {start() } }
// SDK 初始化(密钥建议从服务端动态获取)@WorkerThread
fun initEngine(accessKey: String, secret: String) {val config = SpeechRecognizerConfigure().apply {
appKey = "YOUR_APP_KEY"
token = generateToken(accessKey, secret) // 需实现 HMAC-SHA1 签名
format = AudioFormat.PCM
sampleRate = 16000
enableIntermediateResult = true
}
SpeechRecognizer.getInstance().init(context, config)
}
// PCM 流式识别
@WorkerThread
fun startStreaming() {val audioRecord = createAudioRecord() // 创建上文配置的 AudioRecord
val buffer = ByteArray(4096)
audioRecord.startRecording()
isRecording = true
while (isRecording) {val read = audioRecord.read(buffer, 0, buffer.size)
if (read > 0) {SpeechRecognizer.getInstance().sendAudio(buffer, 0, read, false)
}
}
}
// 错误处理(1003= 网络超时,2001= 鉴权失败)fun handleError(code: Int) {when(code) {
1003 -> {
// 网络问题自动重试
startStreaming()}
2001 -> {
// 重新获取 token
refreshToken()}
}
}
}
必须注意的兼容性问题
-
Android 11 文件访问 :使用 MediaStore API 保存识别结果,或声明 MANAGE_EXTERNAL_STORAGE 权限(需上架审核)
-
Proguard 混淆规则 :确保 SDK 的 JNI 方法不被混淆,在 proguard-rules.pro 中添加:
-keep class com.aliyun.speech.** {*;}
-keep class com.alibaba.** {*;}
进阶优化方向
- 离线 VAD 检测 :可在本地通过计算短时能量和过零率,判断是否有人声,减少无效网络请求
- 多声道处理 :对双麦克风设备,使用 FFT 转换频域后做波束形成(Beamforming)增强目标声源
- 内存优化 :IoT 设备可降低采样率至 8kHz,使用 opus 等低比特率 codec 压缩音频
整个集成过程中最重要的是处理好音频采集与网络传输的协同关系,建议通过性能分析工具(如 Android Profiler)持续监控 CPU 和内存占用。当出现识别率下降时,优先检查音频格式是否严格符合 16kHz PCM 单声道的要求。
正文完
