用C实现实时语音识别的客户端

news/2024/11/18 4:20:23/

目前网上到处都可以找得到用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采样率的话,你就得要做采样率转换了。

整个语音识别的客户端的代码就讲这么多,本人的下一篇博客里会把源码都上传,感兴趣的朋友可以下载,谢谢!


http://www.ppmy.cn/news/173492.html

相关文章

set up ovn development env (by quqi99)

作者&#xff1a;张华 发表于&#xff1a;2022-07-08 版权声明&#xff1a;可以任意转载&#xff0c;转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明 编译ovs并启动ovs-vswitchd #https://docs.ovn.org/en/latest/intro/install/general.html sudo apt-get…

ssl_write

TCP OPENSSL 前言TCP封装SSL封装 前言 关于TCP/IP和OPENSSL相关的描述我想很多人都知道&#xff0c;这里也不做什么陈述&#xff0c;如果刚接触的话可以去搜搜相关的文章&#xff0c;有很多写的不错的例子让你来更充分地了解他们。 这里的示例是由于要用到TLS1.2协议中的AEA…

31.openssl编程——SSL实现

31.1 概述 SSL协议最先由netscape公司提出&#xff0c;包括sslv2和sslv3两个版本。当前形成标准为tls协议&#xff08;rfc2246规范&#xff09;和DTLS(rfc4347&#xff0c;用于支持UDP协议)。sslv3和tls协议大致一样。 SSL协议能够保证通信双方的信道安全。他能提供数据加密、身…

用paddleseg跑segformer自己的数据集

框架是paddlepaddle&#xff1b; 单卡跑&#xff1b; segformer-b5&#xff1b; 数据集是 voc 格式&#xff1b; 代码&#xff1a;https://github.com/PaddlePaddle/PaddleSeg/blob/release/2.4/docs/whole_process_cn.md 环境的安装&#xff1a;https://blog.csdn.net/Scener…

Play with OVN (by quqi99)

作者&#xff1a;张华 发表于&#xff1a;2019-11-22 版权声明&#xff1a;可以任意转载&#xff0c;转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明 OVN is the replacement of Neutron, so we need to have a look at OVN. Refer - https://zhhuabj.blog…

厨电「前浪」压「后浪」

作者 | 辰纹 来源 | 洞见新研社 俗话说&#xff0c;姜是老的辣。说的是老年人有经验&#xff0c;办事稳重老练。 俗话也说&#xff0c;长江后浪推前浪。说的是经过历练的新人新事胜过旧人旧事。 这两句话看似矛盾&#xff0c;实则充满哲理&#xff0c;老而弥坚的老手与初生牛…

OSI七层协议模型和TCP/IP四层模型

TCP/IP 协议栈及 OSI 参考模型详解&#xff1a;https://blog.csdn.net/guobing19871024/article/details/79415846 OSI七层网络模型&#xff0c;TCP/IP四层网络模型与网络协议解析&#xff1a;http://www.360doc.com/content/13/1123/17/7267612_331579105.shtml [网络必学]TCP…

hive详解(二)

2 hive的两种访问方式 2.4.1 命令行的方式 在前面的操作中&#xff0c;我们都是通过cli的方式访问hive的。我们可以切身的体会到&#xff0c;通过cli的方式访问hive的不足&#xff0c;如&#xff1a;cli太过笨重&#xff0c;需要hive的jar支持。 2.4.2 HiveServer2模式 1.JD…