1.准备工作

1.1 开通讯飞开放平台账号

https://www.xfyun.cn/

可以领取免费套餐:https://www.xfyun.cn/free

1.2 下载语音处理类库TarsosDSP

https://0110.be/releases/TarsosDSP/TarsosDSP-2.4/TarsosDSP-2.4.jar

建议发布到maven仓库,便于集成。参考命令(本地安装):

mvn install:install-file -Dfile=F:\tarsosdsp\TarsosDSP-2.4.jar -DgroupId=be.tarsos -DartifactId=dsp -Dversion=2.4 -Dpackaging=jar

POM文件添加依赖:

        <dependency><groupId>be.tarsos</groupId><artifactId>dsp</artifactId><version>2.4</version></dependency>

1.3 下载媒体处理软件ffmpeg

科大讯飞的接口不支持AMR等格式的音频文件,需要转换为PCM格式,因此需要使用功能强大的ffmpeg进行转码。为简化ffmpeg的使用,我们将利用TarsosDSP类库。TarsosDSP类库会在指定位置启动ffmpeg命令,我们预先下载好,可以避免TarsosDSP类库动态下载ffmpeg导致的长时间等待。

ffmpeg下载地址:

https://0110.be/releases/TarsosDSP/TarsosDSP-static-ffmpeg/linux_64_bits_ffmpeg

https://0110.be/releases/TarsosDSP/TarsosDSP-static-ffmpeg/windows_64_bits_ffmpeg.exe

win7下的存储路径:

C:\Users\<你的用户名>\AppData\Local\Temp\windows_64_bits_ffmpeg.exe

linux下的存储路径:

/tmp/linux_64_bits_ffmpeg

--------------------------------------------------------        华丽的分割线    ------------------------------------------------------------

下面都是干(yuan)货(ma)。

2. 定义一个音频转换处理工具类

我们首先定义一个音频转换处理的工具类,用于将任意格式的音频文件转换为科大讯飞接口支持的PCM格式。

工具类主体代码:

@Slf4j
public class AudioTransformUtil {/*** 将任意的音频文件转换为PCM格式的文件*/public static void convertAudioFileToPCM(String originalFile, String targetFile) {try {double speedFactor = 1; // 变速率 (0,2) 大于1为加快语速,小于1为放慢语速double rateFactor = 1; // 音调变化率 (0,2) 大于1为降低音调(深沉),小于1为提升音调(尖锐)// 变声处理器(似乎不是必要的功能,但具有令人惊奇的效果)WaveformSimilarityBasedOverlapAdd waveformSimilarityBasedOverlapAdd = new WaveformSimilarityBasedOverlapAdd(WaveformSimilarityBasedOverlapAdd.Parameters.speechDefaults(speedFactor, 8000));  // 8000是采样率,不懂啥意思,但也没影响AudioDispatcher dispatcher = AudioDispatcherFactory.fromPipe(originalFile, 8000,   // fromPipe是关键。这里会启动ffmpeg命令进行转码,并使用管道(pipe)与ffmpeg命令通信waveformSimilarityBasedOverlapAdd.getInputBufferSize(),waveformSimilarityBasedOverlapAdd.getOverlap());waveformSimilarityBasedOverlapAdd.setDispatcher(dispatcher);AudioOutputToFile out = new AudioOutputToFile(targetFile);   // AudioOutputToFile是我们自定义的文件输出工具类,代码在后面dispatcher.addAudioProcessor(waveformSimilarityBasedOverlapAdd);dispatcher.addAudioProcessor(new RateTransposer(rateFactor));   // 变声处理器(似乎不是必要的功能,但具有令人惊奇的效果)dispatcher.addAudioProcessor(out);dispatcher.run();   // TarsosDSP类库会启动一个线程异步处理音频文件// 等待文件输出结束out.waitUntilDone();if (log.isDebugEnabled()) {log.debug("[convertAudioFileToPCM] ok. originalFile : " + originalFile + ", targetFile : " + targetFile);}} catch (Exception e) {String errMsg = "convertAudioFileToPCM failed. originalFile : " + originalFile;log.error(errMsg, e);}}
文件输出工具类代码:
public class AudioOutputToFile implements AudioProcessor {FileOutputStream target;String targetFile;boolean isDone = false;AudioOutputToFile(String targetFile) {try {this.targetFile = targetFile;target = new FileOutputStream(new File(targetFile));   // 初始化文件输出流} catch (Exception e) {String errMsg = "failed to create file : " + targetFile;log.error(errMsg, e);throw new RuntimeException(errMsg);}}@Overridepublic boolean process(AudioEvent audioEvent) {   // TarsosDSP类库每次处理完一部分音频数据就会调用此方法,我们将结果写入到输出流try {target.write(audioEvent.getByteBuffer(), 0, audioEvent.getByteBuffer().length);target.flush();if (log.isDebugEnabled()) {log.debug("[AudioOutputToFile] process ok. file : " + targetFile + ", length : "+ audioEvent.getByteBuffer().length);}} catch (IOException e) {String errMsg = "failed to write file : " + targetFile;log.error(errMsg, e);}return true;}@Overridepublic void processingFinished() {   // TarsosDSP类库在处理完整个音频文件时会调用此方法,我们将输出流关闭try {target.close();if (log.isDebugEnabled()) {log.debug("[AudioOutputToFile] processingFinished. file : " + targetFile);}} catch (IOException e) {String errMsg = "failed to close file : " + targetFile;log.error(errMsg, e);}isDone = true;}public void waitUntilDone() {   // 我们用一个简陋的方法等待TarsosDSP类库的异步处理过程while (!isDone) {try {Thread.sleep(10);} catch (InterruptedException e) {}}}}

我们可以用以下代码试听一下处理后的文件内容:

