需求
2019年6月20日
1,案场销售和客服的谈话录音,需要将其翻译成文字,入库,后台审核。
需求分析
技术选型
讯飞接口
语音有方言
1,因为方言的存在,只能使用语音听写接口。
拿到的是语音文件
1,所以只能使用WebAPI方式调用讯飞接口。
讯飞语音听写有音频格式限制
1,所以只能使用ffmpeg进行格式转换
2,[下载地址](http://www.ffmpeg.org/download.html)
3,[基本使用](https://doc.xfyun.cn/rest_api/%E9%9F%B3%E9%A2%91%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E.html)
接口对语音时长限制60秒内
1,将转换格式后的文件根据时长进行分片。
2,采用多线程将分割后的文件交由讯飞处理,等待返回结果(语音听写没有异步接口)。
3,根据分片计算每个线程需要处理的分片数量,采用分页思路,决定开启多少线程。
4,将处理结果放入一个并发集合中(生产环境在redis中)
5,如果当期分片失败,或者异常,使用补偿机制,默认5次,如果还失败手动补偿。
6,将结果集进行拼接格式化。
实现思路
1,用户分片上传文件到服务器,服务器进行合并。
2,文件合并完成后生成一条数据,将id放入MQ中。
3,消费者接收到MQ中的消息,将服务器文件下载到当前机器,进行格式化,分片操作。
4,分片完成后进行任务分配。
5,获取执行结果,进行合并,并将其更新到数据库中。
总结
1,典型的生产-消费模式
2,使用MQ原因,因为并不是主线业务,不能让其占用了太多内存和带宽,所以采用单线程处理。
3,对大文件进行先分后合中间采用多线程提高效率。
4,采用补偿机制,提高结果的可靠性,最后提供手动补偿,也是防止程序补偿失败问题。
实现代码
1,文件转换工具类
package com.itpengwei.util;
import java.io.File;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
/**
* @author 彭伟
* @Date 2019/6/21 15:50
* 文件格式转换工具类
*/
public class Video2Voice {
// private static Logger logger = Logger.getLogger(Video2Voice.class);
/**
* 将音频文件转16k的pcm文件
*
* @param fileName
* @return 音频文件名
* @throws Exception
*/
public static String transform(String fileName) throws Exception {
File file = new File(fileName);
if (!file.exists()) {
System.out.println("文件不存在:" + fileName);
throw new RuntimeException("文件不存在:" + fileName);
}
// 讯飞现在支持pcm,wav的语音流文件
String name = fileName.substring(0, fileName.lastIndexOf(".")) + ".pcm";
System.out.println("获取到的音频文件:" + name);
// 提取视频中的音频文件。 根据讯飞要求设置采样率, 位数,
File ffmpegPath = new File("D:\\软件\\ffmpeg-20190620-86f04b9-win64-static\\bin\\ffmpeg.exe");
String cmd = ffmpegPath + " -y -i " + fileName + " -acodec pcm_s16le -f s16le -ac 1 -ar 16000 " + name;
System.out.println("命令===》" + cmd);
File tagret = new File(name);
if (tagret.exists()) {
System.out.println("文件存在,删除文件,进行覆盖操作");
tagret.delete();
}
Process process = Runtime.getRuntime().exec(cmd);// 执行命令
InputStreamReader ir = new InputStreamReader(process.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
String line;
// 输出结果,需要有这部分代码, 否则不能生产抽取的音频文件
while ((line = input.readLine()) != null) {
System.out.println(line);
}
process.destroy();
return name;
}
public static void main(String[] args) {
try {
String transform = transform("C:\\Users\\彭伟\\Desktop\\temp\\java.mp3");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2,文件分片工具类
package com.itpengwei.xunfei;
import com.itpengwei.util.Video2Voice;
import it.sauronsoftware.jave.Encoder;
import it.sauronsoftware.jave.MultimediaInfo;
import java.io.*;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author 彭伟
* @Date 2019/6/21 15:50
* 文件操作工具类
*/
public class FileUtil {
/**
* 读取文件内容为二进制数组
*
* @param filePath
* @return
* @throws IOException
*/
public static byte[] read(String filePath) throws IOException {
InputStream in = new FileInputStream(filePath);
byte[] data = inputStream2ByteArray(in);
in.close();
return data;
}
/**
* 流转二进制数组
*
* @param in
* @return
* @throws IOException
*/
private static byte[] inputStream2ByteArray(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n = 0;
while ((n = in.read(buffer)) != -1) {
out.write(buffer, 0, n);
}
return out.toByteArray();
}
/**
* 保存文件
*
* @param filePath
* @param fileName
* @param content
*/
public static void save(String filePath, String fileName, byte[] content) {
try {
File filedir = new File(filePath);
if (!filedir.exists()) {
filedir.mkdirs();
}
File file = new File(filedir, fileName);
OutputStream os = new FileOutputStream(file);
os.write(content, 0, content.length);
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}