|
@@ -1,244 +0,0 @@
|
|
|
-package com.zhili.stationcontrol.tts.component;
|
|
|
-
|
|
|
-import com.alibaba.fastjson.JSON;
|
|
|
-import com.zhili.stationcontrol.tts.util.WavPlayUtil;
|
|
|
-import com.sun.jna.Library;
|
|
|
-import com.sun.jna.Native;
|
|
|
-import com.sun.jna.Pointer;
|
|
|
-import com.sun.jna.ptr.IntByReference;
|
|
|
-import lombok.extern.slf4j.Slf4j;
|
|
|
-import org.springframework.beans.factory.DisposableBean;
|
|
|
-import org.springframework.beans.factory.annotation.Autowired;
|
|
|
-import org.springframework.beans.factory.annotation.Value;
|
|
|
-import org.springframework.data.redis.core.RedisTemplate;
|
|
|
-import org.springframework.stereotype.Component;
|
|
|
-
|
|
|
-import javax.annotation.PostConstruct;
|
|
|
-import java.io.IOException;
|
|
|
-import java.io.RandomAccessFile;
|
|
|
-import java.util.Map;
|
|
|
-import java.util.concurrent.TimeUnit;
|
|
|
-
|
|
|
-/**
|
|
|
- * @author :HuangBin
|
|
|
- * @description:TODO
|
|
|
- * @date :2023/5/17 19:00
|
|
|
- */
|
|
|
-@Component
|
|
|
-@Slf4j
|
|
|
-public class XunFeiTextToSpeech implements DisposableBean, Runnable {
|
|
|
-
|
|
|
- Thread thread;
|
|
|
-
|
|
|
- public static String loginParams;
|
|
|
-
|
|
|
- public static String sessionBeginParams;
|
|
|
-
|
|
|
- public static String filename;
|
|
|
-
|
|
|
- public static String linkFile;
|
|
|
-
|
|
|
- public static String playWay;
|
|
|
- @Autowired
|
|
|
- RedisTemplate redisTemplate;
|
|
|
-
|
|
|
- @Value("${xunfei_tts.play_way}")
|
|
|
- public void setPlayWay(String p) {
|
|
|
- playWay = p;
|
|
|
- }
|
|
|
-
|
|
|
- @Value("${xunfei_tts.login_params}")
|
|
|
- public void setLoginParams(String p) {
|
|
|
- loginParams = p;
|
|
|
- }
|
|
|
-
|
|
|
- @Value("${xunfei_tts.session_begin_params}")
|
|
|
- public void setSessionBeginParams(String p) {
|
|
|
- sessionBeginParams = p;
|
|
|
- }
|
|
|
-
|
|
|
- @Value("${xunfei_tts.filename}")
|
|
|
- public void setFilename(String p) {
|
|
|
- filename = p;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- @Value("${xunfei_tts.link_file}")
|
|
|
- public void setLinkFile(String p) {
|
|
|
- linkFile = p;
|
|
|
- }
|
|
|
-
|
|
|
- @PostConstruct
|
|
|
- public void init() {
|
|
|
- log.info("login_params: {}", loginParams);
|
|
|
- //登录参数,appid与msc库绑定,请勿随意改动
|
|
|
- int loginCode = MscLibrary.INSTANCE.MSPLogin(null, null, loginParams);
|
|
|
- log.info("loginCode: {}", loginCode);
|
|
|
- if (loginCode != 0) {
|
|
|
- //登录失败
|
|
|
- MscLibrary.INSTANCE.MSPLogout();
|
|
|
- log.info("loginfail:登录失败");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- this.thread = new Thread(this);
|
|
|
- this.thread.start();
|
|
|
- log.info("start:线程启动");
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void run() {
|
|
|
- while (true) {
|
|
|
- RandomAccessFile raf = null;
|
|
|
- String sessionId = null;
|
|
|
- try {
|
|
|
- Object voiceObj = redisTemplate.opsForList().leftPop("ttsList", 500l, TimeUnit.MILLISECONDS);
|
|
|
- if (voiceObj == null) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- Map<String, Object> voice = JSON.parseObject(voiceObj.toString(), Map.class);
|
|
|
- String text = voice.get("txt").toString();
|
|
|
- Integer vol = Integer.valueOf(voice.get("vol").toString());
|
|
|
- log.info("speak:{},vol:{}", text, vol);
|
|
|
- IntByReference errCode = new IntByReference();
|
|
|
- sessionId = MscLibrary.INSTANCE.QTTSSessionBegin(sessionBeginParams, errCode);
|
|
|
- if (errCode.getValue() != 0) {
|
|
|
- log.error("会话失败:" + errCode.getValue());
|
|
|
- throw new RuntimeException("会话失败");
|
|
|
- }
|
|
|
- //放入文本
|
|
|
- int textPutCode = MscLibrary.INSTANCE.QTTSTextPut(sessionId, text, text.getBytes().length, null);
|
|
|
- if (textPutCode != 0) {
|
|
|
- log.error("放入文本失败:" + textPutCode);
|
|
|
- throw new RuntimeException("放入文本失败");
|
|
|
- }
|
|
|
- raf = new RandomAccessFile(this.filename, "rw");
|
|
|
- raf.write(new byte[44]);
|
|
|
- int dataSize = 0;
|
|
|
- IntByReference audioLen = new IntByReference();
|
|
|
- IntByReference synthStatus = new IntByReference();
|
|
|
- while (true) {
|
|
|
- Pointer pointer = MscLibrary.INSTANCE.QTTSAudioGet(sessionId, audioLen, synthStatus, errCode);
|
|
|
- if (pointer != null && audioLen.getValue() > 0) {
|
|
|
- //写入合成内容
|
|
|
- raf.write(pointer.getByteArray(0, audioLen.getValue()));
|
|
|
- //记录数据长度
|
|
|
- dataSize += audioLen.getValue();
|
|
|
- }
|
|
|
- //转换异常或转换结束跳出循环
|
|
|
-// log.error("errCode.getValue():{}", errCode.getValue());
|
|
|
- if (errCode.getValue() != 0 || synthStatus.getValue() == 2) {
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- if (textPutCode != 0) {
|
|
|
- //获取转换数据失败
|
|
|
- log.error("获取转换数据失败");
|
|
|
- throw new RuntimeException("获取转换数据失败");
|
|
|
- }
|
|
|
- //定位到文件起始位置
|
|
|
- raf.seek(0);
|
|
|
- //写入真实头格式
|
|
|
- raf.write(getWavHeader(dataSize, 16000, 32000, 1, 16));
|
|
|
- if (playWay.equals("java")) {
|
|
|
- WavPlayUtil.play(filename);
|
|
|
- } else if (playWay.equals("sox")) {
|
|
|
- WavPlayUtil.play2(filename, vol);
|
|
|
- }
|
|
|
- } catch (Exception e) {
|
|
|
- e.printStackTrace();
|
|
|
- } finally {
|
|
|
- if (sessionId != null) {
|
|
|
- MscLibrary.INSTANCE.QTTSSessionEnd(sessionId, "Normal");
|
|
|
- }
|
|
|
- if (raf != null) {
|
|
|
- try {
|
|
|
- raf.close();
|
|
|
- } catch (IOException e) {
|
|
|
- e.printStackTrace();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void destroy() throws Exception {
|
|
|
- MscLibrary.INSTANCE.MSPLogout();
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- public interface MscLibrary extends Library {
|
|
|
-
|
|
|
- // DLL文件默认路径为项目根目录,若DLL文件存放在项目外,请使用绝对路径
|
|
|
- MscLibrary INSTANCE = Native.load("lib/" + XunFeiTextToSpeech.linkFile, MscLibrary.class);
|
|
|
-
|
|
|
- int MSPLogin(String username, String password, String param);
|
|
|
-
|
|
|
- int MSPLogout();
|
|
|
-
|
|
|
- String QTTSSessionBegin(String params, IntByReference errorCode);
|
|
|
-
|
|
|
- int QTTSTextPut(String sessionID, String textString, int textLen, String params);
|
|
|
-
|
|
|
- Pointer QTTSAudioGet(String sessionID, IntByReference audioLen, IntByReference synthStatus, IntByReference errorCode);
|
|
|
-
|
|
|
- int QTTSSessionEnd(String sessionID, String hints);
|
|
|
- }
|
|
|
-
|
|
|
- static byte[] getWavHeader(int totalAudioLen, int sampleRate, int byteRate, int nChannels, int weikuan) {
|
|
|
- long totalDataLen = totalAudioLen + 36;
|
|
|
- byte[] header = new byte[44];
|
|
|
- header[0] = 'R'; // RIFF/WAVE header
|
|
|
- header[1] = 'I';
|
|
|
- header[2] = 'F';
|
|
|
- header[3] = 'F';
|
|
|
- header[4] = (byte) (totalDataLen & 0xff);
|
|
|
- header[5] = (byte) ((totalDataLen >> 8) & 0xff);
|
|
|
- header[6] = (byte) ((totalDataLen >> 16) & 0xff);
|
|
|
- header[7] = (byte) ((totalDataLen >> 24) & 0xff);
|
|
|
- header[8] = 'W';
|
|
|
- header[9] = 'A';
|
|
|
- header[10] = 'V';
|
|
|
- header[11] = 'E';
|
|
|
- header[12] = 'f'; // 'fmt ' chunk
|
|
|
- header[13] = 'm';
|
|
|
- header[14] = 't';
|
|
|
- header[15] = ' ';
|
|
|
- header[16] = 16; // 4 bytes: size of 'fmt ' chunk
|
|
|
- header[17] = 0;
|
|
|
- header[18] = 0;
|
|
|
- header[19] = 0;
|
|
|
- header[20] = 1; // format = 1
|
|
|
- header[21] = 0;
|
|
|
- header[22] = (byte) (nChannels & 0xff);
|
|
|
- header[23] = (byte) ((nChannels >> 8) & 0xff);
|
|
|
-
|
|
|
- header[24] = (byte) (sampleRate & 0xff);//采样率
|
|
|
- header[25] = (byte) ((sampleRate >> 8) & 0xff);
|
|
|
- header[26] = (byte) ((sampleRate >> 16) & 0xff);
|
|
|
- header[27] = (byte) ((sampleRate >> 24) & 0xff);
|
|
|
-
|
|
|
- header[28] = (byte) (byteRate & 0xff);//取八位
|
|
|
- header[29] = (byte) ((byteRate >> 8) & 0xff);
|
|
|
- header[30] = (byte) ((byteRate >> 16) & 0xff);
|
|
|
- header[31] = (byte) ((byteRate >> 24) & 0xff);
|
|
|
-
|
|
|
- int b = weikuan * nChannels / 8;//每次采样的大小
|
|
|
- header[32] = (byte) (b & 0xff); // block align
|
|
|
- header[33] = (byte) ((b >> 8) & 0xff);
|
|
|
-
|
|
|
- header[34] = (byte) (weikuan & 0xff);//位宽
|
|
|
- header[35] = (byte) ((weikuan >> 8) & 0xff);
|
|
|
-
|
|
|
- header[36] = 'd';//data
|
|
|
- header[37] = 'a';
|
|
|
- header[38] = 't';
|
|
|
- header[39] = 'a';
|
|
|
- header[40] = (byte) (totalAudioLen & 0xff);
|
|
|
- header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
|
|
|
- header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
|
|
|
- header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
|
|
|
- return header;
|
|
|
- }
|
|
|
-}
|