微信小程序语音识别探索
背景:最近在做一个旅游相关的工程,其中有微信小程序模块,需求方希望我们在小程序加上语音识别景点的功能,可是小程序并没有支持这个功能,所以小程序的同学就扔给我这个安卓端的来做(笑)
参考资料:
1.http://www.jianshu.com/p/b092da81feb0 本文可以说是在这篇文章的基础上完成的,但一开始遇到了很大的困难(涉及到一个坑下文会提及),学过node.js的同学应该可以做到即插即用,但是本工程使用的是javaweb完成的,还是衷心感谢下作者,虽然不回我私信
2.https://github.com/kn007/silk-v3-decoder 将silk v3转化成wav使用的库,作者十分热心
为了帮助像我这样比较着急完成工程的同学,这次探索过程的苦水我就不倒了,简单粗暴上结果:
在使用微信web开发工具进行调试和真机上使用的时候,录音文件保存的格式是不一样的,本文以这个为分水岭进行介绍:
1.微信web开发者工具(0.18.182200)
总体处理流程(简述):
本地录音储存文件格式:webm类型的base64字符串
开头指明格式,随后是base64字符串
这里我的处理方法是:
1.忽略前面的类型说明,读入base64字符串
//将微信中的webm格式的base64字符提取出来
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
StringBuilder sb = new StringBuilder("");
String temp;
while((temp=reader.readLine())!=null)
{
sb.append(temp);
}
reader.close();
temp = sb.toString();
int index = temp.indexOf("base64,");
temp = temp.substring(index+7);
System.out.println(temp.substring(0,3));
byte[] nfile = decodebase64(temp);
//以字节的方式写入新文件
FileOutputStream fos = new FileOutputStream(webm);
fos.write(nfile);
fos.close();
2.使用Base64.decodeBase64(code)进行转化,得到byte[]
public byte[] decodebase64(String code)
{
return Base64.decodeBase64(code);
}
3.写入新的文件,得到真正的webm文件
4.使用ffmpeg命令的帮助:
ffmpeg -i input.webm -ac 1 -ar 16000 wav
这里ar是采样率,ac是声道数,按照百度语音的推荐设置来的,还可以设成8000.实测效果一般
这里使用的是java的Runtime环境,实测对于ffmpeg命令应该写绝对路径,不然会失败
Runtime runtime = Runtime.getRuntime();
Process proce;
String cmd = "/usr/local/bin/ffmpeg -i "+webm+" -ac 1 -ar 16000 "+wav;
proce = runtime.exec(cmd);
proce.waitFor();
BufferedReader br = new BufferedReader(new InputStreamReader(proce.getInputStream()));
StringBuffer msb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
msb.append(line).append("\n");
}
String mresult = msb.toString();
System.out.println("exec result = "+mresult);
5.使用百度语音识别client进行在线识别,并返回识别结果
// 初始化一个FaceClient
AipSpeech client = new AipSpeech(APP_ID, API_KEY, SECRET_KEY);
client.setConnectionTimeoutInMillis(2000);
client.setSocketTimeoutInMillis(60000);
res = client.asr(wav, "wav", 16000, null);
System.out.println(res.toString(4));
response.setContentType("text/html; charset=UTF8");
response.getWriter().print("start handle");
JSONObject jb = new JSONObject(res.toString());
JSONArray result = jb.getJSONArray("result");
response.getWriter().print("get->"+result);
2.手机端语音保存格式:silk-v3
总体流程跟本地调试类似,只是文件格式不一样
手机保存的录音格式为silk v3,需要使用v3库https://github.com/kn007/silk-v3-decoder 转化
作者提供了一个脚本文件converter.shi进行转化,命令为:sh converter.sh inputName wav
为了适应百度语音识别的格式,我们还需要对脚本进行一定的修改,类似上面的调整采样率和声道数
ffmpeg -y -f s16le -ar 24000 -ac 1 -i "$1.pcm" "${1%.*}.$2" > /dev/null 2>&1
剩下的步骤和上文类似。
下面是踩过的坑,希望对别的同学能有帮助:
1.版本:服务器端一开始使用的是apt-get安装ffmpeg,版本是2.8.0(好像),结果转换的时候失败了,目前使用的版本为3.3.3,这一点上库的作者没有提及,目前我的版本可以成功,供大家参考
2.ffmpeg其实包含几个模块,有的需要单独编译,参考文章http://thierry-xing.iteye.com/blog/2017864 ,一开始调查的时候找到很多文章,对这一块没有说清楚,对于时间紧迫而且对视频编码这一块没有怎么了解的确实很难办,这里提供一个方法:使用brew安装,这一一开始是在mac上用的,后来发现ubuntu也可以,还能保持两边版本相同,何乐而不为(笑)。使用方法:敲brew,聪明的系统会提示你该怎么做的。
3.命令路径:由于本工程是在java平台上完成的,因此使用了javaRuntime运行命令,发现使用命令时需要写上命令绝对路径,不然会出现执行失败的情况。可以使用命令
which ffmpeg
确定你的命令具体位置
下面会讲到一些具体的操作步骤,属于具体细节的处理,可以给大家参考一下
1.微信语音上传
wx.uploadFile({
url: 'https://xxx.xxx/xxx/upload',
filePath: res.tempFilePath,
name: 'file',
// header: {}, // 设置请求的 header
formData: {
'msg': 'voice'
}, // HTTP 请求中其他额外的 form data
success: function (res) {
...
}
})
2.文件上传接收:
使用apache的fileupload模块
// 1.创建一个DiskFileItemFactory工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,就会生成一个临时文件存放到指定的临时目录当中
factory.setSizeThreshold(1024 * 10);// 设置缓冲区的大小为100KB,如果不指定,那么默认缓冲区的大小是10KB
// 设置上传时生成的临时文件的保存目录
factory.setRepository(tmpFile);
// 2.创建一个文件上传解析器
ServletFileUpload upload = new ServletFileUpload(factory);
// 监听文件上传进度
upload.setProgressListener(new ProgressListener() {
@Override
public void update(long readedBytes, long totalBytes, int currentItem) {
// TODO Auto-generated method stub
System.out.println("current modify:" + readedBytes + "-----------file size:" + totalBytes + "--" + currentItem);
}
});
// 设置上传单个文件的最大值
upload.setFileSizeMax(1024 * 1024 * 10);// 10M
List items = upload.parseRequest(request);
Iterator itr = items.iterator();
while (itr.hasNext()) {//开始遍历
FileItem item = (FileItem) itr.next();
//保存文件
File file = new File(savePath, saveFileName);
item.write(file);
}
3.格式转换处理
Runtime runtime = Runtime.getRuntime();
Process proce;
String cmd = "/usr/local/ffmpeg -i "+webm+" -ac 1 -ar 16000 "+wav;
proce = runtime.exec(cmd);
proce.waitFor();
//监听命令执行结果
BufferedReader br = new BufferedReader(new InputStreamReader(proce.getInputStream()));
StringBuffer msb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
msb.append(line).append("\n");
}
String mresult = msb.toString();
System.out.println("exec result = "+mresult);
4.返回结果
JSONObject jb = new JSONObject(res.toString());
JSONArray result = jb.getJSONArray("result");
response.getWriter().print("get->"+result);
https://github.com/SGZoom/WechatServer