package com.ypp.tts.sdk.ali;
import cn.hutool.core.io.FileUtil;
import com.alibaba.nls.client.AccessToken;
import com.alibaba.nls.client.protocol.NlsClient;
import com.alibaba.nls.client.protocol.OutputFormatEnum;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizer;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerListener;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerResponse;
import com.google.gson.Gson;
import com.ypp.media.sdk.util.AudioUtils;
import com.ypp.tts.sdk.ali.listener.FileSpeechSynthesizerListener;
import com.ypp.tts.sdk.ali.request.BaseSpeechSynthesisRequest;
import com.ypp.tts.sdk.ali.request.SpeechSynthesisBatchRequest;
import com.ypp.tts.sdk.ali.request.SpeechSynthesisRequest;
import com.ypp.tts.sdk.ali.response.SpeechSynthesisBatchResponse;
import com.ypp.util.string.StringUtils;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 阿里云TTS客户端
*
* @author ypp
* @version 1.0
* @date 2019-11-19 20:24:52
**/
@Slf4j
public class AliTTSClient {
/**
* 尝试次数
*/
private static int TRY_NUM = 3;
/**
* 交互客户端,初始化一次就行
*/
private NlsClient nlsClient;
/**
* 项目appKey
*/
private String appKey;
/**
* RAM 用户AccessKeyId
*/
private String accessKeyId;
/**
* RAM 用户AccessKeySecret
*/
private String accessKeySecret;
/**
* 获取到的token
*/
private String token;
/**
* token过期时间戳
*/
private Long expireTime;
public AliTTSClient(String appKey, String accessKeyId, String accessKeySecret) {
log.info("############# 阿里云TTS初始化 #############");
this.appKey = appKey;
this.accessKeyId = accessKeyId;
this.accessKeySecret = accessKeySecret;
boolean isSuccess = false;
int index = 0;
AccessToken accessToken = new AccessToken(accessKeyId, accessKeySecret);
while(!isSuccess && index < TRY_NUM) {
try {
// 获取初始token
accessToken.apply();
this.token = accessToken.getToken();
this.expireTime = accessToken.getExpireTime();
// 建立连接
nlsClient = new NlsClient(accessToken.getToken());
isSuccess = true;
} catch (Throwable e) {
e.printStackTrace();
log.error("初始化阿里云TTS发生异常", e);
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
log.error("线程休眠发生异常", ex);
}
}
}
}
/**
* 批量文本合成一个语音
*
* @param speechSynthesisBatchRequest 批量文本合成语音请求参数
* @param outFilePath 输出文件绝对路径
* @throws Exception
*/
public SpeechSynthesisBatchResponse speechSynthesisBatch(SpeechSynthesisBatchRequest speechSynthesisBatchRequest, String outFilePath) throws Exception {
if (StringUtils.isEmpty(outFilePath)) {
throw new Exception("outFile not empty!");
}
SpeechSynthesizer synthesizer = null;
FileUtil.newFile(outFilePath);
OutputStream outputStream = null;
try {
File file = new File(outFilePath);
String tmpFilePath = "";
if (speechSynthesisBatchRequest.getFormat() == null || speechSynthesisBatchRequest.getFormat() == OutputFormatEnum.PCM) {
// 如果生成pcm文件,则直接生成
outputStream = new FileOutputStream(file);
} else {
// 如果生成wav或者mp3文件,则需要先生成pcm文件,再将pcm文件进行格式转换
// 创建临时pcm文件
String tmpFileName = UUID.randomUUID().toString() + ".pcm";
tmpFilePath = file.getParentFile().getAbsolutePath() + File.separatorChar + tmpFileName;
FileUtil.newFile(tmpFilePath);
outputStream = new FileOutputStream(new File(tmpFilePath));
}
// 创建实例,建立连接
FileSpeechSynthesizerListener listener = new FileSpeechSynthesizerListener(outputStream);
synthesizer = new SpeechSynthesizer(this.nlsClient, listener);
synthesizer.setAppKey(this.appKey);
// 初始化synthesizer参数
initSynthesizer(synthesizer, speechSynthesisBatchRequest);
//重新设置返回音频的编码格式(这里设置为PCM,才能完整接收多个文本生成的语音流)
synthesizer.setFormat(OutputFormatEnum.PCM);
SpeechSynthesisBatchResponse speechSynthesisBatchResponse = new SpeechSynthesisBatchResponse();
Map<String, SpeechSynthesizerResponse> responseMap = new HashMap<>();
speechSynthesisBatchResponse.setMap(responseMap);
//设置用于语音合成的文本
for (Map.Entry<String, String> entry : speechSynthesisBatchRequest.getTextMap().entrySet()) {
String text = entry.getValue();
synthesizer.setText(text);
long start = System.currentTimeMillis();
synthesizer.start();
log.info("tts start latency " + (System.currentTimeMillis() - start) + " ms");
//等待语音合成结束
synthesizer.waitForComplete();
if (!listener.isSuccess()) {
log.error("合成语音失败,文本:{},结果:{}", text, new Gson().toJson(listener.getSpeechSynthesizerResponse()));
responseMap.put(entry.getKey(), listener.getSpeechSynthesizerResponse());
speechSynthesisBatchResponse.setSuccess(false);
return speechSynthesisBatchResponse;
} else {
responseMap.put(entry.getKey(), listener.getSpeechSynthesizerResponse());
}
}
// 音频格式转换
if (speechSynthesisBatchRequest.getFormat() == OutputFormatEnum.WAV) {
outputStream.close();
AudioUtils.convertPcmToWav(tmpFilePath, outFilePath);
} else if (speechSynthesisBatchRequest.getFormat() == OutputFormatEnum.MP3) {
outputStream.close();
AudioUtils.convertPcmToMp3(tmpFilePath, outFilePath);
}
speechSynthesisBatchResponse.setSuccess(true);
return speechSynthesisBatchResponse;
} catch (Exception e) {
log.info("语音合成失败", e);
throw e;
} finally {
if (synthesizer != null) {
synthesizer.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
/**
* 语音合成(短文本,不超过300个字符)
*
* @param speechSynthesisRequest 语音合成请求参数
* @param outFilePath 输出文件路径
* @throws Exception
*/
public SpeechSynthesizerResponse speechSynthesis(SpeechSynthesisRequest speechSynthesisRequest, String outFilePath) throws Exception {
if (StringUtils.isEmpty(outFilePath)) {
throw new Exception("outFile not empty!");
}
FileUtil.newFile(outFilePath);
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(new File(outFilePath));
FileSpeechSynthesizerListe