手把手,撸出java-web机器人 二(完结篇)

2,262 阅读5分钟

完整项目地址:gitee.com/Tziq/withou…

一、前言

上篇文章我们大概讲了一下,使用Springboot搭配阿里云小蜜实现文本机器人的方案。上一篇地址:juejin.cn/post/687488…

本章会提供智能语音机器人的解决方案及集成方案。

二、解决方案

问题:

想实现集成智能语音机器人的话,主要有3个痛点。

  1. 如何把麦克风的字节流转换为文本
  2. 拿到用户文本之后,如何分析用户的意图并给出对应答案
  3. 拿到答案之后,如何把对应答案转为语音

解决:

  1. 获取麦克风的字节流可以使用现在主流的语音转写功能(ASR)
  2. 拿到文本之后使用自然语言理解能力(NLU),进行解析用户意图。
  3. 拿到答案之后,使用文本转语音功能(TTS)。

所以一直知道如何解决的话,就选择使用哪家厂商的能力,我这边主要是选择阿里云的AI能力来实现咱们的智能语音机器人

三、技术选型及时序图

后台

  1. springboot
  2. WebSocket
  3. 阿里asr(实时)
  4. 阿里tts
  5. 阿里云小蜜

前台

  1. HZRecorder2.js(h5录音功能,解码js)
  2. Socket.io(集成websocket的js)

时序图:

四、集成阿里asr与tts

asr地址:help.aliyun.com/document_de…
tts地址:help.aliyun.com/document_de…

  1. 在maven项目引入所需SDK

          <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>4.5.2</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba.nls</groupId>
                <artifactId>nls-sdk-tts</artifactId>
                <version>2.1.6</version>
            </dependency>
    <dependency>    
          <groupId>com.alibaba.nls</groupId>  
          <artifactId>nls-sdk-transcriber</artifactId>   
          <version>2.1.6</version>
    </dependency>
    
  2. 集成asr代码到项目中

     public void process() {
            SpeechSynthesizer synthesizer = null;
            try {
                //创建实例,建立连接。
                synthesizer = new SpeechSynthesizer(client, getSynthesizerListener());
                synthesizer.setAppKey(appKey);
                //设置返回音频的编码格式
                synthesizer.setFormat(OutputFormatEnum.WAV);
                //设置返回音频的采样率
                synthesizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);
                //发音人
                synthesizer.setVoice("siyue");
                //语调,范围是-500~500,可选,默认是0。
                synthesizer.setPitchRate(100);
                //语速,范围是-500~500,默认是0。
                synthesizer.setSpeechRate(100);
                //设置用于语音合成的文本
                synthesizer.setText("欢迎使用阿里巴巴智能语音合成服务,您可以说北京明天天气怎么样啊");
                // 是否开启字幕功能(返回相应文本的时间戳),默认不开启,需要注意并非所有发音人都支持该参数。
                synthesizer.addCustomedParam("enable_subtitle", false);
                //此方法将以上参数设置序列化为JSON格式发送给服务端,并等待服务端确认。
                long start = System.currentTimeMillis();
                synthesizer.start();
                logger.info("tts start latency " + (System.currentTimeMillis() - start) + " ms");
                SpeechSynthesizerDemo.startTime = System.currentTimeMillis();
                //等待语音合成结束
                synthesizer.waitForComplete();
                logger.info("tts stop latency " + (System.currentTimeMillis() - start) + " ms");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //关闭连接
                if (null != synthesizer) {
                    synthesizer.close();
                }
            }
        }
    
  3. 集成TTS方法到项目中

    public void process() {
    		SpeechSynthesizer synthesizer = null;
    		try {
    			//创建实例,建立连接。
    			synthesizer = new SpeechSynthesizer(client, getSynthesizerListener());
    			synthesizer.setAppKey(appKey);
    			//设置返回音频的编码格式
    			synthesizer.setFormat(OutputFormatEnum.valueOf(ttsParam.getFormat()));
    			//设置返回音频的采样率
    			synthesizer.setSampleRate(ttsParam.getSampleRate() == 8000 ? SampleRateEnum.SAMPLE_RATE_8K : SampleRateEnum.SAMPLE_RATE_16K);
    			//发音人
    //			synthesizer.setVoice("Aixia");
    			synthesizer.setVoice(ttsParam.getVoice());
    			//语调,范围是-500~500,可选,默认是0。
    			synthesizer.setPitchRate(0);
    			//语速,范围是-500~500,默认是0。
    			synthesizer.setSpeechRate(ttsParam.getSpeechRate());
    			//设置用于语音合成的文本
    			synthesizer.setText(ttsParam.getText());
    			// 是否开启字幕功能(返回相应文本的时间戳),默认不开启,需要注意并非所有发音人都支持该参数。
    			synthesizer.addCustomedParam("enable_subtitle", false);
    			//此方法将以上参数设置序列化为JSON格式发送给服务端,并等待服务端确认。
    			long start = System.currentTimeMillis();
    			synthesizer.start();
    			log.info("tts start latency " + (System.currentTimeMillis() - start) + " ms");
    			SpeechSynthesizerUtil.startTime = System.currentTimeMillis();
    			//等待语音合成结束
    			synthesizer.waitForComplete();
    			log.info("tts stop latency " + (System.currentTimeMillis() - start) + " ms");
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			//关闭连接
    			if (null != synthesizer) {
    				synthesizer.close();
    			}
    		}
    	}
    

