微信小程序语音同步智能识别的实现案例
一、背景
在小程序的一些应用场景中,会有语音转文字的需求。原有的做法一般是先通过小程序的录音功能录下语音文件,然后再通过调用语音智能识别WebApi(比如百度云AI平台,科大讯飞平台)将语音文件转成文字信息,以上的做法比较繁琐且用户的体验性较差。
为解决此问题,微信直接开放了同声传译的插件,小程序作者可以直接使用该插件进行语音同声传译的开发。此文章将通过前后端整合应用的完整案例完成语音的实时转换,并将语音上传到服务端后台备份。
二、同声传译插件介绍
微信同声传译由微信智聆语音团队、微信翻译团队与公众平台联合推出的同传开放接口,首期开放语音转文字、文本翻译、语音合成接口,为开发者赋能。
1、 微信小程序后台添加插件
进入微信小程序后台-->进入设置-->第三方设置-->添加插件->搜索同声传译-->完成添加。


2、 微信小程序启用插件
在小程序app.json文件中增加插件版本等信息:
"plugins": {
"WechatSI": {
"version": "0.3.3",
"provider": "wx069ba97219f66d99"
}
},
在页面程序文件中引入插件:
/* index.js */
const plugin = requirePlugin("WechatSI")
// 获取**全局唯一**的语音识别管理器**recordRecoManager**
const manager = plugin.getRecordRecognitionManager()
recordRecoManager 对象的方法列表:
| 方法 | 参数 | 说明 |
|---|---|---|
| start | options | 开始识别 |
| stop | 结束识别 | |
| onStart | callback | 正常开始录音识别时会调用此事件 |
| onRecognize | callback | 有新的识别内容返回,则会调用此事件 |
| onStop | callback | 识别结束事件 |
| onError | callback | 识别错误事件 |
官方开发文档:插件的语音识别管理器
三、语音同步转换的前端实现
1、界面UI与操作
UI参考微信官方的DEMO:长按按钮进行录音,松开按钮实时将录音转换为文字。

用户可对同步转换的文字进行编辑,同时可将原始语音文件与文字上传后台服务端。