        // 播放目标文件FileInputStream fileInputStream = new FileInputStream(new File(targetFile));TarsosDSPAudioFormat format = new TarsosDSPAudioFormat(8000, 16, 1, true, false);AudioInputStream inputStream = new AudioInputStream(fileInputStream, JVMAudioInputStream.toAudioFormat(format),AudioSystem.NOT_SPECIFIED);JVMAudioInputStream stream = new JVMAudioInputStream(inputStream);AudioDispatcher dispatcher = new AudioDispatcher(stream, 1024, 0);dispatcher.addAudioProcessor(new AudioPlayer(format, 1024));dispatcher.run();

3. 定义一个语音识别工具类

POM加入netty依赖,用于与科大讯飞平台建立WebSocket通信。

        <!-- https://mvnrepository.com/artifact/io.netty/netty-all --><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId></dependency>

主体代码:

@Component
@Slf4j
public class XFYunAudioRecognizer {...@Async("myAsyncServiceExecutor") // 语音识别过程耗时较长,建议使用线程池进行处理public void doRecognizeAsync(...) {// 使用netty建立连接EventLoopGroup eventLoopGroup = new NioEventLoopGroup();Bootstrap bootstrap = new Bootstrap();bootstrap.option(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.TCP_NODELAY, true).group(eventLoopGroup).handler(new LoggingHandler(LogLevel.INFO)).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new ChannelHandler[] { new HttpClientCodec(),new HttpObjectAggregator(1024 * 1024 * 10) });pipeline.addLast("hookedHandler", new XFYunWebSocketClientHandler());   // XFYunWebSocketClientHandler是自定义的WebSocket客户端处理器,代码在后面}});FileInputStream fileInputStream = null;try {// 初始化连接参数URI serverURI = generateURI();  // generateURI方法按照科大讯飞的接口文档要求,在URL后面拼接各种鉴权参数,代码在后面HttpHeaders httpHeaders = new DefaultHttpHeaders();int port = serverURI.getPort();if (port < 0) {if (xfyunAudioRecognizerAddress.startsWith("https")) {   // xfyunAudioRecognizerAddress是科大讯飞接口地址。例如:ws://iat-api.xfyun.cn/v2/iatport = 443;} else {port = 80;}}final Channel channel = bootstrap.connect(serverURI.getHost(), port).sync().channel();// 初始化处理器XFYunWebSocketClientHandler handler = (XFYunWebSocketClientHandler) channel.pipeline().get("hookedHandler");handler.setXXX();  // 设置自定义的WebSocket客户端处理器各种属性,具体按需求调整handler.setYYY();// 握手WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(serverURI,WebSocketVersion.V13, (String) null, true, httpHeaders);handler.setHandshaker(handshaker);handshaker.handshake(channel);handler.getHandshakeFuture().sync();if (log.isDebugEnabled()) {log.debug("connected to : " + serverURI);}// 获取音频文件内容String originalFilePath = xxx(); // 根据具体情况,获取原始音频文件完整路径String filePath = originalFilePath + "-convert.pcm";  // 转换为PCM格式后的音频文件存储全路径// 原始音频转换为PCM格式AudioTransformUtil.convertAudioFileToPCM(originalFilePath, filePath);  // 此处用到上面的工具类// 下面的代码用于封装科大讯飞请求数据包,有点丑,可按需要优化fileInputStream = new FileInputStream(new File(filePath));int frameSize = 1280; // 每一帧音频大小的整数倍 TODO 根据音频文件格式调整int intervel = 40; // 两个数据包间隔,单位:毫秒String format = "audio/L16;rate=8000"; // TODO 支持更多格式String encoding = "raw"; // TODO 支持更多格式byte[] buffer = new byte[frameSize];JSONObject req = new JSONObject();JSONObject common = new JSONObject();common.put("app_id", xfyunAppId);    // 讯飞开放平台申请的APPIDJSONObject business = new JSONObject();business.put("language", "zh_cn");business.put("domain", "iat");business.put("accent", "mandarin");JSONObject data = new JSONObject();data.put("status", 0); // 第一帧音频data.put("format", format);data.put("encoding", encoding);// 准备第一帧数据int size = fileInputStream.read(buffer);byte[] sendBytes = null;if (size < frameSize) {sendBytes = new byte[size];System.arraycopy(buffer, 0, sendBytes, 0, size);} else {sendBytes = buffer;}String audio = new String(Base64Util.encode(sendBytes));   // Base64Util是将原始byte数组转换为base64编码的工具类,可自行百度data.put("audio", audio);req.put("common", common);req.put("business", business);if (log.isDebugEnabled()) {log.debug("req without data : " + req.toJSONString());}req.put("data", data);// 发送第一帧TextWebSocketFrame textFrame = new TextWebSocketFrame(req.toJSONString());channel.writeAndFlush(textFrame);req = new JSONObject();data.put("status", 1); // 中间音频req.put("data", data);while ((size = fileInputStream.read(buffer)) > 0) {// 发送后续帧Thread.sleep(intervel);   // 按照科大讯飞接口文档要求,每两帧之间适当等待,防止识别效果出问题sendBytes = null;if (size < frameSize) {sendBytes = new byte[size];System.arraycopy(buffer, 0, sendBytes, 0, size);} else {sendBytes = buffer;}audio = new String(Base64Util.encode(sendBytes));data.put("audio", audio);textFrame = new TextWebSocketFrame(req.toJSONString());channel.writeAndFlush(textFrame);}// 发送结束帧Thread.sleep(intervel);data = new JSONObject();data.put("status", 2); // 结束帧req.put("data", data);textFrame = new TextWebSocketFrame(req.toJSONString());channel.writeAndFlush(textFrame);} catch (Exception e) {String errMsg = "doRecognize failed : " + e.getMessage();log.error(errMsg, e);} finally {if (null != fileInputStream) {try {fileInputStream.close();} catch (Exception e2) {}}}
}

URL拼接方法:

private URI generateURI() throws UnsupportedEncodingException, URISyntaxException {URI tmp0 = new URI(xfyunAudioRecognizerAddress);  // 原始的科大讯飞语音识别接口地址,例如:ws://iat-api.xfyun.cn/v2/iatStringBuffer stringBuffer = new StringBuffer();stringBuffer.append(xfyunAudioRecognizerAddress).append("?authorization=");   // 准备拼接authorization参数String date = timeUtil.timeToGMTString(new Date());  // timeUtil是自定义的时间转换工具,此处将当前时间转换为GMT格式字符串。实现方法参考:String GMT_TIME_FORMAT = "EEE, dd MMM yyyy HH:mm:ss 'GMT'";StringBuffer tmp1 = new StringBuffer();tmp1.append("host: ").append(tmp0.getHost()).append("\ndate: ").append(date).append("\nGET ").append(tmp0.getPath()).append(" HTTP/1.1");if (log.isDebugEnabled()) {log.debug("tmp1 : " + tmp1.toString());}String signature = EncryptUtil.hmacSHA256(tmp1.toString(), xfyunAPISecret); // EncryptUtil是自定义的加密工具,此处将tmp1按照"HmacSHA256"算法加密,并返回base64编码结果,可自行百度;xfyunAPISecret是讯飞平台申请的APISecretif (log.isDebugEnabled()) {log.debug("signature : " + signature);}StringBuffer tmp2 = new StringBuffer();tmp2.append("api_key=\"").append(xfyunAPIKey)   // xfyunAPIKey是讯飞平台申请的APIKey.append("\",algorithm=\"hmac-sha256\",headers=\"host date request-line\",signature=\"").append(signature).append("\"");if (log.isDebugEnabled()) {log.debug("tmp2 : " + tmp2.toString());}String authorization = new String(Base64Util.encode(tmp2.toString().getBytes("UTF-8")));   // Base64Util是自定义的base64编解码工具,可自行百度stringBuffer.append(authorization).append("&date=").append(URLEncoder.encode(date, "UTF-8")).append("&host=").append(tmp0.getHost());  // 注意对date的处理,这里要防止date中空格符等影响URL的解析if (log.isDebugEnabled()) {log.debug("stringBuffer : " + stringBuffer.toString());}String encodedURL = stringBuffer.toString();if (log.isDebugEnabled()) {log.debug("encodedURL : " + encodedURL);}return new URI(encodedURL);}
自定义的WebSocket客户端处理器:
@Data
@EqualsAndHashCode(callSuper = false)
@Slf4j
public class XFYunWebSocketClientHandler extends SimpleChannelInboundHandler<Object> {...WebSocketClientHandshaker handshaker;/*** 语音识别返回文本*/StringBuffer resultBuffer = new StringBuffer();@Overrideprotected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {Channel ch = ctx.channel();FullHttpResponse response = null;if (!this.handshaker.isHandshakeComplete()) {  // 处理握手的响应数据try {response = (FullHttpResponse) msg;// 握手协议返回,设置结束握手this.handshaker.finishHandshake(ch, response);// 设置成功this.handshakeFuture.setSuccess();if (log.isDebugEnabled()) {log.debug("WebSocket Client connected! response headers[sec-websocket-extensions]:{}",response.headers());}} catch (WebSocketHandshakeException e) {FullHttpResponse res = (FullHttpResponse) msg;String errMsg = String.format("WebSocket Client failed to connect,status:%s,reason:%s", res.status(),res.content().toString(CharsetUtil.UTF_8));log.error(errMsg, e);this.handshakeFuture.setFailure(new Exception(errMsg));}} else if (msg instanceof FullHttpResponse) {  // 收到未知的数据包response = (FullHttpResponse) msg;String errMsg = "Unexpected FullHttpResponse,status:" + response.status() + ",reason:"+ response.content().toString(CharsetUtil.UTF_8);log.error(errMsg);} else {WebSocketFrame frame = (WebSocketFrame) msg;if (frame instanceof TextWebSocketFrame) {   // 科大讯飞的解析结果都是text类型(JSON)TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;if (log.isDebugEnabled()) {log.debug("TextWebSocketFrame : " + textFrame.text());}dealWithText(textFrame.text());   // 处理解析结果,具体看下文} else if (frame instanceof BinaryWebSocketFrame) {  // 收到未知的数据包BinaryWebSocketFrame binFrame = (BinaryWebSocketFrame) frame;if (log.isDebugEnabled()) {log.debug("BinaryWebSocketFrame");}} else if (frame instanceof PongWebSocketFrame) {log.debug("WebSocket Client received pong");} else if (frame instanceof CloseWebSocketFrame) {log.debug("receive close frame");ch.close();}}}@Overridepublic void handlerAdded(ChannelHandlerContext ctx) {  // 由上层自动触发的回调this.handshakeFuture = ctx.newPromise();}/*** 处理讯飞返回的文本* * @param text*/private void dealWithText(String text) {try {JSONObject jsonObject = JSONObject.parseObject(text);   // 使用阿里的fastJSONint code = jsonObject.getIntValue("code");String message = jsonObject.getString("message");if (code != 0) {log.error("error! text : " + text);return;}JSONObject data = jsonObject.getJSONObject("data");JSONArray ws = data.getJSONObject("result").getJSONArray("ws");if (null != ws) {for (Object wObject : ws) {JSONArray cw = ((JSONObject) wObject).getJSONArray("cw");if (null != cw) {String word = ((JSONObject) cw.get(0)).getString("w");   // 只处理第一个词(识别结果中可能会有备选词语,简单起见无视之)if (StringUtils.isNoneBlank(word)) {resultBuffer.append(word);    // 拿到一个有效的词,拼接到返回结果中}}}}int status = data.getIntValue("status");if (2 == status) {// 结束帧,提交返回值... // 此处建议使用MQ将最终的结果返回给业务}} catch (Exception e) {log.error("unknown error" + text, e);}}
}

科大讯飞语音识别技术实(yuan)战(ma)小结相关推荐

  1. 科大讯飞语音识别_科大讯飞 语音识别_科大讯飞语音识别系统 - 云+社区 - 腾讯云...

    广告关闭 腾讯云双11爆品提前享,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高满返5000元! 到目前为止科大讯飞的javasdk不支持客户端和服务端分开的情况,也就是说,语音合 ...

  2. 科大讯飞刘聪:如何持续保持语音识别技术的领先

    2020科大讯飞全球1024开发者节今天正式拉开帷幕,伴随着AI的发展,我们得以更全面和细致地洞察人们的生活习惯,并为人们提供更加智能和便捷的服务.在人工智能的应用场景中,最重要的一个就是语音交互.针 ...

  3. 在应用中集成科大讯飞的语音识别技术

    语音识别技术最近貌似是越来越火了.再前几天科大讯飞还刚刚发布了讯飞语点--一个据说要挑战siri的应用.--好吧,对这些的东西讨论要说起来就多了. 本文主要讲如何在自己的android应用中集成语音识 ...

  4. 技术解读 | 科大讯飞语音技术最新进展之二:语音识别与语音合成

    这一篇内容将围绕语音识别与合成方向,继续为大家带来相关技术解析. "风物长宜放眼量".面向人机交互更加自然流畅的未来,智能语音技术进展如何?该走向何方? 以下内容根据讯飞研究院杰出 ...

  5. 科大讯飞发布四川话语音识别技术,号称识别准确率超过85%

    如果你实在闲的蛋疼,不妨拿起你的 iPhone 用标准的四川话对 Siri 说,"放心巴适得很绝对正宗 ",当然来自美帝国的 Siri 是不可能听明白四川话的(但她可以听懂粤语.. ...

  6. 语音识别技术的前世今生之前世

    最近在做脑磁图的信号处理方面的工作,在网上查阅信号处理相关工作的资料时,看到了CMU的王赟博士关于语音识别技术的介绍,个人感觉讲的很精彩,同时,语音识别技术已经渗入到我们生活的方方面面,抱着学习者的心 ...

  7. 语音识别技术的前世今生【前世篇】

    目录 1.背景 2.孤立词识别 2.1 特征提取 2.2 动态弯算法 2.3 GMM(Gaussian mixture model) 2.4 HMM(Hidden markov model) 2.5 ...

  8. 从不温不火到炙手可热:语音识别技术简史

    作者 | 陈孝良,冯大航,李智勇 出品 | AI科技大本营(ID: rgznai100) [导读]语音识别自半个世纪前诞生以来,一直处于不温不火的状态,直到 2009 年深度学习技术的长足发展才使得语 ...

  9. 关于在呼叫中心业务中应用语音识别技术的探讨

    关于在呼叫中心业务中应用语音识别技术的探讨 摘要:本文首先给出了语音技术的应用现状,接着对语音识别技术在呼叫中心中可应用可尝试的业务进行探讨,最后提出呼叫中心业务中应用语音识别技术的虚拟CSR概念. ...

最新文章

  1. 变速后没有声音_CVT不仅平顺省油还是运动型变速箱?
  2. F2etest+UIRecorder(录制脚本)【2】
  3. word文档图标变成白纸_挽救你的文件 修复变成乱码的Word文档
  4. ABAP, Maven, CF App和Webpack的build
  5. ubuntu12.04
  6. TensorFlow框架案例实战
  7. 【前端 · 面试 】HTTP 总结(六)—— HTTP 版本区别
  8. *第八周*数据结构实践项目二【建设链串算法库】
  9. 辽宁专科php教材用什么,辽宁新高考改革方案的具体内容是什么?
  10. app源码+php+l,android商城APP全套源码(服务端+客户端)
  11. 儿童python编程入门软件_一款儿童编程入门的理想工具——PythonTurtle
  12. php支付宝发卡源码,个人发卡系统支付宝即时到帐大气源码
  13. 正则应用(用户名输入框)
  14. JAVA知识整理(一)
  15. 天正暖通天圆地方在哪_体现了“天圆地方”的中国古典家具有哪些?我们来聊一下!...
  16. linux开启并进入Mysql
  17. 电路的基本定律--基尔霍夫电流定律和电压定律
  18. 计算机报录比10:1,中国石油大学(华东)比去年大幅增加!
  19. python爬pdf的曲线_Python爬取读者并制作成PDF
  20. 在wps里面怎么设置触发器_利用WPS演示中的触发器控制板书内容的方法

热门文章

  1. 蓄势迎接 Google 谷歌开发者大会:开发者,你准备好了吗?
  2. SQL语句增加列、修改列、删除列
  3. 推荐算法面试集锦--算法模型
  4. 2021了,真的不要再说 Node.js 是一门编程语言了
  5. 360新logo设计的考虑因素
  6. Maven module 出现 Ignored pom.xml 问题
  7. 邮件客户端 web linux,分享|开始使用 Isotope 吧,一款开源的 Web 邮件客户端
  8. Eighth season eighteenth episode,the 35th wedding anniversary!!!!!!
  9. ARM嵌入式体系结构与接口技术:实现A/D转换器
  10. 可能是国内第一篇全面解读 Java 现状及趋势的文章