避开Google Voice Search利用Google Speech API实现Android语音识别之原理
最近在做一款Android下的语音识别软件。Android下面的语音识别做起来很容易,只需要调用Google的VoiceSearch就可以了,具体方法如下:http://www.cnblogs.com/TerryBlog/archive/2010/11/12/1875875.html使用Voice Search提供的接口虽然能很方便地实现语音识别,但是却要额外安装2M多的Voice
最近在做一款Android下的语音识别软件。Android下面的语音识别做起来很容易,只需要调用Google的Voice Search就可以了,具体方法如下:
http://www.cnblogs.com/TerryBlog/archive/2010/11/12/1875875.html
使用Voice Search提供的接口虽然能很方便地实现语音识别,但是却要额外安装2M多的Voice Search,很不方便。能不能不借助Voice Search实现语音识别呢?
Voice Search的语音识别是在云端识别的,那么必然就有网络接口,只是Google没有公开而已。使用强大的Google搜索了一下,还真发现了一篇介绍这个接口的文章:http://blog.csdn.net/dlangu0393/article/details/7214728。
根据文章的描述,Gogole语音识别的网络接口地址是:
http://www.google.com/speech-api/v1/recognize?xjerr=1&client=chromium&lang=zh-CN&maxresults=1
这个接口在国外应该可以,但是在国内就要把www.google.com换成www.google.com.hk,于是接口地址改成这样:
http://www.google.com.hk/speech-api/v1/recognize?xjerr=1&client=chromium&lang=zh-CN&maxresults=1(其中lang参数表示的是语言类型,maxresults表示的是最多返回多少个结果)
接口找到了,那么直接上代码吧:
一、识别所需的音频信息采集
根据文章作者的描述,需要把语音信息通过POST方式传给服务端,并且只支持wav和SPEEX这两种格式的语音。而Android上,有两个可以录音的类:MediaRecorder和AudioRecorder。前者可以录制amr格式的录音,而后者比较底层,可以录制pcm格式的录音。pcm到wav的转换相当简单,只需要加一个头信息就可以了。因此采用AudioRecorder类录音。录音的关键代码如下:
mPCMSize = 0;
int readed = 0;
int bufferSize = AudioRecord.getMinBufferSize(mFrequence,
CHANNEL_CONFIG, AUDIO_ENCODING);
byte[] buffer = new byte[bufferSize];
AudioRecord recorder = new AudioRecord(
MediaRecorder.AudioSource.MIC, mFrequence, CHANNEL_CONFIG,
AUDIO_ENCODING, bufferSize);
recorder.startRecording();
while (mRecordLock) {
readed = recorder.read(buffer, 0, buffer.length);
if (mMaxVoiceSize < readed + mPCMSize) {
stop();
continue;
}
byteCopy(mPCMSize, readed, buffer, mPcmData);
mPCMSize += readed;
}
recorder.stop();
recorder.release();
recorder = null;
其中 mMaxVoiceSize是音频缓存的最大大小,mPcmData 是一个mMaxVoiceSize大小的byte数组,用来缓存音频信息。本程序mMaxVoiceSize的值为200k,设置的比较大。一般的音频数据只要30k的缓存就足够了。mPCMSize是语音数据的总大小。
二、pcm转wav
wav其实就是一种特殊的pcm,pcm转wav只需要在pcm原始数据之前加上一段44字节的描述音频数据的头即可。Wav格式文件头说明如下:
|
偏移地址 |
字节数 |
数据类型 |
内 容 |
|
00H |
4 |
char |
"RIFF"标志 |
|
04H |
4 |
long int |
文件长度 |
|
08H |
4 |
char |
"WAVE"标志 |
|
0CH |
4 |
char |
"fmt"标志 |
|
10H |
4 |
|
过渡字节(不定) |
|
14H |
2 |
int |
格式类别(0x0001H为PCM形式的声音数据) |
|
16H |
2 |
int |
通道数,单声道为1,双声道为2 |
|
18H |
2 |
int |
采样率(每秒样本数),表示每个通道的播放速度, |
|
1CH |
4 |
long int |
波形音频数据传送速率,其值为通道数×每秒数据位数×每样本的数据位数/8。播放软件利用此值可以估计缓冲区的大小。 |
|
20H |
2 |
int |
数据块的调整数(按字节算的),其值为通道数×每样本的数据位值/8。播放软件需要一次处理多个该值大小的字节数据,以便将其值用于缓冲区的调整。 |
|
22H |
2 |
|
每样本的数据位数,表示每个声道中各个样本的数据位数。如果有多个声道,对每个声道而言,样本大小都一样。 |
|
24H |
4 |
char |
数据标记符"data" |
|
28H |
4 |
long int |
语音数据的长度 |
代码如下:
private class WaveHeader {
public static final int HEAD_LENGTH = 44;
public final char dataType[] = { 'R', 'I', 'F', 'F' };
public int dataLength;
public char wave[] = { 'W', 'A', 'V', 'E' };
public char fmt[] = { 'f', 'm', 't', ' ' };
public int fmtHeadLength;
public short format;
public short channels;
public int frequence;
public int tranSpeed;
public short blockAlign;
public short bits;
public char data[] = { 'd', 'a', 't', 'a' };
public int pcmLength;
public WaveHeader() {
}
public byte[] getHeader() {
byte[] head = new byte[HEAD_LENGTH];
int position = 0;
position += writeChars(head, dataType, position);
position += writeInt(head, dataLength, position);
position += writeChars(head, wave, position);
position += writeChars(head, fmt, position);
position += writeInt(head, fmtHeadLength, position);
position += writeShort(head, format, position);
position += writeShort(head, channels, position);
position += writeInt(head, frequence, position);
position += writeInt(head, tranSpeed, position);
position += writeShort(head, blockAlign, position);
position += writeShort(head, bits, position);
position += writeChars(head, data, position);
position += writeInt(head, pcmLength, position);
return head;
}
private int writeChars(byte[] head, char[] chars, int start) {
for (int i = 0; i < 4; i++) {
head[i + start] = (byte) chars[i];
}
return 4;
}
private int writeInt(byte[] head, int value, int start) {
int i = start;
start += 4;
while (i < start) {
head[i] = (byte) value;
value >>= 8;
i++;
}
return 4;
}
private int writeShort(byte[] head, short value, int start) {
int i = start;
start += 2;
while (i < start) {
head[i] = (byte) value;
value >>= 8;
i++;
}
return 2;
}
}
通过getHeader()方法获得文件头之后,把这个头放在pcm数据之前就可以了。
三、wav格式语音数据上传
核心代码如下:
URL httpUrl = null;
httpUrl = new URL(getApiUrl());
HttpURLConnection urlConnection = null;
urlConnection = (HttpURLConnection) httpUrl.openConnection();
urlConnection.setConnectTimeout(HTTP_CONNECT_TIMEOUT);
urlConnection.setReadTimeout(HTTP_READ_TIMEOUT);
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.setUseCaches(false);
urlConnection.setRequestMethod("POST");
urlConnection.setAllowUserInteraction(true);
urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0");
urlConnection.setRequestProperty("Content-Type", "audio/L16; rate="
+ mFrequence);
urlConnection.setRequestProperty("Connection", "Keep-Alive");
OutputStream outputStream = urlConnection.getOutputStream();
outputStream.write(mVoiceData);
四、JSON解析
返回结果的一个典型的JSONObject为:
{"status":0,"id":"1ab4d16b0e55172689edaa8c1d2acb99-1",
"hypotheses":[
{"utterance":"明天早上九点开会","confidence":0.2452792},
{"utterance":"明天早上 9 点开会"},
{"utterance":"明天早上九点开会了"},
{"utterance":"明天早上九点开会呢"},
{"utterance":"明天早上九点开会吗"},
{"utterance":"明天早上九点开会啦"},
{"utterance":"明天早上九点开会儿"},
{"utterance":"明天早上九点开会啊"},
{"utterance":"明天早上九点开会的"},
{"utterance":"明天早上 9 点开会吗"}]}
其中hypotheses数组里面存放的就是结果,使用Android的JSONObject类和JSONArray类很容易就能解析出结果。
更多推荐



所有评论(0)