博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
微信小程序:将中文语音直接转化成英文语音
阅读量:7119 次
发布时间:2019-06-28

本文共 20484 字,大约阅读时间需要 68 分钟。

作者:    文章来源《》

准备工作:

准备工具:Eclipse、FileZilla、微信开发者工具、一个配置好SSL证书(https)的有域名的服务器

所需知识:SpringMVC框架、Java+HTML+CSS+JS、文件上传技术、Tomcat虚拟目录、接口调用与发布

成品介绍:将中文语音直接转化成英文语音。好久不用现在已下线。。。可以在微信搜我另外一个作品“壹曲觅知音”玩玩,博客地址:

最近新作:python scrapy爬取豆瓣即将上映电影用邮件定时推送给自己

一、服务端

基本思路

1、将汉语语音转化为汉语文字

2、将汉语文字转化为英语文字

3、将英语文字转化为英语语音

步骤

1、注册百度语音账户,创建应用,获取API Key 和Secret Key,参照

2、看百度提供的文档编写识别语音的工具类

import java.io.BufferedReader;import java.io.DataOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.HttpURLConnection;import java.net.URL;import javax.xml.bind.DatatypeConverter;import priv.xxf.jgsawvoice.entity.JSONArray;import priv.xxf.jgsawvoice.entity.JSONObject;@SuppressWarnings("restriction")public class VoiceRecogUtil {	private static final String serverURL = "http://vop.baidu.com/server_api";// API地址	// 开发密钥	private static final String apiKey = "你的apiKey";	private static final String secretKey = "你的secretKey";	private static final String cuid = "随便定义个字符串";	private static String token = "";// 根据密钥获取的token	public static String getTextByVoice(String fileName) throws Exception {		getToken();// 获取token		// 发送请求得到结果		String string = getResultString(fileName);		// 解析json		JSONArray jsonArray = new JSONObject(string).getJSONArray("result");		int begin = jsonArray.toString().indexOf("\"");		int end = jsonArray.toString().lastIndexOf("\"");		String result = jsonArray.toString().substring(begin + 1, end);		return result;	}	private static void getToken() throws Exception {		String getTokenURL = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials" + "&client_id="				+ apiKey + "&client_secret=" + secretKey;		HttpURLConnection conn = (HttpURLConnection) new URL(getTokenURL).openConnection();		token = new JSONObject(getResponse(conn)).getString("access_token");	}	@SuppressWarnings("restriction")	private static String getResultString(String fileName) throws Exception {		File pcmFile = new File(fileName);		int index = fileName.lastIndexOf(".");		String suffix = fileName.substring(index + 1);		HttpURLConnection conn = (HttpURLConnection) new URL(serverURL).openConnection();		// construct params		JSONObject params = new JSONObject();		params.put("format", suffix);// 音频后缀		params.put("rate", 16000);// 比特率		params.put("channel", "1");// 固定值		params.put("token", token);// token		params.put("cuid", cuid);// 用户请求的唯一标识		params.put("len", pcmFile.length());// 文件长度		params.put("speech", DatatypeConverter.printBase64Binary(loadFile(pcmFile)));// base64编码后的音频文件		// add request header		conn.setRequestMethod("POST");		conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");		conn.setDoInput(true);		conn.setDoOutput(true);		// send request		DataOutputStream wr = new DataOutputStream(conn.getOutputStream());		wr.writeBytes(params.toString());		wr.flush();		wr.close();		return getResponse(conn);	}	private static String getResponse(HttpURLConnection conn) throws Exception {		if (conn.getResponseCode() != 200) {			// request error			return "";		}		InputStream is = conn.getInputStream();		BufferedReader rd = new BufferedReader(new InputStreamReader(is));		String line;		StringBuffer response = new StringBuffer();		while ((line = rd.readLine()) != null) {			response.append(line);			response.append('\r');		}		rd.close();		return response.toString();	}	private static byte[] loadFile(File file) throws IOException {		InputStream is = new FileInputStream(file);		long length = file.length();		byte[] bytes = new byte[(int) length];		int offset = 0;		int numRead = 0;		while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {			offset += numRead;		}		if (offset < bytes.length) {			is.close();			throw new IOException("Could not completely read file " + file.getName());		}		is.close();		return bytes;	}}

3、创建百度翻译账户,获取securityKey,编写文字翻译工具类

import java.io.UnsupportedEncodingException;import java.util.HashMap;import java.util.Map;public class LanguageTranslateUtil {	private static final String TRANS_API_HOST = "http://api.fanyi.baidu.com/api/trans/vip/translate";// API地址	private static final String appid = "你的appid";// 应用的id	private static final String securityKey = "百度翻译的securityKey"//securityKey	public static String getTransResultFromChToEn(String query) throws UnsupportedEncodingException {		return getTransResult(query, "auto", "en");	}	public static String getTransResult(String query, String from, String to) throws UnsupportedEncodingException {		Map
params = buildParams(query, from, to); String string = HttpGet.get(TRANS_API_HOST, params); // 解析json串得到结果 int start = string.indexOf("\"dst\":"); int end = string.lastIndexOf("\""); String result = string.substring(start + 7, end); return result; } private static Map
buildParams(String query, String from, String to) throws UnsupportedEncodingException { Map
params = new HashMap
(); params.put("q", query); params.put("from", from); params.put("to", to); params.put("appid", appid); // 随机数 String salt = String.valueOf(System.currentTimeMillis()); params.put("salt", salt); // 签名 String src = appid + query + salt + securityKey; // 加密前的原文 params.put("sign", MD5.md5(src)); return params; } // public static void main(String[] args) throws // UnsupportedEncodingException {
// String en = getTransResultFromChToEn("你好"); // System.out.println(en); // }}

4、下载百度语音合成的SDK,放到WEB-INF下的lib目录,添加至构建路径,编写语音合成工具类,直接调用SDK的API

import java.io.File;import java.io.IOException;import java.util.HashMap;import org.json.JSONObject;import com.baidu.aip.speech.AipSpeech;import com.baidu.aip.speech.TtsResponse;import com.baidu.aip.util.Util;public class TextRecogUtil {	// 设置APPID/AK/SK	public static final String APP_ID = "你的appID";	public static final String API_KEY = "和语音识别一样的api_key";	public static final String SECRET_KEY = "和语音识别一样的secret_key";	public static final String FILE_ROOT = "你的服务器上放语音文件的根目录";	public static String getVoiceByText(String text, String file) {		// 初始化一个AipSpeech		AipSpeech client = new AipSpeech(APP_ID, API_KEY, SECRET_KEY);		// 可选:设置网络连接参数		client.setConnectionTimeoutInMillis(2000);		client.setSocketTimeoutInMillis(60000);		// 可选:设置代理服务器地址, http和socket二选一,或者均不设置		// client.setHttpProxy("proxy_host", proxy_port); // 设置http代理		// client.setSocketProxy("proxy_host", proxy_port); // 设置socket代理		// 调用接口		HashMap
options = new HashMap
(); options.put("spd", "5");// 语速 options.put("pit", "0");// 音调 options.put("vol", "15");// 音量 options.put("per", "3");// 音色 TtsResponse res = client.synthesis(text, "zh", 1, options); byte[] data = res.getData(); JSONObject res1 = res.getResult(); if (data != null) { try { // 若没有文件夹创建文件夹 int index = file.lastIndexOf("/"); String substring = file.substring(0, index); File temp1 = new File(substring); if (!temp1.exists()) { temp1.mkdirs(); } File temp2 = new File(file); temp2.createNewFile(); Util.writeBytesToFileSystem(data, file); } catch (IOException e) { e.printStackTrace(); } } if (res1 != null) { System.out.println(res1.toString(2)); } int path = file.indexOf("项目名"); String suffix = file.substring(path); return FILE_ROOT + "/" + suffix; }}
5、编写上传音频文件的工具类
import java.io.File;import java.io.FileOutputStream;import java.io.InputStream;import java.io.OutputStream;import org.springframework.web.multipart.MultipartFile;public class WeChatFileUploadUtil {	private static final String TEMP_DIR = "/root/file/temp";//你服务器的临时文件路径         //返回文件的唯一标识	public static String getUploadFilePath(MultipartFile file) {		String result;		try {			InputStream in = file.getInputStream();			// 真正写到磁盘上			String uuid = IDUtil.generateString(10);			String filePath = TEMP_DIR + "/" + uuid + ".mp3";			File temp = new File(filePath);			OutputStream out = new FileOutputStream(temp);			int length = 0;			byte[] buf = new byte[100];			while ((length = in.read(buf)) != -1) {				out.write(buf, 0, length);			}			in.close();			out.close();			result = filePath;		} catch (Exception e) {			result = TEMP_DIR + "/error.mp3";//全局异常结果		}		return result;	}	public static void deleteTempFile(String file) {		File temp = new File(file);		if (temp.exists()) {			temp.delete();		}	}}
6、由于小程序录音的采样率与百度语音识别的采用率不同,还需要在服务器上安装ffmpeg转码。安装教程看 。然后编写转码工具类
import java.io.File;import java.io.IOException;import java.util.ArrayList;import java.util.Date;import java.util.List;public class FileFormatTransformUtil {	// private static final String FFMPEG_LOCATION =	// "D:\\Eclipse\\eclipse\\WorkSpace\\src\\main\\resources\\ffmpeg.exe";//Windows下的ffmpeg路径	public static String transformSound(String mp3, String path) {		File file = new File(path);		if (!file.exists()) {			file.mkdirs();		}		String uuid = IDUtil.generateString(10);		String result = path + "/" + uuid;		List
commend = new ArrayList
(); commend.add("ffmpeg");// 如果在Windows下换成FFMPEG_LOCATION commend.add("-y"); commend.add("-i"); commend.add(mp3); commend.add("-acodec"); commend.add("pcm_s16le"); commend.add("-f"); commend.add("s16le"); commend.add("-ac"); commend.add("1"); commend.add("-ar"); commend.add("16000"); commend.add(result + ".pcm"); StringBuffer test = new StringBuffer(); for (int i = 0; i < commend.size(); i++) test.append(commend.get(i) + " "); System.out.println("转化" + result + ".pcm成功" + new Date()); ProcessBuilder builder = new ProcessBuilder(); builder.command(commend); try { builder.start(); } catch (IOException e) { e.printStackTrace(); } return uuid; } // public static void main(String[] args) {
// FileFormatTransformUtil.transformSound("D:\\temp\\1.mp3", // "D:\\temp\\2.pcm"); // }}

7、自定义消息实体类,用于返回请求的json串

public class Message {	private String code;// 200正常,404异常	private String msg;// 提示消息	private String content;// 正常返回声音的url,异常返回全局异常声音的url	public String getCode() {		return code;	}	public void setCode(String code) {		this.code = code;	}	public String getMsg() {		return msg;	}	public void setMsg(String msg) {		this.msg = msg;	}	public String getContent() {		return content;	}	public void setContent(String content) {		this.content = content;	}}

8、编写Controller,发布接口以便小程序调用

import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.multipart.MultipartFile;import net.sf.json.JSONObject;import priv.xxf.jgsawvoice.entity.Message;import priv.xxf.jgsawvoice.utils.FileFormatTransformUtil;import priv.xxf.jgsawvoice.utils.LanguageTranslateUtil;import priv.xxf.jgsawvoice.utils.TextRecogUtil;import priv.xxf.jgsawvoice.utils.VoiceRecogUtil;import priv.xxf.jgsawvoice.utils.WeChatFileUploadUtil;@Controllerpublic class VoiceTansformContoller {	private static final String DEFAULT_USER_ID = "defaultdir";// 如果用户不授权就使用默认文件夹授权就根据用户名创建一个文件夹	private static final String SRC_PATH = "/root/file/source";// 待转化音频	private static final String DST_PATH = "/root/file/destination";// 转化后的音频	private static final String ERROR_RESULT = "https:你的网站域名/资源文件夹/error.mp3";// 全局异常结果	@RequestMapping("/getVoice")	@ResponseBody	public JSONObject index(@RequestParam(value = "file") MultipartFile file, String userId) throws Exception {		// userId标识唯一用户,如果不授权存放至默认文件夹		if (userId == null) {			userId = DEFAULT_USER_ID;		}		Message message = new Message();		try {			// 中文的id转成英文			try {				userId = Long.parseLong(userId) + "";			} catch (Exception e) {				userId = LanguageTranslateUtil.getTransResultFromChToEn(userId).replaceAll(" ", "");			}			// 将用户的音频存放在服务器的临时文件夹,并获取唯一文件名			String sourceFile = WeChatFileUploadUtil.getUploadFilePath(file);			// 转化成能被百度语音识别的文件			String uuid = FileFormatTransformUtil.transformSound(sourceFile, SRC_PATH + "/" + userId);			// 删除临时文件			WeChatFileUploadUtil.deleteTempFile(sourceFile);			// 将音频识别成文字			String text = VoiceRecogUtil.getTextByVoice(SRC_PATH + "/" + userId + "/" + uuid + ".pcm");			System.out.println(text);			// 翻译			String englishText = LanguageTranslateUtil.getTransResultFromChToEn(text);			System.out.println(englishText);			// 将文字转化成语音存到对应文件夹			String resultFile = TextRecogUtil.getVoiceByText(englishText,					DST_PATH + "/" + userId + "/" + uuid + ".mp3");			message.setCode("200");			message.setMsg("success");			message.setContent(resultFile);		} catch (Exception e) {			// 异常处理			message.setCode("404");			message.setMsg("fail");			message.setContent(ERROR_RESULT);		}		JSONObject result = JSONObject.fromObject(message);		return result;	}}

9、将项目maven install一下,打包成war文件,丢到tomcat的webapp文件夹下,tomcat会自动解包,访问接口是否能调用成功

二、小程序

基本思路

1、调用小程序API录音

2、调用服务端接口转化音频

3、获取接口数据让小程序使用

步骤

1、到微信公众平台注册小程序开发账户并下载微信开发者工具,打开工具,输入项目目录、AppID、项目名,其中项目目录为将要创建的小程序的代码的根目录或是已有的项目的根目录

2、查看小程序API,先将界面绘画出来,编写wxml(相当于html),这里的很多i标签是配合wxss(相当于css)渲染页面用的

作者邮箱: 1553197252@qq.com
3、编写wxss文件(太长,给出部分)
/**index.wxss**/.container{overflow: hidden;background: #3e6fa3;height: 150%;}.userinfo {  display: flex;  flex-direction: column;  align-items: center;}.userinfo-avatar {  width: 128rpx;  height: 128rpx;  margin: 20rpx;  border-radius: 50%;}.userinfo-nickname {  color: #aaa;}.usermotto {  margin-top: 100px;}.btn-record{  width: 120px;  height: 60px;  font: 13pt;  line-height: 3.5em;  background-color: green;  display: inline-block;}.btn-replay{  width: 120px;  height: 60px;  font: 13pt;  line-height: 3.5em;  background-color: white;  display: inline-block;}.audio{  display: none;}.wrapper {  position: absolute;  top: 50%;  left: 50%;  z-index: 2;  -moz-perspective: 500px;  -webkit-perspective: 500px;  perspective: 500px;}i {  display: block;  position: absolute;  width: 8px;  height: 8px;  border-radius: 8px;  opacity: 0;  background: rgba(255, 255, 255, 0.5);  box-shadow: 0px 0px 10px white;  animation-name: spin;  animation-duration: 3s;  animation-iteration-count: infinite;  animation-timing-function: ease-in-out;}i:nth-child(1) {  -moz-transform: rotate(11.6129deg) translate3d(80px, 0, 0);  -ms-transform: rotate(11.6129deg) translate3d(80px, 0, 0);  -webkit-transform: rotate(11.6129deg) translate3d(80px, 0, 0);  transform: rotate(11.6129deg) translate3d(80px, 0, 0);  animation-delay: 0.04839s;}i:nth-child(2) {  -moz-transform: rotate(23.22581deg) translate3d(80px, 0, 0);  -ms-transform: rotate(23.22581deg) translate3d(80px, 0, 0);  -webkit-transform: rotate(23.22581deg) translate3d(80px, 0, 0);  transform: rotate(23.22581deg) translate3d(80px, 0, 0);  animation-delay: 0.09677s;}i:nth-child(3) {  -moz-transform: rotate(34.83871deg) translate3d(80px, 0, 0);  -ms-transform: rotate(34.83871deg) translate3d(80px, 0, 0);  -webkit-transform: rotate(34.83871deg) translate3d(80px, 0, 0);  transform: rotate(34.83871deg) translate3d(80px, 0, 0);  animation-delay: 0.14516s;}i:nth-child(4) {  -moz-transform: rotate(46.45161deg) translate3d(80px, 0, 0);  -ms-transform: rotate(46.45161deg) translate3d(80px, 0, 0);  -webkit-transform: rotate(46.45161deg) translate3d(80px, 0, 0);  transform: rotate(46.45161deg) translate3d(80px, 0, 0);  animation-delay: 0.19355s;}i:nth-child(5) {  -moz-transform: rotate(58.06452deg) translate3d(80px, 0, 0);  -ms-transform: rotate(58.06452deg) translate3d(80px, 0, 0);  -webkit-transform: rotate(58.06452deg) translate3d(80px, 0, 0);  transform: rotate(58.06452deg) translate3d(80px, 0, 0);  animation-delay: 0.24194s;}@keyframes spin {  from {    opacity: 0.0;  }  to {    opacity: 0.6;    transform: translate3d(-4px, -4px, 570px);  }}#black {  position: absolute;  left: 10px;  bottom: 10px;  color: rgba(255, 255, 255, 0.6);  text-decoration: none;}#black:after {  content: 'Black & white';}#black:target {  top: 0;  left: 0;  width: 100%;  height: 100%;  z-index: 1;  background: #111;  cursor: default;}#black:target:after {  content: 'xxxx';}author-info{ color: #840b2a;}
4、编写js文件
//index.js//获取应用实例const app = getApp()const recorderManager = wx.getRecorderManager()const options = {  duration: 60000,  sampleRate: 16000,  numberOfChannels: 1,  format: 'mp3',  encodeBitRate: 24000  //frameSize: 50}var resultPage({  data: {    motto: '自定义motto',    userInfo: {},    hasUserInfo: false,    canIUse: wx.canIUse('button.open-type.getUserInfo')  },  //事件处理函数  bindViewTap: function() {    wx.navigateTo({      url: '../logs/logs'    })  },  onLoad: function () {    if (app.globalData.userInfo) {      this.setData({        userInfo: app.globalData.userInfo,        hasUserInfo: true      })    } else if (this.data.canIUse){      // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回      // 所以此处加入 callback 以防止这种情况      app.userInfoReadyCallback = res => {        this.setData({          userInfo: res.userInfo,          hasUserInfo: true        })      }    } else {      // 在没有 open-type=getUserInfo 版本的兼容处理      wx.getUserInfo({        success: res => {          app.globalData.userInfo = res.userInfo          this.setData({            userInfo: res.userInfo,            hasUserInfo: true          })        }      })    }  },  getUserInfo: function(e) {    console.log(e)    app.globalData.userInfo = e.detail.userInfo    this.setData({      userInfo: e.detail.userInfo,      hasUserInfo: true    })  },  //录音键长按按钮  startRecord: function(e){    recorderManager.start(options)    wx.showLoading({      title: '正在录音',    })  },  endRecord: function (e) {    wx.hideLoading()    recorderManager.stop()    wx.showLoading({      title: '正在翻译',    })    setTimeout(function () {      wx.hideLoading()    }, 5000)    recorderManager.onStop((res) => {
//录音结束后上传音频文件 console.log('本地audio路径:', res.tempFilePath) wx.uploadFile({ url: '接口地址', filePath: res.tempFilePath, name: 'file',//上传文件名(controller参数1) formData: { 'userId': app.globalData.userInfo != null ? app.globalData.userInfo.nickName :'defaultdir'//userId(controller参数2) }, success: function (res) { var data = res.data var start = data.indexOf('https'); var end = data.indexOf('mp3'); result = data.substring(start,end+3) console.log(result) this.audioctx = wx.createAudioContext('myAudio'); this.audioctx.setSrc(result); wx.hideLoading(); // this.audioctx.autoplay=true; this.audioctx.play(); // wx.playBackgroundAudio({
// dataUrl: result, // title: '丁', // coverImgUrl: '啊' // }) }, fail: function (res) { var data = res.data wx.showToast({ title: '失败那', icon: 'none', duration: 1000 }) } }) }) recorderManager.onError((res) => { console.log('recorder stop', res) wx.showToast({ title: '录音时间太短', icon: 'none', duration: 1000 }) }) }, replayRecord: function(e) {
//回放函数 audioctx = wx.createInnerAudioContext('myAudio'); if (result == undefined){ result ='提示url';//如果还没录音就点回放 } audioctx.src = result; audioctx.play(); console.log(app.globalData.userInfo != null ? app.globalData.userInfo : 'defaultdir') }})

5、上传小程序代码,查看审核规范,确保符合规范后再提交审核,等待结果,特别注意UI要符合规范,要提供作者联系方式

6、审核通过前,可以在小程序管理中添加用户并授予体验者权限,发送体验版二维码来访问体验版的小程序,审核通过后就直接能在微信小程序上搜到了

结语

百度还有很多API,都可以调用玩玩啊,还有其他公司的API,也可以尝试自己写个很6的算法做一个能很好地满足用户的程序,又或者是你手头有很好又很难找到的合法的资源都可以做个不错的个人小程序,我这个小程序实际上毫无软用,体验一下小程序开发罢了。总之我觉得有创新的思路还是最重要的,技术其次,可以慢慢学。

 

---------------------
作者:瘟小驹
来源:CSDN
原文:
版权声明:本文为博主原创文章,转载请附上博文链接!

________________________________________

你可能感兴趣的文章
全栈开发工程师微信小程序-上(中)
查看>>
spring boot2 整合(三)JOOQ工具
查看>>
【实战】颠覆银行基础架构的区块链
查看>>
第十六章:SpringCloud Config 配置自动刷新
查看>>
iOS APP内弹窗推送版本更新信息(实现跳转、强制更新等)
查看>>
Flutter 系列文章:Flutter Text 控件介绍
查看>>
二、SpringBoot配置文件讲解
查看>>
HTML基础:web前端建站流程
查看>>
http
查看>>
导航栏与scrollerview(或scrollerview的子类)
查看>>
建立个人Maven仓库
查看>>
阿里架构师手写Tomcat——Session源码解析
查看>>
世界杯来了!小程序赛事操作来一波~
查看>>
一个维护版本日志整洁的Git提交规范
查看>>
单例模式总结
查看>>
bootstrapSwitch bootstrap 的开关组件扩展
查看>>
冒泡排序
查看>>
阿里云 OSS 如何设置防盗链, 上个月图床流量耗费50G+,请求次数10W+,什么鬼?
查看>>
Node.js折腾记一(改进):文件夹目录树获取
查看>>
【机器学习】深度学习开发环境搭建
查看>>