今天看了看讯飞的语音合成接口,写了个工具类:
@Component
public class XunFeiUtils {
private XunFeiUtils(){}
@Resource
private XunFeiProperties xf;
private static XunFeiProperties xunFeiProperties;
//注入静态Bean
@PostConstruct
public void init() {
xunFeiProperties=xf;
}
//======================================= 基本方法 ================================================================
//执行语音合成
/**
*
* @param text 文件名
* @param uri 保存路径
* @param name 生成文件名
* @return 生成文件路径
* @throws Exception exception
*/
public static String doSynthesize(String text,String uri,String name) throws Exception {
//验证自定义配置
if(StringUtils.isNotNull(uri)){
checkDictionary(uri);
}
//默认MP3
String type=StringUtils.ifNullToString(xunFeiProperties.getType(),"mp3");
switch (type) {
case "mp3":
return doSynthesizeMp3(text, uri, name);
case "wav":
return doSynthesizeWav(text, uri, name);
case "pcm":
return doSynthesizePcm(text, uri, name);
default:
return "";
}
}
//执行语音合成
public static String doSynthesize(String text,String uri) throws Exception {
return doSynthesize(text,uri,null);
}
//执行语音合成
public static String doSynthesize(String text) throws Exception {
return doSynthesize(text,null);
}
//======================================= 异步语音合成任务 ==========================================================
/**
* @param text 文字 必填
* @param url 保存路径 必填
* @throws Exception exception
*/
public static void doSynthesizeAsync(String text,String url){
//默认MP3
String type=StringUtils.ifNullToString(xunFeiProperties.getType(),"mp3");
try{
switch (type) {
case "mp3":
doSynthesizeMp3Sync(text,url);
case "wav":
doSynthesizeWavSync(text,url);
case "pcm":
doSynthesizePcmSync(text,url);
}
}catch (Exception e){
//转为非受检异常
ExceptionUtils.<RuntimeException>throwAs(e);
}
}
//异步任务生成MP3格式
public static void doSynthesizeMp3Sync(String text, String mp3Url) throws Exception {
//默认参数生成MP3
String wavUrl=doSynthesizeWavSync(text);
//Wav转Mp3格式
convertWav2Mp3(new File(wavUrl),new File(mp3Url));
}
//异步任务执行语音合成
private static String doSynthesizeWavSync(String text) throws Exception {
return doSynthesizeWavSync(text,getTempPath("wav"));
}
//执行语音合成(生成Wav)
public static String doSynthesizeWavSync(String text,String wavUrl) throws Exception {
//默认参数生成pcm
String pcmUrl= doSynthesizePcmSync(text);
convertPcm2Wav(pcmUrl,wavUrl);
return wavUrl;
}
//执行语音合成Uri
private static String doSynthesizePcmSync(String text){
return doSynthesizePcmSync(text,getTempPath("pcm"));
}
//执行语音合成Uri
public static String doSynthesizePcmSync(String text,String pcmUrl){
SpeechSynthesizer mTts=initSunthesize();
//合成文件
mTts.synthesizeToUri(text,pcmUrl,synthesizeToUriListener);
try {
Thread.sleep(100); //先等待100ms
}catch (InterruptedException e){
ExceptionUtils.<RuntimeException>throwAs(e);
}
//返回保存路径
return pcmUrl;
}
//========================================== 异步中间文件清理 =======================================================
//清理中间文件
/**
* @param path 最终生成文件指定的路径,可为null,则开启默认清理
*/
private void CleanTempFile(String path){
}
//======================================= MP3语音生成 ===============================================================
//执行语音合成(生成Mp3)
public static String doSynthesizeMp3(String text) throws Exception {
return doSynthesizeMp3(text,null);
}
//执行语音合成(生成Mp3)
public static String doSynthesizeMp3(String text,String uri) throws Exception {
return doSynthesizeMp3(text, uri,null);
}
//执行语音合成(生成Mp3)
/**
* @param text 转换的文字(必填)
* @param uri 保存地址
* @param name 文件名称
* @return 生成路径
* @throws Exception Exception
*/
public static String doSynthesizeMp3(String text,String uri,String name) throws Exception {
//验证配置路径
checkDictionary(xunFeiProperties.getSaveMp3Path());
//默认参数生成MP3
String wavUrl=doSynthesizeWav(text);
//获取路径
StringBuilder mp3Uri=new StringBuilder(StringUtils.ifNullToString(uri,xunFeiProperties.getSaveMp3Path()));
if (StringUtils.isNotNull(name)){
//自定义文件名
mp3Uri.append(File.separator).append(name).append(".mp3");
}else{
//生成文件名
mp3Uri.append(File.separator).append(getName()).append(".mp3");
}
String mp3Url=StringUtils.pathParse(mp3Uri.toString());
boolean succeed=convertWav2Mp3(new File(wavUrl),new File(mp3Url));
if(succeed){
return mp3Url;
}else{
return "";
}
}
//======================================= WAV语音生成 ===============================================================
//执行语音合成(生成Wav)
private static String doSynthesizeWav(String text,String uri,String name) throws Exception {
//验证配置路径
checkDictionary(xunFeiProperties.getSaveWavPath());
//默认参数生成pcm
String pcmUrl= doSynthesizePcm(text);
//获取路径
StringBuilder wavUri=new StringBuilder(StringUtils.ifNullToString(uri,xunFeiProperties.getSaveWavPath()));
if (StringUtils.isNotNull(name)){
//自定义文件名
wavUri.append(File.separator).append(name).append(".wav");
}else{
//生成文件名
wavUri.append(File.separator).append(getName()).append(".wav");
}
String wavUrl=StringUtils.pathParse(wavUri.toString());
convertPcm2Wav(pcmUrl,wavUrl);
return wavUrl;
}
//执行语音合成(生成Wav)
private static String doSynthesizeWav(String text,String uri) throws Exception {
return doSynthesizeWav(text,uri,null);
}
//执行语音合成(生成Wav)
public static String doSynthesizeWav(String text) throws Exception {
return doSynthesizeWav(text,null);
}
//======================================= PCM语音生成 ===============================================================
//执行语音合成Uri
public static String doSynthesizePcm(String text){
return doSynthesizePcm(text,null);
}
//执行语音合成Uri
public static String doSynthesizePcm(String text,String uri){
return doSynthesizePcm(text,uri,null);
}
//执行语音合成Uri
public static String doSynthesizePcm(String text,String uri,String name){
//验证配置路径
checkDictionary(xunFeiProperties.getSavePCMPath());
//初始化配置
SpeechSynthesizer mTts=initSunthesize();
//参数为null则使用配置参数
StringBuilder pcmUri=new StringBuilder(StringUtils.ifNullToString(uri,xunFeiProperties.getSavePCMPath()));
if (StringUtils.isNotNull(name)){
//自定义文件名
pcmUri.append(File.separator).append(name).append(".pcm");
}else{
//生成文件名
pcmUri.append(File.separator).append(getName()).append(".pcm");
}
//路径转换
String pcmUrl=StringUtils.pathParse(pcmUri.toString());
//执行语音合成(Windows PCM文件)
mTts.synthesizeToUri(text,pcmUrl,synthesizeToUriListener);
try {
Thread.sleep(100); //先等待100ms
}catch (InterruptedException e){
ExceptionUtils.<RuntimeException>throwAs(e);
}
//返回保存路径
return pcmUrl;
}
//=========================================== 格式转换 =============================================================
//PCM转WAV
private static void convertPcm2Wav(String src, String target) throws Exception {
//确认文件是否存在
File pcmFile=new File(src);
while(!pcmFile.exists()){ //不存在
pcmFile=new File(src);
Thread.sleep(50); //循环等待 50ms一次
}
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(target);
//计算长度
byte[] buf = new byte[1024 * 4];
int size = fis.read(buf);
int PCMSize = 0;
while (size != -1) {
PCMSize += size;
size = fis.read(buf);
}
fis.close();
//填入参数,比特率等等。这里用的是16位单声道 8000 hz
WaveHeader header = new WaveHeader();
//长度字段 = 内容的大小(PCMSize) + 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
header.fileLength = PCMSize + (44 - 8);
header.FmtHdrLeth = 16;
header.BitsPerSample = 16;
header.Channels = 2;
header.FormatTag = 0x0001;
header.SamplesPerSec = 8000;
header.BlockAlign = (short)(header.Channels * header.BitsPerSample / 8);
header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
header.DataHdrLeth = PCMSize;
byte[] h = header.getHeader();
assert h.length == 44; //WAV标准,头部应该是44字节
//write header
fos.write(h, 0, h.length);
//write data stream
fis = new FileInputStream(src);
size = fis.read(buf);
while (size != -1) {
fos.write(buf, 0, size);
size = fis.read(buf);
}
fis.close();
fos.close();
System.out.println("Convert OK!");
}
//wav转MP3
private static boolean convertWav2Mp3(File source, File target) {
boolean succeeded = true;
try {
AudioAttributes audio = new AudioAttributes();
audio.setCodec("libmp3lame");
audio.setBitRate(128000);
audio.setChannels(2);
audio.setSamplingRate(44100);
audio.setVolume(256);
EncodingAttributes attrs = new EncodingAttributes();
attrs.setFormat("mp3");
attrs.setAudioAttributes(audio);
Encoder encoder = new Encoder();
encoder.encode(new MultimediaObject(source), target, attrs);
} catch (Exception ex) {
ex.printStackTrace();
succeeded = false;
}
return succeeded;
}
//=========================================== 文件与路径处理 ========================================================
//获取保存文件的名称
private static String getName(){
return String.valueOf(new Date().getTime());
}
//获取最终路径(用于异步任务路径返回及控制)
public static String getRealPath(String path, String name){
//默认MP3
String type=StringUtils.ifNullToString(xunFeiProperties.getType(),"mp3");
StringBuilder realPath =new StringBuilder("");
if(type.equals("pcm")){ //pcm
if (StringUtils.isNotNull(path)){ //路径不为null
realPath.append(path).append(File.separator);
}else{
checkDictionary(xunFeiProperties.getSavePCMPath());
realPath.append(xunFeiProperties.getSavePCMPath()).append(File.separator);
}
if(StringUtils.isNotNull(name)){ //文件名不为空
realPath.append(name).append(".pcm");
}else{
realPath.append(getName()).append(".pcm");
}
}else if(type.equals("wav")){ //wav
if (StringUtils.isNotNull(path)){ //路径不为null
realPath.append(path).append(File.separator);
}else{
checkDictionary(xunFeiProperties.getSaveWavPath());
realPath.append(xunFeiProperties.getSaveWavPath()).append(File.separator);
}
if(StringUtils.isNotNull(name)){ //文件名不为空
realPath.append(name).append(".wav");
}else{
realPath.append(getName()).append(".wav");
}
}else{ //mp3
if (StringUtils.isNotNull(path)){ //路径不为null
realPath.append(path).append(File.separator);
}else{
checkDictionary(xunFeiProperties.getSaveWavPath());
realPath.append(xunFeiProperties.getSaveMp3Path()).append(File.separator);
}
if(StringUtils.isNotNull(name)){ //文件名不为空
realPath.append(name).append(".mp3");
}else{
realPath.append(getName()).append(".mp3");
}
}
return StringUtils.pathParse(realPath.toString());
}
//异步任务中间路径生成
private static String getTempPath(String type){
if("pcm".equals(type)&&!"pcm".equals(xunFeiProperties.getType())){
checkDictionary(xunFeiProperties.getSavePCMPath());
//单行代码拼接String是StringBuilder语法糖,不用StringBuilder
return StringUtils.pathParse( xunFeiProperties.getSavePCMPath()+File.separator + getName() + ".pcm");
}else if("wav".equals(type)&&"mp3".equals(xunFeiProperties.getType())){
checkDictionary(xunFeiProperties.getSaveWavPath());
return StringUtils.pathParse(xunFeiProperties.getSaveWavPath()+File.separator + getName() + ".wav");
}else{
return "";
}
}
//确认目录
private static void checkDictionary(String uri){
File file=new File(uri);
if(!file.exists()){ //不存在创建
boolean f=file.mkdir();
}
}
//=========================================== 辅助项 ===============================================================
//初始化
private static SpeechSynthesizer initSunthesize(){
//AppId
SpeechUtility.createUtility(SpeechConstant.APPID +"="+xunFeiProperties.getAppId());
//创建合成器
SpeechSynthesizer mTts = SpeechSynthesizer.createSynthesizer();
//设置合成器参数
//发音人
mTts.setParameter(SpeechConstant.VOICE_NAME,xunFeiProperties.getVoiceName());
//语速,范围0~100
mTts.setParameter(SpeechConstant.SPEED,xunFeiProperties.getSpeed());
//语调,范围0~100
mTts.setParameter(SpeechConstant.PITCH,xunFeiProperties.getPitch());
//音量,范围0~100
mTts.setParameter(SpeechConstant.VOLUME,xunFeiProperties.getVolume());
//文件保存路径
mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH,xunFeiProperties.getSavePCMPath());
//可能会对象逃逸,谨慎使用
return mTts;
}
//合成监听器
private static SynthesizeToUriListener synthesizeToUriListener = new SynthesizeToUriListener() {
//进度条中
@Override
public void onBufferProgress(int progress) {
}
//生成成功
@Override
public void onSynthesizeCompleted(String uri, SpeechError error) {
if (error!=null){
System.out.println(error.getErrorDescription(true));
}else{
System.out.println("生成语音成功");
}
}
//监听某一事件
@Override
public void onEvent(int i, int i1, int i2, int i3, Object o, Object o1) {
}
};
//用于生成头部信息
static class WaveHeader {
public final char[] fileID = {'R', 'I', 'F', 'F'};
public int fileLength;
public short FormatTag;
public short Channels;
public int SamplesPerSec;
public int AvgBytesPerSec;
public short BlockAlign;
public short BitsPerSample;
public char[] DataHdrID = {'d','a','t','a'};
public int DataHdrLeth;
public char[] wavTag = {'W', 'A', 'V', 'E'};
public char[] FmtHdrID = {'f', 'm', 't', ' '};
public int FmtHdrLeth;
public WaveHeader() {}//无参构造方法
public byte[] getHeader() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
WriteChar(bos, fileID);
WriteInt(bos, fileLength);
WriteChar(bos, wavTag);
WriteChar(bos, FmtHdrID);
WriteInt(bos,FmtHdrLeth);
WriteShort(bos,FormatTag);
WriteShort(bos,Channels);
WriteInt(bos,SamplesPerSec);
WriteInt(bos,AvgBytesPerSec);
WriteShort(bos,BlockAlign);
WriteShort(bos,BitsPerSample);
WriteChar(bos,DataHdrID);
WriteInt(bos,DataHdrLeth);
bos.flush();
byte[] r = bos.toByteArray();
bos.close();
return r;
}
private void WriteShort(ByteArrayOutputStream bos, int s) throws IOException {
byte[] mybyte = new byte[2];
mybyte[1] =(byte)( (s << 16) >> 24 );
mybyte[0] =(byte)( (s << 24) >> 24 );
bos.write(mybyte);
}
private void WriteInt(ByteArrayOutputStream bos, int n) throws IOException {
byte[] buf = new byte[4];
buf[3] =(byte)( n >> 24 );
buf[2] =(byte)( (n << 8) >> 24 );
buf[1] =(byte)( (n << 16) >> 24 );
buf[0] =(byte)( (n << 24) >> 24 );
bos.write(buf);
}
private void WriteChar(ByteArrayOutputStream bos, char[] id) {
for (char c : id) {
bos.write(c);
}
}
}
}
Maven依赖
<!--讯飞SDK-->
<dependency>
<groupId>com.xunfei</groupId>
<artifactId>sdk</artifactId>
<version>1.0</version>
<!--system,类似provided,需要显式提供依赖的jar以后,Maven就不会在Repository中查找它-->
<scope>system</scope>
<!--项目根目录下的lib文件夹下-->
<systemPath>${basedir}/lib/Msc.jar</systemPath>
</dependency>
<dependency>
<groupId>com.xunfei</groupId>
<artifactId>json</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${basedir}/lib/json-jena-1.0.jar</systemPath>
</dependency>
<!--语音格式转换需要的包-->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>
<version>2.4.4</version>
</dependency>
<!--mac导入这个-->
<!--<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-native-osx64</artifactId>
<version>2.4.6</version>
</dependency>-->
<!--linux导入这个-->
<!--
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-native-linux64</artifactId>
<version>2.4.4</version>
</dependency>
-->
<!--widows-->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-native-win64</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>com.googlecode.soundlibs</groupId>
<artifactId>mp3spi</artifactId>
<version>1.9.5.4</version>
</dependency>
讯飞配置类
@Data
@Configuration
@ConfigurationProperties("xun-fei")
public class XunFeiProperties {
//讯飞APPId
private String appId;
//发音人
private String voiceName;
//设置语速,范围0~100
private String speed;
//设置语调,范围0~100
private String pitch;
//设置音量,范围0~100
private String volume;
//保存路径(PCM)
private String savePCMPath="";
//保存路径(Wav)
private String saveWavPath="";
//保存路径(Mp3)
private String saveMp3Path="";
//在线、离线、混合
private String engineType;
//生成文件类型 mp3,pcm和wav
private String type;
//播放类型
private String streamType;
//背景音乐
private String backgroundSound;
//合成音频缓冲时间
private String ttsBufferTime;
//采样率,取值{8KHZ,16KHZ}
private String sampleRate;
}
StringUtils
public class StringUtils {
private StringUtils(){}
//判断是否为空字符串或null
public static boolean isNull(String str){
return GenericReference.checkObject(str, StringUtils::isNullRef);
}
//不为空字符串或null
public static boolean isNotNull(String str){
return !isNull(str);
}
//字符串为null或空字符串设置为默认值
public static String ifNullToString(String str,String defaultStr){
return GenericReference.checkToDefault(str,defaultStr, StringUtils::isNullRef);
}
//null转空字符串
public static String ifNullToString(String str){
return ifNullToString(str,"");
}
//判断是否为空
private static boolean isNullRef(String str){
return str==null||"".equals(str);
}
//路径字符串转换
public static String pathParse(String str){
//防止转义
return str.replaceAll("/", Matcher.quoteReplacement(File.separator));
}
}
ExceptionUtils
public class ExceptionUtils {
private ExceptionUtils(){}
//受检异常转非受检异常(强制转换法)
@SuppressWarnings("unchecked")
public static <T extends RuntimeException> void throwAs(Throwable e) throws T{
T t =(T)new RuntimeException();
t.setStackTrace(e.getStackTrace());
throw t;
}
//受检异常转非受检异常(包装法)
public static void changeToRuntimeException(Throwable e){
throw new RuntimeException(e);
}
//受检异常转非受检异常并输出日志
@SuppressWarnings("unchecked")
public static <T extends RuntimeException> void throwAsWithLog(Logger log,Throwable e) throws T{
log.error(getMessage(e));
throw (T) e;
}
//受检异常转非受检异常并输出日志(包装法)
public static <T extends RuntimeException> void changeToRuntimeExceptionWithLog(Logger log,Throwable e) throws T{
log.error(getMessage(e));
throw new RuntimeException(e);
}
//收集详细堆栈信息
public static String getMessage(Throwable e) {
StringWriter sw = null;
PrintWriter pw = null;
try {
sw = new StringWriter();
pw = new PrintWriter(sw);
// 将出错的栈信息输出到printWriter中
e.printStackTrace(pw);
pw.flush();
sw.flush();
} finally {
if (sw != null) {
try {
sw.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (pw != null) {
pw.close();
}
}
return sw.toString();
}
}
简单函数式接口封装
public class GenericReference<E> {
//对E进行判断
public static <E> boolean checkObject(E e,Predicate<E> predicate){
return predicate.test(e);
}
//判定并转默认
public static <E> E checkToDefault(E e,E defaultE,Predicate<E> predicate){
if(predicate.test(e)){
return defaultE;
}
return e;
}
//生产者
public static <E> E get(Supplier<? extends E> supplier){
return supplier.get();
}
//消费者
public static <E> void consumer(E e,Consumer<? super E> consumer){
consumer.accept(e);
}
//int转E
public static <E> E intToE(int i, IntFunction<E> intFunction){
return intFunction.apply(i);
}
//E转int
public static <E> int eToInt(E e, ToIntFunction<E> intFunction){
return intFunction.applyAsInt(e);
}
//对象转换T转换为R
public static <R,T> R TtoR(T t,Function<T,R> function){
return function.apply(t);
}
}
网友评论