|
@@ -25,7 +25,7 @@ public class DouBaoTtsService {
|
|
|
@Resource
|
|
@Resource
|
|
|
private YudaoAiProperties yudaoAiProperties;
|
|
private YudaoAiProperties yudaoAiProperties;
|
|
|
|
|
|
|
|
- public byte[] convertTextToSpeech(AiTtsDO aiTtsDO, String content) throws IOException {
|
|
|
|
|
|
|
+ public byte[] convertTextToSpeech(AiTtsDO aiTtsDO, String content, String command) throws IOException {
|
|
|
YudaoAiProperties.DouBaoProperties doubaoProperties = yudaoAiProperties.getDoubao();
|
|
YudaoAiProperties.DouBaoProperties doubaoProperties = yudaoAiProperties.getDoubao();
|
|
|
if (doubaoProperties == null) {
|
|
if (doubaoProperties == null) {
|
|
|
throw new IllegalArgumentException("豆包配置未设置");
|
|
throw new IllegalArgumentException("豆包配置未设置");
|
|
@@ -36,14 +36,9 @@ public class DouBaoTtsService {
|
|
|
throw new IllegalArgumentException("豆包TTS配置未设置");
|
|
throw new IllegalArgumentException("豆包TTS配置未设置");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- logger.info("豆包配置: enable={}, apiKey={}", doubaoProperties.getEnable(), doubaoProperties.getApiKey());
|
|
|
|
|
- logger.info("豆包TTS配置: appId={}, accessKey={}, resourceId={}, baseUrl={}",
|
|
|
|
|
- doubaoTtsProperties.getAppId(), doubaoTtsProperties.getAccessKey(),
|
|
|
|
|
- doubaoTtsProperties.getResourceId(), doubaoTtsProperties.getBaseUrl());
|
|
|
|
|
-
|
|
|
|
|
String ttsUrl = doubaoTtsProperties.getBaseUrl() != null ? doubaoTtsProperties.getBaseUrl() : "https://openspeech.bytedance.com/api/v3/tts/unidirectional";
|
|
String ttsUrl = doubaoTtsProperties.getBaseUrl() != null ? doubaoTtsProperties.getBaseUrl() : "https://openspeech.bytedance.com/api/v3/tts/unidirectional";
|
|
|
String appId = doubaoTtsProperties.getAppId();
|
|
String appId = doubaoTtsProperties.getAppId();
|
|
|
- String accessKey = doubaoTtsProperties.getAccessKey();
|
|
|
|
|
|
|
+ String accessKey = doubaoTtsProperties.getAccessToken() != null ? doubaoTtsProperties.getAccessToken() : doubaoTtsProperties.getAccessKey();
|
|
|
String resourceId = doubaoTtsProperties.getResourceId() != null ? doubaoTtsProperties.getResourceId() : "seed-tts-2.0";
|
|
String resourceId = doubaoTtsProperties.getResourceId() != null ? doubaoTtsProperties.getResourceId() : "seed-tts-2.0";
|
|
|
|
|
|
|
|
if (appId == null || accessKey == null) {
|
|
if (appId == null || accessKey == null) {
|
|
@@ -51,9 +46,6 @@ public class DouBaoTtsService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
OkHttpClient client = new OkHttpClient();
|
|
OkHttpClient client = new OkHttpClient();
|
|
|
-
|
|
|
|
|
- // 构造请求体,按照接口文档要求组装JSON参数
|
|
|
|
|
-
|
|
|
|
|
// 使用Map构建请求体,按照文档要求组装参数
|
|
// 使用Map构建请求体,按照文档要求组装参数
|
|
|
Map<String, Object> requestMap = new HashMap<>();
|
|
Map<String, Object> requestMap = new HashMap<>();
|
|
|
Map<String, Object> reqParams = new HashMap<>();
|
|
Map<String, Object> reqParams = new HashMap<>();
|
|
@@ -63,7 +55,7 @@ public class DouBaoTtsService {
|
|
|
Map<String, Object> audioParams = new HashMap<>();
|
|
Map<String, Object> audioParams = new HashMap<>();
|
|
|
audioParams.put("format", "mp3"); // 输出音频格式
|
|
audioParams.put("format", "mp3"); // 输出音频格式
|
|
|
audioParams.put("sample_rate", 16000); // 推荐采样率
|
|
audioParams.put("sample_rate", 16000); // 推荐采样率
|
|
|
-// audioParams.put("emotion", "带有感情的朗读诗词,要深情的朗读。");
|
|
|
|
|
|
|
+// audioParams.put("emotion", "开心");
|
|
|
|
|
|
|
|
// 语速和音量参数
|
|
// 语速和音量参数
|
|
|
if (aiTtsDO.getSpeechRate() != null) {
|
|
if (aiTtsDO.getSpeechRate() != null) {
|
|
@@ -78,15 +70,19 @@ public class DouBaoTtsService {
|
|
|
// 额外参数
|
|
// 额外参数
|
|
|
Map<String, Object> additions = new HashMap<>();
|
|
Map<String, Object> additions = new HashMap<>();
|
|
|
//音调
|
|
//音调
|
|
|
-// Map<String, Object> post_process = new HashMap<>();
|
|
|
|
|
-// post_process.put("pitch", aiTtsDO.getVolume());
|
|
|
|
|
-// additions.put("post_process", post_process);
|
|
|
|
|
|
|
+ Map<String, Object> post_process = new HashMap<>();
|
|
|
|
|
+ post_process.put("pitch", aiTtsDO.getVolume());
|
|
|
|
|
+ additions.put("post_process", post_process);
|
|
|
|
|
|
|
|
//语音指令
|
|
//语音指令
|
|
|
- String[] context_texts = {"带有感情的朗读诗词,要深情的朗读。"};
|
|
|
|
|
- additions.put("context_texts", context_texts);
|
|
|
|
|
|
|
+ if (command != null && !command.isEmpty()) {
|
|
|
|
|
+ additions.put("context_texts", List.of(command));
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- reqParams.put("additions", additions);
|
|
|
|
|
|
|
+ // 将 additions 映射序列化为 JSON 字符串
|
|
|
|
|
+ ObjectMapper additionsMapper = new ObjectMapper();
|
|
|
|
|
+ String additionsJson = additionsMapper.writeValueAsString(additions);
|
|
|
|
|
+ reqParams.put("additions", additionsJson);
|
|
|
requestMap.put("req_params", reqParams);
|
|
requestMap.put("req_params", reqParams);
|
|
|
|
|
|
|
|
|
|
|
|
@@ -96,12 +92,8 @@ public class DouBaoTtsService {
|
|
|
|
|
|
|
|
MediaType mediaType = MediaType.parse("application/json");
|
|
MediaType mediaType = MediaType.parse("application/json");
|
|
|
RequestBody body = RequestBody.create(mediaType, requestBody);
|
|
RequestBody body = RequestBody.create(mediaType, requestBody);
|
|
|
-
|
|
|
|
|
String requestId = UUID.randomUUID().toString();
|
|
String requestId = UUID.randomUUID().toString();
|
|
|
- logger.info("发送豆包TTS请求,url: {}, appId: {}, resourceId: {}, requestId: {}",
|
|
|
|
|
- ttsUrl, appId, resourceId, requestId);
|
|
|
|
|
- logger.debug("请求体: {}", requestBody);
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 构建请求
|
|
// 构建请求
|
|
|
Request request = new Request.Builder()
|
|
Request request = new Request.Builder()
|
|
|
.url(ttsUrl)
|
|
.url(ttsUrl)
|
|
@@ -113,16 +105,6 @@ public class DouBaoTtsService {
|
|
|
.post(body)
|
|
.post(body)
|
|
|
.build();
|
|
.build();
|
|
|
|
|
|
|
|
- // 打印完整的请求头信息(不包含敏感信息)
|
|
|
|
|
- logger.debug("请求头信息:");
|
|
|
|
|
- for (String name : request.headers().names()) {
|
|
|
|
|
- if (!name.equals("X-Api-Access-Key")) {
|
|
|
|
|
- logger.debug("{}: {}", name, request.headers().get(name));
|
|
|
|
|
- } else {
|
|
|
|
|
- logger.debug("{}: ******", name);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
// 发送请求并流式接收响应
|
|
// 发送请求并流式接收响应
|
|
|
try (Response response = client.newCall(request).execute()) {
|
|
try (Response response = client.newCall(request).execute()) {
|
|
|
if (!response.isSuccessful()) {
|
|
if (!response.isSuccessful()) {
|
|
@@ -133,61 +115,57 @@ public class DouBaoTtsService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 读取响应体并逐行解析JSON,处理SSE流式响应
|
|
// 读取响应体并逐行解析JSON,处理SSE流式响应
|
|
|
- try (InputStream inputStream = response.body().byteStream();
|
|
|
|
|
- java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream))) {
|
|
|
|
|
- String line;
|
|
|
|
|
- StringBuilder base64AudioBuilder = new StringBuilder();
|
|
|
|
|
- ObjectMapper objectMapper2 = new ObjectMapper();
|
|
|
|
|
- boolean hasAudioData = false;
|
|
|
|
|
-
|
|
|
|
|
- while ((line = reader.readLine()) != null) {
|
|
|
|
|
- if (line.trim().isEmpty()) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- logger.debug("豆包TTS响应行: {}", line);
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- // 解析单行JSON
|
|
|
|
|
- Map<String, Object> responseMap = objectMapper2.readValue(line, Map.class);
|
|
|
|
|
-
|
|
|
|
|
- // 检查响应状态
|
|
|
|
|
- int code = (int) responseMap.get("code");
|
|
|
|
|
- if (code != 0 && code != 20000000) {
|
|
|
|
|
- String message = (String) responseMap.get("message");
|
|
|
|
|
- throw new IOException("豆包TTS服务返回错误: code=" + code + ", message=" + message);
|
|
|
|
|
|
|
+ if (response.body() != null) {
|
|
|
|
|
+ try (InputStream inputStream = response.body().byteStream();
|
|
|
|
|
+ java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream))) {
|
|
|
|
|
+ String line;
|
|
|
|
|
+ StringBuilder base64AudioBuilder = new StringBuilder();
|
|
|
|
|
+ ObjectMapper objectMapper2 = new ObjectMapper();
|
|
|
|
|
+ boolean hasAudioData = false;
|
|
|
|
|
+
|
|
|
|
|
+ while ((line = reader.readLine()) != null) {
|
|
|
|
|
+ if (line.trim().isEmpty()) {
|
|
|
|
|
+ continue;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 提取音频数据
|
|
|
|
|
- Object data = responseMap.get("data");
|
|
|
|
|
- if (data != null && data instanceof String) {
|
|
|
|
|
- String chunk = data.toString();
|
|
|
|
|
- if (!chunk.isEmpty()) {
|
|
|
|
|
- base64AudioBuilder.append(chunk);
|
|
|
|
|
- hasAudioData = true;
|
|
|
|
|
- logger.debug("提取到音频数据块,长度: {} 字符,累计长度: {} 字符",
|
|
|
|
|
- chunk.length(), base64AudioBuilder.length());
|
|
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 解析单行JSON
|
|
|
|
|
+ Map<String, Object> responseMap = objectMapper2.readValue(line, Map.class);
|
|
|
|
|
+
|
|
|
|
|
+ // 检查响应状态
|
|
|
|
|
+ int code = (int) responseMap.get("code");
|
|
|
|
|
+ if (code != 0 && code != 20000000) {
|
|
|
|
|
+ String message = (String) responseMap.get("message");
|
|
|
|
|
+ throw new IOException("豆包TTS服务返回错误: code=" + code + ", message=" + message);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取音频数据
|
|
|
|
|
+ Object data = responseMap.get("data");
|
|
|
|
|
+ if (data instanceof String) {
|
|
|
|
|
+ String chunk = data.toString();
|
|
|
|
|
+ if (!chunk.isEmpty()) {
|
|
|
|
|
+ base64AudioBuilder.append(chunk);
|
|
|
|
|
+ hasAudioData = true;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.warn("解析响应行失败: {}", e.getMessage());
|
|
|
}
|
|
}
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- logger.warn("解析响应行失败: {}", e.getMessage());
|
|
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (hasAudioData && base64AudioBuilder.length() > 0) {
|
|
|
|
|
- String base64Audio = base64AudioBuilder.toString();
|
|
|
|
|
- logger.info("成功提取完整base64音频数据,总长度: {} 字符", base64Audio.length());
|
|
|
|
|
-
|
|
|
|
|
- // 解码base64音频数据
|
|
|
|
|
- byte[] audioBytes = java.util.Base64.getDecoder().decode(base64Audio);
|
|
|
|
|
- logger.info("成功解码音频数据,长度: {} 字节", audioBytes.length);
|
|
|
|
|
- return audioBytes;
|
|
|
|
|
- } else {
|
|
|
|
|
- // 没有音频数据
|
|
|
|
|
- logger.warn("豆包TTS响应没有音频数据");
|
|
|
|
|
- throw new IOException("豆包TTS响应没有音频数据");
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (hasAudioData && !base64AudioBuilder.isEmpty()) {
|
|
|
|
|
+ String base64Audio = base64AudioBuilder.toString();
|
|
|
|
|
+
|
|
|
|
|
+ // 解码base64音频数据
|
|
|
|
|
+ return java.util.Base64.getDecoder().decode(base64Audio);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 没有音频数据
|
|
|
|
|
+ logger.warn("豆包TTS响应没有音频数据");
|
|
|
|
|
+ throw new IOException("豆包TTS响应没有音频数据");
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ return new byte[0];
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|