不废话,直接上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 schema
private 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 hz
WaveHeader 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 header
fos.write(h, 0, h.length);
//write data stream
fis = 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 {
// 构建鉴权url
String 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() {
@Override
public 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();
// 填充common
common.addProperty("app_id", appid);
//填充business
business.addProperty("aue", "raw");
business.addProperty("tte", "UTF8");
business.addProperty("ent", "intp65");
business.addProperty("vcn", "x_yifeng");//到控制台-我的应用-语音合成-添加试用或购买发音人,添加后即显示该发音人参数值,若试用未添加的发音人会报错11200
business.addProperty("pitch", 50);
business.addProperty("speed", 60);
//填充data
data.addProperty("status", 2);//固定位2
data.addProperty("text", Base64.getEncoder().encodeToString(text.getBytes()));
data.addProperty("encoding", "");
//填充frame
frame.add("common", common);
frame.add("business", business);
frame.add("data", data);
webSocket.send(frame.toString());
}
@Override
public 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 block
e1.printStackTrace();
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
super.onMessage(webSocket, bytes);
}
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
super.onClosing(webSocket, code, reason);
System.out.println("socket closing");
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
super.onClosed(webSocket, code, reason);
System.out.println("socket closed");
}
@Override
public 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; // 合成进度
}
}