TTS 返回语音优化

This commit is contained in:
2025-10-19 18:35:54 +08:00
parent a3ef5d1959
commit a839571b4e
4 changed files with 66 additions and 18 deletions

View File

@@ -144,8 +144,10 @@ public class ChatWebSocketHandler {
log.info("3、开始进行AI回答时间:{}",System.currentTimeMillis()/1000);
//持续返回数据流给客户端
try {
String resultOutPathUrl = RuoYiConfig.getProfile() + VOICE_STORAGE_RESULT_DIR + "110_"+resultFileName;
handleVoice(resultPathUrl,resultOutPathUrl);
//文件转换成文件流
ByteBuffer outByteBuffer = convertFileToByteBuffer(resultPathUrl);
ByteBuffer outByteBuffer = convertFileToByteBuffer(resultOutPathUrl);
//发送文件流数据
session.getBasicRemote().sendBinary(outByteBuffer);
// 发送响应确认
@@ -404,6 +406,50 @@ public class ChatWebSocketHandler {
}
}
private void handleVoice(String inputPath,String outputPath){
double trimMs = 270; // 要去掉的尾部时长(毫秒)
try {
// 1. 解析音频格式和总长度
AudioInputStream audioIn = AudioSystem.getAudioInputStream(new File(inputPath));
AudioFormat format = audioIn.getFormat();
long totalBytes = audioIn.getFrameLength() * format.getFrameSize(); // 总字节数
// 2. 计算300毫秒对应的字节数
float sampleRate = format.getSampleRate(); // 采样率Hz
int frameSize = format.getFrameSize(); // 每帧字节数(位深/8 * 声道数)
double trimSeconds = trimMs / 1000.0; // 转换为秒
long trimBytes = (long) (sampleRate * trimSeconds * frameSize); // 要去掉的字节数
// 3. 计算需要保留的字节数(避免负数)
long keepBytes = Math.max(0, totalBytes - trimBytes);
if (keepBytes == 0) {
System.out.println("音频长度小于300毫秒无法截断");
return;
}
// 4. 读取并保留前半部分去掉最后300毫秒
try (InputStream in = new FileInputStream(inputPath);
OutputStream out = new FileOutputStream(outputPath)) {
byte[] buffer = new byte[4096];
long totalRead = 0;
int bytesRead;
while (totalRead < keepBytes && (bytesRead = in.read(buffer)) != -1) {
long remaining = keepBytes - totalRead;
int writeBytes = (remaining < bytesRead) ? (int) remaining : bytesRead;
out.write(buffer, 0, writeBytes);
totalRead += writeBytes;
}
System.out.println("处理完成,去掉了最后" + trimMs + "毫秒,保留了" + totalRead + "字节");
}
} catch (UnsupportedAudioFileException | IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -38,7 +38,7 @@ public class AiCommonController extends BaseController
@GetMapping("/handleTextToVice")
public AjaxResult handleTextToVice()
{
elevenLabsClient.handleTextToVoice("我只是测试的文本转换成语音","/Users/wangxiangshun/Desktop/临时文件/output1112.mp3");
elevenLabsClient.handleTextToVoice("Hello ! I can","/Users/wangxiangshun/Desktop/临时文件/output1112.wav");
return success();
}

View File

@@ -145,7 +145,8 @@ verification:
# 文本转语音
elevenLabs:
baseUrl: https://api.elevenlabs.io/v1
apiKey: sk_5240d8f56cb1eb5225fffcf903f62479884d1af5b3de6812
# apiKey: sk_5240d8f56cb1eb5225fffcf903f62479884d1af5b3de6812
apiKey: sk_88f5a560e1bbde0e5b8b6b6eb1812163a98bfb98554acbec
modelId: eleven_turbo_v2_5
# 语音转文本
whisper:

View File

@@ -115,21 +115,22 @@ public class OpenAiStreamClient {
.getJSONObject("delta")
.getStr("content");
if (content != null && !content.isEmpty()) {
if(punctuationSet.contains(content)){
//说明有标点啦,直接返回
bufferStr.append(content);
listener.onMessage(bufferStr.toString());
}else{
//加入缓冲区
if(StrUtil.isEmpty(bufferStr.toString())){
bufferStr.append(content);
}else {
bufferStr.append(" ").append(content);
}
}
}
// if (content != null && !content.isEmpty()) {
// if(punctuationSet.contains(content)){
// //说明有标点啦,直接返回
// bufferStr.append(content);
// listener.onMessage(bufferStr.toString());
// }else{
// //加入缓冲区
// if(StrUtil.isEmpty(bufferStr.toString())){
// bufferStr.append(content);
// }else {
// bufferStr.append(" ").append(content);
// }
// }
//
// }
listener.onMessage(content);
} catch (Exception e) {
listener.onError(new IOException("Parse error: " + e.getMessage()));
}