Android 集成文档
最近更新时间:2023-06-26 15:32:38 前往 Coding 编辑 导出文档
语音命令(commands) 集成步骤
1. 把模型(ptl格式)文件导入到 assets 目录内
demo 模型下载地址
https://qpt-dev-voices-1251626569.cos.ap-beijing.myqcloud.com/ican/speechcommand-demo.ptl
2. 添加 pytorch_android_lite 和 android-vad 依赖,并在 manifest 文件中添加 录音 权限
implementation 'org.pytorch:pytorch_android_lite:1.12.2'
implementation 'com.github.gkonovalov:android-vad:1.0.1'
<uses-permission android:name="android.permission.RECORD_AUDIO" />
3. 申请录音权限,开启录音,监听语音流信息
startRecordAndTimer()
3. 将每一帧的流信息先暂存在一个临时的 ShortArray 中, 通过 Vad 检测每一帧流信息是否是静音,如果是静音,静音累计次数增加1,如果不是静音,则重置静音累计次数,当有连续的 25 帧为静音时且之前包含非静音帧,即认为当前由非静音转换成静音状态,然后在通过临时的 ShortArray 中流信息长度检测语音最短是否超过 300ms,如果未超过则认为是 无效语音,否则 将处理后(截掉25帧的ShortArray)的流信息交给 转译线程池 去处理
1s 16K采样率 16位 单声道的音频所采集数据大小为 1s -> 16000 * 16 * 1 = 256000bit = 32000 byte = 16000 short
正常的音频传输为 20ms 一帧,即 20ms -> 320 short
saveToTempArray(shortArrayData)
//检测是否静音
val isSpeech = vad?.isSpeech(shortArrayData) ?: false
if (isSpeech) {
isContainSpeechBuffer = true
muteTempCount = 0
} else {
muteTempCount += 1
//持续0.5s没有声音,认为当前是 静音 状态
if (muteTempCount >= 25) {
if (isContainSpeechBuffer) {
tempArray?.let {
//语音最少时长为0.3s认为是有效时长
if (it.size - 320 * 25 >= (16000 * 0.3)) {
val newBuffer = it.copyOfRange(0, it.size - 320 * 25)
isContainSpeechBuffer = false
audioNoMuteToMuteMuteCallBack.callBack(newBuffer)
}
}
}
muteTempCount = 0
tempArray = null
}
}
4. 转译工作线程中,会先加载模型加载模型获取到 Module
val modelPath = assetFilePath(applicationContext, "speechcommand-demo.ptl.ptl")
module = LiteModuleLoader.load(modelPath)
5. 将VadUtils中回调的流信息转换格式,并获取到 Tensor 对象
val recordingLength = shortArrayData.size
val floatInputBuffer = FloatArray(recordingLength)
for (i in 0 until recordingLength) {
floatInputBuffer[i] = shortArrayData[i] / Short.MAX_VALUE.toFloat()
}
val inTensor: Tensor = Tensor.fromBlob(floatInputBuffer, longArrayOf(1, floatInputBuffer.size.toLong()))
6. 将 Tensor转换成IValue并调用 Module 的 forward() 获取转译结果 Tensor
val tensor = module?.forward(IValue.from(inTensor))?.toTensor()
7. 解析 Tensor,获取Tensor中的 dataAsLongArray 遍历,并通过ConvertCommandUtils类中covertCommand()从关键词集合中转译成具体命令
tensor.dataAsLongArray.forEach {
result = it
}
ConvertCommandUtils.covertCommand(result)
8. 在退出时 停止录音,取消录音计时器, 关闭线程池任务
isRecording.set(false)