超大文件调用讯飞语音听写解决方案
生活随笔
收集整理的這篇文章主要介紹了
超大文件调用讯飞语音听写解决方案
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
需求
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();}}/*** @param sourcefile 源文件路徑* @param targetfile 目標文件路徑* @param start 開始時間* @param end 結束時間* @param postname 文件后綴名* @return*/public static boolean cut(String sourcefile, String targetfile, int start, int end, String postname) {try {if (!sourcefile.toLowerCase().endsWith(postname) || !targetfile.toLowerCase().endsWith(postname)) {return false;}File wav = new File(sourcefile);if (!wav.exists()) {return false;}long t1 = getTimeLen(wav); //總時長(秒)if (start < 0 || end <= 0 || start >= t1 || end > t1 || start >= end) {return false;}FileInputStream fis = new FileInputStream(wav);long wavSize = wav.length() - 44; //音頻數據大小(44為128kbps比特率wav文件頭長度)long splitSize = (wavSize / t1) * (end - start); //截取的音頻數據大小long skipSize = (wavSize / t1) * start; //截取時跳過的音頻數據大小int splitSizeInt = Integer.parseInt(String.valueOf(splitSize));int skipSizeInt = Integer.parseInt(String.valueOf(skipSize));ByteBuffer buf1 = ByteBuffer.allocate(4); //存放文件大小,4代表一個int占用字節數buf1.putInt(splitSizeInt + 36); //放入文件長度信息byte[] flen = buf1.array(); //代表文件長度ByteBuffer buf2 = ByteBuffer.allocate(4); //存放音頻數據大小,4代表一個int占用字節數buf2.putInt(splitSizeInt); //放入數據長度信息byte[] dlen = buf2.array(); //代表數據長度flen = reverse(flen); //數組反轉dlen = reverse(dlen);byte[] head = new byte[44]; //定義wav頭部信息數組fis.read(head, 0, head.length); //讀取源wav文件頭部信息for (int i = 0; i < 4; i++) { //4代表一個int占用字節數head[i + 4] = flen[i]; //替換原頭部信息里的文件長度head[i + 40] = dlen[i]; //替換原頭部信息里的數據長度}byte[] fbyte = new byte[splitSizeInt + head.length]; //存放截取的音頻數據for (int i = 0; i < head.length; i++) { //放入修改后的頭部信息fbyte[i] = head[i];}byte[] skipBytes = new byte[skipSizeInt]; //存放截取時跳過的音頻數據fis.read(skipBytes, 0, skipBytes.length); //跳過不需要截取的數據fis.read(fbyte, head.length, fbyte.length - head.length); //讀取要截取的數據到目標數組fis.close();File target = new File(targetfile);FileOutputStream fos = new FileOutputStream(target);fos.write(fbyte);fos.flush();fos.close();} catch (IOException e) {e.printStackTrace();return false;}return true;}/*** @param file 目標文件* @return 獲取文件時長*/public static long getTimeLen(File file) {long tlen = 0;if (file != null && file.exists()) {Encoder encoder = new Encoder();try {MultimediaInfo m = encoder.getInfo(file);long ls = m.getDuration();tlen = ls / 1000;} catch (Exception e) {e.printStackTrace();}}return tlen;}public static byte[] reverse(byte[] array) {byte temp;int len = array.length;for (int i = 0; i < len / 2; i++) {temp = array[i];array[i] = array[len - 1 - i];array[len - 1 - i] = temp;}return array;}/*** @param sourcePath 源文件全路徑* @param tagretPath 臨時操作目錄路徑* @param suffixName 文件后綴名* @param filesize 切片時長* 將文件根據時長進行切片*/public static void sectionFile(String sourcePath, String tagretPath, String suffixName, int filesize) {File sourcefile = new File(sourcePath);File tagretfile = new File(tagretPath);if (!sourcefile.exists()) {throw new RuntimeException("源文件不存在,請檢查文件路徑");}//刪除臨時操作目錄下所有文件delAllFile(tagretPath);if (!tagretfile.exists()) {tagretfile.mkdir();}//獲取文件時長long timeLen = getTimeLen(sourcefile);//計算切片數int count = (int) (timeLen / filesize) + 1;for (int i = 0; i < count; i++) {int start = i * filesize;int end = start + filesize;if ((timeLen - start) < filesize) {end = (int) timeLen;}StringBuilder tagretpath = new StringBuilder(tagretPath);tagretpath.append(i).append(suffixName);//切片開始boolean result = cut(sourcePath, tagretpath.toString(), start, end, suffixName);//切片結束System.out.println(result);}}public static void main(String[] args) throws Exception {String fileName = "C:\\Users\\彭偉\\Desktop\\temp\\java.mp3";String transform = Video2Voice.transform(fileName);System.out.println("格式化后的文件名稱=====>" + transform);String targetName = "C:\\Users\\彭偉\\Desktop\\temp\\qie\\";sectionFile(transform, targetName, ".pcm", 60);}/*** @param path 文件夾路徑* @return 刪除一個文件夾下面所有的文件以及文件夾(不刪除自己)*/public static boolean delAllFile(String path) {boolean flag = false;File file = new File(path);if (!file.exists()) {return flag;}if (!file.isDirectory()) {return flag;}String[] tempList = file.list();File temp = null;for (int i = 0; i < tempList.length; i++) {if (path.endsWith(File.separator)) {temp = new File(path + tempList[i]);} else {temp = new File(path + File.separator + tempList[i]);}if (temp.isFile()) {temp.delete();}if (temp.isDirectory()) {delAllFile(path + "/" + tempList[i]);//遞歸刪除文件夾flag = true;}}file.delete();return flag;} }3,線程實現類
package com.itpengwei.util;import com.alibaba.fastjson.JSON; import com.itpengwei.po.Message; import com.itpengwei.xunfei.FileUtil; import com.itpengwei.xunfei.HttpUtil; import com.itpengwei.xunfei.WebIAT; import org.apache.commons.codec.binary.Base64;import java.net.URLEncoder; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;/*** @author 彭偉* @Date 2019/6/24 11:41* 訊飛語音聽寫線程池任務處理類*/ public class ThreadTask implements Runnable {private Map<String, String> header;//請求頭private String path;//目標文件夾private List<String> fileList;//文件名稱集合private int flag;//當前開啟的線程private int totalsize;//分片總數private ConcurrentHashMap<String, Object> rs;private int compensateTatol = 5;//失敗,補償總數public ThreadTask() {}public ThreadTask(Map<String, String> header, String path, List<String> fileList, int flag, int totalsize, ConcurrentHashMap<String, Object> rs) {this.header = header;this.path = path;this.fileList = fileList;this.flag = flag;this.totalsize = totalsize;this.rs = rs;}public void run() {Thread.currentThread().setName("處理訊飛語音分片線程----序號----》" + flag);System.out.println(Thread.currentThread().getName() + "--線程處理文件-----》" + fileList);for (int i = 0; i < fileList.size(); i++) {int compensateCount = 0;//失敗,補償計數器Message result = getResult(i, compensateCount);rs.put(fileList.get(i), result);}if (rs.size() == totalsize) {formatResult(rs);}}/*** @param i 當前處理文件角標* @param compensateCount 失敗補償計數器* @return 處理返回結果并添加重試機制*/public Message getResult(int i, int compensateCount) {Message message = null;try {byte[] audioByteArray = FileUtil.read(path + "\\" + fileList.get(i));String audioBase64 = new String(Base64.encodeBase64(audioByteArray), "UTF-8");String result = HttpUtil.doPost1(WebIAT.WEBIAT_URL, header, "audio=" + URLEncoder.encode(audioBase64, "UTF-8"));message = JSON.parseObject(result, Message.class);if (!message.getCode().equals("0")) {System.out.println(fileList.get(i) + "----文件處理失敗,異常碼" + message.getCode() + ",重試===>" + compensateCount + "次");compensateCount++;//記錄失敗次數if (compensateCount <= compensateTatol) {//在失敗補償次數內進行補償getResult(i, compensateCount);}}} catch (Exception e) {e.printStackTrace();System.out.println(fileList.get(i) + "----文件處理異常,重試第====》" + compensateCount + "次");compensateCount++;//記錄失敗次數if (compensateCount <= compensateTatol) {//在失敗補償次數內進行補償getResult(i, compensateCount);}}return message;}/*** @param result 未格式化結果,將結果格式化*/public void formatResult(Map<String, Object> result) {StringBuilder rs = new StringBuilder();for (int i = 0; i < result.size(); i++) {Message message = (Message) result.get(i + ".pcm");if (!message.getCode().equals("0")) {System.out.println("當前分片轉換失敗,分片名稱====>" + i + ".pcm");}rs.append(message.getData());}System.out.println("最后結果為====》" + rs);} }4,程序測試主類
package com.itpengwei.xunfei; /*** 語音聽寫 WebAPI 接口調用示例* 運行方法:直接運行 main() 即可* 結果: 控制臺輸出語音聽寫結果信息** @author iflytek*/import com.itpengwei.util.ThreadTask; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.digest.DigestUtils;import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;/*** 語音聽寫 WebAPI 接口調用示例 接口文檔(必看):https://doc.xfyun.cn/rest_api/%E8%AF%AD%E9%9F%B3%E5%90%AC%E5%86%99.html* webapi 聽寫服務參考帖子(必看):http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=38947&extra=* 語音聽寫WebAPI 服務,熱詞使用方式:登陸開放平臺https://www.xfyun.cn/后,找到控制臺--我的應用---語音聽寫---服務管理--上傳熱詞* (Very Important)創建完webapi應用添加聽寫服務之后一定要設置ip白名單,找到控制臺--我的應用--設置ip白名單,如何設置參考:http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=41891* 注意:熱詞只能在識別的時候會增加熱詞的識別權重,需要注意的是增加相應詞條的識別率,但并不是絕對的,具體效果以您測試為準。* 錯誤碼鏈接:https://www.xfyun.cn/document/error-code (code返回錯誤碼時必看)** @author iflytek*/public class WebIAT {public static final int ThreadPooSize = 5;public static ExecutorService executorService = Executors.newFixedThreadPool(ThreadPooSize);// 聽寫webapi接口地址public static final String WEBIAT_URL = "http://api.xfyun.cn/v1/service/v1/iat";// 應用APPID(必須為webapi類型應用,并開通語音聽寫服務,參考帖子如何創建一個webapi應用:http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=36481)private static final String APPID = "";// 接口密鑰(webapi類型應用開通聽寫服務后,控制臺--我的應用---語音聽寫---相應服務的apikey)private static final String API_KEY = "";// 音頻編碼private static final String AUE = "raw";// 引擎類型//(聽寫服務:engine_type為識別引擎類型,開通webapi聽寫服務后默認識別普通話與英文:示例音頻請在聽寫接口文檔底部下載// sms16k(16k采樣率、16bit普通話音頻、單聲道、pcm或者wav)// sms8k(8k采樣率、16bit普通話音頻、單聲道、pcm或者wav)// sms-en16k(16k采樣率,16bit英語音頻,單聲道,pcm或者wav)// sms-en8k(8k采樣率,16bit英語音頻,單聲道,pcm或者wav)// 請使用cool edit Pro軟件查看音頻格式是否滿足相應的識別引擎類型,不滿足則識別為空(即返回的data為空,code為0),或者識別為錯誤文本)// 音頻格式轉換可參考(訊飛開放平臺語音識別音頻文件格式說明):https://doc.xfyun.cn/rest_api/%E9%9F%B3%E9%A2%91%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E.htmlprivate static final String ENGINE_TYPE = "sms16k";// 后端點(取值范圍0-10000ms)private static final String VAD_EOS = "10000";// 音頻文件地址,示例音頻請在聽寫接口文檔底部下載private static final String AUDIO_PATH = "C:\\Users\\彭偉\\Desktop\\temp\\qie";/*** 組裝http請求頭*/private static Map<String, String> buildHttpHeader() throws UnsupportedEncodingException {String curTime = System.currentTimeMillis() / 1000L + "";String param = "{\"aue\":\"" + AUE + "\"" + ",\"engine_type\":\"" + ENGINE_TYPE + "\"" + ",\"vad_eos\":\"" + VAD_EOS + "\"}";String paramBase64 = new String(Base64.encodeBase64(param.getBytes("UTF-8")));String checkSum = DigestUtils.md5Hex(API_KEY + curTime + paramBase64);Map<String, String> header = new HashMap<String, String>();header.put("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");header.put("X-Param", paramBase64);header.put("X-CurTime", curTime);header.put("X-CheckSum", checkSum);header.put("X-Appid", APPID);return header;}/*** 聽寫 WebAPI 調用示例程序** @param args* @throws IOException*/public static void main(String[] args) throws Exception {testThreadPool();}/*** @throws Exception 測試使用線程池進行處理并合并結果*/public static void testThreadPool() throws Exception {//1,構建請求頭Map<String, String> header = buildHttpHeader();String path = "C:\\Users\\彭偉\\Desktop\\temp\\qie";File file = new File(path);String[] list = file.list();//開始計算每條線程執行多少分片int taskSize = list.length / ThreadPooSize;//任務分片結束int count = ThreadPooSize;if (taskSize <=0) {count = taskSize == 0 ? 0 : taskSize;}ConcurrentHashMap<String, Object> rs = new ConcurrentHashMap<String, Object>();//收集結果集for (int i = 0; i < count; i++) {//計算每條線程處理的任務,設置界限隔離int start = i * taskSize, end = taskSize * i + taskSize - 1;if (end < 0) {//第一次end = 0;}if (i == (count - 1)) {//最后一次end = list.length - 1;}List<String> fileList = new ArrayList<String>();for (int j = start; j <= end; j++) {fileList.add(list[j]);}executorService.execute(new ThreadTask(header, path, fileList, i, list.length, rs));}executorService.shutdown();//關閉線程池}}5,http工具類
package com.itpengwei.xunfei;import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.HashMap; import java.util.Map;/*** Http Client 工具類** @author 彭偉*/ public class HttpUtil {/*** 發送post請求,根據 Content-Type 返回不同的返回值** @param url* @param header* @param body* @return*/public static Map<String, Object> doPost2(String url, Map<String, String> header, String body) {Map<String, Object> resultMap = new HashMap<String, Object>();PrintWriter out = null;try {// 設置 urlURL realUrl = new URL(url);URLConnection connection = realUrl.openConnection();HttpURLConnection httpURLConnection = (HttpURLConnection) connection;// 設置 headerfor (String key : header.keySet()) {httpURLConnection.setRequestProperty(key, header.get(key));}// 設置請求 bodyhttpURLConnection.setDoOutput(true);httpURLConnection.setDoInput(true);out = new PrintWriter(httpURLConnection.getOutputStream());// 保存bodyout.print(body);// 發送bodyout.flush();if (HttpURLConnection.HTTP_OK != httpURLConnection.getResponseCode()) {System.out.println("Http 請求失敗,狀態碼:" + httpURLConnection.getResponseCode());return null;}// 獲取響應headerString responseContentType = httpURLConnection.getHeaderField("Content-Type");if ("audio/mpeg".equals(responseContentType)) {// 獲取響應bodybyte[] bytes = toByteArray(httpURLConnection.getInputStream());resultMap.put("Content-Type", "audio/mpeg");resultMap.put("sid", httpURLConnection.getHeaderField("sid"));resultMap.put("body", bytes);return resultMap;} else {// 設置請求 bodyBufferedReader in = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()));String line;String result = "";while ((line = in.readLine()) != null) {result += line;}resultMap.put("Content-Type", "text/plain");resultMap.put("body", result);return resultMap;}} catch (Exception e) {return null;}}/*** 發送post請求** @param url* @param header* @param body* @return*/public static String doPost1(String url, Map<String, String> header, String body) {String result = "";BufferedReader in = null;PrintWriter out = null;try {// 設置 urlURL realUrl = new URL(url);URLConnection connection = realUrl.openConnection();HttpURLConnection httpURLConnection = (HttpURLConnection) connection;// 設置 headerfor (String key : header.keySet()) {httpURLConnection.setRequestProperty(key, header.get(key));}// 設置請求 bodyhttpURLConnection.setDoOutput(true);httpURLConnection.setDoInput(true);out = new PrintWriter(httpURLConnection.getOutputStream());// 保存bodyout.print(body);// 發送bodyout.flush();if (HttpURLConnection.HTTP_OK != httpURLConnection.getResponseCode()) {System.out.println("Http 請求失敗,狀態碼:" + httpURLConnection.getResponseCode());return null;}// 獲取響應bodyin = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()));String line;while ((line = in.readLine()) != null) {result += line;}} catch (Exception e) {return null;}return result;}/*** 流轉二進制數組** @param in* @return* @throws IOException*/private static byte[] toByteArray(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();} }pom文件
<dependencies><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.4</version></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.9</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.1.37</version></dependency></dependencies>maven中沒有的jar,自己添加到lib下去的包
jave-1.0.2.jar
下載地址
總結
以上是生活随笔為你收集整理的超大文件调用讯飞语音听写解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jax指标的用法_股票指标参数用法.do
- 下一篇: ADC0804工作原理及过程