在项目中需要用到将景点文字合成语音,通过语音方式向用户介绍景点信息,需要用到文字转语音的在线合成解决方案。通过对各种文字转语音合成方案与效果比较,觉得讯飞的效果最好,语音拟人效果、文章断词都非常不错,并且有一年10万次的免费使用量,因此对比后决定使用讯的在线语音合成解决方案。由于这信主题网上教程非常少,只找到了一个没提供完整源代码的参考案例,结合官网资料,搞定的完整解决方案和效果图如下:

在线语音合成集成后效果

一、注册讯飞开发者,获取访问Key

到讯飞开发者平台(https://console.xfyun.cn/app/myapp),用实名注册好讯飞开发者用户,添加好自己拟开发的应用系统,申请好应用访问讯飞平台的访问参数。由于自己的服务器性能不行,是使用在线语音合成,采用的是WebAPI方式。此外,读飞还提供了一相demo程序,新手需要下载后反复研读。

讯飞接口4个参数

二、创建在线语音转换的Util

讯飞demo提供的是一个独立运行的方法,将且用PCM格式,将转换后文件保存在服务器上,需要对此程序进行改造,我主要进行了以下几个方面的改造:

1、将访问参数放到properties文件,以免放在程序代码段被泄漏,由于这部分代码定义的是静态变量,参数注入方式有变化

2、对前端传递过来的TEXT文本,转换后使用MP3格式输出给前端,完整代码如下

//静态参数注入,必须增加@Component注解@Componentpublic class XunFeiUtil {    protected static final Logger log = LoggerFactory.getLogger(XunFeiUtil.class);    //讯飞四个注入参数,保存在配置文件,便于复用和避免代码上传gitee后泄漏    private static String hostUrl;    @Value("${xunfei.hostUrl}")    public void setHostUrl(String hostUrl) {        XunFeiUtil.hostUrl = hostUrl;    }    private static String appid;    @Value("${xunfei.appid}")    public void setAppid(String appid) {        XunFeiUtil.appid = appid;    }    private static String apiSecret;    @Value("${xunfei.apiSecret}")    public void setApiSecret(String apiSecret) {        XunFeiUtil.apiSecret = apiSecret;    }    private static String apiKey;    @Value("${xunfei.apiKey}")    public void setApiKey(String apiKey) {        XunFeiUtil.apiKey = apiKey;    }    public static final Gson json = new Gson();    private static String base64 = "";    private static volatile boolean lock = true;    /**     * 将文本转换为MP3格语音base64文件     *     * @param text 要转换的文本(如JSON串)     * @return 转换后的base64文件     * @throws IOException 异常     */    public static String convertText(String text) throws Exception {        lock = true;        base64 = "";        // 构建鉴权url        String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);        OkHttpClient client = new OkHttpClient.Builder().build();        //将url中的 schema http://和https://分别替换为ws:// 和 wss://        String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");        Request request = new Request.Builder().url(url).build();        List list = Lists.newArrayList();        WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {            @Override            public void onOpen(WebSocket webSocket, Response response) {                super.onOpen(webSocket, response);               try {                    System.out.println(response.body().string());                } catch (IOException e) {                    e.printStackTrace();                }                //发送数据                JsonObject frame = new JsonObject();                JsonObject business = new JsonObject();                JsonObject common = new JsonObject();                JsonObject data = new JsonObject();                // 填充common                common.addProperty("app_id", appid);                //填充business,AUE属性lame是MP3格式,raw是PCM格式                business.addProperty("aue", "lame");                business.addProperty("sfl", 1);                business.addProperty("tte", "UTF8");//小语种必须使用UNICODE编码                business.addProperty("vcn", "xiaoyan");//到控制台-我的应用-语音合成-添加试用或购买发音人,添加后即显示该发音人参数值,若试用未添加的发音人会报错11200                business.addProperty("pitch", 50);                business.addProperty("speed", 50);                //填充data                data.addProperty("status", 2);//固定位2                try {                    data.addProperty("text", Base64.getEncoder().encodeToString(text.getBytes("utf8")));                    //使用小语种须使用下面的代码,此处的unicode指的是 utf16小端的编码方式,即"UTF-16LE"”                    //data.addProperty("text", Base64.getEncoder().encodeToString(text.getBytes("UTF-16LE")));                } catch (UnsupportedEncodingException e) {                    e.printStackTrace();                }                //填充frame                frame.add("common", common);                frame.add("business", business);                frame.add("data", data);                webSocket.send(frame.toString());            }            @Override            public void onMessage(WebSocket webSocket, String text) {                super.onMessage(webSocket, text);                //处理返回数据                System.out.println("receive=>");                ResponseData resp = null;                try {                    resp = json.fromJson(text, ResponseData.class);                } catch (Exception e) {                    e.printStackTrace();                }                if (resp != null) {                    if (resp.getCode() != 0) {                        System.out.println("error=>" + resp.getMessage() + " sid=" + resp.getSid());                        return;                    }                    if (resp.getData() != null) {                        String result = resp.getData().audio;                        byte[] audio = Base64.getDecoder().decode(result);                        list.add(audio);                        // 说明数据全部返回完毕,可以关闭连接,释放资源                        if (resp.getData().status == 2) {                            String is = base64Concat(list);                            base64 = is;                            lock = false;                            webSocket.close(1000, "");                        }                    }                }            }            @Override            public void onMessage(WebSocket webSocket, ByteString bytes) {                super.onMessage(webSocket, bytes);            }            @Override            public void onClosing(WebSocket webSocket, int code, String reason) {                super.onClosing(webSocket, code, reason);                System.out.println("socket closing");            }            @Override            public void onClosed(WebSocket webSocket, int code, String reason) {                super.onClosed(webSocket, code, reason);                System.out.println("socket closed");            }            @Override            public void onFailure(WebSocket webSocket, Throwable t, Response response) {                super.onFailure(webSocket, t, response);                System.out.println("connection failed" + response.message());            }        });        while (lock) {        }        return base64;    }    /**     *  * base64拼接     *       */    static String base64Concat(List list) {        int length = 0;        for (byte[] b : list) {            length += b.length;        }        byte[] retByte = new byte[length];        for (byte[] b : list) {            retByte = ByteUtils.concat(retByte, b);        }        return cn.hutool.core.codec.Base64.encode(retByte);    }    /**     *  * 获取权限地址     *  *     *  * @param hostUrl     *  * @param apiKey     *  * @param apiSecret     *  * @return     *       */    private static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {        URL url = new URL(hostUrl);        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);        format.setTimeZone(TimeZone.getTimeZone("GMT"));        String date = format.format(new Date());        StringBuilder builder = new StringBuilder("host: ").append(url.getHost()).append("").                append("date: ").append(date).append("").                append("GET ").append(url.getPath()).append(" HTTP/1.1");        Charset charset = Charset.forName("UTF-8");        Mac mac = Mac.getInstance("hmacsha256");        SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), "hmacsha256");        mac.init(spec);        byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset));        String sha = Base64.getEncoder().encodeToString(hexDigits);        String authorization = String.format("hmac username="%s", algorithm="%s", headers="%s", signature="%s"", apiKey, "hmac-sha256", "host date request-line", sha);        HttpUrl httpUrl = HttpUrl.parse("https://" + url.getHost() + url.getPath()).newBuilder().                addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(charset))).                addQueryParameter("date", date).                addQueryParameter("host", url.getHost()).                build();        return httpUrl.toString();    }    public static class ResponseData {        private int code;        private String message;        private String sid;        private Data data;        public int getCode() {            return code;        }        public String getMessage() {            return this.message;        }        public String getSid() {            return sid;        }        public Data getData() {            return data;        }    }    private static class Data {        //标志音频是否返回结束  status=1,表示后续还有音频返回,status=2表示所有的音频已经返回        private int status;        //返回的音频,base64 编码        private String audio;        // 合成进度        private String ced;    }}

三、构建前后端接口控制文件

构建建前后端访问的接口,由于前后端通过VUE参数方式传递过来,使用Post方式,我另有文章介绍为什么要用Post,这是VUE的约定,用Get有问题。

@RestController@RequestMapping("/data/xunfei")public class TextToAudioController {    private static final Logger log = LoggerFactory.getLogger(TextToAudioController.class);    @ApiOperation(value = "文字转语音", notes = "文字转语音")    @PostMapping(value = "text_to_audio")    public void textToAudio(String text, HttpServletRequest request , HttpServletResponse response) throws IOException {        if (StringUtils.isNotBlank(text)) {            //过滤图片,h5标签            text = text.replaceAll("&[a-zA-Z]{1,10};", "").replaceAll("]*>", "").replaceAll("[(/>)]*>", "").replaceAll("[(/>)

四、前端访问请求修改

在request.js拦截器中增加语音处理部分,让前端能正确接收语音语件

// 响应拦截器service.interceptors.response.use(res => {  //语音处理    const headers = res.headers    if (headers['content-type'] === 'application/octet-stream;charset=UTF-8') {      return res.data    }}

五、构建前端访问接口

// 讯飞语音获取/** * 文字转语音接口 */export function textToAudio(data) {  return request({    url: '/data/xunfei/text_to_audio',    method: 'post',    data: data,    responseType: "blob"//后台返回的为语音的流数据  })}

六、前端程序

为了 便于介绍,单独做了一个独立,完整的程序,这个组件功能就是选取textarea中的内容,进行后台转换为MP3语音,并且增加了避免重复转换判断,增加暂停与继续播放按钮,效果如后面图。

效果图

语音转文字的完整方案还是比较少,经过几天努力搞定了此问题,特完整地记录一下,后续将增加转换MP3保存在OSS功能,避免重复调服务,毕竟后续服务要费用,OSS还是比较便宜。

亚索全部语音原声mp3_Spring Boot+VUE集成科大讯飞语音在线合成解决方案相关推荐

  1. Spring Boot+VUE集成科大讯飞语音在线合成解决方案

    在项目中需要用到将景点文字合成语音,通过语音方式向用户介绍景点信息,需要用到文字转语音的在线合成解决方案.通过对各种文字转语音合成方案与效果比较,觉得讯飞的效果最好,语音拟人效果.文章断词都非常不错, ...

  2. android集成科大讯飞语音听写和语音合成

    android集成科大讯飞语音听写和语音合成 集成科大讯飞语音听写和语音合成,语音听写只是语音识别下面的一部分,别弄混淆了,由于科大讯飞暂未开放gradle引包方式,所以目前集成还是手动引包.我的流程 ...

  3. 集成科大讯飞语音听写功能

    一.准备工作 1.创建应用,并获取appId: 2.下载科大讯飞语音听写功能的jar包和so包(http://www.xfyun.cn/sdk/dispatcher): 3.将jar包添加到libs中 ...

  4. AndroidStudio集成科大讯飞语音SDK

    AndroidStudio集成科大讯飞语音SDK 讯飞开放平台作为全球首个开放的智能交互技术服务平台,致力于为开发者打造一站式智能人机交互解决方案.用户可通过互联网.移动互联网,使用任何设备.在任何时 ...

  5. 保姆级教程:js前端接入科大讯飞语音评测,实现朗读打分,vue对接科大讯飞语音评测,还可以实现句子逐词分析对错以及打分,将demo接入vue项目中

    需求说明:实现一个点击录制英文句子朗读,然后调用科大讯飞接口分析朗读准确度,并逐词分析朗读正确性. 步骤一.下载运行demo 首先附上科大讯飞语音评测流式版的文档链接:链接在此 然后在文档里找到dem ...

  6. Android studio语音识别集成科大讯飞语音转文字

    老规矩:第一步,上效果图 事先说明: 语音听写SDK适配安卓6.0需要手动申请权限,各位可以自信查询资料实现,关于语音听写SDK的开发,参考科大讯飞开放平台官网为准 步骤一:百度科大讯飞开发者平台,找 ...

  7. 亚索全部语音原声mp3_常见语音模块的语音格式有哪些

    声音一直是人类精神之食,而语音模块具备播放功能与储存功能的录音芯片模组:想要把复杂而繁多的声音在不失真的情况原汁原味的播放出去,一个好的语音格式非常重要.那么常见的语音模块格式有哪些呢?九芯电子的小编 ...

  8. Vue,js前端实现语音实时转换文字,前端实现浏览器语音实时转换为文字,vue阿里云语音转文字

    Vue,js前端实现浏览器语音实时转换文字功能详解 1.首先总结一下,前端使用实时语音需要使用到HZRecorder.js这个JS文件来实现获取浏览器麦克风话筒权限 大注意:HZRecorder.js ...

  9. 开源~~~~spring boot +vue 前后端分离 在线考试系统 加自动组卷!!!!

    在线考试系统+自动组卷!!! springboot +vue 前后端分离系统 想要源码的可以B站搜索 技术小余哥

最新文章

  1. KernelIoControl和OEMIoControl的分析和使用(作者:wogoyixikexie@gliet)
  2. leetcode:242 : 有效的字母异位词
  3. 【算法】ROI Align 原理
  4. OpenGL shader interpolation 着色器插值的实例
  5. fasthttp 快在哪里
  6. oracle成本模块培训,Oracle App 培训笔记(5) -- 成本管理模块表结构整理 续
  7. python执行shellcode_python exec shellcode
  8. php读写文件要加锁
  9. 《Producter:让产品从0到1》一导读
  10. 20k超声波电路原理图讲解_超声波液位开关和液位开关的区别,它们的工作原理分别是什么?...
  11. 《集成电路先进光刻技术与版图设计优化》课程分享之一:典型显微系统的光学成像原理
  12. matlab编写LDA,lda算法matlab实现
  13. 如何下载谷歌地球高程为TIF格式的文件
  14. smobiler自适应不同手机分辨率
  15. android checkboxpreference属性,如何更改android中CheckBoxPreference标题的文本颜色?
  16. 推荐Python、Django中文文档地址
  17. Multisim简体中文汉化包下载安装指南
  18. jsonp跨域请求原理
  19. 解决COVID-19的7个开放硬件项目
  20. css画直角三角形,关于CSS画三角形的那些事

热门文章

  1. Camera成像原理(二十四)
  2. Mac可读可写remount硬盘
  3. 世上最好的共享内存(Linux共享内存最透彻的一篇)上集
  4. Android开发重要参考资料
  5. Erlang初学:Erlang的一些特点和个人理解总结
  6. IOS硬件解码VTDecompressionSession失效
  7. python基础系统性学习
  8. vue-router 报错:Navigation cancelled from“/…“ to “/…“ with a new navigation.
  9. java二维码生成代码_java快速开发平台功能特点之代码生成器
  10. redis哨兵模式原理_Redis哨兵原理,我忍你很久了