2、代码实现
语音同步转换的主要代码:
//导入插件
const plugin = requirePlugin("WechatSI");
// 获取**全局唯一**的语音识别管理器**recordRecoManager**
const manager = plugin.getRecordRecognitionManager();
/**
* 加载进行初始化
*/
onLoad: function () {
//获取录音权限
app.getRecordAuth();
//初始化语音识别回调
this.initRecord();
},
...
/**
* 初始化语音识别回调
* 绑定语音播放开始事件
*/
initRecord: function () {
//有新的识别内容返回,则会调用此事件
manager.onRecognize = (res) => {
let currentData = Object.assign({}, this.data.currentTranslate, {
text: res.result,
});
this.setData({
currentTranslate: currentData,
});
this.scrollToNew();
};
// 识别结束事件
manager.onStop = (res) => {
let text = res.result;
console.log(res.tempFilePath);
if (text == "") {
this.showRecordEmptyTip();
return;
}
let lastId = this.data.lastId + 1;
let currentData = Object.assign({}, this.data.currentTranslate, {
text: res.result,
translateText: "正在识别中",
id: lastId,
voicePath: res.tempFilePath,
duration: res.duration
});
this.setData({
currentTranslate: currentData,
recordStatus: 1,
lastId: lastId,
});
//将当前识别内容与语音文件加入列表
this.addRecordFile(currentData, this.data.dialogList.length);
//刷新列表
this.scrollToNew();
};
// 识别错误事件
manager.onError = (res) => {
this.setData({
recording: false,
bottomButtonDisabled: false,
});
};
},
/**
* 按住按钮开始语音识别
*/
streamRecord: function (e) {
let detail = e.detail || {};
let buttonItem = detail.buttonItem || {};
//开始中文录音
manager.start({
lang: buttonItem.lang,
});
this.setData({
recordStatus: 0,
recording: true,
currentTranslate: {
// 当前语音输入内容
create: util.recordTime(new Date()),
text: "正在聆听中",
lfrom: buttonItem.lang,
lto: buttonItem.lto,
},
});
//刷新列表
this.scrollToNew();
},
/**
* 松开按钮结束语音识别
*/
streamRecordEnd: function (e) {
let detail = e.detail || {}; // 自定义组件触发事件时提供的detail对象
let buttonItem = detail.buttonItem || {};
// 防止重复触发stop函数
if (!this.data.recording || this.data.recordStatus != 0) {
console.warn("has finished!");
return;
}
manager.stop();
this.setData({
bottomButtonDisabled: true,
});
},
编辑识别文字并完上传的主要代码:
/**
* 页面的初始数据
*/
data: {
edit_text_max: 200,
remain_length: 200,
edit_text: "",
is_focus: false,
tips: "",
index: -1,
voicePath: "",
},
/**
* 加载初始化
*/
onLoad: function (options) {
//根据传入的文字内容填充编辑框
this.setEditText(options.content)
this.setData({
index: index,
oldText:options.content,
voicePath: options.voicePath
})
},
/**
* 编辑文字
*/
editInput: function (event) {
console.log(event)
if (event.detail.value.length > this.getEditTextMax()) {
} else {
this.data.edit_text = event.detail.value
this.updateRemainLength(this.data.edit_text)
}
},
/**
* 上传文字与语音文件
*/
editConfirm: function (event) {
let json=this.data.edit_text
//调用微信上传文件api将信息上传至服务端webApi
wx.uploadFile({
url: api.wxFileUploadUrl,
filePath: this.data.voicePath,
name: "file",
header: {
Authorization: wx.getStorageSync("loginFlag"),
"Content-Type": "multipart/form-data",
},
formData: {
openId: app.globalData.userInfo.openId,
realName: "语音文件",
json: JSON.stringify(json),
},
success: (result) => {
console.log("success:", result);
if (result.statusCode == "200") {
let data = JSON.parse(result.data);
console.log("data", data);
if (data.success == true) {
let module = data.module;
console.log("module", module);
app.showInfo("上传成功");
setTimeout( ()=>{
wx.navigateBack();
}, 2000)
} else {
app.showInfo("异常错误" + data.errMsg + ",请重新进入");
wx.navigateTo({
url: "/pages/index/index",
});
}
} else {
app.showInfo("访问后台异常,重新进入系统");
wx.navigateTo({
url: "/pages/index/index",
});
}
},
fail: (result) => {
console.log("fail", result);
wx.navigateTo({
url: "/pages/index/index",
});
},
complete: () => {},
});
},
四、后端SpringBoot实现语音文件上传webApi
1、SpringBoot项目API相关结构树

2、文件上传工具类的实现
tools工具类包中主要存文件通用的文件上传工具类,该工具类会将文件上传至配置指定的文件夹下,并将文件信息写入upload_file表中。
- 文件信息实体类:与数据库中表upload_file对应;
- 文件存储仓库类:通过Spring Data JPA接口实现数据的CRUD;
- 文件上传工具接口:对外统一封装文件上传方法;
- 文件上传工具实现类:实现文件上传方法接口。
文件信息实体类:UploadFile.java
/**
* 文件信息表
*
* @author zhuhuix
* @date 2020-04-20
*/
@Entity
@Getter
@Setter
@Table(name = "upload_file")
public class UploadFile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@NotNull(groups = Update.class)
private Long id;
/**
* 文件实际名称
*/
@Column(name = "real_name")
private String realName;
/**
* 文件名
*/
@NotNull
@Column(name = "file_name")
private String fileName;
/**
* 文件主名称
*/
@NotNull
@Column(name = "primary_name")
private String primaryName;
/**
* 文件扩展名
*/
@NotNull
private String extension;
/**
* 存放路径
*/
@NotNull
private String path;
/**
* 文件类型
*/
private String type;
/**
* 文件大小
*/
private Long size;
/**
* 上传人
*/
private String uploader;
@JsonIgnore
@Column(name = "create_time")
@CreationTimestamp
private Timestamp createTime;
public UploadFile(String realName, @NotNull String fileName, @NotNull String primaryName, @NotNull String extension, @NotNull String path, String type, Long size, String uploader) {
this.realName = realName;
this.fileName = fileName;
this.primaryName = primaryName;
this.extension = extension;
this.path = path;
this.type = type;
this.size = size;
this.uploader = uploader;
}
@Override
public String toString() {
return "UploadFile{" +
"fileName='" + fileName + '\'' +
", uploader='" + uploader + '\'' +
", createTime=" + createTime +
'}';
}
}
文件存储仓库类:UploadFileRepository.java
/**
* 上传文件DAO接口层
*
* @author zhuhuix
* @date 2020-04-03
*/
public interface UploadFileRepository extends JpaRepository<UploadFile, Long>, JpaSpecificationExecutor<UploadFile> {
//该接口继承JpaRepository及CrudRepository接口,已实现了如findById,save,delete等CRUD方法
}
UploadFileRepository 接口继承JpaRepository及CrudRepository接口,已实现了如findById,save,delete等CRUD方法

