一、官网地址
语音识别 SDK 概览 - SDK 文档 - 文档中心 - 腾讯云
需要对接的朋友们,需要咨仔细的看一下文档,主要是一些重要参数,但是小编觉得,腾讯的这个SDK 真的不太友好,demo给的也不是很直接,需要我们自己再次封装,并且SDK不能从中央 仓库直接获取,需要我们自己下载源码,自己搞。。。。
二、对接流程
2.1 先搞jar
我们需要从官网地址下载SDK源码,然后将源码导入我们的IDE中,将out文件夹中的real_asr_sdk_1.6.jar 核心jar包导入到我们自己项目中,如果你的项目是maven方式的话,可以参考小编的文档《maven手动将本地jar包加入到本地maven仓库》。然后还需要引入一下辅助jar,也就是 源码中lib文件夹下面的jar,如果项目中已经有对应的jar,可以直接使用,这些jar可以在中央仓库直接下载。
2.2 代码
我才用的是异步回传结果的方式,因为我的上游是一个ws接口,这个接口不断的接受到语音流,然后我调用腾讯的识别接口,将需要识别的语音流添加到任务中,然后在回调函数中获得识别结果。
任务类:
package com.jack.chat.asrtencent.service; import com.tencent.cloud.asr.realtime.sdk.asyn_sender.ReceiverEntrance; import com.jack.chat.fs.service.FsService; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import javax.sound.midi.Soundbank; public class VoiceTask { private ReceiverEntrance receiverEntrance; private String taskId; public VoiceTask(String taskId) { this.taskId = taskId; } /** * @Description: * @author: zhenghao * @date: 2020/8/13 19:00 */ public void init(FsService fsService,String tel) { System.out.println("初始化成功"); // 新建一个服务 this.receiverEntrance = new ReceiverEntrance(Integer.parseInt(taskId)); // 启动服务 this.receiverEntrance.start(); // 注册N个回调Handler this.receiverEntrance.registerReponseHandler(new MyResponseHandler(this.taskId,tel,fsService)); System.out.println("初始化完成"); } /** * 创建和启动服务线程。包括:数据添加线程、发送线程、通知线程。 */ public void start(byte[] contentStream) { receiverEntrance.add(contentStream); // 开始添加数据 // this.voiceAddingTask = new VoiceAddingTask(this.receiverEntrance,contentStream ); // this.voiceAddingTask.start(); // 10秒后停止任务/关闭服务。如需一直使用,则不要调用它。 /*this.sleepSomeTime(10000); this.stop();*/ } public void stop() { this.receiverEntrance.stopService(); } }
核心类:每一个通道我们都需要new 一个核心类
package com.jack.chat.asrtencent.service; import com.tencent.cloud.asr.realtime.sdk.config.AsrBaseConfig; import com.tencent.cloud.asr.realtime.sdk.config.AsrGlobelConfig; import com.tencent.cloud.asr.realtime.sdk.config.AsrInternalConfig; import com.tencent.cloud.asr.realtime.sdk.config.AsrPersonalConfig; import com.tencent.cloud.asr.realtime.sdk.model.enums.ResponseEncode; import com.tencent.cloud.asr.realtime.sdk.model.enums.SdkRole; import com.tencent.cloud.asr.realtime.sdk.model.enums.VoiceFormat; import com.tencent.cloud.asr.realtime.sdk.utils.ByteUtils; import com.jack.chat.fs.service.FsService; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.util.ArrayList; import java.util.List; public class RasrAsynRequestSample { private VoiceTask voiceTask; private FsService fsService; static { initBaseParameters(); } public RasrAsynRequestSample(String taskId, VoiceTask voiceTask) { this.taskId = taskId; this.voiceTask = voiceTask; } public void init(String tel) { System.out.println("开始反射fsservice"); WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext(); this.fsService = (FsService) wac.getBean("fsService"); System.out.println("反射完成"); this.voiceTask.init(this.fsService,tel); } private int threadNumber = 1; private String taskId = "0"; // private String voiceFile = "test_wav/8k/8k.wav"; private List<VoiceTask> taskList = new ArrayList<VoiceTask>(); public static void main(String[] args) throws Exception { RasrAsynRequestSample rasrRequestSample = new RasrAsynRequestSample("1", new VoiceTask("1")); rasrRequestSample.init("18333612608"); rasrRequestSample.setArguments(args); String audio_data = "C:\\data\\8k.wav"; int subLength = 320; // byte[] stream = new byte[320]; // BufferedInputStream bufferedInputSteam = new BufferedInputStream(new FileInputStream(new File(audio_data))); // int len; // while ((len = bufferedInputSteam.read(stream)) > 0) { // rasrRequestSample.start(stream); // Thread.sleep(125); // } List<byte[]> list = ByteUtils.subToSmallBytes(new File(audio_data), subLength); for (int i = 0; i < list.size(); i++) { rasrRequestSample.start(list.get(i)); // sleepSomeTime(125); // 对于8K的语音,每秒发出2048*8 = 16KB数据,与实际相符。 } // rasrRequestSample.start(); sleepSomeTime(600000); // 10分钟后停止示例程序。 rasrRequestSample.stop(); System.exit(0); } /** * 根据需求启动多个任务,每个任务都独立运行,互不干扰。 */ public void start(byte[] contentSteam) { for (int i = 1; i <= this.threadNumber; i++) { this.taskList.add(voiceTask); voiceTask.start(contentSteam); sleepSomeTime(20); } } /** * 停止全部任务。 */ public void stop() { voiceTask.stop(); } /** * 初始化基础参数, 请将下面的参数值配置成你自己的值。 * <p> * 参数获取方法可参考: <a href="https://cloud.tencent.com/document/product/441/6203">签名鉴权 获取签名所需信息</a> */ private static void initBaseParameters() { // Required AsrBaseConfig.appId = ""; AsrBaseConfig.secretKey = ""; AsrBaseConfig.secretId = ""; // optional,根据自身需求配置值 AsrInternalConfig.setSdkRole(SdkRole.VAD); // VAD版用户请务必赋值为 SdkRole.VAD AsrPersonalConfig.responseEncode = ResponseEncode.UTF_8; AsrPersonalConfig.engineModelType = "8k_0"; AsrPersonalConfig.voiceFormat = VoiceFormat.wav; // optional, 可忽略 AsrGlobelConfig.CUT_LENGTH = 4096; // 每次发往服务端的语音分片的字节长度,8K语音建议设为4096,16K语音建议设为8192。 // AsrGlobelConfig.NEED_VAD = 0; // 是否要做VAD,默认为1,表示要做。线上用户不适用,请忽略。 AsrGlobelConfig.NOTIFY_ALL_RESPONSE = true; // 是否回调每个分片的回复。如只需最后的结果,可设为false。 // AsrBaseConfig.PRINT_CUT_REQUEST = true; // 打印每个分片的请求,用于测试。 AsrBaseConfig.PRINT_CUT_RESPONSE = true; // 打印中间结果,用于测试,生产环境建议设为false。 // 默认使用自定义连接池,连接数可在AsrGlobelConfig中修改,更多细节参数,可直接修改源码HttpPoolingManager.java,然后自行打Jar包。 // AsrGlobelConfig.USE_CUSTOM_CONNECTION_POOL = true; } private static void sleepSomeTime(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { // ignore } } /** * 生成可执行Jar后,运行Jar时传入参数的简单处理。不建议这样子传参数。 * <p> * 比如这个命令传入了4个参数: java -jar realAsrSdk_run.jar 10 test_wav/8k/8k_19s.wav 8k false * <p> * 详情可见:out_runnable_jar/command_reference.txt */ private void setArguments(String[] args) { // if (args.length > 0) // this.threadNumber = Integer.parseInt(args[0]); // 使用传入的参数赋值线程个数 // if (args.length > 1) { this.voiceFile = args[1]; this.checkSetVoiceFormat(this.voiceFile); // } // if (args.length > 2) { // AsrPersonalConfig.engineModelType = args[2]; // if (AsrPersonalConfig.engineModelType == "16k_0") // AsrGlobelConfig.CUT_LENGTH = 8192; // 16K语音 也设置成每秒发4次请求,优化演示效果。 // } // if (args.length > 3){ // AsrGlobelConfig.NOTIFY_ALL_RESPONSE = Boolean.parseBoolean(args[3]); // } // } private void checkSetVoiceFormat(String voiceFile) { int index = voiceFile.lastIndexOf("."); if (index == -1){ return; } String formatName = voiceFile.substring(index + 1).trim().toLowerCase(); AsrPersonalConfig.voiceFormat = VoiceFormat.parse(formatName); } }
回调类:
package com.jack.chat.asrtencent.service;// -------------------------------------------------------------------------------------------------------------- import com.tencent.cloud.asr.realtime.sdk.cache_handler.FlowHandler; import com.tencent.cloud.asr.realtime.sdk.model.response.TimeStat; import com.tencent.cloud.asr.realtime.sdk.model.response.VadResponse; import com.tencent.cloud.asr.realtime.sdk.model.response.VadResult; import com.tencent.cloud.asr.realtime.sdk.model.response.VoiceResponse; import com.tencent.cloud.asr.realtime.sdk.utils.JacksonUtil; import com.jack.chat.asr.model.AsrResultModel; import com.jack.chat.fs.service.FsService; import com.jack.chat.socket.service.WebSocketMapUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; /** * 用户自己写的回调Handler.被NotifyService服务线程(可理解为用户线程B)调用。 */ public class MyResponseHandler implements FlowHandler { private String handlerId; private FsService fsService; private String tel; public AtomicInteger okLineCount = new AtomicInteger(1); private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); public MyResponseHandler(String handlerId, String tel, FsService fsService) { this.handlerId = handlerId; this.tel = tel; this.fsService = fsService; } /** * 回复数据通过此方法通知到用户。 */ @Override public void onUpdate(Object... params) { VadResponse response = (VadResponse) params[0]; // Vad版用户请用此行代替下面一行 // VoiceResponse response = (VoiceResponse) params[0]; // Your own logic. System.out.println(sdf.format(new Date()) + " Handler_" + this.handlerId + ", received response -->: " + response.getOriginalText()); System.out.println("所有:" + JacksonUtil.toJsonString(response)); // 可以查看延迟。 // this.printDelay(response); // 可以提取出当前Vad断句回复。适用于Vad版用户,线上用户请忽略。 if (response.getResultList() != null) { for (VadResult vadResult : response.getResultList()) { int lineNo = okLineCount.get(); String content = vadResult.getVoiceTextStr(); //判断是否是整句结束 if (2 == vadResult.getSliceType()) { okLineCount.addAndGet(1); } System.out.println("Received vad response: " + vadResult.getVoiceTextStr()); AsrResultModel asrResultModel = new AsrResultModel(); asrResultModel.setLineNo(lineNo); asrResultModel.setResult(content); try { if (StringUtils.isNotEmpty(content)) { String telChannel = fsService.getTelChannel(tel); String message = fsService.message(tel, asrResultModel); System.out.println("telChannel:" + telChannel + "message" + message); WebSocketMapUtil.getUserWs(telChannel).sendMessage(message); } } catch (Exception e) { System.out.println("tencent发送消息失败:" + e.getMessage()); } } } } /** * 查看和打印延迟信息。延迟统计方法可从项目docs目录中查看,或浏览下面的含义解释。 * * <pre> * 实例如下: * <<<End_Cut>>> write delay: 103 ms, node delay: 103 ms, notify delay: 105 ms. Pre average write: 101 ms, node: 102 ms. * 分别表示:发送延迟、节点延迟、通知延迟。前面N个分片的平均发送、平均节点延迟。 * * 如需测试语音发完后多久能收完全部识别结果,可从:“<<<End_Cut>>>” 中的notify delay获得参考。建议以write Delay作为服务性能考量。 * * 延迟含义解释: * WriteDelay: 数据收发和网络延迟+解析Delay(1-2ms)。 * NodeDelay: 数据滞留延迟 + WriteDelay。 即:从客户add完成1个分片数据开始,至分片结果收到为止,期间总的时间消耗。 * NotifyDelay: NodeDelay + 客户onHander Delay(处理回复耗时)。此值若大于NodeDelay且在增长,会导致Response堆积,最终内存溢出。 * </pre> */ private void printDelay(VoiceResponse voiceResponse) { TimeStat timeStat = voiceResponse.getTimeStat(); if (voiceResponse.isEndCut()) { this.printDelay("<<<End_Cut>>>", timeStat); } else { this.printDelay("<<<Middle_Cut>>>", timeStat); } } private void printDelay(String cutType, TimeStat timeStat) { System.out.println(sdf.format(new Date()) + " " + cutType + " write delay: " + timeStat.getWriteDelay() + " ms, node delay: " + timeStat.getNodeDelay() + " ms, notify delay: " + timeStat.getNotifyDelay() + " ms. Pre average write: " + timeStat.getPreAverageWriteDelay() + " ms, node: " + timeStat.getPreAverageNodeDelay() + " ms."); } }
调用类:
System.out.println("使用腾讯"); String s = RandomUtils.generateNumber(9); rasrAsynRequestSample = new RasrAsynRequestSample(s, new VoiceTask(s)); rasrAsynRequestSample.init(tel);
在建立ws接口成功以后执行上面调用代码,从传输的参数可以知道,我是根据不同的手机号码进行区分回调通道的, 每个回调类中都有一个私有变量tel,用来区分回调通道。