ws %!s(int64=2) %!d(string=hai) anos
pai
achega
864b292a42

+ 1 - 1
voice/magic-api/src/main/java/com/magicapi/frame/SubFrame.java

@@ -44,7 +44,7 @@ public class SubFrame extends JFrame {
 
 	public SubFrame(String path) {
 		Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize();
-		y = screensize.height - height - 200;
+		y = screensize.height - height - 100;
 		frame = this;
 		jfxPanel = new JFXPanel();
 		frame.setLayout(new BorderLayout());

+ 5 - 0
voice/magic-api/src/main/java/com/magicapi/util/AutoMationUtil.java

@@ -45,6 +45,7 @@ public class AutoMationUtil extends Thread {
 		STEP_INDEX = 0;
 		STEP_STATUS = false;
 		j = -1;
+		isStart = -2;
 		getProcess(planId);
 	}
 	
@@ -68,6 +69,10 @@ public class AutoMationUtil extends Thread {
 		while (isStatus) {
 			isStart++;
 			for (j = 0; j < processArray.size(); j++) {
+				if (isStart < 0) {
+					isStart++;
+					j = 0;
+				}
 				JSONObject process = processArray.getJSONObject(j);
 				String audio = "dpq-auto_" + process.get("plan_id") + "_" + process.get("id");
 				System.err.println(audio);

+ 174 - 0
voice/magic-api/src/main/java/com/magicapi/util/YzsUtil.java

@@ -0,0 +1,174 @@
+package com.magicapi.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Base64;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.opencv.core.Core;
+import org.opencv.core.Mat;
+import org.opencv.core.Point;
+import org.opencv.core.Scalar;
+import org.opencv.imgcodecs.Imgcodecs;
+import org.opencv.imgproc.Imgproc;
+
+import com.magicapi.voice.WebsocketLister;
+
+import cn.hutool.core.lang.UUID;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+
+public class YzsUtil {
+	
+	private static final String FILE_PATH = "D:/cgj/xfyunAudio/";
+	
+	public static void main(String[] args) {
+//		System.err.println(Base64.getDecoder().decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"));
+		String path = tts("我国从北到南将呈现出“雨晴雨夹心状”的天气格局。");
+//		new SimplyPlayer(path).play();
+	}
+	
+	public static String tts(String txt) {
+		long start = System.currentTimeMillis();
+		// 获取appkey
+		String result = HttpUtil.get("https://ai.unisound.com/manager/captcha/get_service_link?aiCode=tts-short");
+		System.err.println(result);
+		JSONObject appObj = JSONUtil.parseObj(result).getJSONObject("result");
+		String appkey = appObj.getStr("appkey");
+		// 请求验证
+		result = HttpUtil.get("https://ai.unisound.com/manager/captcha/register");
+		System.err.println(result);
+		JSONObject obj = JSONUtil.parseObj(result).getJSONObject("result");
+		// 获取x
+		int x = checkImg(obj.getStr("bgImg"), obj.getStr("sliceImg")) + RandomUtil.randomInt(-1, 1);
+		JSONObject params = new JSONObject();
+		params.set("aiCode", "");
+		params.set("sliceX", x);
+		params.set("token", obj.get("token"));
+		result = HttpRequest.post("https://ai.unisound.com/manager/captcha/check").header("content-type", "application/json;charset=UTF-8")
+				.body(params.toJSONString(0)).execute().body();
+		System.err.println(result);
+		if (JSONUtil.parseObj(result).getInt("errorCode") == 0) {
+			params = new JSONObject();
+			params.set("aiCode", "tts-short");
+			params.set("appKey", appkey);
+			params.set("sha", "sha256");
+			long time = System.currentTimeMillis();
+			params.set("time", time);
+			JSONObject captcha = new JSONObject();
+			captcha.set("sliceX", x);
+			captcha.set("token", obj.get("token"));
+			params.set("captcha", captcha);
+			result = HttpRequest.post("https://ai.unisound.com/manager/captcha/auth").header("content-type", "application/json;charset=UTF-8")
+					.body(params.toJSONString(0)).execute().body();
+			System.err.println(result);
+			// 创建websocket
+			String path = FILE_PATH + "yzs/" + UUID.randomUUID() + ".wav";
+			CountDownLatch handshakeSuccess = new CountDownLatch(1);
+			OkHttpClient client = new OkHttpClient.Builder().connectTimeout(2000, TimeUnit.MILLISECONDS).build();
+			Request request = new Request.Builder().url(appObj.getStr("cn.url") + "?appkey="+ appkey +"&time="
+					+ time +"&sign=" + JSONUtil.parseObj(result).getJSONObject("result").getStr("sign")).build();
+			client.newWebSocket(request, new WebsocketLister(txt, path, handshakeSuccess));
+			client.dispatcher().executorService().shutdown();
+			try {
+				handshakeSuccess.await();
+			} catch (InterruptedException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+			System.err.println("耗时:" + (System.currentTimeMillis() - start));
+			return path;
+		} 
+		return null;
+	}
+	
+	/**
+	 * 处理图片获取x值
+	 * @param bg
+	 * @param slice
+	 * @return
+	 */
+	private static int checkImg(String bg, String slice) {
+		String bgPath = saveImage(bg);
+		String slicePath = saveImage(slice);
+		int x = getSlideDistance(slicePath, bgPath);
+		new File(bgPath).delete();
+		new File(slicePath).delete();
+		return x;
+	}
+	
+	/**
+	 * 保存图片
+	 * @param base64Img
+	 * @return
+	 */
+	private static String saveImage(String base64Img) {
+		byte[] b = Base64.getDecoder().decode(base64Img);
+		String imgPath = FILE_PATH + UUID.randomUUID() + ".jpg";
+		try {
+			OutputStream out = new FileOutputStream(imgPath);
+			out.write(b);  
+			out.flush();  
+			out.close(); 
+		} catch (IOException e) {
+			e.printStackTrace();
+		}  
+		return imgPath;
+	}
+	
+	/**
+	 * 获取x值
+	 * @param slideBlockPicPath
+	 * @param slideBgPicPath
+	 * @return
+	 */
+	private static int getSlideDistance(String slideBlockPicPath, String slideBgPicPath) {
+		System.load(FILE_PATH + "opencv_java340-x64.dll");
+		//对滑块进行处理
+		Mat slideBlockMat = Imgcodecs.imread(slideBlockPicPath);
+		//1、灰度化图片
+		Imgproc.cvtColor(slideBlockMat, slideBlockMat, Imgproc.COLOR_BGR2GRAY);
+		//2、去除周围黑边
+		for (int row = 0; row < slideBlockMat.height(); row++) {
+		    for (int col = 0; col < slideBlockMat.width(); col++) {
+		        if (slideBlockMat.get(row, col)[0] == 0) {
+		            slideBlockMat.put(row, col, 96);
+		        }
+		    }
+		}
+		//3、inRange二值化转黑白图
+		Core.inRange(slideBlockMat, Scalar.all(96), Scalar.all(96), slideBlockMat);
+		  
+		
+		//对滑动背景图进行处理
+		Mat slideBgMat = Imgcodecs.imread(slideBgPicPath);
+		//1、灰度化图片
+		Imgproc.cvtColor(slideBgMat, slideBgMat, Imgproc.COLOR_BGR2GRAY);
+		//2、二值化
+		Imgproc.threshold(slideBgMat, slideBgMat, 127, 255, Imgproc.THRESH_BINARY);
+		Mat g_result = new Mat();
+		/*
+		 * matchTemplate:在模板和输入图像之间寻找匹配,获得匹配结果图像
+		 * result:保存匹配的结果矩阵
+		 * TM_CCOEFF_NORMED标准相关匹配算法
+		 */
+		Imgproc.matchTemplate(slideBgMat, slideBlockMat, g_result, Imgproc.TM_CCOEFF_NORMED); 
+		/* minMaxLoc:在给定的结果矩阵中寻找最大和最小值,并给出它们的位置
+		 * maxLoc最大值
+		 */
+		Point matchLocation = Core.minMaxLoc(g_result).maxLoc;
+		//返回匹配点的横向距离
+		return (int) matchLocation.x;
+
+
+	}
+
+}

+ 88 - 0
voice/magic-api/src/main/java/com/magicapi/voice/Step3_tts_thread.java

@@ -0,0 +1,88 @@
+package com.magicapi.voice;
+
+import com.sun.jna.Pointer;
+import com.magicapi.util.Constant;
+import com.sun.jna.ptr.IntByReference;
+
+public class Step3_tts_thread extends Thread {
+
+	private String ttsTxt;
+
+	public Step3_tts_thread(String ttsTxt) {
+		this.ttsTxt = ttsTxt;
+	}
+
+	@Override
+	public void run() {
+		super.run();
+		// 开始会话
+		String session_begin_params = "engine_type = local, voice_name = xiaoyan, text_encoding = UTF8, tts_res_path = fo|res/tts/xiaoyan.jet;fo|res/tts/common.jet, sample_rate = 16000, speed = 50, volume = 50, pitch = 50, rdn = 2";
+		IntByReference errorCode = new IntByReference(-100);
+		String session_id = Step1_ivw_dll.INSTANCE.QTTSSessionBegin(session_begin_params, errorCode);
+		if (errorCode.getValue() == 0) {
+			System.out.println("开启普通离线语音合成成功,session_id是:" + session_id);
+		} else {
+			System.out.println("开启普通离线语音合成失败:" + errorCode.getValue());
+		}
+
+		int ret = Step1_ivw_dll.INSTANCE.QTTSTextPut(session_id, ttsTxt, ttsTxt.getBytes().length, null);
+		if (ret == 0) {
+			System.out.println("写入合成文本成功...");
+		} else {
+			System.out.println("写入合成文本失败:" + ret);
+		}
+
+		// 循环获取离线合成的音频,并实时进行播放
+		IntByReference audio_len = new IntByReference(-100);
+		IntByReference synth_status = new IntByReference(-100);
+		errorCode = new IntByReference(-100);
+		try {
+			// 实时播放
+			Constant.sourceDataLine.open(Constant.audioFormat);
+			Constant.sourceDataLine.start();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+		while (true) {
+			Pointer audioPointer = Step1_ivw_dll.INSTANCE.QTTSAudioGet(session_id, audio_len, synth_status, errorCode);
+			byte[] audioDataByteArray = null;
+			if (audioPointer != null) {
+				audioDataByteArray = audioPointer.getByteArray(0, audio_len.getValue());
+			}
+			if (errorCode.getValue() == 0) {
+				// System.out.println("正常获取音频中...");
+			} else {
+				System.out.println("获取音频发生错误:" + errorCode);
+				break;
+			}
+			if (audioDataByteArray != null) {
+				try {
+					// 实时播放
+					Constant.sourceDataLine.write(audioDataByteArray, 0, audio_len.getValue());
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+			if (synth_status.getValue() == 2) {
+				// 说明音频已经取完,退出本次循环
+				try {
+					Constant.sourceDataLine.drain();
+					Constant.sourceDataLine.close();
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+				break;
+			}
+		}
+
+		// 结束本次普通离线语音合成
+		ret = Step1_ivw_dll.INSTANCE.QTTSSessionEnd(session_id, "正常退出");
+		if (ret == 0) {
+			 System.out.println("离线语音合成正常退出...");
+		} else {
+			System.out.println("离线语音合成退出异常:" + ret);
+		}
+
+	}
+}

+ 145 - 0
voice/magic-api/src/main/java/com/magicapi/voice/WebsocketLister.java

@@ -0,0 +1,145 @@
+package com.magicapi.voice;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+
+import javax.sound.sampled.AudioFileFormat;
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import okhttp3.Response;
+import okhttp3.WebSocket;
+import okhttp3.WebSocketListener;
+import okio.ByteString;
+
+public class WebsocketLister extends WebSocketListener {
+	
+	// 文本
+	private String txt;
+	// 发音人
+	private String vcn = "kiyo-base";
+	private int bright = 50;
+	// 音高
+	private int pitch = 50;
+	// 样本
+	private int sample = 24000;
+	// 语速
+	private int speed = 50;
+	// 音量
+	private int volume = 50;
+	
+	private String path;
+	
+	private ByteArrayOutputStream baos;
+	
+	private CountDownLatch handshakeSuccess;
+	
+	public WebsocketLister(String txt, String path, CountDownLatch handshakeSuccess) {
+		this.txt = txt;
+		this.path = path;
+		this.handshakeSuccess = handshakeSuccess;
+		baos = new ByteArrayOutputStream();
+	}
+	
+	public WebsocketLister(String vcn, String txt, String path, CountDownLatch handshakeSuccess) {
+		this.vcn = vcn;
+		this.txt = txt;
+		this.path = path;
+		this.handshakeSuccess = handshakeSuccess;
+		baos = new ByteArrayOutputStream();
+	}
+
+	@Override
+	public void onOpen(WebSocket webSocket, Response response) {
+		System.err.println("语音websocket连接成功");
+		// 发送初始帧
+		JSONObject obj = new JSONObject();
+		obj.set("bright", bright);
+		obj.set("format", "pcm");
+		obj.set("pitch", pitch);
+		obj.set("sample", sample);
+		obj.set("speed", speed);
+		obj.set("text", txt);
+		obj.set("vcn", vcn);
+		obj.set("volume", volume);
+		webSocket.send(obj.toJSONString(0));
+	}
+
+	@Override
+	public void onMessage(WebSocket webSocket, String text) {
+		System.err.println(text);
+		JSONObject obj = JSONUtil.parseObj(text);
+		String sid = obj.getStr("sid");
+		// 保存文件
+		byte audioData[] = baos.toByteArray();
+		ByteArrayInputStream bais = new ByteArrayInputStream(audioData);
+		AudioFormat audioFormat = getAudioFormat();
+		AudioInputStream ais = new AudioInputStream(bais, audioFormat,
+				audioData.length / audioFormat.getFrameSize());
+		// 定义最终保存的文件名
+		System.out.println("开始生成语音文件");
+		
+		File file = new File(path);
+		try {
+			AudioSystem.write(ais, AudioFileFormat.Type.WAVE, file);
+			ais.close();
+			bais.close();
+			baos.close();
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		// 发送结束标识
+		JSONObject params = new JSONObject();
+		params.set("aiCode", "tts-short");
+		params.set("sid", sid);
+		HttpRequest.post("https://ai.unisound.com/manager/product/experience").header("content-type", "application/json;charset=UTF-8")
+		.body(params.toJSONString(0)).execute().body();
+		handshakeSuccess.countDown();
+	}
+	
+	@Override
+	public void onMessage(WebSocket webSocket, ByteString bytes) {
+		super.onMessage(webSocket, bytes);
+		try {
+			baos.write(bytes.toByteArray());
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	@Override
+	public void onClosing(WebSocket webSocket, int code, String reason) {
+		super.onClosing(webSocket, code, reason);
+		System.err.println("websocket event closing :" + code + " | " + reason);
+		// 客户端关闭
+		webSocket.close(1000, "");
+	}
+
+	@Override
+	public void onClosed(WebSocket webSocket, int code, String reason) {
+		super.onClosed(webSocket, code, reason);
+		System.err.println("websocket closed: " + code + " | " + reason);
+	}
+
+	@Override
+	public void onFailure(WebSocket webSocket, Throwable t, Response response) {
+		super.onFailure(webSocket, t, response);
+		System.err.println("websocket failure");
+		System.err.println(response);
+		System.err.println(t);
+	}
+	
+	public static AudioFormat getAudioFormat() {
+		AudioFormat audioFormat = new AudioFormat(24000F, 16, 1, true, false);
+		// true,false 指示是以 big-endian 顺序还是以 little-endian 顺序存储音频数据。
+		return audioFormat;// 构造具有线性 PCM 编码和给定参数的 AudioFormat。
+	}
+}