不废话,直接上code
package com.iflytek.voicecloud.webapi.demo;import com.google.gson.Gson;
import com.google.gson.JsonObject;
import okhttp3.*;
import okio.ByteString;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.io.ByteArrayOutputStream;
import java.io.IOException;public class WebTTSWS {private static final String hostUrl = "https://tts-api.xfyun.cn/v2/tts"; //http url 不支持解析 ws/wss schemaprivate static final String appid = "";//到控制台-语音合成页面获取private static final String apiSecret = "";//到控制台-语音合成页面获取private static final String apiKey = "";//到控制台-语音合成页面获取private static final String text = "在全系5款车型中,上市专享版的性价比最为突出,在有限的价格下提供了大多数实用配置,而且是唯一标配车内氛围灯的车型,不失为价值之选。时尚致雅型和时尚动感型居于中配角色,配置方面较低配车型更加全面,面子和里子都比较到位,符合人们对豪华品牌的期待,同样值得考虑。";public static final Gson json = new Gson();public static class WaveHeader {public final char fileID[] = { 'R', 'I', 'F', 'F' };public int fileLength;public char wavTag[] = { 'W', 'A', 'V', 'E' };;public char FmtHdrID[] = { 'f', 'm', 't', ' ' };public int FmtHdrLeth;public short FormatTag;public short Channels;public int SamplesPerSec;public int AvgBytesPerSec;public short BlockAlign;public short BitsPerSample;public char DataHdrID[] = { 'd', 'a', 't', 'a' };public int DataHdrLeth;public byte[] getHeader() throws IOException {ByteArrayOutputStream bos = new ByteArrayOutputStream();writeChar(bos, fileID);writeInt(bos, fileLength);writeChar(bos, wavTag);writeChar(bos, FmtHdrID);writeInt(bos, FmtHdrLeth);writeShort(bos, FormatTag);writeShort(bos, Channels);writeInt(bos, SamplesPerSec);writeInt(bos, AvgBytesPerSec);writeShort(bos, BlockAlign);writeShort(bos, BitsPerSample);writeChar(bos, DataHdrID);writeInt(bos, DataHdrLeth);bos.flush();byte[] r = bos.toByteArray();bos.close();return r;}private void writeShort(ByteArrayOutputStream bos, int s) throws IOException {byte[] mybyte = new byte[2];mybyte[1] = (byte) ((s << 16) >> 24);mybyte[0] = (byte) ((s << 24) >> 24);bos.write(mybyte);}private void writeInt(ByteArrayOutputStream bos, int n) throws IOException {byte[] buf = new byte[4];buf[3] = (byte) (n >> 24);buf[2] = (byte) ((n << 8) >> 24);buf[1] = (byte) ((n << 16) >> 24);buf[0] = (byte) ((n << 24) >> 24);bos.write(buf);}private void writeChar(ByteArrayOutputStream bos, char[] id) {for (int i = 0; i < id.length; i++) {char c = id[i];bos.write(c);}}
}public static void convertAudioFiles(String src, String target) throws Exception {FileInputStream fis = new FileInputStream(src);FileOutputStream fos = new FileOutputStream(target);//计算长度byte[] buf = new byte[1024 * 4];int size = fis.read(buf);int PCMSize = 0;while (size != -1) {PCMSize += size;size = fis.read(buf);}fis.close();//填入参数,比特率等等。这里用的是16位单声道 8000 hzWaveHeader header = new WaveHeader();//长度字段 = 内容的大小(PCMSize) + 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)header.fileLength = PCMSize + (44 - 8);header.FmtHdrLeth = 16;header.BitsPerSample = 16;header.Channels = 1;header.FormatTag = 0x0001;header.SamplesPerSec = 14500;header.BlockAlign = (short)(header.Channels * header.BitsPerSample / 8);header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;header.DataHdrLeth = PCMSize;byte[] h = header.getHeader();assert h.length == 44; //WAV标准,头部应该是44字节//write headerfos.write(h, 0, h.length);//write data streamfis = new FileInputStream(src);size = fis.read(buf);while (size != -1) {fos.write(buf, 0, size);size = fis.read(buf);}fis.close();fos.close();System.out.println("Convert OK!");}public static void main(String[] args) throws Exception {// 构建鉴权urlString authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);OkHttpClient client = new OkHttpClient.Builder().build();//将url中的 schema http://和https://分别替换为ws:// 和 wss://String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");Request request = new Request.Builder().url(url).build();// 存放音频的文件SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");String date = sdf.format(new Date());File f = new File("resource/tts/" + date + ".pcm");if (!f.exists()) {f.createNewFile();}FileOutputStream os = new FileOutputStream(f);WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {@Overridepublic void onOpen(WebSocket webSocket, Response response) {super.onOpen(webSocket, response);try {System.out.println(response.body().string());} catch (IOException e) {e.printStackTrace();}//发送数据JsonObject frame = new JsonObject();JsonObject business = new JsonObject();JsonObject common = new JsonObject();JsonObject data = new JsonObject();// 填充commoncommon.addProperty("app_id", appid);//填充businessbusiness.addProperty("aue", "raw");business.addProperty("tte", "UTF8");business.addProperty("ent", "intp65");business.addProperty("vcn", "x_yifeng");//到控制台-我的应用-语音合成-添加试用或购买发音人,添加后即显示该发音人参数值,若试用未添加的发音人会报错11200business.addProperty("pitch", 50);business.addProperty("speed", 60);//填充datadata.addProperty("status", 2);//固定位2data.addProperty("text", Base64.getEncoder().encodeToString(text.getBytes()));data.addProperty("encoding", "");//填充frameframe.add("common", common);frame.add("business", business);frame.add("data", data);webSocket.send(frame.toString());}@Overridepublic void onMessage(WebSocket webSocket, String text) {super.onMessage(webSocket, text);//处理返回数据System.out.println("receive=>" + text);ResponseData resp = null;try {resp = json.fromJson(text, ResponseData.class);} catch (Exception e) {e.printStackTrace();}if (resp != null) {if (resp.getCode() != 0) {System.out.println("error=>" + resp.getMessage() + " sid=" + resp.getSid());return;}if (resp.getData() != null) {String result = resp.getData().audio;byte[] audio = Base64.getDecoder().decode(result);try {os.write(audio);os.flush();} catch (IOException e) {e.printStackTrace();}if (resp.getData().status == 2) {// todo resp.data.status ==2 说明数据全部返回完毕,可以关闭连接,释放资源System.out.println("session end ");System.out.println("合成的音频文件保存在:" + f.getPath());webSocket.close(1000, "");try {convertAudioFiles(f.getPath(),f.getPath()+".mp3");} catch (Exception e1) {// TODO Auto-generated catch blocke1.printStackTrace();}try {os.close();} catch (IOException e) {e.printStackTrace();}}}}}@Overridepublic void onMessage(WebSocket webSocket, ByteString bytes) {super.onMessage(webSocket, bytes);}@Overridepublic void onClosing(WebSocket webSocket, int code, String reason) {super.onClosing(webSocket, code, reason);System.out.println("socket closing");}@Overridepublic void onClosed(WebSocket webSocket, int code, String reason) {super.onClosed(webSocket, code, reason);System.out.println("socket closed");}@Overridepublic void onFailure(WebSocket webSocket, Throwable t, Response response) {super.onFailure(webSocket, t, response);System.out.println("connection failed");}});}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("hmac username=\"%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 ResponseData {private int code;private String message;private String sid;private Data data;public int getCode() {return code;}public String getMessage() {return this.message;}public String getSid() {return sid;}public Data getData() {return data;}}public static class Data {private int status; //标志音频是否返回结束 status=1,表示后续还有音频返回,status=2表示所有的音频已经返回private String audio; //返回的音频,base64 编码private String ced; // 合成进度}
}