IESM项目实训五
语音录入方式设计有两种
第一种:系统从后端获取未录入(初始成绩值为空或者为0)的学生列表,由系统播放学生姓名,教师只需要说明成绩即可。规定时间间隔内只需说明一次,否则获取到重复内容,录入错误数据。
第二种:学生姓名和成绩都由教师说明,系统分析姓名和成绩部分。为获取音频,每间隔六秒录入一位同学,播放下一位同学的音频提示。
两种方式共同步骤:播放开始音频录制成绩提示音,教师语音说明录入成绩类型。
语音提醒接口
涉及不同字符串提醒,除姓名外,设计统一接口。
//前端接口
startInputScoresVoice: "ScoresInput/scores/startInputScoresVoice",
//后端实现
//合成开始语音或者其他语音录入语音
@getMapping(value = "/startInputScoresVoice")
public Result<Boolean> startInputScoresVoice(@RequestParam(name = "startVoice") String startVoice) {
VoiceCompose voiceCompose = new VoiceCompose();
Boolean result = false;
if (!voiceCompose.getMP3ByText(startVoice)) {
System.out.println("转换失败");
} else {
result = true;
voiceCompose.playMP3();
}
return Result.OK(result);
}
完整播放提示语音超过服务器设定响应时间9秒,为避免超时,将系统的响应时间上限进行了修改。
识别教师录入成绩类型
单独识别录入成绩类型,后续按照对应类型获取下一个录入学生索引和成绩更新操作的执行。
@ResponseBody
@RequestMapping(value = "/voiceInputType")
public Result<Integer> voiceInputType(@RequestParam(name = "voiceBlobFile") MultipartFile voiceBlobFile) {
int type = 0;
byte[] voiceData = new byte[1024];
//将前端的参数转换为byte数组,进行识别处理
try {
voiceData = voiceBlobFile.getBytes();
} catch (IOException e) {
e.printStackTrace();
}
VoiceCompose voiceCompose = new VoiceCompose();
VoiceRecognition voiceRecognition = new VoiceRecognition();
boolean typeJudge = voiceRecognition.recognizeDataVoice(voiceData);
if (typeJudge) {
String scoreType = VoiceRecognition.getResultText();
System.out.println(scoreType);
if (scoreType.contains("平时成绩")) {
type = 3;
String usualScores = "现在开始语音录入平时成绩";
if (!voiceCompose.getMP3ByText(usualScores)) {
System.out.println("转换失败");
} else {
voiceCompose.playMP3();
}
} else if (scoreType.contains("考试成绩")) {
type = 1;
String testScores = "现在开始语音录入考试成绩";
if (!voiceCompose.getMP3ByText(testScores)) {
System.out.println("转换失败");
} else {
voiceCompose.playMP3();
}
} else if (scoreType.contains("实验成绩")) {
type = 2;
String examScores = "现在开始语音录入实验成绩";
if (!voiceCompose.getMP3ByText(examScores)) {
System.out.println("转换失败");
} else {
voiceCompose.playMP3();
}
} else {
String remind = "请按要求说明";
if (!voiceCompose.getMP3ByText(remind)) {
System.out.println("转换失败");
} else {
voiceCompose.playMP3();
}
}
} else {
String remind = "语音识别失败,请您重说一次";
if (!voiceCompose.getMP3ByText(remind)) {
System.out.println("转换失败");
} else {
voiceCompose.playMP3();
}
}
return Result.OK(type);
}
//
实现前端和后端音频传递和语音合成的交互
识别类型交互:在开始提示语音播放后,教师说明录入成绩类型,按照提示在五秒内说“平时成绩”“实验成绩”“考试成绩”,教师可以重复说明。识别成功后提示开始录入成绩,识别失败后台合成“语音识别失败,请您重说一次”。其中关键是,该过程不能视为简单的循环,因为音频录制需要时间,并且在录制几秒后将音频数据传给后端识别,识别很快速返回成功失败信息,如果成功录音停止,如果失败还要再次录音,而且并不确定会失败几次。为实现使用setInterval()
方法可按照指定的周期(以毫秒计)来调用识别音频等的方法。
下面是可以定期获取音频再识别的简单测试:
voiceRecorder: function (stuNum) {
this.timer = setInterval(() => {
//判断是否存在未录入的学生,不存在则不需要录入成绩,n是测试的一个固定数,测试不断交互是否成功
if (n <= stuNum) {
Recorder.get(function (rec) {
recorder = rec
recorder.start()
})
voiceBlobFile = recorder.getBlob(); //得到需要的pcm文件
this.voiceInputScoresType(n, voiceBlobFile);
console.log(voiceBlobFile)
console.log("执行" + n + "次");
} else if (n > stuNum) {
n = 0;
recorder.stop();
this.voiceController = true;
clearInterval(this.timer);
console.log("终止该循环");
}
n = n + 1;
}, 5000);
},
//随便写的测试接收前端数据方法
@ResponseBody
@RequestMapping(value = "/voiceInputScoresTwo")
public Result<?> voiceInputScoresTwo(@RequestParam(name = "n") int n, @RequestParam(name = "voiceBlobFile") MultipartFile voiceBlobFile) {
System.out.println(n);
System.out.println(voiceBlobFile.getSize());
byte[] testFile = new byte[1024];
VoiceRecognition voiceRecognition = new VoiceRecognition();
try {
testFile = voiceBlobFile.getBytes();
voiceRecognition.recognizeNum(testFile);
System.out.println("输出结果是" + voiceRecognition.getResultText());
} catch (IOException e) {
e.printStackTrace();
}
return Result.OK(voiceRecognition.getResultText());
}
前端生成音频数据为BLOB格式,后端需要使用 MultipartFile
类型接收,且添加注解@ResponseBody
@RequestMapping
,不可以使用getMapping
等较为常规的方式,后端也不可以使用Blob直接接收,报错会String不能与Blob匹配。
尝试将Blob转为byte[]数组,再进行语音识别,后使用MultipartFile
类型,可以调用getBytes()
方法转为byte[]。
//blob转为byte数组,音频文件用MultipartFile接收
public byte[] blobToByte(Blob blob) throws Exception {
byte[] bytes = null;
try {
InputStream in = blob.getBinaryStream();
BufferedInputStream inBuffered = new BufferedInputStream(in);
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
byte[] temp = new byte[1024];
int size = 0;
while ((size = inBuffered.read(temp)) != -1) {
out.write(temp, 0, size);
}
inBuffered.close();
in.close();
bytes = out.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
}
return bytes;
}
如果使用该方法需要明确所传递音频数据是哪一阶段的,否则识别结果会与插入数据库内容不匹配。
测试成功后,实现了类型识别前端方法,测试发现语音输入类型用户感觉并不太好,存在两个原因。一是交互时间过长,包括音频录入和与语音合成播放,如果用户多次使用都要经过该过程有一些累赘。二是识别结果存在一定问题,用户反应时间和交互时间冲突的话,识别结果不完整意味失败,需要再次说明,开始录入过程太长,交互不合理。所以后面选择使用弹窗的形式手动选择录入成绩类型,尽量简化交互方式,可以更快速的录入成绩。