五子棋调用百度语音接口实现语音识别
Step1前端上传语音到后端
前端通过ajax进行数据传输
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>五子棋</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
margin-top: 20px;
margin-left: 20px;
}
canvas {
background-image: url("img/backgroud.jpg");
border: 1px solid #000;
}
.mybutton {
width: 200px;
line-height: 40px;
text-align: center;
background-color: cornflowerblue;
margin: 0 auto;
margin-top: 20px;
font-size: 20px;
color: #fff;
}
</style>
</head>
<body>
<canvas width="600" height="600" onclick="play(event)"></canvas>
<div>
<input type="button" value="重新开始" onclick="replay()" class="mybutton">
<input type="button" value="开始录音" onclick="startVoice()" class="mybutton">
<input type="button" value="结束录音" onclick="endVoice()" class="mybutton">
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="../templates/recorder.js" type="text/javascript" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA=="
crossorigin="anonymous"></script>
<script>
/*准备工作: 1获取画布,获取画笔对象 */
var recorder = new Recorder({
sampleRate: 16000,
bitRate:16,
});
var mcanvas = document.querySelector("canvas");
var ctx = mcanvas.getContext("2d");
/*准备工作:2创建一个二维数组 用来定义绘制棋盘线*/
var count = 15;//用来定义棋盘的行数和列数
var map = new Array(count);
var startVoice = function() {
recorder.start().then(function() {
// 开始录音
});
}
var endVoice = function() {
// 结束录音
recorder.stop();
// 下载pcm文件
// recorder.downloadPCM('test');
// 获取 PCM 数据(Blob)
console.log(recorder.getPCMBlob());
let voiceBlob = recorder.getPCMBlob();
var body;
var formdata = new FormData(); // form 表单 {key:value}
console.log(voiceBlob)
formdata.append("voice", voiceBlob); // form input type="file"
$.ajax({
url: "http://127.0.0.1:8080/voice2word/store",//请求的url地址
type: 'post',//设置请求的http方式,method也可以
dataType: 'json',//将服务器端返回的数据直接认定为是这个格式,然后会做一些自动的处理(如果是json字符串,会自动转化为js对象),服务器返回的默认格式是text/html格式
processData:false,
contentType:false,
data: formdata,
success: function (data) {//请求成功之后执行的回调函数
console.log(data.code);
body = data.body;
voiceplay(body);
},
error: function(error){
console.log(error)
}
});
}
for (var i = 0; i < map.length; i++) {
map[i] = new Array(count);
for (var j = 0; j < map[i].length; j++) {
map[i][j] = 0;
}
}
/*准备工作:3初始化棋子*/
var black = new Image();
var white = new Image();
black.src = "img/black.png";
white.src = "img/white.png";
//开始绘制 1绘制棋盘线
ctx.strokeStyle = "#fff";
var rectWH = 40; //设置绘制矩形的大小
for (var i = 0; i < map.length; i++) {
for (var j = 0; j < map[i].length; j++) {
ctx.strokeRect(j * rectWH, i * rectWH, rectWH, rectWH);
}
}
// 用来进行黑白子的切换
var isBlack = true;
function voiceplay(body) {
row = body[0];
col = body[1];
if (map[row][col] != 0) {
alert("此处已经落子");
return;
}
// 切换绘制黑白子
if (isBlack) {
ctx.drawImage(black, col * 40 - 20, row * 40 - 20);
isBlack = false;
map[row][col] = 1;
$.ajax({
url: "http://127.0.0.1:5000/yes",//请求的url地址
type: 'post',//设置请求的http方式,method也可以
dataType: 'json',//将服务器端返回的数据直接认定为是这个格式,然后会做一些自动的处理(如果是json字符串,会自动转化为js对象),服务器返回的默认格式是text/html格式
data: {//向服务器端发送的数据
t: 1,
row: row,
col: col,
},
success: function (data) {//请求成功之后执行的回调函数
if(data.code===201){
alert('黑棋获胜')
}else if(data.code===202){
alert('白棋获胜')
}
},
error: function(error){
console.log(error)
}
});
// Yes(1,row,col)
} else {
ctx.drawImage(white, col * 40 - 20, row * 40 - 20);
isBlack = true;
map[row][col] = 2;
$.ajax({
url: "http://127.0.0.1:5000/yes",//请求的url地址
type: 'post',//设置请求的http方式,method也可以
dataType: 'json',//将服务器端返回的数据直接认定为是这个格式,然后会做一些自动的处理(如果是json字符串,会自动转化为js对象),服务器返回的默认格式是text/html格式
data: {//向服务器端发送的数据
t: 2,
row: row,
col: col,
},
success: function (data) {//请求成功之后执行的回调函数
if(data.code===201){
alert('黑棋获胜')
}else if(data.code===202){
alert('白棋获胜')
}
},
error: function(error){
console.log(error)
}
});
// Yes(2,row,col)
}
}
//开始绘制 2下子
function play(e) {
//获取点击canvas的位置值默认,canvas的左上角为(0,0) 点
var leftOffset = 20;//body 的margin
var x = e.clientX - leftOffset;
var y = e.clientY - leftOffset;
// console.log(x+" "+y);
// 设置点击点后棋子下在哪里,获取点击的位置进行判断如果超过格子的一半则绘制到下一个点如果小于 则绘制在上一个点上
var xv = (x - rectWH / 2) / rectWH;
var yv = (y - rectWH / 2) / rectWH;
var col = parseInt(xv) + 1;
var row = parseInt(yv) + 1;
console.log(xv + " " + yv + " , " + col + " " + row);
//严格点需要验证 ,验证所输入的点是否在数组中已经存在 ,如果存在 则返回
if (map[row][col] != 0) {
alert("此处已经落子");
return;
}
// 切换绘制黑白子
if (isBlack) {
ctx.drawImage(black, col * 40 - 20, row * 40 - 20);
isBlack = false;
map[row][col] = 1;
$.ajax({
url: "http://127.0.0.1:5000/yes",//请求的url地址
type: 'post',//设置请求的http方式,method也可以
dataType: 'json',//将服务器端返回的数据直接认定为是这个格式,然后会做一些自动的处理(如果是json字符串,会自动转化为js对象),服务器返回的默认格式是text/html格式
data: {//向服务器端发送的数据
t: 1,
row: row,
col: col,
},
success: function (data) {//请求成功之后执行的回调函数
if(data.code===201){
alert('黑棋获胜')
}else if(data.code===202){
alert('白棋获胜')
}
},
error: function(error){
console.log(error)
}
});
// Yes(1,row,col)
} else {
ctx.drawImage(white, col * 40 - 20, row * 40 - 20);
isBlack = true;
map[row][col] = 2;
$.ajax({
url: "http://127.0.0.1:5000/yes",//请求的url地址
type: 'post',//设置请求的http方式,method也可以
dataType: 'json',//将服务器端返回的数据直接认定为是这个格式,然后会做一些自动的处理(如果是json字符串,会自动转化为js对象),服务器返回的默认格式是text/html格式
data: {//向服务器端发送的数据
t: 2,
row: row,
col: col,
},
success: function (data) {//请求成功之后执行的回调函数
if(data.code===201){
alert('黑棋获胜')
}else if(data.code===202){
alert('白棋获胜')
}
},
error: function(error){
console.log(error)
}
});
// Yes(2,row,col)
}
}
function replay(){
$.ajax({
url: "http://127.0.0.1:5000/replay",//请求的url地址
type: 'post',//设置请求的http方式,method也可以
dataType: 'json',//将服务器端返回的数据直接认定为是这个格式,然后会做一些自动的处理(如果是json字符串,会自动转化为js对象),服务器返回的默认格式是text/html格式
data: {//向服务器端发送的数据
isReplay: true
},
success: function (data) {//请求成功之后执行的回调函数
window.location.href = "game.html";
},
error: function(error){
console.log(error)
}
});
}
</script>
</body>
</html>
Step2后端接收到语音文件缓存下来
这一步有待优化,涉及到的IO比较多。
package com.yokna.homeworkhelper.service.impl;
import com.yokna.homeworkhelper.entity.RespMsg;
import com.yokna.homeworkhelper.service.UploadService;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
@Service
public class UploadServiceImpl implements UploadService {
@Override
public RespMsg<String> upload(MultipartFile file) {
if(file.isEmpty()) {
return RespMsg.fail("400","上传的文件为空");
}
String originalFileName = file.getOriginalFilename();
String newFileName = System.currentTimeMillis()+"."+ originalFileName;
String filePath = "C:\\Users\\Yokna\\IdeaProjects\\homeworkhelper\\src\\main\\resources\\static\\voice\\";
File dest = new File(filePath+newFileName);
//判断是否存在这样的一个目录来存放我们上传的文件
if (!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
try {
file.transferTo(dest);
}catch (Exception e){
e.printStackTrace();
return RespMsg.fail("500","上传失败");
}
return RespMsg.success(filePath+newFileName);
}
}
Step3转发给百度语音接口
package com.yokna.homeworkhelper.utils;
import com.baidu.aip.speech.AipSpeech;
import org.json.JSONObject;
import org.junit.Test;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
public class BaiduVoice {
//设置APPID/AK/SK
public static final String APP_ID = "24287980";
public static final String API_KEY = "gnXf3eNkMNIFSvrm91QSQioN";
public static final String SECRET_KEY = "YPYtZ3Ny8iHyOpMEgd4DC0b9KO628G75";
public static HashMap<String,Integer> dict = new HashMap<String, Integer>(){
{
put("一",1);
put("二",2);
put("三",3);
put("四",4);
put("五",5);
put("六",6);
put("七",7);
put("八",8);
put("九",9);
put("十",10);
put("11",11);
put("12",12);
put("13",13);
put("14",14);
put("15",15);
put("16",16);
put("17",17);
}
};
public int[] voice2word(String src){
// 初始化一个AipSpeech
AipSpeech client = new AipSpeech(APP_ID, API_KEY, SECRET_KEY);
// 可选:设置网络连接参数
client.setConnectionTimeoutInMillis(2000);
client.setSocketTimeoutInMillis(60000);
// 可选:设置log4j日志输出格式,若不设置,则使用默认配置
// 也可以直接通过jvm启动参数设置此环境变量
//System.setProperty("aip.log4j.conf", "path/to/your/log4j.properties");
// 调用接口
JSONObject res = client.asr(src, "pcm", 16000, null);
System.out.println(res.toString(2));
String commandStr = (String) res.getJSONArray("result").get(0);
int[] result = this.str2Integer(commandStr);
return result;
}
public int[] str2Integer(String str){
int[] result = new int[5];
// str = "";
String regex = "\u4e00|\u4e8c|\u4e09|\u56db|\u4e94|\u516d|\u4e03|\u516b|\u4e5d|\u5341|[10-18]";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(str);
int i = 0;
while (m.find()){
// System.out.println(m.start());
// System.out.println(m.end());
// System.out.println(str.substring(m.start(), m.end())+1);
try{
result[i] = dict.get(str.substring(m.start(),m.end()+1)+"");
}catch (Exception e){
continue;
}
i++;
// System.out.println("i =" + i);
// System.out.println(str.charAt(m.start()));
// System.out.println(this.dict.get(str.charAt(m.start())+""));
// System.out.println(m.start());
// System.out.println(m.);
}
int[] tmp = new int[2];
int k = 0;
for (int j = 0 ; j < result.length ;j++){
if (result[j] != 0){
if (result[j] != 10){
tmp[k] = result[j];
k++;
}else {
tmp[k] = result[j]+result[j+1];
j++;
k++;
}
}else continue;
}
return tmp;
}
// public int[] gobangVoiceAPI(String src){
// this.str2Integer(voice2word(src));
//
// }
@Test
public void test(){
for (int res : str2Integer("第第11行行11列")) {
System.out.println(res);
}
}
}
Step4处理百度语音返回的语音识别结果
package com.yokna.homeworkhelper.controller;
import com.yokna.homeworkhelper.entity.RespMsg;
import com.yokna.homeworkhelper.service.UploadService;
import com.yokna.homeworkhelper.utils.BaiduVoice;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@CrossOrigin
@RestController
@RequestMapping(value = "voice2word",method = RequestMethod.POST)
public class VoiceController {
@Autowired
private BaiduVoice baiduVoice;
@Autowired
private UploadService uploadService;
@RequestMapping("/store")
public RespMsg storePCM(@RequestParam("voice") MultipartFile file){
RespMsg respMsg = uploadService.upload(file);
String newFileName = (String) respMsg.getBody();
int[] result = baiduVoice.voice2word(newFileName);
return RespMsg.success(result);
}
}
五子棋其他逻辑在python后端,java后端只完成语音识别、识别到字符处理。具体后端详见之前发过的flask博文。