My Avatar

Shadow

I love bleak day, like something will happen

讯飞语音识别(WebAPI)

2017年09月29日 星期五, 发表于 北京

如果你对本文有任何的建议或者疑问, 可以在 这里给我提 Issues, 谢谢! :)

  今天是周五,2017年9月29号,后天国庆节,我要去当人生的第一次伴郎,所以明天回家,嘿嘿。

  最近弄了下讯飞的语音识别,之前讯飞是提供了SDK来做这件事,后来讯飞又出了个AIUI开放平台,在AIUI开放平台里支持了WebApi,目前提供了语义分析、语音识别、语音识别后语义分析三个接口。我主要用到了其中的语音识别接口,但是使用过程中也是有不少坑的哦,所以这里记录下。


讯飞语音识别接口

  首先,我们得在AIUI开放平台注册一个账号,然后新建一个应用,这些我就不介绍了,自己去官网看,建完应用后,可以看到应用如下图:

  然后,看下他们的webapi的文档说明:

  其中语音识别的接口说明如下:

  可以看到,对于所有的webapi,我们都需要在header中加上四个字段,分别是X-Appid,X-CurTime,X-Param,X-CheckSum

  下面,我们就先一一说明怎么获得这些header

X-Appid

  这个好说,就是你的应用的appid,直接在应用界面就能拿到

X-CurTime

  这个也好说,就是当前时间戳,不过是秒为单位,如果是java(我都是用java),可以用如下方式获得

1
System.currentTimeMillis() / 1000

X-Param

  首先,这是一个字符串,这个字符串是通过对一个json进行base64编码获得的。那么首先你要知道这个json是啥,对于不同的接口,你要传的json是不一样的,看上面的语音识别借口说明,我们可以知道我们的json是如下:

1
2
3
{"auf":"8k","aue":"raw","scene":"main"} 
或者
{"auf":"16k","aue":"raw","scene":"main"} 

  那么,我们只要将这个json首先encode成字符串,然后通过base64编码方法获得字符串即可,在我的实现中是这么做的:(JsonObject是vertx里面的类)

1
2
3
4
5
6
7
//params是JsonObject对象
String xParam = CryptoUtils.base64Encode(params.encode().getBytes("UTF-8"));

//CryptoUtils.base64Encode方法如下:
public static String base64Encode(byte[] bytes) {
	return Base64.getEncoder().encodeToString(bytes);
}

X-CheckSum

  checksum是一个MD5哈希值,哈希的内容是apikey+curtime+param+http_body,其中apikey在应用界面可以获得,如果忘记了点重置就能获得新的,curtime是上面算出来的header中的X-CurTime字段的值,param也是header中的X-Param字段的值,最重要的是http_body如何计算,对于不同的接口,http_body不同,对于语音识别来说,http_body是:

1
2
data=xxxxxxxxxxxxxxx
//其中xxxxxxxxx是对音频文件进行base64编码后的字符串

  所以如果ApiKey是abcd1234, CurTime是1502607694,Param是eyJzY2VuZSI6Im1haW4ifQ==, http_body是data=5LuK5aSp5pif5pyf5Yeg。那么CheckSum为MD5(abcd12341502607694eyJzY2VuZSI6Im1haW4ifQ==data=5LuK5aSp5pif5pyf5Yeg)

  所以checksum计算方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
String xCheckSum =
    CryptoUtils.string2MD5(xfconf.getString("apikey") + xCurTime + xParam + body);

//CryptoUtils.string2MD5方法如下:
public static String string2MD5(String inStr) {
	MessageDigest md5 = null;
	try {
  		md5 = MessageDigest.getInstance("MD5");
	} catch (Exception e) {
  		System.out.println(e.toString());
  		e.printStackTrace();
  		return "";
	}
	char[] charArray = inStr.toCharArray();
	byte[] byteArray = new byte[charArray.length];

	for (int i = 0; i < charArray.length; i++)
  		byteArray[i] = (byte) charArray[i];
	byte[] md5Bytes = md5.digest(byteArray);
	StringBuffer hexValue = new StringBuffer();
	for (int i = 0; i < md5Bytes.length; i++) {
  		int val = ((int) md5Bytes[i]) & 0xff;
  		if (val < 16)
    		hexValue.append("0");
  		hexValue.append(Integer.toHexString(val));
	}
	return hexValue.toString();
}

其他调用接口注意事项

  首先,我们注意到接口的Content-Type:application/x-www-form-urlencoded; charset=utf-8,所以不要忘了在header中机上Content-type字段

  然后不要忘了加IP白名单

  最重要的是注意讯飞能处理的音频文件的内容,从接口描述上来看,目前只支持PCM或者WAV格式的音频,WAV是在PCM的基础上加了一些文件头,播放器可以直接播放的音频,而PCM则无法直接播放。

  另外,支持的音频采样率只有8k和16k两种,其实还有一个隐藏没说的参数要求就是每样本比特数,貌似讯飞只支持16的

  最开始我们用HTML5录音的功能去录音,然后发到讯飞去识别,根本识别不出来,result为空,后来我仔细对比了讯飞合成的音频文件(能很好的识别出来)和html录音获得的音频文件的(都是wav格式)之间的区别。最后发现,html录音的采样率为7350,每样本比特数为8,这样就识别不出来,所以后来改了那个录音的js,改了采样率为8K和每样本bit为16,这样就能比较好的识别了。

  还有一个值得注意的,前端录音的数据通过编码为字符串后传到后台解码,默认采用的utf-16编码格式,所以后台解码成byte数组时要指定字符集为utf-16。另外通过对比,发现解码后的字节流开头多了两个字节,并且后面每两个字节的顺序是颠倒的,需要处理一下就可以了。