第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > Vue+SpringBoot+Audio+科大讯飞 语音合成技术

Vue+SpringBoot+Audio+科大讯飞 语音合成技术

时间:2024-01-09 08:49:08

相关推荐

Vue+SpringBoot+Audio+科大讯飞 语音合成技术

最终思路

思路就是vue前端向后台发送需要播放的语音信息(文字),然后后台返回语音流数据,通过URL.createObjectURL(data)这个API生成一个URL,然后给audio标签附上url,网页进行语音播放,在网页播放语音就可以避免用户的本地语音库的安装。

在Vue项目中用Audio实现语音的播放(基础版)

1.axios 拦截处理// respone拦截器service.interceptors.response.use(response => {const headers = response.headersif (headers['content-type'] === 'application/octet-stream;charset=UTF-8') {return response.data}})2.接口请求/*** 文字转语音接口*/export function textToAudio(text) {let jsonData = {text: text,}return request({url: '/api/audio/text_to_audio',method: 'post',data: Qs.stringify(jsonData),responseType: "blob"//后台返回的为语音的流数据})}3.请求后台接口//调用后台getAudio(text) {textToAudio(text).then(response => {let url = URL.createObjectURL(response);//通过这个API让语音数据转为成一个url地址let audio = new Audio();//在VUE中使用audio标签audio.src = url;//设置audio的src为上面生成的urllet playPromiser = audio.play();//进行播放//在谷歌内核中,audio.play()会返回一个promise的值,在IE内核中就不会返回任何的值//所以如果你要分浏览器,可以判断playPromiser的值来进行操作哦audio.onended = () => {//onended可以检测语音是否播完//dosometin};}).catch(err => {});},

4.springboot