文件上传工具接口:UploadFileTool.java
/**
* 文件上传接口定义
*
* @author zhuhuix
* @date 2020-04-20
*/
public interface UploadFileTool {
/**
* 文件上传
* @param multipartFile 文件
* @return 上传信息
*/
UploadFile upload(String uploader,String realName,MultipartFile multipartFile);
}
文件上传工具实现类:UploadFileToolImpl.java
/**
* 文件上传实现类
*
* @author zhuhuix
* @date 2020-04-20
*/
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class UploadFileToolImpl implements UploadFileTool {
private final UploadFileRepository uploadFileRepository;
@Value("${uploadFile.path}")
private String path;
@Value("${uploadFile.maxSize}")
private long maxSize;
public UploadFileToolImpl(UploadFileRepository uploadFileRepository) {
this.uploadFileRepository = uploadFileRepository;
}
@Override
@Transactional(rollbackFor = Exception.class)
public UploadFile upload(String uploader, String realName, MultipartFile multipartFile) {
//检查文件大小
if (multipartFile.getSize() > maxSize * Constant.MB) {
throw new RuntimeException("超出文件上传大小限制" + maxSize + "MB");
}
//获取上传文件的主文件名与扩展名
String primaryName = FileUtil.mainName(multipartFile.getOriginalFilename());
String extension = FileUtil.extName(multipartFile.getOriginalFilename());
//根据文件扩展名得到文件类型
String type = getFileType(extension);
//给上传的文件加上时间戳
LocalDateTime date = LocalDateTime.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyyMMddhhmmssS");
String nowStr = "-" + date.format(format);
String fileName = primaryName + nowStr + "." + extension;
try {
String filePath = path + type + File.separator + fileName;
File dest = new File(filePath).getCanonicalFile();
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
multipartFile.transferTo(dest);
if (ObjectUtil.isNull(dest)) {
throw new RuntimeException("上传文件失败");
}
UploadFile uploadFile = new UploadFile(realName, fileName, primaryName, extension, dest.getPath(), type, multipartFile.getSize(), uploader);
return uploadFileRepository.save(uploadFile);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
/**
* 根据文件扩展名给文件类型
*
* @param extension 文件扩展名
* @return 文件类型
*/
private static String getFileType(String extension) {
String document = "txt doc pdf ppt pps xlsx xls docx csv";
String music = "mp3 wav wma mpa ram ra aac aif m4a";
String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
if (image.contains(extension)) {
return "image";
} else if (document.contains(extension)) {
return "document";
} else if (music.contains(extension)) {
return "music";
} else if (video.contains(extension)) {
return "video";
} else {
return "other";
}
}
}
注意,该程序代码中用到了@Value注解获取配置文件中的uploadFile.path及uploadFile.maxsize参数,一般在项目静态配置文件中按如下书写(yml配置文件)。
# 测试环境文件存储路径
uploadFile:
path: C:\startup\file\
# 文件大小 /M
maxSize: 50
3、小程序上传文件接口的实现
wx-miniprogram包定义了小程序CRM webApi的接口,小程序调用webApi实现文件的上传及其他功能。
- 微信小程序 webApi:对外提供小程序上传文件webApi;
- 微信小程序服务接口:封装小程序上传文件服务接口;
- 微信小程序服务实现:小程序上传文件服务的实现,该服务实现中会调用tools包中的UploadFile接口进行文件的上传。
微信小程序CRM webApi:WxMiniCrmController.java
/**
* 微信小程序Crm webApi
*
* @author zhuhuix
* @date 2020-03-30
*/
@Slf4j
@RestController
@RequestMapping("/api/wx-mini")
@Api(tags = "微信小程序Crm接口")
public class WxMiniCrmController {
private final WxMiniCrm wxMiniCrm;
public WxMiniCrmController(WxMiniCrm wxMiniCrm) {
this.wxMiniCrm = wxMiniCrm;
}
@ApiOperation(value = "微信小程序端上传文件")
@PostMapping(value = "/fileUpload")
public ResponseEntity fileUpload(HttpServletRequest request) {
MultipartHttpServletRequest req = (MultipartHttpServletRequest) request;
MultipartFile multipartFile = req.getFile("file");
String openId = req.getParameter("openId");
String realName = req.getParameter("realName");
String json = req.getParameter("json");
return ResponseEntity.ok(wxMiniCrm.uploadFile(json, openId,realName, multipartFile));
}
}
微信小程序CRM服务接口:WxMiniCrm.java
/**
* 微信小程序CRM服务接口定义
*
* @author zhuhuix
* @date 2020-04-20
*/
public interface WxMiniCrm {
/**
* 将微信小程序传入的json对象写入数据库,并同时将文件上传至服务端
*
* @param json 微信端传入json对象
* @param openId 上传人
* @param realName 文件实际名称
* @param multipartFile 上传文件
* @return 返回上传信息
*/
Result<UploadFile> uploadFile(String json, String openId, String realName,MultipartFile multipartFile);
}
微信小程序CRM服务实现:WxMiniCrmImpl.java
/**
* 微信小程序CRM实现类
*
* @author zhuhuix
* @date 2020-04-20
*/
@Slf4j
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class WxMiniCrmImpl implements WxMiniCrm {
private final UploadFileTool uploadFileTool;
public WxMiniCrmImpl(UploadFileTool uploadFileTool) {
this.uploadFileTool = uploadFileTool;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<UploadFile> uploadFile(String json, String openId,String realName, MultipartFile multipartFile) {
return new Result<UploadFile>().ok(uploadFileTool.upload(openId,realName, multipartFile));
}
}
4、小程序上传文件接口的查看
访问Swagger2可查看该接口,Swagger2与SpringBoot的集成可参考SpringBoot JWT认证机制项目集成Swagger2

五、实际测试
语音测试正常

上传文件至后台:

上传的日志信息查看:

微信小程序语音同步智能识别的实现案例的更多相关文章
- 微信小程序语音与讯飞语音识别接口(Java)
项目需求,需要使用讯飞的语音识别接口,将微信小程序上传的录音文件识别成文字返回 而微信小程序上传的文件格式是silk的,而讯飞接口能识别wav 格式的文件,所以需要将小程序上传的silk文件转成wav ...
- 微信小程序语音与讯飞语音识别接口(Java),Kronopath/SILKCodec,ffmpeg处理silk,pcm,wav转换
项目需求,需要使用讯飞的语音识别接口,将微信小程序上传的录音文件识别成文字返回 首先去讯飞开放平台中申请开通语音识别功能 在这里面下载sdk,然后解压,注意appid与sdk是关联的,appid在初始 ...
- 微信小程序中同步 异步的使用
https://www.jianshu.com/p/e92c7495da76 微信小程序中使用Promise进行异步流程处理 https://www.cnblogs.com/cckui/p/102 ...
- 微信小程序request同步请求
今天在搞微信小程序的时候顺手用了async,await死活不起作用,后来查了一下子,竟然不支持,那没办法就换了一种实现wx.request同步请求的方案 祭出promise来搞一搞,下面直接贴代码,简 ...
- 微信小程序语音(A)发给别人(B),也能播放,是需要先把语音上传到自己的服务器上才可以
小程序语音(A)发给别人(B),也能播放,是需要先把语音上传到自己的服务器上才可以. https://developers.weixin.qq.com/miniprogram/dev/api/medi ...
- 微信小程序开发(三)----- 云开发案例
1.发送请求 2.云函数中发送请求,例子request https://github.com/request/request-promise 创建云函数movielist,右键在终端打开,输入 ...
- 微信小程序语音提示
一. 老规矩, 先上demo图: 然后通过 wx.createInnerAudioContext 创建内部 audio 上下文 InnerAudioContext 对象 就能播放 filename ...
- 微信小程序二维码识别
目前市场上二维码识别的软件或者网站越来越多,可是真正方便,无广告的却少之很少. 于是,自己突发奇想做了一个微信二维码识别的小程序. 包含功能: 1.识别二维码 ①普通二维码 ②条形码 ③只是复制解析出 ...
- 博客与微信小程序的同步
在此之前,先说说自己最近的打算,才购买了阿里云的服务器,想做一个网站和图床网盘之类的方便自己使用. 考虑到小程序,又打算将自己的博客内容放到小程序中.从零开发实属困难,应该还要一段时间才能完成. 目前 ...
随机推荐
- airtest+poco多脚本、多设备批处理运行测试用例自动生成测试报告
一:主要内容 框架功能及测试报告效果 airtest安装.环境搭建 框架搭建.框架运行说明 airtest自动化脚本编写注意事项 二:框架功能及测试报告效果 1. 框架功能: 该框架笔者用来作为公司的 ...
- pycharm添加头注释
1.进入setting->Editor->File and Code Templates->Python Script 2.添加内容 # coding = 'utf-8'# @作者: ...
- struts2初始化探索(一)
上篇文章已经介绍了struts2的简单使用,现在开始源码的学习. 本篇主要介绍struts2的初始化.对应的源码为StrutsPrepareAndExecuteFilter中的init方法. 先贴源码 ...
- Coursera课程笔记----计算导论与C语言基础----Week 4
感性认识计算机程序(Week 4) 引入 编程序 = 给计算机设计好运行步骤 程序 = 人们用来告诉计算机应该做什么的东西 问题➡️该告诉计算机什么?用什么形式告诉? 如果要创造一门"程序设 ...
- 在windows环境里,用Docker搭建Redis开发环境(新书第一个章节)
大家都知道高并发分布式组件的重要性,而且如果要进大厂,这些技术不可或缺.但这些技术的学习难点在于,大多数项目里的分布式组件,都是搭建在Linux系统上,在自己的windows机器上很难搭建开发环境,如 ...
- What?废柴, 模拟登陆,代码控制滑动验证真的很难吗?Are you kidding???
1.简介 在前边的python接口自动化的时候,我们由于博客园的登录机制的改变,没有用博客园的登录测试接口.那么博客园现在变成了滑动验证登录,而且现在绝大多数的登录都变成这种滑动验证和验证码的登录验证 ...
- Windows 10 IoT Core用PWM控制器控制树莓派LED灯亮度
我接到一个需求,需要调节LED灯的亮度,且是从上位机进行控制,我了解到树莓派也有PWM,就准备通过PWM来控制灯的亮度. PWM又叫脉宽调制,是用微处理器的数字输出来对模拟电路进行控制,对模拟信号电平 ...
- 浅谈字典树Trie
\(\;\) 本文是作者学习<算法竞赛进阶指南>的所得,有些语言是摘自其中. \(\;\) 基础知识 定义 \(\;\) 字典树(Trie):是一种支持字符串查询的多叉树结构.其中的每个节 ...
- GitHub使用SSH连接以及生成修改添加密钥详细过程
目录 1. 先看看本地有没有SSH密钥 2. 生成/修改密钥 3. 把SSH密钥添加到ssh-agent 4. 把SSH密钥添加到GitHub账户里 5. 测试使用ssh地址clone仓库 6. 把远 ...
- nth-of-child和nth-of-type的区别
p:nth-of-child(2) 翻译过来就是,必需是p元素,并且是父标签的第二个元素,满足以上两个条件,这些样式才会渲染. p:nth-of-type(2) 翻译过来就是,必需是p ...