对于语音整合的初衷主要是想做一款日语背单词的app,目前的app都有一个弊端就是没有引导记忆的方法,所以想自己研发一个。目前基本需要的词库数据和后端技术栈已经都完全具备了,虽然自己也会前端和混合app的开发,但是毕竟不是特别擅长前端,所以在这里希望如果有兴趣的前端或Android的朋友可以联系我。当然对技术充满热情的朋友也可以加我。
说实话本来对接api就是一个踩坑的过程,毕竟每个官方文档也好博客也好,都可能出现坑。所以这里也只能将我踩过的一些坑告诉大家,主要分享一下小语种语音合成的流程。如果只是想中英文的语音合成直接用百度开放的接口就好,流程很简单这里就不多说了,可以参考一下这篇博客。
之前找好词库之后因为没有找到既有音频又有词库的统一资料,所以我也不可能再去单独去找音频然后在和单词进行人工的绑定,只有考虑对接语音合成的api将单词进行语音合成,结果貌似大多厂商都只是支持了汉语和英语两种类型的合成,小语种找了半天好像也就讯飞科技和Google两个平台提供。然而Google云开发平台从去年就停止对中国提供开放了。无奈只有微笑。
最后选择了讯飞语音。
1.用户注册
传送门
2.注册好后先创建一个自己的应用
创建完成后进入应用可以看到我们开发需要的三个参数:APPID,APISecret,APIKey
3.因为平台提供的SDK中只支持了简单的中英两种语言语音合成,所以这里我们不会用提供的Java的SDK包。
这里是直接使用WebAPI的方式进行整合。到这里你可以直接下载他提供的demo代码,然后把上面我们提到的三个参数换成你自己的就可以完成简单的中文语音的合成了,实际上还是挺简单的。当然后面我也会提供给大家我整合好的demo。
4.后续如果大家需要把demo中的jar使用到项目中可以将jar包安装到自己的maven仓库,安装方法的话可以参考这篇博客。
我们主要使用到是WebTTSWS这个类。
1.替换三个参数,以及你需要合成的文本内容。
2.其实已经提示的很明显了,小语种需要和对应的小语种发音人进行配合使用,所以我们还需要在应用中添加对应的小语种发音人。
进入我们的控制台选流式版的语音合成,添加对应的小语种发音人。然后小语种发音人的参数就是我们需要在代码进行配置的。
3.修改几个小语种合成的参数。
参数名 | 类型 | 必传 | 描述 | 示例 |
---|---|---|---|---|
ent | string | 否 | 引擎类型,可选值: aisound(普通效果) intp65(中文) intp65_en(英文) mtts(小语种,需配合小语种发音人使用) xtts(优化效果) 默认为intp65 |
"intp65" |
vcn | string | 是 | 发音人,可选值:请到控制台添加试用或购买发音人,添加后即显示发音人参数值 | "xiaoyan" |
tte | string | 是 | 文本编码格式 GB2312 GBK BIG5 UNICODE(小语种必须使用UNICODE编码,合成的文本需使用utf16小端的编码方式,详见java示例demo) GB18030 UTF8 |
"UTF8" |
4.现在可以运行一下demo。运行成功,在对应的路径下就是我们合成的音频文件。如果你还是踩到其他坑了,那么后面就需要你自己去根据返回的错误代码去官方文档一步一步排坑了。
因为官方demo中合成的是pcm的格式,而一般我们使用的可播放格式是wav和mp3的格式。所以在使用api合成后我们还不能直接播放,所以我们需要在进行格式的转换。
转换工具类:
package com.clf.word2audio;
import ws.schild.jave.AudioAttributes;
import ws.schild.jave.Encoder;
import ws.schild.jave.EncodingAttributes;
import ws.schild.jave.MultimediaObject;
import java.io.*;
/**
* @Author: clf
* @Date: 2020-03-08
* @Description: 语音合成工具类
*/
public class ConvertUtils {
/**
* 转换音频文件
* @param src 需要转换的pcm音频路径
* @param target 保存转换后wav格式的音频路径
* @throws Exception
*/
public static void convertPcm2Wav(String src, String target) throws Exception {
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格式
* @param source 源文件
* @param target 目标文件
* @return
*/
public 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(new Integer(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;
}
}
pcm文件转换wav需要的header:
package com.clf.word2audio;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @Author: clf
* @Date: 2020-03-07
* @Description: wav转换mp3的header
*/
public class WaveHeader {
public final char fileID[] = {'R', 'I', 'F', 'F'};
public int fileLength;
public char wavTag[] = {'W', 'A', 'V', 'E'};;
public char FmtHdrID[] = {'f', 'm', 't', ' '};
public int FmtHdrLeth;
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 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 (int i=0; i
需要使用到的相关pom依赖:
ws.schild
jave-core
2.4.4
ws.schild
jave-native-osx64
2.4.6
com.googlecode.soundlibs
mp3spi
1.9.5.4
当然如果还是需要源码的话,可以从这里下载。
顺便做一波推广,对jdk源码有兴趣的朋友可以关注一下: JDK-Source,也欢迎大家PR。