【AIGC】讯飞长录音ASR转写,使用JAVA实现科大讯飞语音服务ASR转录功能:完整指南
文章目录
- 讯飞ASR转写API完整指南
- 0. 引言
- 1. 流程图
- 2. 讯飞ASR API介绍
- 3. API参数说明
- 3.1 认证参数
- 3.2 上传基本参数
- 3.3 查询结果参数
- 3.4 orderResult 字段
- 3.5 Lattice 字段
- 3.6 json_1best 字段
- 3.7 st 字段
- 4. java代码实现
- 4.1 生成签名
- 签名
- 4.2 上传音频文件
- 4.3 获取转写结果
- 4.4 循环获取转写结果
- 4.5解析转写结果
- 5. 请求与返回示例
- 5.1 成功返回示例
- 5.2 异步回调
- 1. 转写结束异步回调状态
- 5.3注意 QPS 和 API 限流
- 6. 文档地址
- 7. 常见问题
- 8. 错误码
- 9. 结论
讯飞ASR转写API完整指南
0. 引言
在这篇博客中,我们将详细解析如何使用讯飞ASR(自动语音识别)API进行音频转写,包括上传音频、查询转写结果及解析返回数据。本文将涵盖API的参数说明,并提供完整的Python代码,确保代码能够顺利执行。
1. 流程图
2. 讯飞ASR API介绍
讯飞ASR API提供了一整套音频转写的解决方案,主要流程如下:
-
生成签名 - 认证请求的合法性。
-
上传音频 - 通过URL方式或本地文件上传音频。
-
查询结果 - 轮询转写结果,等待识别完成。
-
解析结果 - 处理返回的JSON数据,提取文本和说话人信息。
3. API参数说明
3.1 认证参数
参数名 | 说明 |
---|---|
appId | 讯飞开发者平台分配的应用ID |
secret_key | 用于生成签名的密钥 |
ts | 时间戳,单位为秒 |
signa | 认证签名,由appId、ts和secret_key计算得出 |
3.2 上传基本参数
根据实际情况调优
参数名 | 说明 |
---|---|
fileName | 音频文件名称 |
fileSize | 文件大小(若使用URL方式可随意填写) |
duration | 音频时长(单位秒,可随机填写) |
language | 语言(cn代表中文) |
audioMode | 上传模式(urlLink 代表通过URL上传) |
audioUrl | 音频文件的URL(需要URL编码) |
3.3 查询结果参数
参数名 | 说明 |
---|---|
orderId | 订单ID,用于查询转写结果 |
resultType | 返回结果类型(transfer 表示最终转写文本) |
成功
{"code": "000000","descInfo": "success","content": {"orderId": "DKHJQ202209021522090215490FAAE7DD0008C","taskEstimateTime": 28000}
}
失败
{"code": "26600","descInfo": "转写业务通用错误"
}
3.4 orderResult 字段
参数名 | 类型 | 说明 |
---|---|---|
lattice | List | 做顺滑功能的识别结果 |
lattice2 | List | 未做顺滑功能的识别结果,当开启顺滑和后语规整后 orderResult 才返回 lattice2 字段(需要开通权限) |
label | Object | 转写结果标签信息,用于补充转写结果相关信息,标记转写结果角色和声道的对应关系 |
3.5 Lattice 字段
参数名 | 类型 | 说明 |
---|---|---|
json_1best | String | 单个 VAD 的结果的 JSON 内容 |
3.6 json_1best 字段
参数名 | 类型 | 说明 |
---|---|---|
st | Object | 单个句子的结果对象 |
3.7 st 字段
参数名 | 类型 | 说明 |
---|---|---|
bg | String | 单个句子的开始时间,单位毫秒 |
ed | String | 单个句子的结束时间,单位毫秒 |
rl | String | 分离的角色编号,取值正整数,需开启角色分离的功能才返回对应的分离角色编号 |
rt | List | 输出词语识别结果集合 |
4. java代码实现
4.1 生成签名
/*** 加签加密抽象类**/
public abstract class AbstractSignature {/*** 签名ID*/private String id;/*** 加密key*/private String key;/*** 服务url*/private String url;/*** 加密算法*/private String encryptType;/*** 待加密原始字符*/private String originSign;/*** 最终生成的签名*/protected String signa;/*** 时间戳timestamp*/private String ts;/*** 请求类型,默认get*/protected String requestMethod = "GET";/*** @param id* @param key* @param url*/public AbstractSignature(String id, String key, String url) {this.id = id;this.key = key;this.url = url;this.ts = generateTs();}/*** 可设置请求类型* @param id* @param key* @param url* @param isPost 是否为POST*/public AbstractSignature(String id, String key, String url, boolean isPost) {this.id = id;this.key = key;this.url = url;if (isPost) {this.requestMethod = "POST";}else{this.requestMethod = "GET";}this.ts = generateTs();}/*** 生成ts时间*/public String generateTs() {return String.valueOf(System.currentTimeMillis() / 1000L);}/*** 完成签名,返回完整签名** @return* @throws SignatureException*/public abstract String getSigna() throws SignatureException;public String generateOriginSign() throws SignatureException {try {URL url = new URL(this.getUrl());return "host: " + url.getHost() + "\n" +"date: " + this.getTs() + "\n" +"GET " + url.getPath() + " HTTP/1.1";} catch (MalformedURLException e) {throw new SignatureException("MalformedURLException:" + e.getMessage());}}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getKey() {return key;}public void setKey(String key) {this.key = key;}public String getOriginSign() {return originSign;}public void setOriginSign(String originSign) {this.originSign = originSign;}public String getTs() {return ts;}public void setTs(String ts) {this.ts = ts;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getEncryptType() {return encryptType;}public void setEncryptType(String encryptType) {this.encryptType = encryptType;}
}
签名
public class LfasrSignature extends AbstractSignature {/**** @param appId* @param keySecret*/public LfasrSignature(String appId, String keySecret) {super(appId, keySecret, null);}@Overridepublic String getSigna() throws SignatureException {if (ObjectUtils.isEmpty(this.signa)) {this.setOriginSign(generateOriginSign());this.signa = generateSignature();}return this.signa;}/*** 生成最终的签名,需要先生成原始sign** @throws SignatureException*/public String generateSignature() throws SignatureException {return CryptTools.hmacEncrypt(CryptTools.HMAC_SHA1, this.getOriginSign(), this.getKey());}/*** 生成待加密原始字符** @throws NoSuchAlgorithmException*/@Overridepublic String generateOriginSign() throws SignatureException {return CryptTools.md5Encrypt(this.getId() + this.getTs());}
}
4.2 上传音频文件
1、文件上传
#概述
首先调用文件上传接口,上传待转写音频文件的基本信息(文件名、大小等)和相关的可配置参数。
调用成功,返回订单ID(orderId
,用于查询结果或者联调排查问题时使用),是后续接口的必传参数。
#请求示例
https://raasr.xfyun.cn/v2/api/upload?duration=200&signa=Je5YsBvPcsbB4qy8Qvzd367fiv0%3D&fileName=%E9%98%B3%E5%85%89%E6%80%BB%E5%9C%A8%E9%A3%8E%E9%9B%A8%E5%90%8E.speex-wb&fileSize=11895&sysDicts=uncivilizedLanguage&appId=3e79d91c&ts=1662101767
#URL
POST https: //raasr.xfyun.cn/v2/api/upload
#请求头
Content-Type: application/json; charset=UTF-8,Chunked: false
#signa生成
public String createAsrUpload(String audioUrl, String language, Long orgId, String pd) {HashMap<String, Object> params = new HashMap<>(32);try {String fileName = new File(new URL(audioUrl).getPath()).getName();LfasrSignature lfasrSignature = new LfasrSignature(appid, keySecret);params.put("appId", appid);params.put("signa", lfasrSignature.getSigna());params.put("ts", lfasrSignature.getTs());params.put("fileName", fileName);params.put("fileSize", "url自定义、file实际值");params.put("duration", "200");params.put("audioMode", "urlLink");params.put("roleType", "1");params.put("roleNum", "2");// 远近场模式 1:远场模式 (默认) 2:近场模式params.put("eng_vad_mdn", "2");String encodeAudioUrl = URLEncoder.encode(audioUrl, "UTF-8");params.put("audioUrl", encodeAudioUrl);if (encodeAudioUrl.length() > 512) {throw new RuntimeException("audioUrl length must not be greater than 512");}if (StringUtils.isNotBlank(language)) {params.put("language", language);} else {params.put("language", "cn");}if (StringUtils.isNotBlank(pd)) {params.put("pd", pd);}} catch (Exception e) {throw new Exception("参数构建失败: " + e.getMessage());}// String paramString = HttpUtil.parseMapToPathParam(params);String url ="https://raasr.xfyun.cn" + "/v2/api/upload" + "?" + paramString;String response = HttpUtil.iflyrecUpload(url, null);return response;}
注意对参数的key和value编码
URLEncoder.encode(entry.getKey(), UTF8)
4.3 获取转写结果
status == 4
private static final String API_GET_RESULT = "/v2/api/getResult";/*** 获取任务结果** @param orderId 任务id* @return 请求结果* @throws SignatureException 签名异常*/public String getResult(String orderId) throws SignatureException {HashMap<String, Object> map = new HashMap<>(16);LfasrSignature lfasrSignature = new LfasrSignature(appid, keySecret);map.put("orderId", orderId);map.put("signa", lfasrSignature.getSigna());map.put("ts", lfasrSignature.getTs());map.put("appId", appid);map.put("resultType", "transfer");String paramString = HttpUtil.parseMapToPathParam(map);String url = HOST + API_GET_RESULT + "?" + paramString;String response = HttpUtil.iflyrecGet(url);return response;}
查询结构返回数据
{"code": "000000","descInfo": "success","content": {"orderInfo": {"orderId": "DKHJQ2022090510220905100562536FEF00062","failType": 0,"status": 4,"originalDuration": 200,"realDuration": 1878},"orderResult": "{\"lattice\":[{\"json_1best\":\"{\\\"st\\\":{\\\"sc\\\":\\\"0.86\\\",\\\"pa\\\":\\\"0\\\",\\\"rt\\\":[{\\\"ws\\\":[{\\\"cw\\\":[{\\\"w\\\":\\\"这\\\",\\\"wp\\\":\\\"n\\\",\\\"wc\\\":\\\"1.0000\\\"}],\\\"wb\\\":1,\\\"we\\\":16},{\\\"cw\\\":[{\\\"w\\\":\\\"是\\\",\\\"wp\\\":\\\"n\\\",\\\"wc\\\":\\\"1.0000\\\"}],\\\"wb\\\":17,\\\"we\\\":36},{\\\"cw\\\":[{\\\"w\\\":\\\"一\\\",\\\"wp\\\":\\\"n\\\",\\\"wc\\\":\\\"1.0000\\\"}],\\\"wb\\\":37,\\\"we\\\":52},{\\\"cw\\\":[{\\\"w\\\":\\\"条\\\",\\\"wp\\\":\\\"n\\\",\\\"wc\\\":\\\"1.0000\\\"}],\\\"wb\\\":53,\\\"we\\\":80},{\\\"cw\\\":[{\\\"w\\\":\\\"测试\\\",\\\"wp\\\":\\\"n\\\",\\\"wc\\\":\\\"1.0000\\\"}],\\\"wb\\\":81,\\\"we\\\":116},{\\\"cw\\\":[{\\\"w\\\":\\\"音频\\\",\\\"wp\\\":\\\"n\\\",\\\"wc\\\":\\\"1.0000\\\"}],\\\"wb\\\":117,\\\"we\\\":172},{\\\"cw\\\":[{\\\"w\\\":\\\"。\\\",\\\"wp\\\":\\\"p\\\",\\\"wc\\\":\\\"0.0000\\\"}],\\\"wb\\\":172,\\\"we\\\":172},{\\\"cw\\\":[{\\\"w\\\":\\\"\\\",\\\"wp\\\":\\\"g\\\",\\\"wc\\\":\\\"0.0000\\\"}],\\\"wb\\\":172,\\\"we\\\":172}]}],\\\"bg\\\":\\\"50\\\",\\\"rl\\\":\\\"0\\\",\\\"ed\\\":\\\"1840\\\"}}\"}],\"lattice2\":[{\"lid\":\"0\",\"end\":\"1840\",\"begin\":\"50\",\"json_1best\":{\"st\":{\"sc\":\"0.86\",\"pa\":\"0\",\"rt\":[{\"nb\":\"1\",\"nc\":\"1.0\",\"ws\":[{\"cw\":[{\"w\":\"这\",\"wp\":\"n\",\"wc\":\"1.0000\"}],\"wb\":1,\"we\":16},{\"cw\":[{\"w\":\"是\",\"wp\":\"n\",\"wc\":\"1.0000\"}],\"wb\":17,\"we\":36},{\"cw\":[{\"w\":\"一\",\"wp\":\"n\",\"wc\":\"1.0000\"}],\"wb\":37,\"we\":52},{\"cw\":[{\"w\":\"条\",\"wp\":\"n\",\"wc\":\"1.0000\"}],\"wb\":53,\"we\":80},{\"cw\":[{\"w\":\"测试\",\"wp\":\"n\",\"wc\":\"1.0000\"}],\"wb\":81,\"we\":116},{\"cw\":[{\"w\":\"音频\",\"wp\":\"n\",\"wc\":\"1.0000\"}],\"wb\":117,\"we\":172},{\"cw\":[{\"w\":\"。\",\"wp\":\"p\",\"wc\":\"0.0000\"}],\"wb\":172,\"we\":172},{\"cw\":[{\"w\":\"\",\"wp\":\"g\",\"wc\":\"0.0000\"}],\"wb\":172,\"we\":172}]}],\"pt\":\"reserved\",\"bg\":\"50\",\"si\":\"0\",\"rl\":\"0\",\"ed\":\"1840\"}},\"spk\":\"段落-0\"}]}","taskEstimateTime": 0}
4.4 循环获取转写结果
LfasrResponse uploadResponse = lfasrClient.uploadUrl(AUDIO_URL);if (uploadResponse == null) {logger.error("上传失败,响应为空");return;}if (!StringUtils.equals(uploadResponse.getCode(), "000000")) {logger.error("上传失败,错误码:{},错误信息:{}", uploadResponse.getCode(), uploadResponse.getDescInfo());return;}String orderId = uploadResponse.getContent().getOrderId();logger.info("转写任务orderId:{}", orderId);// 3、查询转写结果int status = LfasrOrderStatusEnum.CREATED.getKey();// 循环直到订单完成或失败while (status != LfasrOrderStatusEnum.COMPLETED.getKey() && status != LfasrOrderStatusEnum.FAILED.getKey()) {LfasrResponse resultResponse = lfasrClient.getResult(orderId, TASK_TYPE);if (!StringUtils.equals(resultResponse.getCode(), "000000")) {logger.error("转写任务失败,错误码:{},错误信息:{}", resultResponse.getCode(), resultResponse.getDescInfo());return;}// 获取订单状态信息if (resultResponse.getContent() != null && resultResponse.getContent().getOrderInfo() != null) {status = resultResponse.getContent().getOrderInfo().getStatus();int failType = resultResponse.getContent().getOrderInfo().getFailType();// 根据状态输出日志LfasrOrderStatusEnum statusEnum = LfasrOrderStatusEnum.getEnum(status);if (statusEnum != null) {logger.info("订单状态:{}", statusEnum.getValue());// 如果订单失败,输出失败原因if (statusEnum == LfasrOrderStatusEnum.FAILED) {LfasrFailTypeEnum failTypeEnum = LfasrFailTypeEnum.getEnum(failType);logger.error("订单处理失败,失败原因:{}", failTypeEnum.getValue());return;}// 如果订单已完成,输出结果if (statusEnum == LfasrOrderStatusEnum.COMPLETED) {printResult(resultResponse);return;}} else {logger.error("未知的订单状态:{}", status);}} else {logger.error("返回结果中缺少订单信息");}TimeUnit.SECONDS.sleep(20);}}
4.5解析转写结果
/*** 从转写结果的lattice数组中提取文本*/private static String getLatticeText(List<LfasrOrderResult.Lattice> latticeList) {StringBuilder resultText = new StringBuilder();for (LfasrOrderResult.Lattice lattice : latticeList) {LfasrOrderResult.Json1Best json1Best = lattice.getJson1Best();if (json1Best == null || json1Best.getSt() == null || json1Best.getSt().getRt() == null) {continue;}String rl = json1Best.getSt().getRl();StringBuilder rlText = getRlText(json1Best);resultText.append("角色-").append(rl).append(":").append(rlText).append("\n");}return resultText.toString();}
/*** 从Json1Best中提取识别结果文本并拼接*/private static StringBuilder getRlText(LfasrOrderResult.Json1Best json1Best) {StringBuilder rlText = new StringBuilder();for (LfasrOrderResult.RecognitionResult rt : json1Best.getSt().getRt()) {if (rt.getWs() == null) {continue;}for (LfasrOrderResult.WordResult ws : rt.getWs()) {if (ws.getCw() != null && !ws.getCw().isEmpty()) {// 获取每个词的识别结果String word = ws.getCw().get(0).getW();if (word != null && !word.isEmpty()) {rlText.append(word);}}}}return rlText;}
def parse_result(self, result_json):"""解析转写结果,按说话人分组"""try:result = json.loads(result_json)speakers = {}if 'lattice2' in result:for item in result['lattice2']:speaker = item.get('spk', '未知')json_1best = json.loads(item['json_1best'])text = "".join(cw['w'] for rt in json_1best.get('st', {}).get('rt', []) for ws in rt.get('ws', []) for cw in ws.get('cw', []) if 'w' in cw)speakers.setdefault(speaker, []).append(text)return speakersexcept Exception as e:return None
5. 请求与返回示例
5.1 成功返回示例
参数回看:3.3 查询结果参数
{"code": "000000","descInfo": "success","content": {"orderId": "DKHJQ202209021522090215490FAAE7DD0008C","taskEstimateTime": 28000}
}
5.2 异步回调
1. 转写结束异步回调状态
当订单转写流程结束时会回调用户(如果录音文件转写接口 upload
传了callbackUrl
),会把订单号和订单状态返回,具体的格式和参数说明如下: 回调地址示例:
GET http://ip:prot/server/xxx?orderId=DKHJQ202004291620042916580FBC96690001F&status=1
5.3注意 QPS 和 API 限流
//{"code":"100012","descInfo":"access too fast, please wait a moment"}// {"code":“26603","descInfo":"接口访问频率受限”}else if (code.equals("100012") || code.equals("26603") || code.equals("26605")) {if (tryCount < MAX_RETRY_COUNT) {// 队列处理this.submitToQue(orderId, tryCount + 1, taskId ,type);} else {throw new Exception("Max retry count reached for orderId: "+orderId);}} else {throw new Exception("讯飞响应异常:" + code + "resp:" + respResult);}
CODE: 100012 26603
可进行队列延迟重试处理
6. 文档地址
讯飞ASR文档
7. 常见问题
录音文件转写支持哪些音频格式?
答:目前录音文件转写支持的音频格式为:已录制音频(5小时内),wav,flac,opus,m4a,mp3,单声道&多声道,支持语种:中文普通话、英语、开通的小语种以及中文方言,采样率:8KHz,16KHz
#录音文件转写支不支持并发?
答:支持,要保证同一个appid每秒请求接口次数最大值在20次以下。
#录音文件转写可以试用吗?
答:可以领取新用户礼包,根据您认证的程度,提供最多50小时的免费时长,有效期为一年。
#录音文件转写支持什么语言?
答:支持语种:中文普通话、英语,小语种以及中文方言可以到控制台-语音转写-方言/语种处添加试用或购买;设置方式参考上述语言参数切换即可
#录音文件转写的套餐扣费顺序是怎样的?
答:扣量优先级:免费试用>批量购买,即在“批量购买”的套餐额度剩余的情况下,又领取了免费试用的体验包,则领取的免费试用体验包立即生效,并被设定为当前扣量套餐。而之前购买的套餐包的额度和到期日不变。
8. 错误码
错误码 描述 | |
26600 转写业务通用错误,检查下请求参数是否正确 | |
26601 非法应用信息,检查下上传的appi是否正确 | |
26602 任务ID不存在 | |
26603 接口访问频率受限 | |
26604 获取结果次数超过限制 | |
26605 任务正在处理中,请稍后重试 | |
26606 空音频,请检查 | |
26607 转写语种未授权或已过有效期 | |
26610 请求参数错误 | |
26621 预处理文件大小受限(500M) | |
26622 预处理音频时长受限(5小时) | |
26623 预处理音频格式受限 | |
26625 预处理服务时长不足。您剩余的可用服务时长不足,请移步产品页http://www.xfyun.cn/services/lfasr 进行购买或者免费领取 | |
26631 音频文件大小受限(500M) | |
26632 音频时长受限(5小时) | |
26633 音频服务时长不足。您剩余的可用服务时长不足,请移步产品页http://www.xfyun.cn/services/lfasr 进行购买或者免费领取 | |
26634 文件下载失败 | |
26635 文件长度校验失败 | |
26640 文件上传失败 | |
26641 上传分片超过限制 | |
26642 分片合并失败 | |
26643 计算音频时长失败,请检查您的音频是否加密或者损坏 | |
26650 音频格式转换失败,请检查您的音频是否加密或者损坏 | |
26660 计费计量失败 | |
26670 转写结果集解析失败 | |
26671 下载转写结果失败 | |
26680 引擎错误 | |
26681 引擎获取订单异常 | |
26682 引擎订单处理中 | |
26689 引擎网络异常 |
9. 结论
本文详细讲解了讯飞ASR API的使用流程,包括如何生成签名、上传音频、查询结果并解析返回数据。希望这篇文章对你有所帮助!如果对你有帮助,帮忙给个一键三连,求求了,各位吴彦祖,刘亦菲们