package com.yeejoin.amos.speech;

import com.alibaba.nls.client.protocol.InputFormatEnum;
import com.alibaba.nls.client.protocol.SampleRateEnum;
import com.alibaba.nls.client.protocol.asr.SpeechTranscriber;
import com.alibaba.nls.client.protocol.asr.SpeechTranscriberListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;


/**
 * 实时语音识别
 * <p>
 * 支持的输入格式：PCM（无压缩的PCM或WAV文件）、16 bit采样位数、单声道（mono）。
 * 支持的音频采样率：8000 Hz和16000 Hz。
 */
public class AppSpeechTranscriber {

    private static final Logger logger = LoggerFactory.getLogger(AppSpeechTranscriber.class);
    private final SpeechTranscriberListener listener;
    private final DatagramSocket serverSocket;
    private final Timer serverSocketTimeoutTimer;
    private final TimerTask timerTask;
    private final String localIpAddress;
    private int idleTime = 0;

    public AppSpeechTranscriber(SpeechTranscriberListener listener, DatagramSocket serverSocket, String localIpAddress) {
        this.localIpAddress = localIpAddress;
        this.listener = listener;
        this.serverSocket = serverSocket;
        serverSocketTimeoutTimer = new Timer();
        //60s未收到任何请求，直接关闭监听
        timerTask = new TimerTask() {
            @Override
            public void run() {
                idleTime++;
                if (idleTime > 60) {
                    timerTask.cancel();
                    serverSocketTimeoutTimer.cancel();
                    logger.warn("serverSocket,port:" + serverSocket.getLocalPort() + " 等待60s无数据回复即将自动关闭！");
                    closeServerSocket();
                }
            }
        };
        serverSocketTimeoutTimer.schedule(timerTask, 0, 1000);
    }

    /**
     * 开始语音识别
     */
    public void process() {
        SpeechTranscriber transcriber = null;
        try {
            //创建实例、建立连接。
            byte[] b = new byte[332];
            DatagramPacket datagramPacket = new DatagramPacket(b, b.length);
            logger.warn("serverSocket已启动，地址：" + localIpAddress
                    + "监听端口：" + serverSocket.getLocalPort() + "  等待语音融合系统推送数据...");
            while (true) {
                serverSocket.receive(datagramPacket);
                if (transcriber == null) {
                    logger.warn("收到第一个数据包：" + b.length + " 开始进行语音翻译");
                    transcriber = new SpeechTranscriber(AppNslClient.instance(), listener);
                    //设置识别参数
                    setParam(transcriber);
                    transcriber.start();
                }
                idleTime = 0;
                logger.warn("收到数据包：" + b.length);
                //去掉前12个字节的rtp包头，后面的332字节为语音数据
                //4秒未再次调用此方法，阿里云会抛出超时异常
                transcriber.send(Arrays.copyOfRange(b, 12, b.length));
            }
        } catch (Exception e) {
            logger.error(e.getMessage());
        } finally {
            if (null != transcriber) {
                transcriber.close();
            }
            closeServerSocket();
            logger.warn("语音转文字已结束");
        }
    }

    /**
     * 关闭Serversocket
     */
    public void closeServerSocket() {
        if (serverSocket != null && !serverSocket.isClosed()) {
            serverSocket.close();
        }
    }

    /**
     * 设置识别参数
     */
    private void setParam(SpeechTranscriber transcriber) {
        transcriber.setAppKey(SpeechConfig.AppKey);
        //输入音频编码方式。
        transcriber.setFormat(InputFormatEnum.PCM);
        //输入音频采样率。
        transcriber.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);
        //是否返回中间识别结果。
        transcriber.setEnableIntermediateResult(true);
        //是否生成并返回标点符号。
        transcriber.setEnablePunctuation(true);
        //是否将返回结果规整化，比如将一百返回为100。
        transcriber.setEnableITN(true);
        //设置vad断句参数。默认值：800ms，有效值：200ms～2000ms。
        transcriber.addCustomedParam("max_sentence_silence", 500);
        //设置是否语义断句。
        //transcriber.addCustomedParam("enable_semantic_sentence_detection",false);
        //设置是否开启顺滑。
        //transcriber.addCustomedParam("disfluency",true);
        //设置是否开启词模式。
        transcriber.addCustomedParam("enable_words", true);
        //设置vad噪音阈值参数，参数取值为-1～+1，如-0.9、-0.8、0.2、0.9。
        //取值越趋于-1，判定为语音的概率越大，亦即有可能更多噪声被当成语音被误识别。
        //取值越趋于+1，判定为噪音的越多，亦即有可能更多语音段被当成噪音被拒绝识别。
        //该参数属高级参数，调整需慎重和重点测试。
        //transcriber.addCustomedParam("speech_noise_threshold",0.3);
        //设置训练后的定制语言模型id。
        //transcriber.addCustomedParam("customization_id","你的定制语言模型id");
        //设置训练后的定制热词id。
        //transcriber.addCustomedParam("vocabulary_id","你的定制热词id");
        //设置是否忽略单句超时。
        transcriber.addCustomedParam("enable_ignore_sentence_timeout", false);
        //vad断句开启后处理。
        //transcriber.addCustomedParam("enable_vad_unify_post",false);
        //此方法将以上参数设置序列化为JSON发送给服务端，并等待服务端确认。
    }
}
