|
|
@@ -1,5 +1,6 @@
|
|
|
-package cn.iocoder.byzs.module.ai.controller.admin.tts;
|
|
|
+package cn.iocoder.byzs.module.ai.util.tts;
|
|
|
|
|
|
+import cn.iocoder.byzs.module.ai.dal.dataobject.tts.AiTtsDO;
|
|
|
import com.alibaba.nls.client.AccessToken;
|
|
|
import com.alibaba.nls.client.protocol.NlsClient;
|
|
|
import com.alibaba.nls.client.protocol.OutputFormatEnum;
|
|
|
@@ -13,11 +14,8 @@ import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
-import javax.sound.sampled.*;
|
|
|
import java.io.IOException;
|
|
|
import java.nio.ByteBuffer;
|
|
|
-import java.util.concurrent.ConcurrentLinkedQueue;
|
|
|
-import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
import java.util.function.Consumer;
|
|
|
|
|
|
@Service
|
|
|
@@ -40,7 +38,6 @@ public class StreamTtsService {
|
|
|
private String ALIYUN_AK_SECRET;
|
|
|
|
|
|
private NlsClient client;
|
|
|
- private PlaybackRunnable playbackRunnable;
|
|
|
private Thread playbackThread;
|
|
|
private StreamInputTts synthesizer;
|
|
|
// ==== 添加音频数据回调 ====
|
|
|
@@ -71,21 +68,10 @@ public class StreamTtsService {
|
|
|
/**
|
|
|
* 开始TTS语音合成
|
|
|
*/
|
|
|
- public void startTts() {
|
|
|
- // 初始化播放器
|
|
|
- playbackRunnable = new PlaybackRunnable(24000);
|
|
|
- try {
|
|
|
- playbackRunnable.prepare();
|
|
|
- } catch (LineUnavailableException e) {
|
|
|
- log.error("初始化音频播放器失败", e);
|
|
|
- throw new RuntimeException("初始化音频播放器失败", e);
|
|
|
- }
|
|
|
- playbackThread = new Thread(playbackRunnable);
|
|
|
- playbackThread.start();
|
|
|
-
|
|
|
+ public void startTts(AiTtsDO aiTtsDO) {
|
|
|
// 创建TTS实例
|
|
|
try {
|
|
|
- synthesizer = new StreamInputTts(client, getSynthesizerListener(playbackRunnable));
|
|
|
+ synthesizer = new StreamInputTts(client, getSynthesizerListener());
|
|
|
} catch (Exception e) {
|
|
|
log.error("创建TTS实例", e);
|
|
|
throw new RuntimeException("创建TTS实例", e);
|
|
|
@@ -93,10 +79,10 @@ public class StreamTtsService {
|
|
|
synthesizer.setAppKey(appKey);
|
|
|
synthesizer.setFormat(OutputFormatEnum.PCM);
|
|
|
synthesizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_24K);
|
|
|
- synthesizer.setVoice("aitong");
|
|
|
- synthesizer.setVolume(50);
|
|
|
- synthesizer.setPitchRate(0);
|
|
|
- synthesizer.setSpeechRate(50);
|
|
|
+ synthesizer.setVoice(aiTtsDO.getModel());
|
|
|
+ synthesizer.setVolume(aiTtsDO.getVolume());
|
|
|
+ synthesizer.setPitchRate(aiTtsDO.getPitchRate());
|
|
|
+ synthesizer.setSpeechRate(aiTtsDO.getSpeechRate());
|
|
|
// synthesizer.setSplitText(true); // 如有类似配置需设为false
|
|
|
// synthesizer.setEnableSplit(false); // 禁用文本拆分,确保整句合成
|
|
|
|
|
|
@@ -132,9 +118,6 @@ public class StreamTtsService {
|
|
|
synthesizer.stopStreamInputTts();
|
|
|
synthesizer.close();
|
|
|
}
|
|
|
- if (playbackRunnable != null) {
|
|
|
- playbackRunnable.stop();
|
|
|
- }
|
|
|
if (playbackThread != null) {
|
|
|
playbackThread.join();
|
|
|
}
|
|
|
@@ -142,12 +125,11 @@ public class StreamTtsService {
|
|
|
log.error("停止TTS服务失败", e);
|
|
|
} finally {
|
|
|
synthesizer = null;
|
|
|
- playbackRunnable = null;
|
|
|
playbackThread = null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private StreamInputTtsListener getSynthesizerListener(final PlaybackRunnable audioPlayer) {
|
|
|
+ private StreamInputTtsListener getSynthesizerListener() {
|
|
|
return new StreamInputTtsListener() {
|
|
|
private boolean firstRecvBinary = true;
|
|
|
|
|
|
@@ -169,7 +151,6 @@ public class StreamTtsService {
|
|
|
@Override
|
|
|
public void onSynthesisComplete(StreamInputTtsResponse response) {
|
|
|
log.info("TTS合成完成: name={}, status={}", response.getName(), response.getStatus());
|
|
|
- audioPlayer.stop();
|
|
|
// 调用完成回调
|
|
|
if (onCompleteCallback != null) {
|
|
|
onCompleteCallback.run();
|
|
|
@@ -184,7 +165,6 @@ public class StreamTtsService {
|
|
|
}
|
|
|
byte[] bytesArray = new byte[message.remaining()];
|
|
|
message.get(bytesArray, 0, bytesArray.length);
|
|
|
-// audioPlayer.put(ByteBuffer.wrap(bytesArray));
|
|
|
|
|
|
// ==== 调用回调传递音频数据 ====
|
|
|
log.info("生成音频数据: 长度={} bytes", bytesArray.length);
|
|
|
@@ -212,7 +192,6 @@ public class StreamTtsService {
|
|
|
response.getTaskId(),
|
|
|
response.getStatus(),
|
|
|
response.getStatusText());
|
|
|
- audioPlayer.stop();
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
@@ -221,69 +200,6 @@ public class StreamTtsService {
|
|
|
this.audioDataCallback = callback;
|
|
|
}
|
|
|
|
|
|
- class PlaybackRunnable implements Runnable {
|
|
|
- private AudioFormat af;
|
|
|
- private DataLine.Info info;
|
|
|
- private SourceDataLine targetSource;
|
|
|
- private AtomicBoolean runFlag;
|
|
|
- private ConcurrentLinkedQueue<ByteBuffer> queue;
|
|
|
-
|
|
|
- public PlaybackRunnable(int sample_rate) {
|
|
|
- af = new AudioFormat(sample_rate, 16, 1, true, false);
|
|
|
- info = new DataLine.Info(SourceDataLine.class, af);
|
|
|
- targetSource = null;
|
|
|
- runFlag = new AtomicBoolean(true);
|
|
|
- queue = new ConcurrentLinkedQueue<>();
|
|
|
- }
|
|
|
-
|
|
|
- public void prepare() throws LineUnavailableException {
|
|
|
- targetSource = (SourceDataLine) AudioSystem.getLine(info);
|
|
|
- targetSource.open(af, 4096);
|
|
|
- targetSource.start();
|
|
|
- }
|
|
|
-
|
|
|
- public void put(ByteBuffer buffer) {
|
|
|
- queue.add(buffer);
|
|
|
- }
|
|
|
-
|
|
|
- public void stop() {
|
|
|
- runFlag.set(false);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void run() {
|
|
|
- if (targetSource == null) {
|
|
|
- return;
|
|
|
- }
|
|
|
- while (runFlag.get()) {
|
|
|
- if (queue.isEmpty()) {
|
|
|
- try {
|
|
|
- Thread.sleep(10);
|
|
|
- } catch (InterruptedException e) {
|
|
|
- Thread.currentThread().interrupt();
|
|
|
- break;
|
|
|
- }
|
|
|
- continue;
|
|
|
- }
|
|
|
- ByteBuffer buffer = queue.poll();
|
|
|
- if (buffer == null) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- byte[] data = buffer.array();
|
|
|
- targetSource.write(data, 0, data.length);
|
|
|
- }
|
|
|
- if (!queue.isEmpty()) {
|
|
|
- ByteBuffer buffer;
|
|
|
- while ((buffer = queue.poll()) != null) {
|
|
|
- byte[] data = buffer.array();
|
|
|
- targetSource.write(data, 0, data.length);
|
|
|
- }
|
|
|
- }
|
|
|
- targetSource.drain();
|
|
|
- targetSource.stop();
|
|
|
- targetSource.close();
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
@PreDestroy
|
|
|
public void destroy() {
|