@ApiOperation(value = "文字转语音", notes = "文字转语音")@RequestMapping(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("[(/>)<]", "").trim();//调用微服务接口获取音频base64String result = "";try {JSONObject json = new JSONObject();JSONObject params = new JSONObject();params.put("content", text);json.put("params", params);String resultStr = HttpClientUtil.postJson(TEXT_TO_ADUIO, json.toString());JSONObject resultJson = JSON.parseObject(resultStr);System.out.println(resultJson.toJSONString());boolean success = resultJson.getInteger("result") == 0;if (!success) {throw new ExternalCallException(resultJson.getString("message"));}result = resultJson.getJSONArray("datas").getJSONObject(0).getString("audioBase64");} catch (Exception e) {log.error("【文字转语音接口调用异常】", e);//throw new ExternalCallException(e.getMessage());}//音频数据byte[] audioByte = Base64.getDecoder().decode(result);response.setContentType("application/octet-stream;charset=UTF-8");OutputStream os = new BufferedOutputStream(response.getOutputStream());SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");String date = sdf.format(new Date());try {//音频流os.write(audioByte);} catch (IOException e) {e.printStackTrace();} finally {if (os != null) {os.flush();os.close();}}}}

防止因为快速的请求语音数据造成语音播放叠在一起

data() {return {audio:true,callmsg:[],}}

//排队队列 data 是文本信息queue(data){this.callmsg.push(data);//this.callmsg就是排队队列,点击一次,放进去一个需要播放的信息if (this.audio) {//如果没人this.audio = false;//改为有人排队了this.getAudio();//进行播放操作}},//语音播放getAudio() {if (this.callmsg.length > 0) {//如果队列是有人在排队的,这进行播放操作textToAudio(this.callmsg[0]).then(response => {let url = URL.createObjectURL(response);//通过这个API让语音数据转为成一个url地址let audio = new Audio();//在VUE中使用audio标签audio.src = url;//设置audio的src为上面生成的urllet playPromiser = audio.play();//进行播放//在这里我用一个标志,设置语音开始播放/* localStorage.setItem("audio", "1");*///在谷歌内核中,audio.play()会返回一个promise的值,在IE内核中就不会返回任何的值//所以如果你要分浏览器,可以判断playPromiser的值来进行操作哦audio.onended = () => {//onended可以检测语音是否播完//dosometingthis.callmsg.splice(0, 1);//队列的第一个播放完毕,所以删除/* localStorage.setItem("audio", "0");//这里是语音播放完毕*/this.getAudio();//进行下一个请求并播放};}).catch(err => {});} else {//this.audio是一个data数据,用来记录是否有人排队this.audio = true; //如果队列没人排队,就告诉外面已经读完啦}},

最终实现前端功能代码

<!--语音播放--><template><div class="audio"><div><svg-icon v-if="audioPlayVisible" icon-class="play" @click.native="pause":class="{'audio-play-style':true, 'audio-play-style-pc': pc}"/><svg-icon v-if="!audioPlayVisible" icon-class="stop_play" @click.native="play":class="{'audio-play-style':true, 'audio-play-style-pc': pc}"/></div></div></template><script>import {textToAudio} from '@/api/file'import {isPc} from '@/utils/common'export default {name: "audioPlay",props: {},components: {},mounted() {this.audioObj = new Audio();//在VUE中使用audio标签},created() {},data() {return {//语音播放开关audioPlayVisible: true,mAudioVisible: true,// 是否是PC端pc: isPc(),audioObj:null}},methods: {//暂停pause() {this.audioObj.pause();this.audioPlayVisible=false;},//播放play(){this.audioPlayVisible=true;},//调用后台getAudio(text) {if(!this.audioPlayVisible){return}textToAudio(text).then(response => {console.log('response', response)let url = URL.createObjectURL(response);//通过这个API让语音数据转为成一个url地址this.audioObj.src = url;//设置audio的src为上面生成的urllet playPromiser = this.audioObj.play();//进行播放//在谷歌内核中,audio.play()会返回一个promise的值,在IE内核中就不会返回任何的值//所以如果你要分浏览器,可以判断playPromiser的值来进行操作哦this.audioObj.onended = () => {};}).catch(err => {});},}}</script><style lang="less">.audio {.audio-play-style {position: absolute;top: 10px;right: 0;font-size: 26px;}.audio-play-style-pc {top: 65px;}}</style>

// 音频方式二 ----- 初始化audioInit() {let AudioContext = window.AudioContext || window.webkitAudioContextif (AudioContext) {this.audioContext = new AudioContext()this.audioContext.resume()}},/*** AudioContext 播放方式** @param response 后台返回音频流*/playAudioMethodTwo(response) {var _this=this;//将Blob音频流转换成 ArrayBuffervar reader = new FileReader();reader.readAsArrayBuffer(response);reader.onload = function (e) {let arrayBuffer=reader.result;_this.audioContext.decodeAudioData(arrayBuffer).then(function (buffer) {var source = _this.audioContext.createBufferSource();source.buffer = buffer;source.connect(_this.audioContext.destination);source.start();}, function (e) {console.log("FAIL:" + arrayBuffer);});}},

科大讯飞java 流demo 接口

注意事项:记得导入相关依赖包,hutool 直接maven库搜索

package com.ylz.springboot.modules.external.service.impl;

import mon.collect.Lists;

import com.google.gson.Gson;

import com.google.gson.JsonObject;

import okhttp3.*;

import okio.ByteString;

import org.springframework.data.redis.util.ByteUtils;

import javax.crypto.Mac;

import javax.crypto.spec.SecretKeySpec;

import java.io.IOException;

import java.io.UnsupportedEncodingException;

import .URL;

import java.nio.charset.Charset;

import java.text.SimpleDateFormat;

import java.util.*;

/**

* 科大讯飞语音合成

*

* @author lhh

* @Date /5/7 11:06

*/

public class WebTTSWS {

private static final String hostUrl = "https://tts-/v2/tts"; //http url 不支持解析 ws/wss schema

private static final String appid = "xxxx";//到控制台-语音合成页面获取

private static final String apiSecret = "xxxxxx";//到控制台-语音合成页面获取

private static final String apiKey = "xxxx";//到控制台-语音合成页面获取

private static final String text = "蜡烛有心,杨柳有心,于是它能低首沉思";

public static String base64 = "";

public static final Gson json = new Gson();

private volatile boolean lock = true;

public static void main(String[] args) throws Exception {

for (int i = 0; i < 1; i++) {

new Thread(() -> {

WebTTSWS w = new WebTTSWS();

try {

String send = w.send();

System.out.println(send);

} catch (Exception e) {

e.printStackTrace();

}

}).start();

}

}

public String send() 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<byte[]> 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

business.addProperty("aue", "lame");

business.addProperty("sfl", 1);

business.addProperty("tte", "UTF8");//小语种必须使用UNICODE编码

business.addProperty("vcn", "aisxping");//到控制台-我的应用-语音合成-添加试用或购买发音人,添加后即显示该发音人参数值,若试用未添加的发音人会报错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=>" + text);

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);

// todo resp.data.status ==2 说明数据全部返回完毕,可以关闭连接,释放资源

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拼接

*/

String base64Concat(List<byte[]> 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

*/

public 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("\n").

append("date: ").append(date).append("\n").

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;

}

}

public static class Data {

//标志音频是否返回结束 status=1,表示后续还有音频返回,status=2表示所有的音频已经返回

private int status;

//返回的音频,base64 编码

private String audio;

// 合成进度

private String ced;

}

}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。