目前网上到处都可以找得到用Java、JavaScript、C#或者C++开发的语音识别的客户端的代码,而这些代码大都是封装好的库,你只管调用api接口就行了,而这些api接口到底都实现了什么,我们都不清楚,本文要介绍的就是用C来实现整个语音识别的客户端。
客户端要做的事情就是从终端的mic上采集我们的声音,将声音编码,然后通过websocket把编码的音频数据发送到服务端,同时接收服务端返回来的解析结果。
首先,客户端需要和服务端建立websocket连接。
而在连接之前,需要先获取到服务端的token。
现在几乎所有的云服务都要求客户端加密的,所以客户端在和服务端建立连接之前必须要做SSL的初始化工作,这个网上有开源的库,我们直接调用就行了。
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
既然是要和服务端通信,显然用的肯定也是socket,其实就是创建socket、根据服务端的端口号、地址connect上服务端。把ClientID和密码按服务端的要求组成post消息发送出去,至于服务端是怎么要求的,可以看服务端的资料。
因为是需要SSL加密的,所以发送消息的时候需要
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, socket_fd); //这里的socket_fd就是上面提到要创建的socket了。
SSL_connect(ssl)
SSL_write(ssl, sendBuf, strlen(sendBuf)); //把上面说的服务端要求的消息发送给服务端。
接下来就是select等服务端给我们返回token。
SSL_read(ssl, recvBuf, sizeof(recvBuf)); //读取到服务端返回的token
获取到token之后,我就可以用这个token去和服务端建立websocket连接了。
接着我们需要给服务端发送一个握手请求。
因为http协议虽然用的是tcp协议,但是客户端和服务端之间的连接是短连接,就是一问一答的形式,而在语音识别上,我需要高效的传输,所以http协议肯定满足不了我们的要求,而websocket协议其实就是客户端和服务端之间先通过http协议去握手,握手成功后,两者就可以把连接升级成tcp长连接,直接可以进行双向的传输。
和上面讲的获取token的消息一样,发送握手消息就是按服务端的要求把token组装成消息发送到服务端。发送成功后,客户端就可以进入循环读取的状态了。
while(1)
{
i = SSL_read(pSsl, recvBuf, sizeof(recvBuf));
//如果服务端返回链接升级成功的话,客户端可以另起一个线程,不停的给服务端发送已经编好码的音频数据了。
//如果服务端返回的是语音解析的结果的话,客户端可以根据这个结果去显示给用户了。
}
这里注意:服务端返回的结果都是json数据,我们需要自己解析这些json数据才能得到真正我们需要的内容,这里就不详细讲怎么解析json数据了,网上资料很多。
下面我们主要讲客户端是怎么通过websocket把音频数据发送到服务端的,就是说websocket协议的数据是怎么封装的。
websocket数据头,官网有比较详细的说明,下面把具体的代码贴上。就是说发送音频数据的第一帧必须在数据前面加上websocket的head。
void generateWavHead(char *outBuf)
{
HXD_WAVFLIEHEAD DestionFileHeader;
DestionFileHeader.RIFFNAME[0] = 'R';
DestionFileHeader.RIFFNAME[1] = 'I';
DestionFileHeader.RIFFNAME[2] = 'F';
DestionFileHeader.RIFFNAME[3] = 'F';
DestionFileHeader.nRIFFLength = 0;
DestionFileHeader.WAVNAME[0] = 'W';
DestionFileHeader.WAVNAME[1] = 'A';
DestionFileHeader.WAVNAME[2] = 'V';
DestionFileHeader.WAVNAME[3] = 'E';
DestionFileHeader.FMTNAME[0] = 'f';
DestionFileHeader.FMTNAME[1] = 'm';
DestionFileHeader.FMTNAME[2] = 't';
DestionFileHeader.FMTNAME[3] = 0x20;
DestionFileHeader.nFMTLength = 16;
DestionFileHeader.nAudioFormat = 1;
DestionFileHeader.nChannleNumber = 1;
DestionFileHeader.nSampleRate = 16000;
DestionFileHeader.nBytesPerSecond = 32000;
DestionFileHeader.nBytesPerSample = 2;
DestionFileHeader.nBitsPerSample = 16;
DestionFileHeader.DATANAME[0] = 'd';
DestionFileHeader.DATANAME[1] = 'a';
DestionFileHeader.DATANAME[2] = 't';
DestionFileHeader.DATANAME[3] = 'a';
DestionFileHeader.nDataLength = 0;
memcpy(outBuf, &DestionFileHeader, sizeof(DestionFileHeader));
}
int generateFrame(const int payLoadLen, char *payLoad, char *sendBuf)
{
int i = 0;
int j = 0;
int k = 0;
int begin = 0;
static int noFirstFrame = 0;
int remaining = 0;
int maskOffset = 0;
char b = 0;
char *p = sendBuf;
unsigned char random[4] = {'\0'};
if (noFirstFrame == 0) {
printf("this is the first data frame!\n");
noFirstFrame = 1;
sendBuf[i++] = 0x2;
} else {
sendBuf[i++] = 0x0;
}
b = -128;
if (payLoadLen < 126) {
b = (char)(b | payLoadLen & 0x7F);
sendBuf[i++] = b;
} else if (payLoadLen > 65535) {
} else {
b = (char)(b | 0x7E);
sendBuf[i++] = b;
b = (char)(payLoadLen >> 8);
sendBuf[i++] = b;
b = (char)(payLoadLen & 0xFF);
sendBuf[i++] = b;
}
srand(time(0));
for (j = 0; j < 4; j++) {
random[j] = rand()%('z' - 'a' + 1) + 'a';
sendBuf[i++] = random[j];
}
while((remaining = payLoadLen - begin) > 0) {
if (remaining >= 4) {
for (k = 0; k < 4; k++) {
sendBuf[i++] = (char)(payLoad[begin++] ^ random[k]);
}
} else {
sendBuf[i++] = (char)(payLoad[begin++] ^ random[maskOffset & 0x3]);
maskOffset++;
}
}
return i;
}
客户端每次发送的音频数据都是需要用随机码做掩码加密后才发出去的,同时客户端和服务端通信必须要先取得token,如果token不对,服务端是不会回复你消息的。这样可以:1、防止服务端被人刻意攻击;2、发送的数据被别人截获。
同时为了传输数据的更加高效,所以一般服务端都要求客户端传输的音频数据是8K采样率的音频数据,所以如果你的mic采集到的数据不是8K采样率的话,你就得要做采样率转换了。
整个语音识别的客户端的代码就讲这么多,本人的下一篇博客里会把源码都上传,感兴趣的朋友可以下载,谢谢!