前言:自行注册讯飞创建对应应用,本文对此不做讲解

官方网址:https://www.xfyun.cn/

语音合成官方文档:https://www.xfyun.cn/doc/tts/online_tts/API.html

注意:使用本工具类需要在resources目录下创建tts文件夹,把申请的appidapiSecretapiKey填入,运行main方法即可跑通,后期的自由发挥就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package com.listen.utils;

import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import okhttp3.HttpUrl;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

/**
* 语音合成 讯飞
* @Author: Chang
* @create: 2023/10/20 10:56
*/
public class TtsUtil {
// 地址与鉴权信息
public static final String hostUrl = "https://tts-api.xfyun.cn/v2/tts";
// 均到控制台-语音合成页面获取
public static final String appid = "自行申请";
public static final String apiSecret = "自行申请";
public static final String apiKey = "自行申请";
// 合成文本
public static final String TEXT = "服务端可能返回data为空的帧,并且错误码为0,这种帧客户端可以直接忽略,不解析";
// 合成文本编码格式
public static final String TTE = "UTF8"; // 小语种必须使用UNICODE编码作为值
// 发音人参数
//讯飞小燕 xiaoyan 讯飞许久 aisjiuxu 讯飞小萍 aisxping 讯飞小婧 aisjinger 讯飞许小宝 aisbabyxu
public static final String VCN = "xiaoyan";
// 合成文件名称
public static final String OUTPUT_FILE_PATH = "src/main/resources/tts/" + System.currentTimeMillis() + ".mp3";
// json
public static final Gson gson = new Gson();
public static boolean wsCloseFlag = false;
// 音频编码(raw合成的音频格式pcm、wav,lame合成的音频格式MP3)
private static final String AUE = "lame";
// 语速(取值范围0-100)
private static final int SPEED = 50;
// 音量(取值范围0-100)
private static final int VOLUME = 50;
// 音调(取值范围0-100)
private static final int PITCH = 50;

public static void main(String[] args) throws Exception {
String wsUrl = getAuthUrl(hostUrl, apiKey, apiSecret).replace("https://", "wss://");
OutputStream outputStream = new FileOutputStream(OUTPUT_FILE_PATH);
websocketWork(wsUrl, outputStream);
}

// Websocket方法
public static void websocketWork(String wsUrl, OutputStream outputStream) {
try {
URI uri = new URI(wsUrl);
WebSocketClient webSocketClient = new WebSocketClient(uri) {
@Override
public void onOpen(ServerHandshake serverHandshake) {
System.out.println("ws建立连接成功...");
}

@Override
public void onMessage(String text) {
JsonParse myJsonParse = gson.fromJson(text, JsonParse.class);
if (myJsonParse.code != 0) {
System.out.println("发生错误,错误码为:" + myJsonParse.code);
System.out.println("本次请求的sid为:" + myJsonParse.sid);
}
if (myJsonParse.data != null) {
try {
byte[] textBase64Decode = Base64.getDecoder().decode(myJsonParse.data.audio);
outputStream.write(textBase64Decode);
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
}
if (myJsonParse.data.status == 2) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("本次请求的sid==>" + myJsonParse.sid);
System.out.println("合成成功,文件保存路径为==>" + OUTPUT_FILE_PATH);
// 可以关闭连接,释放资源
wsCloseFlag = true;
}
}
}

@Override
public void onClose(int i, String s, boolean b) {
System.out.println("ws链接已关闭,本次请求完成...");
}

@Override
public void onError(Exception e) {
System.out.println("发生错误 " + e.getMessage());
}
};
// 建立连接
webSocketClient.connect();
while (!webSocketClient.getReadyState().equals(WebSocket.READYSTATE.OPEN)) {
Thread.sleep(100);
}
MyThread webSocketThread = new MyThread(webSocketClient);
webSocketThread.start();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

// 线程来发送音频与参数
static class MyThread extends Thread {
WebSocketClient webSocketClient;

public MyThread(WebSocketClient webSocketClient) {
this.webSocketClient = webSocketClient;
}

public void run() {
try {
// 创建 common JSON 对象
JSONObject commonJson = new JSONObject();
commonJson.put("app_id", appid);
// 创建 business JSON 对象
JSONObject businessJson = new JSONObject();
businessJson.put("aue", AUE);
businessJson.put("sfl", 1);
businessJson.put("tte", TTE);
businessJson.put("ent", "intp65");
businessJson.put("vcn", VCN);
businessJson.put("pitch", PITCH);
businessJson.put("speed", SPEED);

// 创建 data JSON 对象
JSONObject dataJson = new JSONObject();
dataJson.put("status", 2);
dataJson.put("text", Base64.getEncoder().encodeToString(TEXT.getBytes(StandardCharsets.UTF_8)));

// 创建最终的 request JSON 对象
JSONObject requestJson = new JSONObject();
requestJson.put("common", commonJson);
requestJson.put("business", businessJson);
requestJson.put("data", dataJson);
webSocketClient.send(requestJson.toString());
// 等待服务端返回完毕后关闭
while (!wsCloseFlag) {
Thread.sleep(200);
}
webSocketClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

// 鉴权方法
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());
// 拼接
String preStr = "host: " + url.getHost() + "\n" +
"date: " + date + "\n" +
"GET " + url.getPath() + " HTTP/1.1";
// SHA256加密
Mac mac = Mac.getInstance("hmacsha256");
SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");
mac.init(spec);
byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));
// Base64加密
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 = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().//
addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).//
addQueryParameter("date", date).//
addQueryParameter("host", url.getHost()).//
build();

return httpUrl.toString();
}

//返回的json结果拆解
class JsonParse {
int code;
String sid;
Data data;
}

class Data {
int status;
String audio;
}
}