五、集成websocket

因为是智能语音机器人考虑到需要频繁传输字节,所以使用长连接websocket来交互字节流。

websocket简介:

简单的说: 传统 http 通讯一次交互数据后就断开连接了,服务端没法主动向客户端推送信息。 而长连接的 websocket 解决了这一问题

  1. 在maven项目引入所需SDK

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-websocket</artifactId>
            </dependency>
    
  2. 集成获取语音流方法

        /**
         * 1.获取到前台传输来的语音流,并且储存到asr需要识别的文件中
         * 2.WebScoketUtils.intentSwitchMap判断机器人是否可以说话
         * 3.判断公共变量中使用存有TTS语音文件,如果存在的话,就转换为byte数组传输给前台
         * @param message
         * @param session
         * @param uid
         * @param roleType
         * @throws IOException
         */
    @OnMessage(maxMessageSize = 1024000)
        public void onMessage(byte[] message, Session session, @PathParam("uid") String uid, @PathParam("roleType") int roleType) throws IOException {
            String keyId = WebScoketUtils.getScoketKey(uid, roleType);
            writeFile(message, uid, roleType);
            try {
                if (null != WebScoketUtils.userMap.get(keyId) && !WebScoketUtils.userMap.get(keyId).equals("")) {
                    if ("on".equals(WebScoketUtils.intentSwitchMap.get(keyId))) {
                        log.info("机器人说话");
                        String filePath = WebScoketUtils.userMap.get(keyId);
                        WebScoketUtils.userMap.remove(keyId);
                        onMessageByte(keyId, filePath);
                        WebScoketUtils.intentSwitchMap.put(keyId, "off");
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
  3. 集成监听用户是否离开事件

       @OnClose
        public void onClose(Session session, @PathParam("uid") String uid, @PathParam("roleType") int roleType) {
            remove(session, uid, roleType);
        }
    

六、前台集成HZRecorder2.js(录音插件)

  1. 使用recorder.js可以采集麦克风语音并且进行录音。

  2. 部分代码

            //开始录音        this.start = function () {            audioInput.connect(recorder);            recorder.connect(context.destination);        }
    

七、前台集成websocket进行实时传输语音字节

  1. 使用recorder.js采集麦克风录音,并且通过websocket协议传输到后台进行解析

  2. 部分代码

     var wssurl = WS_SOCKET;    var url = wssurl + 'audioMessage/' + obj.channelId + "/" + obj.roleType;    console.log("url=" + url);    socket = new WebSocket(url);    socket.onopen = onChannelOpened;    socket.onmessage = onChannelMessage;    socket.onclose = onChannelClosed;    socket.onerror = onChannelError;
    

八、使用h5自带的audioContext进行播放语音

  1. 直接通过audio标签播放音乐已经在主流浏览器中不被允许,所以考虑到一些兼容性的问题,故而使用audioContext进行播放,可以很好的兼容各大主流浏览器

  2. 部分代码

    reader.onload = function(evt) {            if (evt.target.readyState == FileReader.DONE) {                var data = new Uint8Array(evt.target.result);                audioContext.decodeAudioData(evt.target.result, function(buffer) {                    //解码成pcmvar audioBufferSouceNode = audioContext.createBufferSource();                    audioBufferSouceNode.buffer = buffer;                    audioBufferSouceNode.connect(audioContext.destination);                    audioBufferSouceNode.start(0);                }, function(e) {                    // alert("Fail to decode the file.");                });            }        }        reader.readAsArrayBuffer(data);
    

九、演示效果

十、结语

好了做完上述操作基本上语音识别就实现了,自己可以先打开html的录音录好后,运行java程序,就可以体验实时语音交互流程了。

好的,就这些了,希望能帮助到需要语音识别的你们。

如果有不懂的地方可以留言,谢谢。