需求

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

下载地址

超大文件调用讯飞语音听写解决方案相关推荐

  1. python调用讯飞语音听写(流式版)

    目录 一.引入所需要的包 二.定义相关常量 三.生成请求url 四.音频相关 1. 首先使用pyaudio获取音频流 2. 从音频流中获取音频并上传到接口 五.语音识别接口调用 六.返回结果处理 七. ...

  2. android 讯飞语音 提示组件未安装,讯飞语音听写API 未安装组件 错误码21001

    在使用讯飞语音听写时,使用云端听写而不是使用本地出现这个未安装组件错误那可能就是so文件没有成功导入.文档中都是ADT环境的配置,在AndroidStudio中jar包等都和ADT差别不大,但是SO文 ...

  3. uni-app 调用讯飞语音。

    uni-app 调用讯飞语音. // //讯飞语音输入接口voice() {var me = this;var options = {};options.engine = 'iFly';options ...

  4. 关于讯飞语音听写RecognizerDialog 去除这个弹框view中的任何控件 更改其中内容

    | | | 上面这是讯飞语音听写sdk的听写弹框下面两张是自己项目中改的 我发现他sdk的assets下的iflytek 中的recognize.xml 里面虽然是乱码 但是可以看到的一些东西是此倾听 ...

  5. 讯飞语音听写常见问题

    最近,项目中要用讯飞语音听写的功能,于是在官网学习了一下,并尝试做个小Demo,期间遇到了一些问题,列举一下: (1)常报错误码11201,是未经授权的语音应用,意思是官方每天允许你使用的次数你已经用 ...

  6. H5调用讯飞语音接口实现在线语音听写测评

    韩顺平老师是我的人生导师,所以,老规矩,先看效果,后讲解:下面是效果图(页面较大,分屏效果) 这是在线语音听写: 这是在线语音测评: 下面我们来看看具体的代码实现: 1.在讯飞语音平台注册,创建一个应 ...

  7. 前端之实现讯飞语音听写(流式版)

    第一次接到语音需求,用了几年的微信,你有关注过微信语音实时转文字功能吗?反正我是经常用,在这人山人海的大城市,为了解放双手又能及时回复好友,我经常用语音转文字. 没想到,一时用一时爽,自己开发火葬场. ...

  8. AndroidStudio快速开发讯飞语音听写

    好久没更新了,差点就忘记这个东东了,不过好在一闲下来就赶紧更新一波 哈哈^_^ **此博客主要针对将要进行语音开发的小白,如有不对的地方,请指正 到科大讯飞官网注册账号并登陆,选择单个服务SDK下载 ...

  9. Unity2021接入讯飞语音听写(Android)

    使用的引擎工具: Unity2021.3.19 android-studio-2021.1.21 第一步: 新建一个Android项目(工程名字随便啦) 然后新建一个library (同上,库名自己命 ...

  10. html5语音听写流式,iOS 讯飞语音听写(流式版)

    最近项目中用到了讯飞的语音识别,然后稍微看了一下,里面有几个值得注意的点,记录一下,先说语音听写(流式版),实时语音转写后期会附上 ,文末有 demo //语音听写(流式版) 语音听写流式版其实没设么 ...

最新文章

  1. 给UIScrollView添加category实现UIScrollView的轮播效果
  2. mysql双主使用reset master清除日志要小心
  3. 18 常用模块 random shutil shevle logging sys.stdin/out/err
  4. 继电器rc吸收电路取值_一文读懂继电器的工作原理以及驱动电路
  5. 管理低代码公民开发人员的8个技巧
  6. Asp.Net MVC学习总结(三)——过滤器你怎么看?
  7. linux编译android源码,ubuntu16.04 Android源码下载编译
  8. 天涯明月刀龙吟曲服务器维护,青龙大区合服公告 12月11进行数据互通
  9. 电感的能量储存在哪里-深度解析(4)
  10. 理论小知识:集合之srem
  11. vue 点击动态展示不同的图片
  12. [转载From少数派] 码字必备:18 款优秀的 Markdown 写作工具 | 2015 年度盘点
  13. 智能微型机器人可随周围环境“变身”
  14. 计算机二级office的考试内容,计算机二级office考试内容有啥
  15. mac性能比服务器好,性能上再次交锋 三大最新OS测试大比拼
  16. 如何给注册中心锦上添花?
  17. 爱豆教育:掌握这7大技巧,让你的亚马逊店铺快速出单
  18. CHERRY G80 3000L 使用一月有感
  19. 物联网网关/智能网关/工控机网关/边缘计算网关/规约采集转换器/通讯管理机等开发总结
  20. 豆瓣7777人评价的小说,一出版就打满马赛克!

热门文章

  1. 我的计算机c盘计划,我的电脑C盘空间不够,该怎么样清理?
  2. ArcGIS | 02小技巧-三调地类转换
  3. 为何Emacs和Vim被称为两大神器
  4. 3D阴影效果css3按钮代码
  5. html日期函数,YEAR 函数 (时间日期函数)
  6. 电脑从硬盘启动计算机,电脑怎么设置第一启动项为硬盘
  7. 华尔街最闪耀的新星:量子计算
  8. Python 3 字符串 rsplit( ) 方法
  9. 2021年第四届“安洵杯”网络安全挑战赛Writeup
  10. 【rzxt】笔者支招:电脑的散热大户显卡温度过高如何解决