功能要求:使用java 调用科大讯飞 实现会议录音,语音转写会议既要功能。
科大讯飞官网有api,还有具体的实例,可以实现关于语音需求的大部分功能。地址如下:
https://www.xfyun.cn/doc/platform/quickguide.html。
先注册,注册后会生成appid和SECRET_KEY 。
根据我的个人需求:我调用的是这个api方法即可。
**
这里涉及一个科大讯飞的sdk包,需要下载,并配置maven。**
具体代码如下,根据提供的实例改写:涉及到了5个方法类第一、LfasrSDKDemo,主函数入口,功能包括一个录音界面方法,以及语音转写方法调用。
package com.jsbs.fxffjcpt.utils.LuyinHy.sdkTurn;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.iflytek.msp.lfasr.LfasrClient;
import com.iflytek.msp.lfasr.model.Message;
import com.jsbs.fxffjcpt.utils.LuyinHy.Luyin;
import com.jsbs.fxffjcpt.utils.LuyinHy.TxtWriteUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;/*** <p>Title : SDK 调用实例</p>* <p>Description : </p>* <p>Date : 2020/4/20 </p>** @author : hejie*/
@Component
public class LfasrSDKDemo {private static final String APP_ID = ""; //注册后你自己的appidprivate static final String SECRET_KEY = ""; //注册后你自己SECRET_KEY //本地文件保存地址static String file = "C:\\Users\\chenjiaxing\\Desktop\\bs\\save";@Autowiredprivate static TxtWriteUtil txtWriteUtil;/*** 简单 demo 样例** @throws InterruptedException e*/public static Map standard(String path) throws InterruptedException {Map<String,String> map=new HashMap<>();//1、创建客户端实例LfasrClient lfasrClient = LfasrClient.getInstance(APP_ID, SECRET_KEY);//2、上传Message task = lfasrClient.upload(path);String taskId = task.getData();System.out.println("转写任务 taskId:" + taskId);//3、查看转写进度int status = 0;while (status != 9) {Message message = lfasrClient.getProgress(taskId);if(message.getOk()==-1){System.out.println("异常----------------------"+message.getFailed());break;}else {JSONObject object = JSON.parseObject(message.getData());status = object.getInteger("status");System.out.println(message.getData());TimeUnit.SECONDS.sleep(2);}}//4、获取结果Message result = lfasrClient.getResult(taskId);System.out.println("转写结果: \n" + result.getData());if(result.getOk()==-1){map.put("code","400");map.put("result",result.getFailed());}else{String content="";ResDemo [] array = new Gson().fromJson(result.getData(),ResDemo[].class);List<ResDemo> list = Arrays.asList(array);for (ResDemo res:list) {content+=res.getOnebest();}//写入会议既要txtWriteUtil.write(path,content);map.put("code","200");map.put("result",content);}return map;//退出程序,关闭线程资源,仅在测试main方法时使用。// System.exit(0);}/*** 带有业务参数,调用样例** @throws InterruptedException e*/private static void businessExtraParams(String path) throws InterruptedException {//1、创建客户端实例LfasrClient lfasrClient = LfasrClient.getInstance(APP_ID, SECRET_KEY);//2、上传//2.1、设置业务参数Map<String, String> param = new HashMap<>(16);//是否开启分词:默认 false//param.put("has_participle","true");//转写结果中最大的候选词个数:默认:0,最大不超过5//param.put("max_alternatives","2");//是否开启角色分离:默认为false//param.put("has_seperate","true");//发音人个数,可选值:0-10,0表示盲分:默认 2//param.put("speaker_number","3");//角色分离类型 1-通用角色分离;2-电话信道角色分离:默认 1//param.put("role_type","1");//语种: cn-中文(默认);en-英文(英文不支持热词)param.put("language", "cn");//垂直领域个性化:法院-court;教育-edu;金融-finance;医疗-medical;科技-tech//param.put("pd","finance");Message task = lfasrClient.upload(path, param);String taskId = task.getData();System.out.println("转写任务 taskId:" + taskId);//3、查看转写进度int status = 0;while (status != 9) {Message message = lfasrClient.getProgress(taskId);JSONObject object = JSON.parseObject(message.getData());status = object.getInteger("status");System.out.println(message.getData());TimeUnit.SECONDS.sleep(2);}//4、获取结果Message result = lfasrClient.getResult(taskId);System.out.println("转写结果: \n" + result.getData());//退出程序,关闭线程资源,仅在测试main方法时使用。System.exit(0);}/*** 设置网络代理,调用样例** @throws InterruptedException e*/private static void netProxy(String path) throws InterruptedException {//1、创建客户端实例, 设置网络代理LfasrClient lfasrClient = LfasrClient.getInstance(APP_ID, SECRET_KEY, "http://x.y.z/");//LfasrClient lfasrClient = LfasrClient.getInstance(APP_ID, SECRET_KEY);//2、上传//2.1、设置业务参数Map<String, String> param = new HashMap<>(16);//语种: cn-中文(默认);en-英文(英文不支持热词)param.put("language", "cn");//垂直领域个性化:法院-court;教育-edu;金融-finance;医疗-medical;科技-tech//param.put("pd","finance");Message task = lfasrClient.upload(path, param);String taskId = task.getData();System.out.println("转写任务 taskId:" + taskId);//3、查看转写进度int status = 0;while (status != 9) {Message message = lfasrClient.getProgress(taskId);JSONObject object = JSON.parseObject(message.getData());status = object.getInteger("status");System.out.println(message.getData());TimeUnit.SECONDS.sleep(2);}//4、获取结果Message result = lfasrClient.getResult(taskId);System.out.println("转写结果: \n" + result.getData());//退出程序,关闭线程资源,仅在测试main方法时使用。System.exit(0);}/*** 性能调优参数,调用样例** @throws InterruptedException e*/private static void performance(String path) throws InterruptedException {//1、创建客户端实例, 设置性能参数LfasrClient lfasrClient =LfasrClient.getInstance(APP_ID,SECRET_KEY,10, //线程池:核心线程数50, //线程池:最大线程数50, //网络:最大连接数10000, //连接超时时间30000, //响应超时时间null);//LfasrClient lfasrClient = LfasrClient.getInstance(APP_ID, SECRET_KEY);//2、上传//2.1、设置业务参数Map<String, String> param = new HashMap<>(16);//语种: cn-中文(默认);en-英文(英文不支持热词)param.put("language", "cn");//垂直领域个性化:法院-court;教育-edu;金融-finance;医疗-medical;科技-tech//param.put("pd","finance");Message task = lfasrClient.upload(path, param);String taskId = task.getData();System.out.println("转写任务 taskId:" + taskId);//3、查看转写进度int status = 0;while (status != 9) {Message message = lfasrClient.getProgress(taskId);JSONObject object = JSON.parseObject(message.getData());status = object.getInteger("status");System.out.println(message.getData());TimeUnit.SECONDS.sleep(2);}//4、获取结果Message result = lfasrClient.getResult(taskId);System.out.println("转写结果: \n" + result.getData());//退出程序,关闭线程资源,仅在测试main方法时使用。System.exit(0);}/*** 获取路径下的所有文件/文件夹* @param directoryPath 需要遍历的文件夹路径* @param isAddDirectory 是否将子文件夹的路径也添加到list集合中* @return*/public static List<String> getAllFile(String directoryPath,boolean isAddDirectory) {List<String> list = new ArrayList<String>();File baseFile = new File(directoryPath);if (baseFile.isFile() || !baseFile.exists()) {return list;}File[] files = baseFile.listFiles();for (File file : files) {if (file.isDirectory()) {if(isAddDirectory){list.add(file.getAbsolutePath());}list.addAll(getAllFile(file.getAbsolutePath(),isAddDirectory));} else {list.add(file.getAbsolutePath());}}return list;}/************************************************/public static class NumFrame extends JFrame implements ActionListener {//设置面板JPanel topjp = new JPanel();JPanel downjp = new JPanel();//按钮JButton jb1 = new JButton("开始录音");JButton jb2 = new JButton("停止录音");//创建JTextField,16表示16列,用于JTextField的宽度显示而不是限制字符个数JTextField textField = new JTextField(10);//标签JLabel label = new JLabel("会议号:");JLabel label2=new JLabel("输入会议号开始录音");String openstr="";public NumFrame() {//标题super("录音控件");setSize(800, 400);//窗口大小setLocationRelativeTo(null);//居中//停止按钮置灰jb1.setEnabled(true);jb2.setEnabled(false);label2.setForeground(Color.red);topjp.add(label2);topjp.add(label);topjp.add(textField);//添加控件topjp.add(jb1);topjp.add(jb2);add(topjp, BorderLayout.NORTH);List<OgMeet> list=new ArrayList<>();try {list= getMeets(999999,1);} catch (IOException e) {e.printStackTrace();}// 表头(列名)Object[] columnNames = {"会议号", "会议名称", "开始时间", "结束时间"};Object[][] rowData=new Object[list.size()][columnNames.length];for(int i=0;i<list.size();i++){for(int j=0;j<columnNames.length;j++){rowData[i][0]=list.get(i).getMeetId();rowData[i][1]=list.get(i).getMeetName();rowData[i][2]=list.get(i).getStartTime();rowData[i][3]=list.get(i).getEndsTime();}}// 创建一个表格,指定 所有行数据 和 表头downjp.setLayout(new BorderLayout());JTable table = new JTable(rowData, columnNames);//创建显示表格的滚动面板JScrollPane scrollpane=new JScrollPane(table);// 把 表头 添加到容器顶部(使用普通的中间容器添加表格时,表头 和 内容 需要分开添加)downjp.add(table.getTableHeader(), BorderLayout.NORTH);// 把 表格内容 添加到容器中心downjp.add(scrollpane, BorderLayout.CENTER);add(downjp, BorderLayout.CENTER);//按钮点击事件jb1.addActionListener((e) -> {onButtonOk();});jb2.addActionListener((e) -> {stopButton();});setDefaultCloseOperation(EXIT_ON_CLOSE);//关闭后退出}@Overridepublic void actionPerformed(ActionEvent e) {}//开始录音事件处理private void onButtonOk(){openstr = textField.getText();//获取输入内容//判断是否输入了if(openstr.equals("")){JOptionPane.showMessageDialog(null,"您还没有输入会议号" );}else{//录音Luyin luyin=new Luyin();luyin.captureAudio(openstr);jb1.setEnabled(false);jb2.setEnabled(true);JOptionPane.showMessageDialog(null,"会议"+openstr+"开始录音" );}}//结束录音事件处理private void stopButton(){String str = textField.getText();//获取输入内容//判断是否输入了if(str.equals("")){JOptionPane.showMessageDialog(this,"您还没有输入会议号" );}else if(!openstr.equals(str)){JOptionPane.showMessageDialog(this,"当前会议号未开始录音" );}else{// 停止录音try {Luyin luyin=new Luyin();luyin.stopLuyin();jb1.setEnabled(true);jb2.setEnabled(false);//弹框提示JOptionPane.showMessageDialog(null, "会议既要生成中,请稍后", "标题",JOptionPane.PLAIN_MESSAGE);//生成会议既要文件List<String> stringList= getAllFile(file+"\\"+str ,false);String content="";for (String path:stringList) {if(path.contains(".pcm") || path.contains("wav")|| path.contains("mp3")){if(standard(path).get("code").equals("200")){content+= standard(path).get("result")+"\n";}}}//调用文件上传接口List<String> allFiles= getAllFile(file+"\\"+str ,false);for (String apath: allFiles){System.out.println("----------------------"+apath);upload(apath);}JOptionPane.showMessageDialog(null,"会议既要成已生成,上传服务器中!本地路径为:" +file+"\\"+str);} catch (Exception e) {e.printStackTrace();}}}}//党建登录接口地址private static String loginUrlPath="http://192.168.3.32:8090/login";//党建会议列表接口地址private static String getHyUrlPath="http://192.168.3.32:8090/organizelife/meet/list";//党建会议文件上传接口地址private static String fileUploadPath="http://192.168.3.32:8090/common/upload";//调用党建接口 登录列表public static String getDangjianLogin() throws IOException {//body参数String jsonParam="{\"username\" : \"admin\", \"password\" : \"admin123\"," +" \"code\" : \"a1223\"}";HttpClient client = HttpClients.createDefault();HttpPost request = new HttpPost(loginUrlPath);// 设置文件类型:request.setHeader("Content-Type", "application/json;charset=UTF-8");//传参StringEntity se = new StringEntity(jsonParam);se.setContentType("text/json");request.setEntity(se);HttpResponse response = client.execute(request);HttpEntity entity = response.getEntity();String result = EntityUtils.toString(entity, "UTF-8");return result;}//调用党建接口 获取会议列表public static String getHy(Integer pageSize,Integer pageNum,String token) throws IOException {HttpClient client = HttpClients.createDefault();HttpGet request = new HttpGet(getHyUrlPath+"?pageSize="+pageSize+"&pageNum="+pageNum);// 设置文件类型:request.setHeader("Authorization", token);//传参HttpResponse response = client.execute(request);HttpEntity entity = response.getEntity();String result = EntityUtils.toString(entity, "UTF-8");return result;}/*** ** 获取会议列表* */private static List<OgMeet> getMeets(Integer pageSize,Integer pageNum) throws IOException {List<OgMeet> ogMeets=new ArrayList<>();JSONObject jsonObject= (JSONObject) JSONObject.parse(getDangjianLogin());System.out.println("jsonObject=================="+jsonObject);if(jsonObject.get("code").equals(200)){System.out.println("token===================="+jsonObject.get("token"));String sss= getHy(pageSize,pageNum,jsonObject.get("token").toString());JSONObject json= (JSONObject) JSONObject.parse(sss);String xx= String.valueOf(json.get("rows"));JSONArray jsonArray = (JSONArray) JSONArray.parse(xx);for (Object object:jsonArray) {JSONObject js= (JSONObject) JSONObject.parse(String.valueOf(object));OgMeet ogMeet=new OgMeet();ogMeet.setMeetId(js.get("meetId").toString());ogMeet.setMeetName((String)js.get("meetName"));ogMeet.setStartTime((String)js.get("startTime"));ogMeet.setEndsTime((String)js.get("endsTime"));ogMeets.add(ogMeet);}}return ogMeets;}//调用党建接口 上传public static String upload(String filepath) throws IOException {HttpClient client = HttpClients.createDefault();HttpPost request = new HttpPost(fileUploadPath);// 设置文件类型:File file = new File(filepath);MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();multipartEntityBuilder.addBinaryBody("file",file);//multipartEntityBuilder.addPart("comment", new StringBody("This is comment", ContentType.TEXT_PLAIN));multipartEntityBuilder.addTextBody("comment", "this is comment");HttpEntity httpEntity = multipartEntityBuilder.build();request.setEntity(httpEntity);//传参HttpResponse response = client.execute(request);HttpEntity entity = response.getEntity();String result = EntityUtils.toString(entity, "UTF-8");return result;}/*** 注意:同时只能执行一个 示例** @param args a* @throws InterruptedException e*/public static void main(String[] args) throws Exception {// getMeets(99999,1);new NumFrame().setVisible(true);// 示例-1:标准用法/*String file = "C:\\Users\\chenjiaxing\\Desktop\\bs\\save";//生成会议既要文件String hyid="2021";List<String> stringList= getAllFile(file+"\\"+hyid ,false);for (String path:stringList) {if(path.contains(".pcm") || path.contains("wav")){standard(path);}}*/// 示例-2:使用扩展业务参数//businessExtraParams();// 示例-3:使用网络代理//netProxy();// 示例-4:使用性能调优参数//performance();}}
第二、OgMeet,自定义的会议对象实体类,没什么好说的
package com.jsbs.fxffjcpt.utils.LuyinHy.sdkTurn;import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;import java.util.Date;/*** 组织生活-会议对象 og_meet* * @author Donfei* @date 2021-04-23*/
public class OgMeet extends BaseEntity
{private static final long serialVersionUID = 1L;/** 会议id */private String meetId;/** 会议名称 */private String meetName;/** 会议开始时间 */@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private String startTime;/** 会议结束时间 */@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private String endsTime;public String getMeetId() {return meetId;}public void setMeetId(String meetId) {this.meetId = meetId;}public String getMeetName() {return meetName;}public void setMeetName(String meetName) {this.meetName = meetName;}public String getStartTime() {return startTime;}public void setStartTime(String startTime) {this.startTime = startTime;}public String getEndsTime() {return endsTime;}public void setEndsTime(String endsTime) {this.endsTime = endsTime;}
}
第三、自定义的ResDemo 科大讯飞实体类,没什么好说的。
package com.jsbs.fxffjcpt.utils.LuyinHy.sdkTurn;/**** 构造科大讯飞接口返回的实体类** */
public class ResDemo {private String bg;private String ed;private String onebest;private String speaker;public String getBg() {return bg;}public void setBg(String bg) {this.bg = bg;}public String getEd() {return ed;}public void setEd(String ed) {this.ed = ed;}public String getOnebest() {return onebest;}public void setOnebest(String onebest) {this.onebest = onebest;}public String getSpeaker() {return speaker;}public void setSpeaker(String speaker) {this.speaker = speaker;}
}
第四、Luyin,关键的录音方法,调用本地电脑的麦克风,保存录音文件功能
package com.jsbs.fxffjcpt.utils.LuyinHy;import org.springframework.stereotype.Component;import javax.sound.sampled.*;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;/**** @author admin_70*/
@Component
public class Luyin {private static final long serialVersionUID = 1L;private static AudioFormat audioFormat;private static TargetDataLine targetDataLine;private static long starttime;private static String filepath= "C:\\Users\\chenjiaxing\\Desktop\\bs\\save\\";public static void main(String args[]) {// new Luyin();}/***停止录音*/public void stopLuyin(){targetDataLine.stop();targetDataLine.close();System.out.println("---------停止录音----------录音了"+(System.currentTimeMillis()-starttime)/1000+"秒!");}/***开启录音线程*/public Map captureAudio(String hyid){Map<String,String> resultMap=new HashMap<>();//随机文件名Date date = new Date(System.currentTimeMillis());SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");String fileName = simpleDateFormat.format(date);try {System.out.println("---------------开启录音线程--------------------");starttime = System.currentTimeMillis();audioFormat = getAudioFormat();//构造具有线性 PCM 编码和给定参数的 AudioFormat。DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat);//根据指定信息构造数据行的信息对象,这些信息包括单个音频格式。此构造方法通常由应用程序用于描述所需的行。//lineClass - 该信息对象所描述的数据行的类//format - 所需的格式targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);//如果请求 DataLine,且 info 是 DataLine.Info 的实例(至少指定一种完全限定的音频格式),//上一个数据行将用作返回的 DataLine 的默认格式。//开启线程new CaptureThread(hyid,fileName).start();resultMap.put("code","200");resultMap.put("result",fileName);} catch (Exception e){e.printStackTrace();resultMap.put("code","400");resultMap.put("result",e.getMessage());}return resultMap;}private AudioFormat getAudioFormat() {float sampleRate = 8000F;// 8000,11025,16000,22050,44100 采样率int sampleSizeInBits = 16;// 8,16 每个样本中的位数int channels = 2;// 1,2 信道数(单声道为 1,立体声为 2,等等)boolean signed = true;// true,falseboolean bigEndian = false;// true,false 指示是以 big-endian 顺序还是以 little-endian 顺序存储音频数据。return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed,bigEndian);//构造具有线性 PCM 编码和给定参数的 AudioFormat。}public class CaptureThread extends Thread {private String hyid;private String fileName;public CaptureThread(String hyid,String fileName){this.hyid = hyid;this.fileName=fileName;}public void run() {//指定的文件类型AudioFileFormat.Type fileType = AudioFileFormat.Type.WAVE;//设置文件夹扩展名String fName = filepath+hyid;// 输出的文件流File sf=new File(fName);if(!sf.exists()){sf.mkdirs();}File audioFile=new File(sf.getPath()+"\\"+fileName+".wav");//根据选择的单选按钮。try {targetDataLine.open(audioFormat);//format - 所需音频格式targetDataLine.start();//当开始音频捕获或回放时,生成 START 事件。AudioSystem.write(new AudioInputStream(targetDataLine),fileType, audioFile);//new AudioInputStream(TargetDataLine line):构造从指示的目标数据行读取数据的音频输入流。该流的格式与目标数据行的格式相同,line - 此流从中获得数据的目标数据行。//stream - 包含要写入文件的音频数据的音频输入流//fileType - 要写入的音频文件的种类//out - 应将文件数据写入其中的外部文件} catch (Exception e) {e.printStackTrace();}}}
}
第五、TxtWriteUtil,txt文件流保存,用于保存语音转写后的会议纪要。
package com.jsbs.fxffjcpt.utils.LuyinHy;import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;public class TxtWriteUtil {public static void main(String[] args) {String content="哎哎哎111222";//write(content);}public static void write(String path,String content){//替换全路径String filepath="";if(path.contains(".wav")){filepath= path.replace("wav","txt");}else if(path.contains(".pcm")){filepath= path.replace("pcm","txt");}else if(path.contains(".mp3")){filepath= path.replace("mp3","txt");}//*************************写入************try {// 输出的文件流File sf=new File(filepath);if(!sf.exists()){sf.createNewFile();}FileWriter fw = new FileWriter(sf);BufferedWriter bw = new BufferedWriter(fw);bw.write(content);System.out.println("写入---------"+content);bw.flush();bw.close();fw.close();} catch (IOException e) {e.printStackTrace();}}}
第六、语音转写sdk,maven配置,这里的sdk需要放到resources目录下,自定义配置
<!--语音转写 SDK--><dependency><groupId>com.iflytek.msp.lfasr</groupId><artifactId>lfasr-sdk</artifactId><version>3.0.1</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/lfasr-sdk-3.0.1.jar</systemPath></dependency><!--第三方依赖包--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.67</version><scope>compile</scope></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpmime</artifactId><version>4.5</version></dependency>`
如此便可实现录音以及会议既要功能,代码中有些是用于调用别人接口联调的,可能会出现异常,仅供参考。资源下载:https://download.csdn.net/download/weixin_43832166/19932122