jfinal-4.2 通过jocab 调用windows的sapi进行语音合成
[产生背景]
1.因为其他厂商语音合成的内网部署都太贵了
2.众所周知,widows系统本身就有语音合成的功能
3.能不能将windows的语音合成对提供成HTTP API服务.让其他程序调用,这样在内网0费用就可以使用语音合成了
搭建工程
添加依赖
<dependency> <groupId>com.jfinal</groupId> <artifactId>jfinal-undertow</artifactId> <version>1.6</version> </dependency> <dependency> <groupId>com.jfinal</groupId> <artifactId>jfinal</artifactId> <version>4.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--桥接器:告诉slf4j使用Log4j2 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.hynnet</groupId> <artifactId>jacob</artifactId> <version>1.18</version> </dependency>
创建在工程目录下添加lib目录将jacob-1.18-x64.dll添加lib目录
LibraryUtil 将当前工程下的lib目录添加到library path
package com.litong.utils.dll;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
public class LibraryUtil {
public static void addLibary() {
File file = new File("lib");
String absolutePath = file.getAbsolutePath();
System.out.println("absolutePath:" + absolutePath);
if (!file.exists()) {
file.mkdirs();
}
try {
addLibDir(absolutePath);
} catch (IOException e1) {
e1.printStackTrace();
}
}
public static void addLibDir(String s) throws IOException {
try {
Field field = java.lang.ClassLoader.class.getDeclaredField("usr_paths");
field.setAccessible(true);
String[] paths = (String[]) field.get(null);
for (int i = 0; i < paths.length; i++) {
if (s.equals(paths[i])) {
return;
}
}
String[] tmp = new String[paths.length + 1];
System.arraycopy(paths, 0, tmp, 0, paths.length);
tmp[paths.length] = s;
field.set(null, tmp);
} catch (IllegalAccessException e) {
throw new IOException("Failed to get permissions to set library path");
} catch (NoSuchFieldException e) {
throw new IOException("Failed to get field handle to set library path");
}
}
}启动类Application的main方法中调用LibraryUtil.addLibary(); 加载
package com.litong.jfinal;
import com.jfinal.config.Constants;
import com.jfinal.config.Handlers;
import com.jfinal.config.Interceptors;
import com.jfinal.config.JFinalConfig;
import com.jfinal.config.Plugins;
import com.jfinal.config.Routes;
import com.jfinal.server.undertow.UndertowConfig;
import com.jfinal.server.undertow.UndertowServer;
import com.jfinal.template.Engine;
import com.litong.jfinal.route.AdminRoutes;
import com.litong.jfinal.route.ApiRoutes;
import com.litong.jfinal.route.FrontRoutes;
import com.litong.jfinal.route.SystemRoutes;
import com.litong.jfinal.utils.PropKitUtil;
import com.litong.jfinal.utils.UndertowUtil;
import com.litong.utils.dll.LibraryUtil;
import com.litong.utils.ip.IPUtil;
public class Application extends JFinalConfig {
private static String configFileName = PropKitUtil.configFileName;
public static void main(String[] args) {
long start = System.currentTimeMillis();
LibraryUtil.addLibary();
UndertowUtil.server = UndertowServer.create(Application.class, configFileName);
// 启动Server全局共享
UndertowUtil.server.addSystemClassPrefix("com.litong.jfinal.utils.UndertowUtil");
UndertowUtil.server.start();
UndertowConfig undertowConfig = UndertowUtil.server.getUndertowConfig();
int port = undertowConfig.getPort();
String contextPath = undertowConfig.getContextPath();
long end = System.currentTimeMillis();
System.out.println(IPUtil.getThisUrl(port, contextPath));
System.out.println("启动完成,共使用了" + (end - start) + "ms");
}
public void configConstant(Constants me) {
me.setInjectDependency(true);
me.setInjectSuperClass(true);
}
public void configRoute(Routes me) {
me.setMappingSuperClass(true);
me.add(new FrontRoutes()); // 前端路由
me.add(new AdminRoutes()); // 后端路由
me.add(new SystemRoutes()); // 系统路由
me.add(new ApiRoutes()); // API路由
}
@Override
public void configEngine(Engine me) {
}
@Override
public void configPlugin(Plugins me) {
}
@Override
public void configInterceptor(Interceptors me) {
}
@Override
public void configHandler(Handlers me) {
}
}配置路由
ApiRoutes
package com.litong.jfinal.route; import com.jfinal.config.Routes; import com.litong.modules.map.controller.MapServiceController; import com.litong.modules.tts.controller.TTSController; /** * @author bill robot * @date 2020年8月16日_下午5:08:54 * @version 1.0 * @desc */ public class ApiRoutes extends Routes { @Override public void config() { add("tts", TTSController.class); } }
TTSController
package com.litong.modules.tts.controller; import java.io.File; import com.jfinal.core.Controller; import com.jfinal.core.paragetter.Para; import com.litong.modules.tts.service.MsTtsUtil; import com.litong.modules.tts.vo.TTSVo; import lombok.extern.slf4j.Slf4j; /** * @author bill robot * @date 2020年8月16日_下午11:43:15 * @version 1.0 * @desc */ @Slf4j public class TTSController extends Controller { public void test(@Para("") TTSVo ttsVo) { renderJson(ttsVo); } public void index(@Para("") TTSVo ttsVo) { // 设置翻发音人需要修改注册表,我放弃修改了; String filePath = MsTtsUtil.stringToFile(ttsVo); if (filePath == null) { renderText("tts fail"); log.error("tts fail"); return; } File file = new File(filePath); if (!file.exists()) { String msg = "file is not exists:" + file.getAbsolutePath(); renderText(msg); log.error(msg); } else { renderFile(file); } return; } }
MsTtsUtil语音合成工具类
package com.litong.modules.tts.service;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
import com.jfinal.kit.HashKit;
import com.litong.modules.tts.vo.TTSVo;
import com.litong.utils.dll.LibraryUtil;
import lombok.extern.slf4j.Slf4j;
/**
* MS 语音合成工具类
*/
@Slf4j
public class MsTtsUtil {
public static void main(String[] args) {
LibraryUtil.addLibary();
TTSVo ttsVo = new TTSVo();
ttsVo.setType(6);
ttsVo.setVolume(100);
ttsVo.setRate(0);
ttsVo.setText("今天猪肉又涨价了,一下子涨了两块钱");
stringToFile(ttsVo);
ttsVo.setText("今天羊肉羊肉也涨价了");
stringToFile(ttsVo);
// String path = "text\\过秦论.txt";
// textToFile(path);
}
/**
* 语音转文字音频
*
* @param txt
*/
public static String stringToFile(TTSVo ttsVo) {
ActiveXComponent ax = new ActiveXComponent("Sapi.SpVoice");
String filePath = getFilePath(ax, ttsVo);
try {
// 运行时输出语音内容
Dispatch spVoice = ax.getObject();
// 下面是构建文件流到成语音文件
ax = new ActiveXComponent("Sapi.SpFileStream");
Dispatch spFileStream = ax.getObject();
ax = new ActiveXComponent("Sapi.SpAudioFormat");
Dispatch spAudioFormat = ax.getObject();
// 设置音频流格式
Dispatch.put(spAudioFormat, "Type", ttsVo.getType());
// 设置文件输出流格式
Dispatch.putRef(spFileStream, "Format", spAudioFormat);
// 调用输出 文件流打开方法,创建一个.wav文件
Dispatch.call(spFileStream, "Open", new Variant(filePath), new Variant(3), new Variant(true));
// 设置声音对象的音频输出流为输出文件对象
Dispatch.putRef(spVoice, "AudioOutputStream", spFileStream);
// 设置音量 0到100
Dispatch.put(spVoice, "Volume", new Variant(ttsVo.getVolume()));
// 设置朗读速度
Dispatch.put(spVoice, "Rate", new Variant(ttsVo.getRate()));
// 开始朗读,实际上是合成得到文件
Dispatch.call(spVoice, "Speak", new Variant(ttsVo.getText()));
// 关闭输出文件
Dispatch.call(spFileStream, "Close");
Dispatch.putRef(spVoice, "AudioOutputStream", null);
spAudioFormat.safeRelease();
spFileStream.safeRelease();
spVoice.safeRelease();
} catch (Exception e) {
e.printStackTrace();
} finally {
ax.safeRelease();
}
return filePath;
}
public static String getFilePath(ActiveXComponent ax, TTSVo ttsVo) {
File file = new File("tts");
if (!file.exists()) {
file.mkdirs();
log.info("mkdir:{}", file.getAbsoluteFile());
}
// 文件名规则
// voice_type_volume_rate_md5
String voice = getCurrentVoice(ax);
String filename = append("_", ttsVo.getType(), ttsVo.getVolume(), ttsVo.getRate());
// 添加文件名称
filename += "_" + HashKit.md5(ttsVo.getText());
// 添加文件后缀
if (ttsVo.getType() == 6) {
filename += ".wav";
}
// 发音人和加目录
filename = "tts/" + voice + "_" + filename;
return filename;
}
private static String append(String string, int... ints) {
String retval = new String();
for (int i : ints) {
retval += "_" + i;
}
return retval;
}
private static String getCurrentVoice(ActiveXComponent ax) {
// 获取Sapi.SpVoice对象的voice属性值,voice的属性值是一个voice对象,对象使用Dispatch包装
Dispatch voiceDispatche = ax.getProperty("voice").toDispatch();
// 执行voice对象的GetDescription方法,返回值可能使用String,使用Variant包装
Variant descripton = Dispatch.call(voiceDispatche, "GetDescription");
return descripton.toString();
}
public static String textToFile(String path,TTSVo ttsVo) {
ActiveXComponent ax = new ActiveXComponent("Sapi.SpVoice");
String filePath = getFilePath(ax, ttsVo);
try {
// 运行时输出语音内容
Dispatch spVoice = ax.getObject();
// 下面是构建文件流到成语音文件
ax = new ActiveXComponent("Sapi.SpFileStream");
Dispatch spFileStream = ax.getObject();
ax = new ActiveXComponent("Sapi.SpAudioFormat");
Dispatch spAudioFormat = ax.getObject();
// 设置音频流格式
Dispatch.put(spAudioFormat, "Type", new Variant(ttsVo.getType()));
// 设置文件输出流格式
Dispatch.putRef(spFileStream, "Format", spAudioFormat);
// 调用输出 文件流打开方法,创建一个.wav文件
Dispatch.call(spFileStream, "Open", new Variant(filePath), new Variant(3), new Variant(true));
// 设置声音对象的音频输出流为输出文件对象
Dispatch.putRef(spVoice, "AudioOutputStream", spFileStream);
// 设置音量 0到100
Dispatch.put(spVoice, "Volume", new Variant(ttsVo.getVolume()));
// 设置朗读速度
Dispatch.put(spVoice, "Rate", new Variant(ttsVo.getRate()));
// 输入文件
File srcFile = new File(path);
// 使用包装字符流读取文件
BufferedReader br = new BufferedReader(new FileReader(srcFile));
String content = br.readLine();
// 开始朗读,实际上是合成得到文件
while (content != null) {
Dispatch.call(spVoice, "Speak", new Variant(content));
content = br.readLine();
}
br.close();
// 关闭输出文件
Dispatch.call(spFileStream, "Close");
Dispatch.putRef(spVoice, "AudioOutputStream", null);
spAudioFormat.safeRelease();
spFileStream.safeRelease();
spVoice.safeRelease();
} catch (Exception e) {
e.printStackTrace();
} finally {
ax.safeRelease();
}
return filePath;
}
}添加完成后启动访问浏览器测试
http://192.168.0.10:11023/litongjava-windows-tts/tts?type=6&volume=100&rate=0&text=秦孝公据崤函之固,拥雍州之地,君臣固守以窥周室,有席卷天下,包举宇内,囊括四海之意,并吞八荒之心。当是时也,商君佐之,内立法度,务耕织,修守战之具,外连衡而斗诸侯。于是秦人拱手而取西河之外。 孝公既没,惠文、武、昭襄蒙故业,因遗策,南取汉中,西举巴、蜀,东割膏腴之地,北收要害之郡。诸侯恐惧,会盟而谋弱秦,不爱珍器重宝肥饶之地,以致天下之士,合从缔交,相与为一。当此之时,齐有孟尝,赵有平原,楚有春申,魏有信陵。此四君者,皆明智而忠信,宽厚而爱人,尊贤而重士,约从离衡,兼韩、魏、燕、楚、齐、赵、宋、卫、中山之众。于是六国之士,有宁越、徐尚、苏秦、杜赫之属为之谋,齐明、周最、陈轸、召滑、楼缓、翟景、苏厉、乐毅之徒通其意,吴 把jacob-1.18-x64.dll下载 https://files.cnblogs.com/files/w1441639547/jacob-1.18-x64.rar