package cn.huge.module.xfyun.iflytek; import cn.huge.module.constant.CacheConsts; import cn.huge.module.xfyun.config.XfyunConfig; import com.google.gson.Gson; import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.CountDownLatch; /** * 语音听写流式 WebAPI 接口调用示例 接口文档(必看):https://doc.xfyun.cn/rest_api/语音听写(流式版).html * webapi 听写服务参考帖子(必看):http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=38947&extra= * 语音听写流式WebAPI 服务,热词使用方式:登陆开放平台https://www.xfyun.cn/后,找到控制台--我的应用---语音听写---个性化热词,上传热词 * 注意:热词只能在识别的时候会增加热词的识别权重,需要注意的是增加相应词条的识别率,但并不是绝对的,具体效果以您测试为准。 * 错误码链接:https://www.xfyun.cn/document/error-code (code返回错误码时必看) * 语音听写流式WebAPI 服务,方言或小语种试用方法:登陆开放平台https://www.xfyun.cn/后,在控制台--语音听写(流式)--方言/语种处添加 * 添加后会显示该方言/语种的参数值 * @author iflytek */ @Slf4j public class WebIATWS extends WebSocketListener { public static final int StatusFirstFrame = 0; public static final int StatusContinueFrame = 1; public static final int StatusLastFrame = 2; private static InputStream inputStream; private static CountDownLatch countDownLatch; private static final SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss.SSS"); private static Date dateBegin = new Date(); private static Date dateEnd = new Date(); public static final Gson json = new Gson(); Decoder decoder = new Decoder(); /** * 构造方法 * @param inputStream * @param countDownLatch */ public WebIATWS(InputStream inputStream, CountDownLatch countDownLatch) { this.inputStream = inputStream; this.countDownLatch = countDownLatch; } /** * main方法 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { } /** * 发送语音 * @param webSocket * @param response */ @Override public void onOpen(WebSocket webSocket, Response response) { super.onOpen(webSocket, response); new Thread(()->{ //连接成功,开始发送数据,每一帧音频的大小,建议每 40ms 发送 122B int frameSize = 1280; int intervel = 40; // 音频的状态 int status = 0; try { byte[] buffer = new byte[frameSize]; // 发送音频 end: while (true) { int len = inputStream.read(buffer); if (len == -1) { //文件读完,改变status 为 2 status = StatusLastFrame; } switch (status) { case StatusFirstFrame: // 第一帧音频status = 0 JsonObject frame = new JsonObject(); //第一帧必须发送 JsonObject business = new JsonObject(); //第一帧必须发送 JsonObject common = new JsonObject(); //每一帧都要发送 JsonObject data = new JsonObject(); // 填充common common.addProperty("app_id", XfyunConfig.appid); //填充business business.addProperty("language", "zh_cn"); business.addProperty("domain", "iat"); //中文方言请在控制台添加试用,添加后即展示相应参数值 business.addProperty("accent", "mandarin"); //动态修正(若未授权不生效,在控制台可免费开通) business.addProperty("dwa", "wpgs"); //填充data data.addProperty("status", StatusFirstFrame); data.addProperty("format", "audio/L16;rate=16000"); data.addProperty("encoding", "raw"); data.addProperty("audio", Base64.getEncoder().encodeToString(Arrays.copyOf(buffer, len))); //填充frame frame.add("common", common); frame.add("business", business); frame.add("data", data); webSocket.send(frame.toString()); // 发送完第一帧改变status 为 1 status = StatusContinueFrame; break; case StatusContinueFrame: //中间帧status = 1 JsonObject frame1 = new JsonObject(); JsonObject data1 = new JsonObject(); data1.addProperty("status", StatusContinueFrame); data1.addProperty("format", "audio/L16;rate=16000"); data1.addProperty("encoding", "raw"); data1.addProperty("audio", Base64.getEncoder().encodeToString(Arrays.copyOf(buffer, len))); frame1.add("data", data1); webSocket.send(frame1.toString()); break; case StatusLastFrame: // 最后一帧音频status = 2 ,标志音频发送结束 JsonObject frame2 = new JsonObject(); JsonObject data2 = new JsonObject(); data2.addProperty("status", StatusLastFrame); data2.addProperty("audio", ""); data2.addProperty("format", "audio/L16;rate=16000"); data2.addProperty("encoding", "raw"); frame2.add("data", data2); webSocket.send(frame2.toString()); System.out.println("sendlast"); break end; } //模拟音频采样延时 Thread.sleep(intervel); } System.out.println("all data is send"); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } /** * 接收识别结果 * @param webSocket * @param text */ @Override public void onMessage(WebSocket webSocket, String text) { super.onMessage(webSocket, text); ResponseData resp = json.fromJson(text, ResponseData.class); if (resp != null) { if (resp.getCode() != 0) { log.error( "code=>" + resp.getCode() + " error=>" + resp.getMessage() + " sid=" + resp.getSid()); log.error( "错误码查询链接:https://www.xfyun.cn/document/error-code"); return; } if (resp.getData() != null) { if (resp.getData().getResult() != null) { Text te = resp.getData().getResult().getText(); try { decoder.decode(te); System.out.println("中间识别结果 ==》" + decoder.toString()); } catch (Exception e) { e.printStackTrace(); } } if (resp.getData().getStatus() == 2) { // resp.data.status ==2 说明数据全部返回完毕,可以关闭连接,释放资源 System.out.println("session end "); dateEnd = new Date(); System.out.println(sdf.format(dateBegin) + "开始"); System.out.println(sdf.format(dateEnd) + "结束"); log.info("耗时:" + (dateEnd.getTime() - dateBegin.getTime()) + "ms"); log.info("最终识别结果 ==》" + decoder.toString()); CacheConsts.XF_RESULT = decoder.toString(); System.out.println("本次识别sid ==》" + resp.getSid()); countDownLatch.countDown(); decoder.discard(); webSocket.close(1000, ""); } else { System.out.println("识别中"); } } } } /** * 失败结果 * @param webSocket * @param t * @param response */ @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { super.onFailure(webSocket, t, response); try { if (null != response) { int code = response.code(); System.out.println("onFailure code:" + code); System.out.println("onFailure body:" + response.body().string()); if (101 != code) { System.out.println("connection failed"); System.exit(0); } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 获取授权URL * @param hostUrl * @param apiKey * @param apiSecret * @return * @throws Exception */ public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception { URL url = new URL(hostUrl); SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); format.setTimeZone(TimeZone.getTimeZone("GMT")); String date = format.format(new Date()); StringBuilder builder = new StringBuilder("host: ").append(url.getHost()).append("\n").// append("date: ").append(date).append("\n").// append("GET ").append(url.getPath()).append(" HTTP/1.1"); Charset charset = Charset.forName("UTF-8"); Mac mac = Mac.getInstance("hmacsha256"); SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), "hmacsha256"); mac.init(spec); byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset)); String sha = Base64.getEncoder().encodeToString(hexDigits); String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha); HttpUrl httpUrl = HttpUrl.parse("https://" + url.getHost() + url.getPath()).newBuilder().// addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(charset))).// addQueryParameter("date", date).// addQueryParameter("host", url.getHost()).// build(); return httpUrl.toString(); } /** * 解析返回数据 */ public static class Decoder { private Text[] texts; private int defc = 10; public Decoder() { this.texts = new Text[this.defc]; } public synchronized void decode(Text text) { if (text.sn >= this.defc) { this.resize(); } if ("rpl".equals(text.pgs)) { for (int i = text.rg[0]; i <= text.rg[1]; i++) { this.texts[i].deleted = true; } } this.texts[text.sn] = text; } public String toString() { StringBuilder sb = new StringBuilder(); for (Text t : this.texts) { if (t != null && !t.deleted) { sb.append(t.text); } } return sb.toString(); } public void resize() { int oc = this.defc; this.defc <<= 1; Text[] old = this.texts; this.texts = new Text[this.defc]; for (int i = 0; i < oc; i++) { this.texts[i] = old[i]; } } public void discard(){ for(int i=0;i