智能语音交互
智能语音交互(Intelligent Speech Interaction),是基于语音识别、语音合成、自然语言理解等技术,为企业在多种实际应用场景下,赋予产品“能听、会说、懂你”式的智能人机交互体验.适用于多个应用场景中,包括智能问答、智能质检、法庭庭审实时记录、实时演讲字幕、访谈录音转写等场景,在金融、保险、司法、电商等多个领域均有应用案例
一.语音术语
1.采样率(sample rate)
音频采样率是指录音设备在一秒钟内对声音信号的采样次数,采样频率越高声音的还原就越真实越自然
目前语音识别服务只支持16000Hz和8000Hz两种采样率,其中8000Hz一般是电话业务使用,其余都使用16000Hz
调用语音识别服务时需要设置采样率参数.参数数值,语音数据和项目配置三者必须一致,否则识别效果会非常差
2.采样位数(sample size)
即采样值或取样值(就是将采样样本幅度量化).它是用来衡量声音波动变化的一个参数,也可以说是声卡的分辨率.它的数值越大,分辨率也就越高,所发出声音的能力越强
每个采样数据记录的是振幅, 采样精度取决于采样位数的大小:
1 字节 最低标准
2 字节 CD标准
4 字节 无特殊标准不必要
3.语音编码(format)
语音编码指语音数据存储和传输的方式,语音编码和语音文件格式不同.例如常见的.WAV文件格式,会在其头部定义语音数据的具体编码,其中的音频数据通常是使用PCM编码,但也有可能是AMR或其他编码
4.声道(sound channel)
声道是指声音在录制时在不同空间位置采集的相互独立的音频信号,所以声道数也就是声音录制时的音源数量.常见的音频数据为单声道或双声道(立体声)
除录音文件识别以外的服务只支持单声道(mono)语音数据,如果数据是双声道或其他,需要先转换为单声道才能识别
5.逆文本规整(ITN)
逆文本规整(inverse text normalization)是指语音转换为文本时使用标准化的格式来展示数字、金额、日期和地址等对象,以便符合通常的阅读习惯.以下是一些例子:
语音原始文本 | 开启ITN的识别结果 |
---|---|
百分之二十 | 20% |
一千六百八十元 | 1680元 |
五月十一号 | 5月11号 |
请拨幺幺零 | 请拨110 |
二.服务相关概念
1.项目标识(Appkey)
您可以在智能语音管控台中创建多个项目,每个项目有一个唯一标识,就是Appkey
服务通过Appkey获得项目的具体配置信息
2.访问标识(access key)
访问标识是您的程序访问API的凭证,能提供此凭证的程序具有您账户完全的权限.访问标识由id和secret两部分组成:Access key ID 是类似身份的标识,而 access key secret 的作用是签名访问参数,以防被篡改.两者必须组合使用,其中Access key secret 类似登录密码
3.中间结果(intermediate result)
在调用语音识别服务时可以设置是否返回中间结果
设置为false时只在语音全部识别完后返回一次完整的结果
设置为true时除了最后一次完整的结果之外,还会在您说话的同时返回中间结果
中间结果可能在后续返回结果中被修正
每次中间结果增量返回的字数并不固定
4.任务标识(task_id)
每一个语音服务请求都会有一个唯一的task_id,由SDK自动生成,可用于定位问题
5.访问令牌(access token)
访问令牌(Access Token)是调用智能语音服务的凭证.您可以使用阿里云公共SDK调用云端服务获取Access Token.调用时需要提供您阿里云账号的AccessKey ID和AccessKey Secret
访问令牌使用前需要通过ExpireTime参数获取有效期时间戳,*过期则需要重新获取.
在管控台点击 总览 > 获取AccessToken,可获取用于测试的Token
调用云端服务的返回示例如下:
{"NlsRequestId": "aed8c1af075347819118ff6bf8111168","RequestId": "0989F63E-5069-4AB0-822B-5BD2D95356DF","Token": {"ExpireTime": 1527592757,"Id": "124fc7526f434b8c8198d6196b0a1c8e","UserId": "123456789012"} }
Token->Id 为本次分配的访问令牌Access token
Token->ExpireTime 为此令牌的有效期时间戳
1.获取Token
1.通过SDK获取
AccessToken token = new AccessToken("your akID", "your akSecret"); token.apply(); String accessToken = token.getToken(); long expireTime = token.getExpireTime();
2.通过CommonRequest获取
使用阿里云公共SDK获取Access token,建议采用RPC风格的API调用.发起一次RPC风格的CommonAPI请求,您需要提供以下几个参数:
参数名 | 参数值 | 说明 |
---|---|---|
domain | nls-meta.cn-shanghai.aliyuncs.com | 即该产品的通用访问域名,固定值 |
region_id | cn-shanghai | 服务的地域ID,固定值 |
action | CreateToken | 该API的名称,固定值 |
version | 2019-02-28 | 该API的版本号,固定值 |
//1.添加Java依赖 <dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>3.7.1</version> </dependency> <!-- http://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.49</version> </dependency> //2.调用服务 import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.aliyuncs.CommonRequest; import com.aliyuncs.CommonResponse; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.http.MethodType; import com.aliyuncs.http.ProtocolType; import com.aliyuncs.profile.DefaultProfile; import java.text.SimpleDateFormat; import java.util.Date; public class CreateTokenDemo {// 您的地域IDprivate static final String REGIONID = "cn-shanghai";// 获取Token服务域名private static final String DOMAIN = "nls-meta.cn-shanghai.aliyuncs.com";// API 版本private static final String API_VERSION = "2019-02-28";// API名称private static final String REQUEST_ACTION = "CreateToken";// 响应参数private static final String KEY_TOKEN = "Token";private static final String KEY_ID = "Id";private static final String KEY_EXPIRETIME = "ExpireTime";public static void main(String args[]) throws ClientException {if (args.length < 2) {System.err.println("CreateTokenDemo need params: <AccessKey Id> <AccessKey Secret>");System.exit(-1);}String accessKeyId = args[0];String accessKeySecret = args[1];// 创建DefaultAcsClient实例并初始化DefaultProfile profile = DefaultProfile.getProfile(REGIONID,accessKeyId,accessKeySecret);IAcsClient client = new DefaultAcsClient(profile);CommonRequest request = new CommonRequest();request.setDomain(DOMAIN);request.setVersion(API_VERSION);request.setAction(REQUEST_ACTION);request.setMethod(MethodType.POST);request.setProtocol(ProtocolType.HTTPS);CommonResponse response = client.getCommonResponse(request);System.out.println(response.getData());if (response.getHttpStatus() == 200) {JSONObject result = JSON.parseObject(response.getData());String token = result.getJSONObject(KEY_TOKEN).getString(KEY_ID);long expireTime = result.getJSONObject(KEY_TOKEN).getLongValue(KEY_EXPIRETIME);System.out.println("获取到的Token: " + token + ",有效期时间戳(单位:秒): " + expireTime);// 将10位数的时间戳转换为北京时间String expireDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(expireTime * 1000));System.out.println("Token有效期的北京时间:" + expireDate);}else {System.out.println("获取Token失败!");}} }
2.Token协议说明
如何实现获取Access Token的客户端程序?
客户端向服务端发送获取Token的请求,服务端返回创建Token结果的响应.客户端发送的请求支持使用HTTP或者HTTPS协议,请求方法支持GET或者POST方法.服务端提供了基于阿里云POP协议的接口,因此客户端需要实现阿里云POP的签名机制
由于HTTPS协议的请求参数设置与HTTP协议相同,下面将以HTTP协议请求为例,介绍如何发送获取Token请求
1.URL
协议 | URL | 方法 |
---|---|---|
HTTP/1.1 | http://nls-meta.cn-shanghai.aliyuncs.com/ | GET 或 POST |
2.请求参数
使用GET方法,需要将请求参数设置到请求行中:
/?请求参数字符串
使用POST方法,需要将请求参数设置到请求的Body中
名称 | 类型 | 是否必需 | 说明 |
---|---|---|---|
AccessKeyId | String | 是 | 阿里云颁发给您的访问服务所用的密钥ID,请填入您的阿里云账号的AccessKey ID |
Action | String | 是 | POP API名称:CreateToken |
Version | String | 是 | POP API版本:2019-02-28 |
Format | String | 是 | 响应返回的类型:JSON |
RegionId | String | 是 | 服务所在的地域ID:cn-shanghai |
Timestamp | String | 是 | 请求的时间戳.日期格式按照ISO 8601标准表示,并需要使用UTC时间,时区为:+0(请勿使用本地时间,如北京时间).格式为YYYY-MM-DDThh:mm:ssZ.例如2019-04-03T06:15:03Z 为UTC时间2019年4月3日6点15分03秒. |
SignatureMethod | String | 是 | 签名算法:HMAC-SHA1 |
SignatureVersion | String | 是 | 签名算法版本:1.0 |
SignatureNonce | String | 是 | 唯一随机数uuid,用于请求的防重放攻击,每次请求唯一,不能重复使用.格式为xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx(8-4-4-4-12),例如8d1e6a7a-f44e-40d5-aedb-fe4a1c80f434 |
Signature | String | 是 | 由所有请求参数计算出的签名结果,生成方法请参考下文签名机制. |
3.HTTP请求头部
HTTP 请求头部由“关键字/值”对组成,每行一对,关键字和值用英文冒号“:”分隔,设置内容为如下表格:
名称 | 类型 | 是否必需 | 描述 |
---|---|---|---|
Host | String | 否 | HTTP请求的服务器域名:nls-meta.cn-shanghai.aliyuncs.com,一般会根据请求链接自动解析 |
Accept | String | 否 | 指定客户端能够接收的内容类型:application/json,不设置默认为 / |
Content-type | String | POST方法必须设置 | 指定POST方法请求的Body数据格式:application/x-www-form-urlencoded |
报文示例
1.HTTP GET请求报文
GET /?Signature=O0s6pfeOxtFM6YKSZKQdSyPR9Vs%3D&AccessKeyId=LTA******F3s&Action=CreateToken&Format=JSON&RegionId=cn-shanghai&SignatureMethod=HMAC-SHA1&SignatureNonce=a1f01895-6ff1-43c1-ba15-6c109fa00106&SignatureVersion=1.0&Timestamp=2019-03-27T09%3A51%3A25Z&Version=2019-02-28 HTTP/1.1 Host: nls-meta.cn-shanghai.aliyuncs.com User-Agent: curl/7.49.1 Accept: */*
2.HTTP POST请求报文
POST / HTTP/1.1 Host: nls-meta.cn-shanghai.aliyuncs.com User-Agent: curl/7.49.1 Accept: */* Content-type: application/x-www-form-urlencoded Content-Length: 276 SignatureVersion=1.0&Action=CreateToken&Format=JSON&SignatureNonce=8d1e6a7a-f44e-40d5-aedb-fe4a1c80f434&Version=2019-02-28&AccessKeyId=LTA******F3s&Signature=oT8A8RgvFE1tMD%2B3hDbGuoMQSi8%3D&SignatureMethod=HMAC-SHA1&RegionId=cn-shanghai&Timestamp=2019-03-25T09%3A07%3A52Z
4.响应结果
发送获取Token的HTTP请求之后,会收到服务端的响应,结果以JSON字符串的形式保存在该响应中.GET方法和POST方法的响应结果相同
成功响应HTTP状态码为200,响应字段说明:
参数 | 类型 | 说明 |
---|---|---|
Token | token对象 | 包含了具体的token值和有效期时间戳 |
Id | String | 本次请求分配的token值 |
ExpireTime | Long | token的有效期时间戳(单位:秒,例如1553825814换算为北京时间为:2019/3/29 10:16:54,即token在该时间之前有效.) |
HTTP/1.1 200 OK Date: Mon, 25 Mar 2019 09:29:24 GMT Content-Type: application/json; charset=UTF-8 Content-Length: 216 Connection: keep-alive Access-Control-Allow-Origin: * Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-Requested-With, X-Sequence, _aop_secret, _aop_signature Access-Control-Max-Age: 172800 Server: Jetty(7.2.2.v20101205) {"NlsRequestId":"dd05a301b40441c99a2671905325fb1f","RequestId":"E11F2DC2-0163-4D97-A704-0BD28045608A","ErrMsg":"","Token":{"ExpireTime":1553592564,"Id":"889******166","UserId":"150**********151"}}
失败响应HTTP状态码为非200,响应字段说明:
参数 | 类型 | 说明 |
---|---|---|
RequestId | String | 请求ID |
Message | String | 失败响应的错误信息 |
Code | String | 失败响应的错误码 |
说明: 请根据错误码和错误信息提示检查请求参数是否设置正确,如不能排查,请将响应信息提交到工单
以重传入阿里云账号的AccessKey Id错误为例:
HTTP/1.1 404 Not Found Date: Thu, 28 Mar 2019 07:23:01 GMT Content-Type: application/json; charset=UTF-8 Content-Length: 290 Connection: keep-alive Access-Control-Allow-Origin: * Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-Requested-With, X-Sequence, _aop_secret, _aop_signature Access-Control-Max-Age: 172800 Server: Jetty(7.2.2.v20101205) {"Recommend":"https://error-center.aliyun.com/status/search?Keyword=InvalidAccessKeyId.NotFound&source=PopGw","Message":"Specified access key is not found.","RequestId":"A51587CB-5193-4DB8-9AED-CD4365C2D1E1","HostId":"nls-meta.cn-shanghai.aliyuncs.com","Code":"InvalidAccessKeyId.NotFound"}
3.签名机制
服务端POP API对每个接口访问请求的发送者都要进行身份验证,所以无论使用HTTP协议还是HTTPS协议提交的请求,都需要在请求中包含签名信息.通过签名机制,服务端可以确认哪位客户在做API请求,并能确认客户请求在网络传输过程中有无被篡改
1.安全验证流程
计算签名时,需要您的阿里云账号的AccessKeyId 和 AccessKeySecret,使用HMAC-SHA1算法进行对称加密.其工作流程如下:
请求端根据API请求内容(包括HTTP 请求参数和Body)生成签名字符串
请求端使用阿里云账号的AccessKeyId 和 AccessKeySecret对第一步生成的签名字符串进行签名,获得该API请求的数字签名
请求端把API请求内容和数字签名一同发送给服务端
服务端在接收到请求后会重复如上的第1、2步工作(服务端会在后台获取该请求使用的用户秘钥)并在服务端计算出该请求期望的数字签名
服务端用期望的数字签名和请求端发送过来的数字签名做对比,如果完全一致则认为该请求通过验证.否则直接拒绝该请求
2.生成请求的签名字符串
1. 构造规范化的请求字符串
将HTTP的请求参数(不包括Signature)构造成规范化的请求字符串.规范化步骤:
参数排序.按参数名的字典顺序,对请求参数进行排序,严格按照大小写敏感排序
编码参数,对排序后的请求参数进行规范化设置. 请求参数的名称和值都要使用UTF-8字符集进行URL编码,URL编码规则如下:
对于字符 A-Z、a-z、0-9以及字符
-
、_
、.
、~
不编码对于其他字符编码成“%XY”的格式,其中XY是字符对应ASCII码的16进制表示.比如英文的双引号(”)对应的编码就是%22
对于扩展的UTF-8字符,编码成“%XY%ZA…”的格式
需要说明的是英文空格要被编码是%20,而不是加号
+
;注:一般支持URL编码的库(比如Java中的java.net.URLEncoder)都是按照“application/x-www-form-urlencoded”的MIME类型的规则进行编码的.实现时可以直接使用此类方式进行编码,然后把编码后的字符串中:加号+
替换为%20
,星号*
替换为%2A
,%7E
替换为波浪号~
,即可得到上述规则描述的编码字符串
使用等号
=
连接URL编码后的参数名和参数值:percentEncode(参数Key) + “=” + percentEncode(参数值)
使用与号
&
连接第4步URL编码后的请求参数对,例如Action=CreateToken&Format=JSON
返回规范化的请求字符串(注意:字符串中第一个参数名前面不需要
&
符号)
构造规范化的请求字符串代码示例:
String percentEncode(String value) throws UnsupportedEncodingException {return value != null ? URLEncoder.encode(value, URL_ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null; } // 对参数Key排序 String[] sortedKeys = queryParamsMap.keySet().toArray(new String[] {}); Arrays.sort(sortedKeys); // 对排序的参数进行编码、拼接 for (String key : sortedKeys) {canonicalizedQueryString.append("&").append(percentEncode(key)).append("=").append(percentEncode(queryParamsMap.get(key))); } queryString = canonicalizedQueryString.toString().substring(1);
构造规范化的请求字符串:
AccessKeyId=LTA******3s2&Action=CreateToken&Format=JSON&RegionId=cn-shanghai&SignatureMethod=HMAC-SHA1&SignatureNonce=f20b1beb-e5dc-4245-9e96-aa582e905c1a&SignatureVersion=1.0&Timestamp=2019-04-03T03%3A40%3A13Z&Version=2019-02-28
2. 构造待签名字符串
将HTTP请求的方法(GET)、URL编码的URL路径(/)、URL编码的第1步获取的规范化的请求字符串使用与符号&
连接成待签名字符串:
HTTPMethod + "&" + percentEncode("/") + "&" + percentEncode(queryString)
构造签名字符串代码示例:
StringBuilder strBuilderSign = new StringBuilder(); strBuilderSign.append(HTTPMethod); strBuilderSign.append("&"); strBuilderSign.append(percentEncode(urlPath)); strBuilderSign.append("&"); strBuilderSign.append(percentEncode(queryString)); stringToSign = strBuilderSign.toString();
构造的签名字符串:
3. 计算签名
签名采用HMAC-SHA1算法 + Base64,编码采用UTF-8
根据您的AccessKeySecret,将第2步构造的待签名字符串使用HMAC-SHA1算法计算出对应的数字签名.其中,计算签名时使用的AccessKeySecret必须在其后面增加一个与字符
&
签名也要做URL编码
计算签名的代码示例:
signature = Base64( HMAC-SHA1(stringToSign, accessKeySecret + "&") );// 进行URL编码signature = percentEncode(signature)
计算得到的签名:
# 签名串AKIktdPUMCV12fTh667BLXeuCtg=# URL编码后的结果AKIktdPUMCV12fTh667BLXeuCtg%3D
计算签名后,将签名的键值对用符号=
连接,并使用符号&
添加到第1步获取的请求字符串中,作为HTTP GET请求参数,发送到服务端,获取token
String queryStringWithSign = "Signature=" + signature + "&" + queryString;
3.Java Demo
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.SimpleTimeZone; import java.util.UUID; public class CreateToken {private final static String TIME_ZONE = "GMT";private final static String FORMAT_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss'Z'";private final static String URL_ENCODING = "UTF-8";private static final String ALGORITHM_NAME = "HmacSHA1";private static final String ENCODING = "UTF-8";private static String token = null;private static long expireTime = 0;/*** 获取时间戳* 必须符合ISO8601规范,并需要使用UTC时间,时区为+0*/public static String getISO8601Time(Date date) {Date nowDate = date;if (null == date) {nowDate = new Date();}SimpleDateFormat df = new SimpleDateFormat(FORMAT_ISO8601);df.setTimeZone(new SimpleTimeZone(0, TIME_ZONE));return df.format(nowDate);}/*** 获取UUID*/public static String getUniqueNonce() {UUID uuid = UUID.randomUUID();return uuid.toString();}/*** URL编码* 使用UTF-8字符集按照 RFC3986 规则编码请求参数和参数取值*/public static String percentEncode(String value) throws UnsupportedEncodingException {return value != null ? URLEncoder.encode(value, URL_ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;}/**** 将参数排序后,进行规范化设置,组合成请求字符串* @param queryParamsMap 所有请求参数* @return 规范化的请求字符串*/public static String canonicalizedQuery( Map<String, String> queryParamsMap) {String[] sortedKeys = queryParamsMap.keySet().toArray(new String[] {});Arrays.sort(sortedKeys);String queryString = null;try {StringBuilder canonicalizedQueryString = new StringBuilder();for (String key : sortedKeys) {canonicalizedQueryString.append("&").append(percentEncode(key)).append("=").append(percentEncode(queryParamsMap.get(key)));}queryString = canonicalizedQueryString.toString().substring(1);System.out.println("规范化后的请求参数串:" + queryString);} catch (UnsupportedEncodingException e) {System.out.println("UTF-8 encoding is not supported.");e.printStackTrace();}return queryString;}/**** 构造签名字符串* @param method HTTP请求的方法* @param urlPath HTTP请求的资源路径* @param queryString 规范化的请求字符串* @return 签名字符串*/public static String createStringToSign(String method, String urlPath, String queryString) {String stringToSign = null;try {StringBuilder strBuilderSign = new StringBuilder();strBuilderSign.append(method);strBuilderSign.append("&");strBuilderSign.append(percentEncode(urlPath));strBuilderSign.append("&");strBuilderSign.append(percentEncode(queryString));stringToSign = strBuilderSign.toString();System.out.println("构造的签名字符串:" + stringToSign);} catch (UnsupportedEncodingException e) {System.out.println("UTF-8 encoding is not supported.");e.printStackTrace();}return stringToSign;}/**** 计算签名* @param stringToSign 签名字符串* @param accessKeySecret 阿里云AccessKey Secret加上与号&* @return 计算得到的签名*/public static String sign(String stringToSign, String accessKeySecret) {try {Mac mac = Mac.getInstance(ALGORITHM_NAME);mac.init(new SecretKeySpec(accessKeySecret.getBytes(ENCODING),ALGORITHM_NAME));byte[] signData = mac.doFinal(stringToSign.getBytes(ENCODING));String signBase64 = DatatypeConverter.printBase64Binary(signData);System.out.println("计算的得到的签名:" + signBase64);String signUrlEncode = percentEncode(signBase64);System.out.println("UrlEncode编码后的签名:" + signUrlEncode);return signUrlEncode;} catch (NoSuchAlgorithmException e) {throw new IllegalArgumentException(e.toString());} catch (UnsupportedEncodingException e) {throw new IllegalArgumentException(e.toString());} catch (InvalidKeyException e) {throw new IllegalArgumentException(e.toString());}}/**** 发送HTTP GET请求,获取token和有效期时间戳* @param queryString 请求参数*/public static void processGETRequest(String queryString) {/*** 设置HTTP GET请求* 1. 使用HTTP协议* 2. Token服务域名:nls-meta.cn-shanghai.aliyuncs.com* 3. 请求路径:/* 4. 设置请求参数*/String url = "http://nls-meta.cn-shanghai.aliyuncs.com";url = url + "/";url = url + "?" + queryString;System.out.println("HTTP请求链接:" + url);Request request = new Request.Builder().url(url).header("Accept", "application/json").get().build();try {OkHttpClient client = new OkHttpClient();Response response = client.newCall(request).execute();String result = response.body().string();if (response.isSuccessful()) {JSONObject rootObj = JSON.parseObject(result);JSONObject tokenObj = rootObj.getJSONObject("Token");if (tokenObj != null) {token = tokenObj.getString("Id");expireTime = tokenObj.getLongValue("ExpireTime");}else{System.err.println("提交获取Token请求失败: " + result);}}else {System.err.println("提交获取Token请求失败: " + result);}response.close();} catch (Exception e) {e.printStackTrace();}}public static void main(String args[]) {if (args.length < 2) {System.err.println("CreateTokenDemo need params: <AccessKey Id> <AccessKey Secret>");System.exit(-1);}String accessKeyId = args[0];String accessKeySecret = args[1];System.out.println(getISO8601Time(null));// 所有请求参数Map<String, String> queryParamsMap = new HashMap<String, String>();queryParamsMap.put("AccessKeyId", accessKeyId);queryParamsMap.put("Action", "CreateToken");queryParamsMap.put("Version", "2019-02-28");queryParamsMap.put("Timestamp", getISO8601Time(null));queryParamsMap.put("Format", "JSON");queryParamsMap.put("RegionId", "cn-shanghai");queryParamsMap.put("SignatureMethod", "HMAC-SHA1");queryParamsMap.put("SignatureVersion", "1.0");queryParamsMap.put("SignatureNonce", getUniqueNonce());/*** 1.构造规范化的请求字符串*/String queryString = canonicalizedQuery(queryParamsMap);if (null == queryString) {System.out.println("构造规范化的请求字符串失败!");return;}/*** 2.构造签名字符串*/String method = "GET"; // 发送请求的 HTTP 方法,GETString urlPath = "/"; // 请求路径String stringToSign = createStringToSign(method, urlPath, queryString);if (null == stringToSign) {System.out.println("构造签名字符串失败");return;}/*** 3.计算签名*/String signature = sign(stringToSign, accessKeySecret + "&");if (null == signature) {System.out.println("计算签名失败!");return;}/*** 4.将签名加入到第1步获取的请求字符串*/String queryStringWithSign = "Signature=" + signature + "&" + queryString;System.out.println("带有签名的请求字符串:" + queryStringWithSign);/*** 5.发送HTTP GET请求,获取token*/processGETRequest(queryStringWithSign);if (token != null) {System.out.println("获取的Token:" + token + ", 有效期时间戳(秒):" + expireTime);// 将10位数的时间戳转换为北京时间String expireDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(expireTime * 1000));System.out.println("Token有效期的北京时间:" + expireDate);}} }
三.服务技术
服务 | 时效性 | 功能 | 适用场景 |
---|---|---|---|
一句话识别 | 实时识别 | 识别一分钟内的短语音 | APP语音搜索、语音电话客服、对话聊天、控制口令等场景 |
实时语音识别 | 实时识别 | 识别长时间的语音数据流 | 会议演讲、视频直播等长时间不间断的场景 |
语音合成 | 实时合成 | 合成长度不超过300个字符(UTF-8编码)的文本内容 | 需要人工合成音的场景 |
录音文件识别 | 24小时内完成识别,非实时识别 | 识别文件大小不超过512MB | 非实时识别场景 |
说明:
除录音文件识别以外的其他识别服务只支持单声道(mono)语音数据
识别服务只支持8000Hz/16000Hz的采样率,16bit的采样位数的音频
1.一句话识别
1.Java SDK 2.0
可从maven 服务器下载最新版本SDK:
<dependency><groupId>com.alibaba.nls</groupId><artifactId>nls-sdk-recognizer</artifactId><version>2.1.1</version> </dependency>
1.服务验证
java -cp nls-example-recognizer-2.0.0-jar-with-dependencies.jar com.alibaba.nls.client.SpeechRecognizerDemo
2.服务压测
java -jar nls-example-recognizer-2.0.0-jar-with-dependencies.jar
3.关键接口
NlsClient:语音处理client,相当于所有语音相关处理类的factory,全局创建一个实例即可.线程安全.
SpeechRecognizer:一句话识别处理类,设置请求参数,发送请求及声音数据.非线程安全.
SpeechRecognizerListener:识别结果监听类,监听识别结果.非线程安全.
4.SDK 调用注意事项
NlsClient对象创建一次可以重复使用,每次创建消耗性能.NlsClient使用了netty的框架,创建时比较消耗时间和资源,但创建之后可以重复利用.建议调用程序将NlsClient的创建和关闭与程序本身的生命周期结合
SpeechRecognizer对象不能重复使用,一个识别任务对应一个SpeechRecognizer对象.例如有N个音频文件,则要进行N次识别任务,创建N个SpeechRecognizer对象.
实现的SpeechRecognizerListener对象和SpeechRecognizer对象是一一对应的,不能将一个SpeechRecognizerListener对象设置到多个SpeechRecognizer对象中,否则不能区分是哪个识别任务
Java SDK依赖了Netty网络库,版本需设置为4.1.17.Final及以上.如果您的应用中依赖了Netty,请确保版本符合要求
5.Java Demo
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import com.alibaba.nls.client.protocol.InputFormatEnum; import com.alibaba.nls.client.protocol.NlsClient; import com.alibaba.nls.client.protocol.SampleRateEnum; import com.alibaba.nls.client.protocol.asr.SpeechRecognizer; import com.alibaba.nls.client.protocol.asr.SpeechRecognizerListener; import com.alibaba.nls.client.protocol.asr.SpeechRecognizerResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /*** 此示例演示了* ASR一句话识别API调用* 动态获取token* 通过本地文件模拟实时流发送* 识别耗时计算* (仅作演示,需用户根据实际情况实现)*/ public class SpeechRecognizerDemo {private static final Logger logger = LoggerFactory.getLogger(SpeechRecognizerDemo.class);private String appKey;NlsClient client;public SpeechRecognizerDemo(String appKey, String id, String secret, String url) {this.appKey = appKey;//TODO 重要提示 创建NlsClient实例,应用全局创建一个即可,生命周期可和整个应用保持一致,默认服务地址为阿里云线上服务地址//TODO 这里简单演示了获取token 的代码,该token会过期,实际使用时注意在accessToken.getExpireTime()过期前再次获取tokenAccessToken accessToken = new AccessToken(id, secret);try {accessToken.apply();System.out.println("get token: " + accessToken.getToken() + ", expire time: " + accessToken.getExpireTime());// TODO 创建NlsClient实例,应用全局创建一个即可if(url.isEmpty()) {client = new NlsClient(accessToken.getToken());}else {client = new NlsClient(url, accessToken.getToken());}} catch (IOException e) {e.printStackTrace();}}private static SpeechRecognizerListener getRecognizerListener(int myOrder, String userParam) {SpeechRecognizerListener listener = new SpeechRecognizerListener() {//识别出中间结果.服务端识别出一个字或词时会返回此消息.仅当setEnableIntermediateResult(true)时,才会有此类消息返回@Overridepublic void onRecognitionResultChanged(SpeechRecognizerResponse response) {//事件名称 RecognitionResultChanged、 状态码(20000000 表示识别成功)、语音识别文本System.out.println("name: " + response.getName() + ", status: " + response.getStatus() + ", result: " + response.getRecognizedText());}//识别完毕@Overridepublic void onRecognitionCompleted(SpeechRecognizerResponse response) {//事件名称 RecognitionCompleted, 状态码 20000000 表示识别成功, getRecognizedText是识别结果文本System.out.println("name: " + response.getName() + ", status: " + response.getStatus() + ", result: " + response.getRecognizedText());}@Overridepublic void onStarted(SpeechRecognizerResponse response) {System.out.println("myOrder: " + myOrder + "; myParam: " + userParam + "; task_id: " + response.getTaskId());}@Overridepublic void onFail(SpeechRecognizerResponse response) {// TODO 重要提示: task_id很重要,是调用方和服务端通信的唯一ID标识,当遇到问题时,需要提供此task_id以便排查System.out.println("task_id: " + response.getTaskId() + ", status: " + response.getStatus() + ", status_text: " + response.getStatusText());}};return listener;}/// 根据二进制数据大小计算对应的同等语音长度/// sampleRate 仅支持8000或16000public static int getSleepDelta(int dataSize, int sampleRate) {// 仅支持16位采样int sampleBytes = 16;// 仅支持单通道int soundChannel = 1;return (dataSize * 10 * 8000) / (160 * sampleRate);}public void process(String filepath, int sampleRate) {SpeechRecognizer recognizer = null;try {// 传递用户自定义参数String myParam = "user-param";int myOrder = 1234;SpeechRecognizerListener listener = getRecognizerListener(myOrder, myParam);recognizer = new SpeechRecognizer(client, listener);recognizer.setAppKey(appKey);//设置音频编码格式 TODO 如果是opus文件,请设置为 InputFormatEnum.OPUSrecognizer.setFormat(InputFormatEnum.PCM);//设置音频采样率if(sampleRate == 16000) {recognizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);} else if(sampleRate == 8000) {recognizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_8K);}//设置是否返回中间识别结果recognizer.setEnableIntermediateResult(true);//此方法将以上参数设置序列化为json发送给服务端,并等待服务端确认long now = System.currentTimeMillis();recognizer.start();logger.info("ASR start latency : " + (System.currentTimeMillis() - now) + " ms");File file = new File(filepath);FileInputStream fis = new FileInputStream(file);byte[] b = new byte[3200];int len;while ((len = fis.read(b)) > 0) {logger.info("send data pack length: " + len);recognizer.send(b);// TODO 重要提示:这里是用读取本地文件的形式模拟实时获取语音流并发送的,因为read很快,所以这里需要sleep// TODO 如果是真正的实时获取语音,则无需sleep, 如果是8k采样率语音,第二个参数改为8000int deltaSleep = getSleepDelta(len, sampleRate);Thread.sleep(deltaSleep);}//通知服务端语音数据发送完毕,等待服务端处理完成now = System.currentTimeMillis();// TODO 计算实际延迟: stop返回之后一般即是识别结果返回时间logger.info("ASR wait for complete");recognizer.stop();logger.info("ASR stop latency : " + (System.currentTimeMillis() - now) + " ms");fis.close();} catch (Exception e) {System.err.println(e.getMessage());} finally {//关闭连接if (null != recognizer) {recognizer.close();}}}public void shutdown() {client.shutdown();}public static void main(String[] args) throws Exception {String appKey = null; // "填写你的appkey";String id = null; // "填写你在阿里云网站上的AccessKeyId";String secret = null; // "填写你在阿里云网站上的AccessKeySecret";String url = ""; // 默认即可,默认值:wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1if (args.length == 3) {appKey = args[0];id = args[1];secret = args[2];} else if (args.length == 4) {appKey = args[0];id = args[1];secret = args[2];url = args[3];} else {System.err.println("run error, need params(url is optional): " + "<app-key> <AccessKeyId> <AccessKeySecret> [url]");System.exit(-1);}SpeechRecognizerDemo demo = new SpeechRecognizerDemo(appKey, id, secret, url);// TODO 重要提示: 这里用一个本地文件来模拟发送实时流数据,实际使用时,用户可以从某处实时采集或接收语音流并发送到ASR服务端demo.process("./nls-sample-16k.wav", 16000);//demo.process("./nls-sample.opus", 16000);demo.shutdown();} }
2.RESTful API 2.0
1.功能介绍
一句话识别RESTful API支持以POST方式整段上传不超过一分钟的语音文件.识别结果将以JSON格式在请求响应中一次性返回,开发者需要保证在识别结果返回之前连接不被中断
支持音频编码格式:pcm(无压缩的pcm文件或wav文件)、opus,16bit采样位数的单声道(mono)
支持音频采样率:8000Hz、16000Hz
支持对返回结果进行设置:是否在后处理中添加标点,是否将中文数字转为阿拉伯数字输出
支持控制台配置项目热词和自学习模型训练
支持多种语言的识别,可在控制台编辑项目进行模型配置
说明:所有服务端的响应都会在返回信息中包含task_id参数,用于表示本次识别任务的ID,请记录下这个值,如果发生错误,请将task_id和错误信息提交到工单
2.服务地址
访问类型 | 说明 | URL | Host |
---|---|---|---|
外网访问 | 所有服务器均可使用外网访问URL | http://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/asr | nls-gateway.cn-shanghai.aliyuncs.com |
阿里云上海ECS内网访问 | 您使用阿里云上海ECS(即ECS地域为华东2(上海)),可使用内网访问URL 说明:使用内网访问方式,将不产生ECS实例的公网流量费用. ECS的经典网络不能访问AnyTunnel,即不能在内网访问语音服务;如果希望使用AnyTunnel,需要创建专有网络后在其内部访问. | http://nls-gateway.cn-shanghai-internal.aliyuncs.com/stream/v1/asr | nls-gateway.cn-shanghai-internal.aliyuncs.com |
3.上传音频文件
一句话识别请求HTTP报文实例:
POST /stream/v1/asr?appkey=23****f5&format=pcm&sample_rate=16000&enable_punctuation_prediction=true&enable_inverse_text_normalization=true HTTP/1.1X-NLS-Token: 450372e4279******bcc2b3c793Content-type: application/octet-streamContent-Length: 94616Host: nls-gateway.cn-shanghai.aliyuncs.com[audio data]
一个完整的一句话识别RESTful API请求需包含以下要素:
1.HTTP 请求行
URL
协议 | URL | 方法 |
---|---|---|
HTTP/1.1 | http://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/asr | POST |
请求参数
Parameter | Type | Description |
---|---|---|
appkey | String | 应用appkey,必填 |
format | String | 音频编码格式,可选,支持的格式:pcm、opus,默认是pcm |
sample_rate | Integer | 音频采样率,可选,16000Hz或者8000Hz,默认是16000Hz |
vocabulary_id | String | 指定添加热词表ID,可选,默认不添加 |
customization_id | String | 指定添加自学习模型ID,可选,默认不添加 |
enable_punctuation_prediction | Boolean | 是否在后处理中添加标点,可选,true或者false,默认false不开启 |
enable_inverse_text_normalization | Boolean | 是否在后处理中执行ITN,可选,true或者false,默认false不开启 |
enable_voice_detection | Boolean | 是否启动语音检测,可选,true或者false,默认false不开启.说明:如果开启语音检测,服务端会对上传的音频进行静音检测,切除静音部分和之后的语音内容,不再对其进行识别;不同的模型表现结果不同. |
示例:
http://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/asr?appkey=Yu1******uncS&format=pcm&sample_rate=16000&vocabulary_id=a17******d6b&customization_id=abd******ed8&enable_punctuation_prediction=true&enable_inverse_text_normalization=true&enable_voice_detection=true
2.HTTP 请求头部
HTTP 请求头部由“关键字/值”对组成,每行一对,关键字和值用英文冒号“:”分隔,设置内容为如下表格:
名称 | 类型 | 需求 | 描述 |
---|---|---|---|
X-NLS-Token | String | 必填 | 服务鉴权Token,获取方法请阅读获取 Token一节 |
Content-type | String | 必填 | 必须为“application/octet-stream”,表明HTTP body的数据为二进制流 |
Content-Length | long | 必填 | HTTP body中请求数据的长度,即音频文件的长度 |
Host | String | 必填 | HTTP请求的服务器域名,必须为“nls-gateway.cn-shanghai.aliyuncs.com” |
3.HTTP 请求体
HTTP请求体传入的是二进制音频数据,因此在HTTP请求头部中的Content-Type必须设置为application/octet-stream
4.响应结果
发送上传音频的HTTP请求之后,会收到服务端的响应,识别的结果以JSON字符串的形式保存在该响应中
1.成功响应
{ "task_id": "cf7b0c5339244ee29cd4e43fb97fd52e", "result": "北京的天气.", "status":20000000, "message":"SUCCESS" }
2.失败响应
以鉴权token错误为例:
{ "task_id": "8bae3613dfc54ebfa811a17d8a7a9ae7", "result": "", "status": 40000001, "message": "Gateway:ACCESS_DENIED:The token 'c0c1e860f3*******de8091c68a' is invalid!" }
响应字段说明
Parameter | Type | Description |
---|---|---|
task_id | String | 32位任务ID,请记录该值,以便于排查错误 |
result | String | 语音识别结果 |
status | Integer | 服务状态码 |
message | String | 服务状态描述 |
服务状态码说明
20000000表示成功,4开头的状态码表示客户端的错误,5开头的错误码表示服务端的错误
服务状态码 | 服务状态描述 | 解决方案 |
---|---|---|
20000000 | 请求成功 | |
40000000 | 默认的客户端错误码 | 查看错误消息或提交工单 |
40000001 | 身份认证失败 | 检查使用的令牌是否正确,是否过期 |
40000002 | 无效的消息 | 检查发送的消息是否符合要求 |
40000003 | 无效的参数 | 检查参数值设置是否合理 |
40000004 | 空闲超时 | 确认是否长时间没有发送数据掉服务端 |
40000005 | 请求数量过多 | 检查是否超过了并发连接数或者每秒钟请求数 |
50000000 | 默认的服务端错误 | 如果偶现可以忽略,重复出现请提交工单 |
50000001 | 内部GRPC调用错误 | 如果偶现可以忽略,重复出现请提交工单 |
5.Java Demo
import com.alibaba.fastjson.JSONPath; import com.alibaba.nls.client.example.utils.HttpUtil; import java.util.HashMap; public class SpeechRecognizerRESTfulDemo {private String accessToken;private String appkey;public SpeechRecognizerRESTfulDemo(String appkey, String token) {this.appkey = appkey;this.accessToken = token;}public void process(String fileName, String format, int sampleRate,boolean enablePunctuationPrediction,boolean enableInverseTextNormalization,boolean enableVoiceDetection) {/*** 设置HTTP REST POST请求* 1.使用http协议* 2.语音识别服务域名:nls-gateway.cn-shanghai.aliyuncs.com* 3.语音识别接口请求路径:/stream/v1/asr* 4.设置必须请求参数:appkey、format、sample_rate,* 5.设置可选请求参数:enable_punctuation_prediction、enable_inverse_text_normalization、enable_voice_detection*/String url = "http://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/asr";String request = url;request = request + "?appkey=" + appkey;request = request + "&format=" + format;request = request + "&sample_rate=" + sampleRate;if (enablePunctuationPrediction) {request = request + "&enable_punctuation_prediction=" + true;}if (enableInverseTextNormalization) {request = request + "&enable_inverse_text_normalization=" + true;}if (enableVoiceDetection) {request = request + "&enable_voice_detection=" + true;}System.out.println("Request: " + request);/*** 设置HTTP 头部字段* 1.鉴权参数* 2.Content-Type:application/octet-stream*/HashMap<String, String> headers = new HashMap<String, String>();headers.put("X-NLS-Token", this.accessToken);headers.put("Content-Type", "application/octet-stream");/*** 发送HTTP POST请求,返回服务端的响应*/String response = HttpUtil.sendPostFile(request, headers, fileName);if (response != null) {System.out.println("Response: " + response);String result = JSONPath.read(response, "result").toString();System.out.println("识别结果:" + result);}else {System.err.println("识别失败!");}}public static void main(String[] args) {if (args.length < 2) {System.err.println("SpeechRecognizerRESTfulDemo need params: <token> <app-key>");System.exit(-1);}String token = args[0];String appkey = args[1];SpeechRecognizerRESTfulDemo demo = new SpeechRecognizerRESTfulDemo(appkey, token);String fileName = SpeechRecognizerRESTfulDemo.class.getClassLoader().getResource("./nls-sample-16k.wav").getPath();String format = "pcm";int sampleRate = 16000;boolean enablePunctuationPrediction = true;boolean enableInverseTextNormalization = true;boolean enableVoiceDetection = false;demo.process(fileName, format, sampleRate, enablePunctuationPrediction, enableInverseTextNormalization, enableVoiceDetection);} }
2.实时语音识别
1.Java SDK2.0
可从maven 服务器下载最新版本SDK:
<dependency> <groupId>com.alibaba.nls</groupId> <artifactId>nls-sdk-transcriber</artifactId> <version>2.1.1</version> </dependency>
1.服务验证
java -cp nls-example-transcriber-2.0.0-jar-with-dependencies.jar com.alibaba.nls.client.SpeechTranscriberDem
2.服务压测
java -jar nls-example-transcriber-2.0.0-jar-with-dependencies.jar
3.关键接口
NlsClient:语音处理client,相当于所有语音相关处理类的factory,全局创建一个实例即可.线程安全
SpeechTranscriber:实时语音识别类,设置请求参数,发送请求及声音数据.非线程安全
SpeechTranscriberListener:实时语音识别结果监听类,监听识别结果.非线程安全
4.SDK 调用注意事项
NlsClient对象创建一次可以重复使用,每次创建消耗性能.NlsClient使用了netty的框架,创建时比较消耗时间和资源,但创建之后可以重复利用.建议调用程序将NlsClient的创建和关闭与程序本身的生命周期结合
SpeechTranscriber对象不能重复使用,一个识别任务对应一个SpeechTranscriber对象.例如有N个音频文件,则要进行N次识别任务,创建N个SpeechTranscriber对象
实现的SpeechTranscriberListener对象和SpeechTranscriber对象是一一对应的,不能将一个SpeechTranscriberListener对象设置到多个SpeechTranscriber对象中,否则不能区分是哪个识别任务
Java SDK依赖了Netty网络库,版本需设置为4.1.17.Final及以上.如果您的应用中依赖了Netty,请确保版本符合要求
5.Java Demo
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import com.alibaba.nls.client.protocol.InputFormatEnum; import com.alibaba.nls.client.protocol.NlsClient; import com.alibaba.nls.client.protocol.SampleRateEnum; import com.alibaba.nls.client.protocol.asr.SpeechTranscriber; import com.alibaba.nls.client.protocol.asr.SpeechTranscriberListener; import com.alibaba.nls.client.protocol.asr.SpeechTranscriberResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /*** 此示例演示了* ASR实时识别API调用* 动态获取token* 通过本地模拟实时流发送* 识别耗时计算* (仅作演示,需用户根据实际情况实现)*/ public class SpeechTranscriberDemo {private String appKey;private NlsClient client;private static final Logger logger = LoggerFactory.getLogger(SpeechTranscriberDemo.class);public SpeechTranscriberDemo(String appKey, String id, String secret, String url) {this.appKey = appKey;//TODO 重要提示 创建NlsClient实例,应用全局创建一个即可,生命周期可和整个应用保持一致,默认服务地址为阿里云线上服务地址//TODO 这里简单演示了获取token 的代码,该token会过期,实际使用时注意在accessToken.getExpireTime()过期前再次获取tokenAccessToken accessToken = new AccessToken(id, secret);try {accessToken.apply();System.out.println("get token: " + ", expire time: " + accessToken.getExpireTime());// TODO 创建NlsClient实例,应用全局创建一个即可,用户指定服务地址if(url.isEmpty()) {client = new NlsClient(accessToken.getToken());}else {client = new NlsClient(url, accessToken.getToken());}} catch (IOException e) {e.printStackTrace();}}private static SpeechTranscriberListener getTranscriberListener() {SpeechTranscriberListener listener = new SpeechTranscriberListener() {//TODO 识别出中间结果.服务端识别出一个字或词时会返回此消息.仅当setEnableIntermediateResult(true)时,才会有此类消息返回@Overridepublic void onTranscriptionResultChange(SpeechTranscriberResponse response) {System.out.println("task_id: " + response.getTaskId() +", name: " + response.getName() +//状态码 20000000 表示正常识别", status: " + response.getStatus() +//句子编号,从1开始递增", index: " + response.getTransSentenceIndex() +//当前的识别结果", result: " + response.getTransSentenceText() +//当前已处理的音频时长,单位是毫秒", time: " + response.getTransSentenceTime());}@Overridepublic void onTranscriberStart(SpeechTranscriberResponse response) {// TODO 重要提示: task_id很重要,是调用方和服务端通信的唯一ID标识,当遇到问题时,需要提供此task_id以便排查System.out.println("task_id: " + response.getTaskId() + ", name: " + response.getName() + ", status: " + response.getStatus());}@Overridepublic void onSentenceBegin(SpeechTranscriberResponse response) {System.out.println("task_id: " + response.getTaskId() + ", name: " + response.getName() + ", status: " + response.getStatus());}//识别出一句话.服务端会智能断句,当识别到一句话结束时会返回此消息@Overridepublic void onSentenceEnd(SpeechTranscriberResponse response) {System.out.println("task_id: " + response.getTaskId() +", name: " + response.getName() +//状态码 20000000 表示正常识别", status: " + response.getStatus() +//句子编号,从1开始递增", index: " + response.getTransSentenceIndex() +//当前的识别结果", result: " + response.getTransSentenceText() +//置信度", confidence: " + response.getConfidence() +//开始时间", begin_time: " + response.getSentenceBeginTime() +//当前已处理的音频时长,单位是毫秒", time: " + response.getTransSentenceTime());}//识别完毕@Overridepublic void onTranscriptionComplete(SpeechTranscriberResponse response) {System.out.println("task_id: " + response.getTaskId() + ", name: " + response.getName() + ", status: " + response.getStatus());}@Overridepublic void onFail(SpeechTranscriberResponse response) {// TODO 重要提示: task_id很重要,是调用方和服务端通信的唯一ID标识,当遇到问题时,需要提供此task_id以便排查System.out.println("task_id: " + response.getTaskId() + ", status: " + response.getStatus() + ", status_text: " + response.getStatusText());}};return listener;}/// 根据二进制数据大小计算对应的同等语音长度/// sampleRate 仅支持8000或16000public static int getSleepDelta(int dataSize, int sampleRate) {// 仅支持16位采样int sampleBytes = 16;// 仅支持单通道int soundChannel = 1;return (dataSize * 10 * 8000) / (160 * sampleRate);}public void process(String filepath) {SpeechTranscriber transcriber = null;try {//创建实例,建立连接transcriber = new SpeechTranscriber(client, getTranscriberListener());transcriber.setAppKey(appKey);//输入音频编码方式transcriber.setFormat(InputFormatEnum.PCM);//输入音频采样率transcriber.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);//是否返回中间识别结果transcriber.setEnableIntermediateResult(false);//是否生成并返回标点符号transcriber.setEnablePunctuation(true);//是否将返回结果规整化,比如将一百返回为100transcriber.setEnableITN(false);//此方法将以上参数设置序列化为json发送给服务端,并等待服务端确认transcriber.start();File file = new File(filepath);FileInputStream fis = new FileInputStream(file);byte[] b = new byte[3200];int len;while ((len = fis.read(b)) > 0) {logger.info("send data pack length: " + len);transcriber.send(b);// TODO 重要提示:这里是用读取本地文件的形式模拟实时获取语音流并发送的,因为read很快,所以这里需要sleep// TODO 如果是真正的实时获取语音,则无需sleep, 如果是8k采样率语音,第二个参数改为8000int deltaSleep = getSleepDelta(len, 16000);Thread.sleep(deltaSleep);}//通知服务端语音数据发送完毕,等待服务端处理完成long now = System.currentTimeMillis();logger.info("ASR wait for complete");transcriber.stop();logger.info("ASR latency : " + (System.currentTimeMillis() - now) + " ms");} catch (Exception e) {System.err.println(e.getMessage());} finally {if (null != transcriber) {transcriber.close();}}}public void shutdown() {client.shutdown();}public static void main(String[] args) throws Exception {String appKey = "填写你的appkey";String id = "填写你在阿里云网站上的AccessKeyId";String secret = "填写你在阿里云网站上的AccessKeySecret";String url = ""; // 默认即可,默认值:wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1if (args.length == 3) {appKey = args[0];id = args[1];secret = args[2];} else if (args.length == 4) {appKey = args[0];id = args[1];secret = args[2];url = args[3];} else {System.err.println("run error, need params(url is optional): " + "<app-key> <AccessKeyId> <AccessKeySecret> [url]");System.exit(-1);}// TODO 重要提示: 这里用一个本地文件来模拟发送实时流数据,实际使用时,用户可以从某处实时采集或接收语音流并发送到ASR服务端String filepath = "nls-sample-16k.wav";SpeechTranscriberDemo demo = new SpeechTranscriberDemo(appKey, id, secret, url);demo.process(filepath);demo.shutdown();} }
3.语音合成
1.Java SDK2.0
可从maven 服务器下载最新版本SDK:
<dependency> <groupId>com.alibaba.nls</groupId> <artifactId>nls-sdk-tts</artifactId> <version>2.1.1</version> </dependency>
1.服务验证
java -cp nls-example-tts-2.0.0-jar-with-dependencies.jar com.alibaba.nls.client.SpeechSynthesizerDemo
2.服务压测
java -jar nls-example-tts-2.0.0-jar-with-dependencies.jar
3.关键接口
NlsClient:语音处理client,相当于所有语音相关处理类的factory,全局创建一个实例即可.线程安全
SpeechSynthesizer:语音合成处理类,设置请求参数,发送请求.非线程安全
SpeechSynthesizerListener:语音合成监听类,监听返回结果.非线程安全.有如下两个抽象方法需要实现:
/** * 接收语音合成二进制数据 */ abstract public void onMessage(ByteBuffer message); /** * 语音合成结束事件通知 * * @param response */ abstract public void onComplete(SpeechSynthesizerResponse response);
4.SDK 调用注意事项
NlsClient对象创建一次可以重复使用,每次创建消耗性能.NlsClient使用了netty的框架,创建时比较消耗时间和资源,但创建之后可以重复利用.建议调用程序将NlsClient的创建和关闭与程序本身的生命周期结合
SpeechSynthesizer对象不能重复使用,一个语音合成任务对应一个SpeechSynthesizer对象.例如有N个文本需要语音合成,则要进行N次语音合成任务,创建N个SpeechSynthesizer对象
实现的SpeechSynthesizerListener对象和SpeechSynthesizer对象是一一对应的,不能将一个SpeechSynthesizerListener对象设置到多个SpeechSynthesizer对象中,否则不能区分是哪个语音合成任务
Java SDK依赖了Netty网络库,版本需设置为4.1.17.Final及以上.如果您的应用中依赖了Netty,请确保版本符合要求
5.Java Demo
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import com.alibaba.nls.client.protocol.NlsClient; import com.alibaba.nls.client.protocol.OutputFormatEnum; import com.alibaba.nls.client.protocol.SampleRateEnum; import com.alibaba.nls.client.protocol.tts.SpeechSynthesizer; import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerListener; import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /*** 此示例演示了* 语音合成API调用* 动态获取token* 流式合成TTS* 首包延迟计算* (仅作演示,需用户根据实际情况实现)*/ public class SpeechSynthesizerDemo {private static final Logger logger = LoggerFactory.getLogger(SpeechSynthesizerDemo.class);private static long startTime;private String appKey;NlsClient client;public SpeechSynthesizerDemo(String appKey, String accessKeyId, String accessKeySecret) {this.appKey = appKey;//TODO 重要提示 创建NlsClient实例,应用全局创建一个即可,生命周期可和整个应用保持一致,默认服务地址为阿里云线上服务地址//TODO 这里简单演示了获取token 的代码,该token会过期,实际使用时注意在accessToken.getExpireTime()过期前再次获取tokenAccessToken accessToken = new AccessToken(accessKeyId, accessKeySecret);try {accessToken.apply();System.out.println("get token: " + accessToken.getToken() + ", expire time: " + accessToken.getExpireTime());client = new NlsClient(accessToken.getToken());} catch (IOException e) {e.printStackTrace();}}public SpeechSynthesizerDemo(String appKey, String accessKeyId, String accessKeySecret, String url) {this.appKey = appKey;//TODO 重要提示 创建NlsClient实例,应用全局创建一个即可,生命周期可和整个应用保持一致,默认服务地址为阿里云线上服务地址//TODO 这里简单演示了获取token 的代码,该token会过期,实际使用时注意在accessToken.getExpireTime()过期前再次获取tokenAccessToken accessToken = new AccessToken(accessKeyId, accessKeySecret);try {accessToken.apply();System.out.println("get token: " + accessToken.getToken() + ", expire time: " + accessToken.getExpireTime());if(url.isEmpty()) {client = new NlsClient(accessToken.getToken());}else {client = new NlsClient(url, accessToken.getToken());}} catch (IOException e) {e.printStackTrace();}}private static SpeechSynthesizerListener getSynthesizerListener() {SpeechSynthesizerListener listener = null;try {listener = new SpeechSynthesizerListener() {File f=new File("tts_test.wav");FileOutputStream fout = new FileOutputStream(f);private boolean firstRecvBinary = true;//语音合成结束@Overridepublic void onComplete(SpeechSynthesizerResponse response) {// TODO 当onComplete时表示所有TTS数据已经接收完成,因此这个是整个合成延迟,该延迟可能较大,未必满足实时场景System.out.println("name: " + response.getName() +", status: " + response.getStatus()+", output file :"+f.getAbsolutePath());}//语音合成的语音二进制数据@Overridepublic void onMessage(ByteBuffer message) {try {if(firstRecvBinary) {// TODO 此处是计算首包语音流的延迟,收到第一包语音流时,即可以进行语音播放,以提升响应速度(特别是实时交互场景下)firstRecvBinary = false;long now = System.currentTimeMillis();logger.info("tts first latency : " + (now - SpeechSynthesizerDemo.startTime) + " ms");}byte[] bytesArray = new byte[message.remaining()];message.get(bytesArray, 0, bytesArray.length);fout.write(bytesArray);} catch (IOException e) {e.printStackTrace();}}@Overridepublic void onFail(SpeechSynthesizerResponse response){// TODO 重要提示: task_id很重要,是调用方和服务端通信的唯一ID标识,当遇到问题时,需要提供此task_id以便排查System.out.println("task_id: " + response.getTaskId() +//状态码 20000000 表示识别成功", status: " + response.getStatus() +//错误信息", status_text: " + response.getStatusText());}};} catch (Exception e) {e.printStackTrace();}return listener;}public void process() {SpeechSynthesizer synthesizer = null;try {//创建实例,建立连接synthesizer = new SpeechSynthesizer(client, getSynthesizerListener());synthesizer.setAppKey(appKey);//设置返回音频的编码格式synthesizer.setFormat(OutputFormatEnum.WAV);//设置返回音频的采样率synthesizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);//发音人synthesizer.setVoice("siyue");//语调,范围是-500~500,可选,默认是0synthesizer.setPitchRate(100);//语速,范围是-500~500,默认是0synthesizer.setSpeechRate(100);//设置用于语音合成的文本synthesizer.setText("欢迎使用阿里巴巴智能语音合成服务,您可以说北京明天天气怎么样啊");//此方法将以上参数设置序列化为json发送给服务端,并等待服务端确认long start = System.currentTimeMillis();synthesizer.start();logger.info("tts start latency " + (System.currentTimeMillis() - start) + " ms");SpeechSynthesizerDemo.startTime = System.currentTimeMillis();//等待语音合成结束synthesizer.waitForComplete();logger.info("tts stop latency " + (System.currentTimeMillis() - start) + " ms");} catch (Exception e) {e.printStackTrace();} finally {//关闭连接if (null != synthesizer) {synthesizer.close();}}}public void shutdown() {client.shutdown();}public static void main(String[] args) throws Exception {String appKey = "填写你的appkey";String id = "填写你在阿里云网站上的AccessKeyId";String secret = "填写你在阿里云网站上的AccessKeySecret";String url = ""; // 默认即可,默认值:wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1if (args.length == 3) {appKey = args[0];id = args[1];secret = args[2];} else if (args.length == 4) {appKey = args[0];id = args[1];secret = args[2];url = args[3];} else {System.err.println("run error, need params(url is optional): " + "<app-key> <AccessKeyId> <AccessKeySecret> [url]");System.exit(-1);}SpeechSynthesizerDemo demo = new SpeechSynthesizerDemo(appKey, id, secret, url);demo.process();demo.shutdown();} }
2.RESTful API2.0
1.功能介绍
语音合成RESTful API支持HTTPS GET和POST两种方法的请求,将待合成的文本上传到服务端,服务端返回文本的语音合成结果,开发者需要保证在语音合成结果返回之前连接不被中断
支持设置合成音频的格式:pcm,wav,mp3
支持设置合成音频的采样率:8000Hz、16000Hz
支持设置多种发音人
支持设置语速、语调、音量
重要提示
随着TTS合成效果的不断提升,算法的复杂度也越来越高,对用户而言,可能会遇到合成耗时变长的可能.因此我们建议您使用流式合成机制.本文档及SDK附带Demo示例中有相关流式处理示例代码可做参考
单次调用传入文本不能超过300个字符,否则超过300字符的内容会被截断,只合成300字符以内的内容,对应更长文本的合成,可以参考SDK附带Demo中的长文本切分及拼接示例
2.服务地址
访问类型 | 说明 | URL | Host |
---|---|---|---|
外网访问 | 所有服务器均可使用外网访问URL | https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts | nls-gateway.cn-shanghai.aliyuncs.com |
阿里云上海ECS内网访问 | 您使用阿里云上海ECS(即ECS地域为华东2(上海)),可使用内网访问URL | http://nls-gateway.cn-shanghai-internal.aliyuncs.com/stream/v1/tts | nls-gateway.cn-shanghai-internal.aliyuncs.com |
以下将以使用外网访问URL的方式进行介绍.如果您使用的是阿里云上海ECS,并想使用内网访问URL,则要使用HTTP协议,并替换外网访问的URL和Host
3.请求参数
语音合成需要设置的请求参数如下表所示.如果使用HTTPS GET方法的请求,需要将这些参数设置到HTTPS的URL请求参数中;如果使用HTTPS POST方法的请求,需要将这些参数设置到HTTPS的请求体(Body)中
名称 | 类型 | 是否必需 | 描述 |
---|---|---|---|
appkey | String | 是 | 应用appkey(获取方法请阅读创建项目一节) |
text | String | 是 | 待合成的文本,需要为UTF-8编码.使用GET方法,需要再采用RFC 3986规范进行urlencode编码,比如加号 + 编码为 %2B ;使用POST方法不需要urlencode编码 |
token | String | 否 | 服务鉴权Token,获取方法请阅读获取访问令牌一节.若不设置token参数,需要在HTTP Headers中设置X-NLS-Token字段来指定Token |
format | String | 否 | 音频编码格式,支持的格式:pcm、wav、mp3,默认是pcm |
sample_rate | Integer | 否 | 音频采样率,支持16000Hz、8000Hz,默认是16000Hz |
voice | String | 否 | 发音人,默认是xiaoyun,其他发音人名称请在简介中选择 |
volume | Integer | 否 | 音量,范围是0~100,默认50 |
speech_rate | Integer | 否 | 语速,范围是-500~500,默认是0 |
pitch_rate | Integer | 否 | 语调,范围是-500~500,可选,默认是0 |
4.GET方法上传文本
一个完整的语音合成RESTful API GET方法的请求包含以下要素:
1.URL
协议 | URL | 方法 |
---|---|---|
HTTPS | https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts | GET |
2.请求参数
见上述请求参数表格.
如上URL和请求参数组成的完整请求链接如下所示,在浏览器中打开该链接可直接获取语音合成的结果:
# appkey请填入您的管控台创建的项目appkey,token请填入您的token,在浏览器中打开该链接,可直接获取语音合成结果. # text的内容为"今天是周一,天气挺好的." https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts?appkey=${您的appkey}&token=${您的token}&text=%E4%BB%8A%E5%A4%A9%E6%98%AF%E5%91%A8%E4%B8%80%EF%BC%8C%E5%A4%A9%E6%B0%94%E6%8C%BA%E5%A5%BD%E7%9A%84%E3%80%82&format=wav&sample_rate=16000
3.HTTPS GET 请求头部
名称 | 类型 | 是否必需 | 描述 |
---|---|---|---|
X-NLS-Token | String | 否 | 服务鉴权Token,获取方法请阅读获取访问令牌一节.若请求参数中没有设置token参数,则需要在这里设置该字段 |
注意:
服务鉴权Token参数既可以在请求参数token中设置,也可以在HTTPS Headers的X-NLS-Token字段设置,推荐使用请求参数token.
参数text必须采用UTF-8编码,在采用RFC 3986规范进行urlencode编码,比如加号
+
编码为%2B
,星号*
编码为%2A
,%7E
编码为~
.
5.POST方法上传文本
一个完整的语音合成RESTful API POST请求包含以下要素:
1.URL
协议 | URL | 方法 |
---|---|---|
HTTPS | https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts | POST |
2.HTTPS POST 请求头部
名称 | 类型 | 是否必需 | 描述 |
---|---|---|---|
X-NLS-Token | String | 否 | 服务鉴权Token,获取方法请阅读获取访问令牌一节.若Body请求参数中没有设置token参数,则需要在这里设置该字段 |
Content-Type | String | 是 | 必须为“application/json”,表明HTTP Body的内容为JSON格式字符串 |
Content-Length | long | 否 | HTTP Body中内容的长度 |
3.HTTPS POST 请求体
HTTPS POST请求体传入的是请求参数组成的JSON格式的字符串,因此在HTTPS POST请求头部中的Content-Type必须设置为”application/json”.示例如下:
{ "appkey":"31f932fb", "text":"今天是周一,天气挺好的.", "token":"45034**********3c793", "format":"wav" }
注意:
服务鉴权Token参数既可以在Body中的请求参数token中设置,也可以在HTTPS Headers的X-NLS-Token字段设置,推荐使用Body参数token.
使用POST方法的请求,Body中的请求参数text必须采用UTF-8编码,但是不进行urlencode编码,注意与GET方法请求的区分.
6.响应结果
使用HTTPS GET方法和使用HTTPS POST方法请求的响应是相同的,响应的结果都包含在HTTPS的Body中.响应结果的成功或失败通过HTTPS Header的Content-Type字段来区分:
成功响应
HTTPS Headers的Content-Type字段内容为
audio/mpeg
,表示合成成功,合成的语音数据在Body中.HTTPS Header的X-NLS-RequestId字段内容为请求任务的task_id,方便调试排查.
Body内容为合成音频的二进制数据.
失败响应
HTTPS Headers没有Content-Type字段,或者Content-Type字段内容为
application/json
,表示合成失败,错误信息在Body中.HTTPS Header的X-NLS-RequestId字段内容为请求任务的task_id,方便调试排查.
Body内容为错误信息,JSON格式的字符串.如下所示:
{ "task_id":"8f95d0b9b6e948bc98e8d0ce64b0cf57", "result":"", "status":40000000, "message":"Gateway:CLIENT_ERROR:in post data, json format illegal" }
1.响应字段
失败响应时的错误信息字段如下表所示:
名称 | 类型 | 描述 |
---|---|---|
task_id | String | 32位请求任务ID,请记录该值,用于排查错误 |
result | String | 服务结果 |
status | Integer | 服务状态码 |
message | String | 服务状态描述 |
2.服务状态码
服务状态码 | 服务状态描述 | 解决办法 |
---|---|---|
20000000 | 请求成功 | |
40000000 | 默认的客户端错误码 | 查看错误消息或提交工单 |
40000001 | 身份认证失败 | 检查使用的令牌是否正确,是否过期 |
40000002 | 无效的消息 | 检查发送的消息是否符合要求 |
40000003 | 无效的参数 | 检查参数值设置是否合理 |
40000004 | 空闲超时 | 确认是否长时间没有发送数据掉服务端 |
40000005 | 请求数量过多 | 检查是否超过了并发连接数或者每秒钟请求数 |
50000000 | 默认的服务端错误 | 如果偶现可以忽略,重复出现请提交工单 |
50000001 | 内部GRPC调用错误 | 如果偶现可以忽略,重复出现请提交工单 |
7.Java Demo
import java.io.File; import java.io.FileOutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import com.alibaba.fastjson.JSONObject; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class SpeechSynthesizerRestfulDemo {private String accessToken;private String appkey;public SpeechSynthesizerRestfulDemo(String appkey, String token) {this.appkey = appkey;this.accessToken = token;}/*** HTTPS GET 请求*/public void processGETRequet(String text, String audioSaveFile, String format, int sampleRate, String voice) {/*** 设置HTTPS GET请求* 1.使用HTTPS协议* 2.语音识别服务域名:nls-gateway.cn-shanghai.aliyuncs.com* 3.语音识别接口请求路径:/stream/v1/tts* 4.设置必须请求参数:appkey、token、text、format、sample_rate* 5.设置可选请求参数:voice、volume、speech_rate、pitch_rate*/String url = "https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts";url = url + "?appkey=" + appkey;url = url + "&token=" + accessToken;url = url + "&text=" + text;url = url + "&format=" + format;url = url + "&voice=" + voice;url = url + "&sample_rate=" + String.valueOf(sampleRate);// voice 发音人,可选,默认是xiaoyun// url = url + "&voice=" + "xiaoyun";// volume 音量,范围是0~100,可选,默认50// url = url + "&volume=" + String.valueOf(50);// speech_rate 语速,范围是-500~500,可选,默认是0// url = url + "&speech_rate=" + String.valueOf(0);// pitch_rate 语调,范围是-500~500,可选,默认是0// url = url + "&pitch_rate=" + String.valueOf(0);System.out.println("URL: " + url);/*** 发送HTTPS GET请求,处理服务端的响应*/Request request = new Request.Builder().url(url).get().build();try {long start = System.currentTimeMillis();OkHttpClient client = new OkHttpClient();Response response = client.newCall(request).execute();System.out.println("total latency :" + (System.currentTimeMillis() - start) + " ms");System.out.println(response.headers().toString());String contentType = response.header("Content-Type");if ("audio/mpeg".equals(contentType)) {File f = new File(audioSaveFile);FileOutputStream fout = new FileOutputStream(f);fout.write(response.body().bytes());fout.close();System.out.println("The GET request succeed!");}else {// ContentType 为 null 或者为 "application/json"String errorMessage = response.body().string();System.out.println("The GET request failed: " + errorMessage);}response.close();} catch (Exception e) {e.printStackTrace();}}/*** HTTPS POST 请求*/public void processPOSTRequest(String text, String audioSaveFile, String format, int sampleRate, String voice) {/*** 设置HTTPS POST请求* 1.使用HTTPS协议* 2.语音合成服务域名:nls-gateway.cn-shanghai.aliyuncs.com* 3.语音合成接口请求路径:/stream/v1/tts* 4.设置必须请求参数:appkey、token、text、format、sample_rate* 5.设置可选请求参数:voice、volume、speech_rate、pitch_rate*/String url = "https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts";JSONObject taskObject = new JSONObject();taskObject.put("appkey", appkey);taskObject.put("token", accessToken);taskObject.put("text", text);taskObject.put("format", format);taskObject.put("voice", voice);taskObject.put("sample_rate", sampleRate);// voice 发音人,可选,默认是xiaoyun// taskObject.put("voice", "xiaoyun");// volume 音量,范围是0~100,可选,默认50// taskObject.put("volume", 50);// speech_rate 语速,范围是-500~500,可选,默认是0// taskObject.put("speech_rate", 0);// pitch_rate 语调,范围是-500~500,可选,默认是0// taskObject.put("pitch_rate", 0);String bodyContent = taskObject.toJSONString();System.out.println("POST Body Content: " + bodyContent);RequestBody reqBody = RequestBody.create(MediaType.parse("application/json"), bodyContent);Request request = new Request.Builder().url(url).header("Content-Type", "application/json").post(reqBody).build();try {OkHttpClient client = new OkHttpClient();Response response = client.newCall(request).execute();String contentType = response.header("Content-Type");if ("audio/mpeg".equals(contentType)) {File f = new File(audioSaveFile);FileOutputStream fout = new FileOutputStream(f);fout.write(response.body().bytes());fout.close();System.out.println("The POST request succeed!");}else {// ContentType 为 null 或者为 "application/json"String errorMessage = response.body().string();System.out.println("The POST request failed: " + errorMessage);}response.close();} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) {if (args.length < 2) {System.err.println("SpeechSynthesizerRestfulDemo need params: <token> <app-key>");System.exit(-1);}String token = args[0];String appkey = args[1];SpeechSynthesizerRestfulDemo demo = new SpeechSynthesizerRestfulDemo(appkey, token);String text = "今天是周一,天气挺好的.";// 采用RFC 3986规范进行urlencode编码String textUrlEncode = text;try {textUrlEncode = URLEncoder.encode(textUrlEncode, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");} catch (UnsupportedEncodingException e) {e.printStackTrace();}System.out.println(textUrlEncode);String audioSaveFile = "syAudio.wav";String format = "wav";int sampleRate = 16000;demo.processGETRequet(textUrlEncode, audioSaveFile, format, sampleRate, "siyue");//demo.processPOSTRequest(text, audioSaveFile, format, sampleRate, "siyue");System.out.println("### Game Over ###");} }
4.录音文件识别
1.Java SDK2.0
录音文件识别的Java Demo使用了阿里云Java SDK的CommonRequest用来提交录音文件识别请求和识别结果查询,采用的是RPC风格的POP API调用
<dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>3.7.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.49</version> </dependency>
1.阿里云鉴权client
使用过程中,所有的调用均通过阿里云账号来完成鉴权操作.通过传入阿里云账号的AccessKey ID和AccessKey Secret,调用阿里云Java SDK,得到client,示例如下:
final String accessKeyId = "您的AccessKey Id"; final String accessKeySecret = "您的AccessKey Secret"; /*** 地域ID*/ final String regionId = "cn-shanghai"; final String endpointName = "cn-shanghai"; final String product = "nls-filetrans"; final String domain = "filetrans.cn-shanghai.aliyuncs.com"; IAcsClient client; // 设置endpoint DefaultProfile.addEndpoint(endpointName, regionId, product, domain); // 创建DefaultAcsClient实例并初始化 DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret); client = new DefaultAcsClient(profile);
2.录音文件识别请求调用接口
Java Demo采用的是轮询的方式,提交录音文件识别请求,获取任务ID,供后续轮询使用
说明:只需设置JSON字符串中的参数,其他方法的参数值保持不变
/*** 创建CommonRequest 设置请求参数*/ CommonRequest postRequest = new CommonRequest(); postRequest.setDomain("filetrans.cn-shanghai.aliyuncs.com"); // 设置域名,固定值 postRequest.setVersion("2018-08-17"); // 设置API的版本号,固定值 postRequest.setAction("SubmitTask"); // 设置action,固定值 postRequest.setProduct("nls-filetrans"); // 设置产品名称,固定值 // 设置录音文件识别请求参数,以JSON字符串的格式设置到请求的Body中 JSONObject taskObject = new JSONObject(); taskObject.put("appkey", "您的appkey"); // 设置appkey,传入您管控台项目的appkey taskObject.put("file_link", "您的录音文件访问链接"); // 设置录音文件访问链接,传入您需要识别的录音文件的链接 taskObject.put(KEY_VERSION, "4.0"); // 新接入请使用4.0版本,已接入(默认2.0)如需维持现状,请注释掉该参数设置 String task = taskObject.toJSONString(); postRequest.putBodyParameter("Task", task); // 设置以上JSON字符串为Body参数 postRequest.setMethod(MethodType.POST); // 设置为POST方式的请求 /*** 提交录音文件识别请求*/ String taskId = ""; // 获取录音文件识别请求任务的ID,以供识别结果查询使用 CommonResponse postResponse = client.getCommonResponse(postRequest); if (postResponse.getHttpStatus() == 200) {JSONObject result = JSONObject.parseObject(postResponse.getData());String statusText = result.getString("StatusText");if ("SUCCESS".equals(statusText)) {System.out.println("录音文件识别请求成功响应: " + result.toJSONString());taskId = result.getString("TaskId");}else {System.out.println("录音文件识别请求失败: " + result.toJSONString());return;} } else {System.err.println("录音文件识别请求失败,Http错误码:" + postResponse.getHttpStatus());System.err.println("录音文件识别请求失败响应:" + JSONObject.toJSONString(postResponse));return; }
3.录音文件识别结果查询
使用上面获得的任务ID,查询录音文件识别的结果
/*** 创建CommonRequest 设置任务ID*/ CommonRequest getRequest = new CommonRequest(); getRequest.setDomain("filetrans.cn-shanghai.aliyuncs.com"); // 设置域名,固定值 getRequest.setVersion("2018-08-17"); // 设置API版本,固定值 getRequest.setAction("GetTaskResult"); // 设置action,固定值 getRequest.setProduct("nls-filetrans"); // 设置产品名称,固定值 getRequest.putQueryParameter("TaskId", taskId); // 设置任务ID为查询参数,传入任务ID getRequest.setMethod(MethodType.GET); // 设置为GET方式的请求 /*** 提交录音文件识别结果查询请求* 以轮询的方式进行识别结果的查询,直到服务端返回的状态描述为“SUCCESS”、“SUCCESS_WITH_NO_VALID_FRAGMENT”,或者为错误描述,则结束轮询.*/ String statusText = ""; while (true) {CommonResponse getResponse = client.getCommonResponse(getRequest);if (getResponse.getHttpStatus() != 200) {System.err.println("识别结果查询请求失败,Http错误码: " + getResponse.getHttpStatus());System.err.println("识别结果查询请求失败: " + getResponse.getData());break;}JSONObject result = JSONObject.parseObject(getResponse.getData());System.out.println("识别查询结果:" + result.toJSONString());statusText = result.getString("StatusText");if ("RUNNING".equals(statusText) || "QUEUEING".equals(statusText)) {// 继续轮询Thread.sleep(3000);}else {break;} } if ("SUCCESS".equals(statusText) || "SUCCESS_WITH_NO_VALID_FRAGMENT".equals(statusText)) {System.out.println("录音文件识别成功!"); } else {System.err.println("录音文件识别失败!"); }
4.Java Demo
import com.alibaba.fastjson.JSONObject; import com.aliyuncs.CommonRequest; import com.aliyuncs.CommonResponse; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.http.MethodType; import com.aliyuncs.profile.DefaultProfile; public class FileTransJavaDemo {// 地域ID,常量内容,请勿改变public static final String REGIONID = "cn-shanghai";public static final String ENDPOINTNAME = "cn-shanghai";public static final String PRODUCT = "nls-filetrans";public static final String DOMAIN = "filetrans.cn-shanghai.aliyuncs.com";public static final String API_VERSION = "2018-08-17";public static final String POST_REQUEST_ACTION = "SubmitTask";public static final String GET_REQUEST_ACTION = "GetTaskResult";// 请求参数keypublic static final String KEY_APP_KEY = "appkey";public static final String KEY_FILE_LINK = "file_link";public static final String KEY_VERSION = "version";public static final String KEY_ENABLE_WORDS = "enable_words";// 响应参数keypublic static final String KEY_TASK = "Task";public static final String KEY_TASK_ID = "TaskId";public static final String KEY_STATUS_TEXT = "StatusText";public static final String KEY_RESULT = "Result";// 状态值public static final String STATUS_SUCCESS = "SUCCESS";private static final String STATUS_RUNNING = "RUNNING";private static final String STATUS_QUEUEING = "QUEUEING";// 阿里云鉴权clientIAcsClient client;public FileTransJavaDemo(String accessKeyId, String accessKeySecret) {// 设置endpointtry {DefaultProfile.addEndpoint(ENDPOINTNAME, REGIONID, PRODUCT, DOMAIN);} catch (ClientException e) {e.printStackTrace();}// 创建DefaultAcsClient实例并初始化DefaultProfile profile = DefaultProfile.getProfile(REGIONID, accessKeyId, accessKeySecret);this.client = new DefaultAcsClient(profile);}public String submitFileTransRequest(String appKey, String fileLink) {/*** 1. 创建CommonRequest 设置请求参数*/CommonRequest postRequest = new CommonRequest();// 设置域名postRequest.setDomain(DOMAIN);// 设置API的版本号,格式为YYYY-MM-DDpostRequest.setVersion(API_VERSION);// 设置actionpostRequest.setAction(POST_REQUEST_ACTION);// 设置产品名称postRequest.setProduct(PRODUCT);/*** 2. 设置录音文件识别请求参数,以JSON字符串的格式设置到请求的Body中*/JSONObject taskObject = new JSONObject();// 设置appkeytaskObject.put(KEY_APP_KEY, appKey);// 设置音频文件访问链接taskObject.put(KEY_FILE_LINK, fileLink);// 新接入请使用4.0版本,已接入(默认2.0)如需维持现状,请注释掉该参数设置taskObject.put(KEY_VERSION, "4.0");// 设置是否输出词信息,默认为false,开启时需要设置version为4.0及以上taskObject.put(KEY_ENABLE_WORDS, true);String task = taskObject.toJSONString();System.out.println(task);// 设置以上JSON字符串为Body参数postRequest.putBodyParameter(KEY_TASK, task);// 设置为POST方式的请求postRequest.setMethod(MethodType.POST);/*** 3. 提交录音文件识别请求,获取录音文件识别请求任务的ID,以供识别结果查询使用*/String taskId = null;try {CommonResponse postResponse = client.getCommonResponse(postRequest);System.err.println("提交录音文件识别请求的响应:" + postResponse.getData());if (postResponse.getHttpStatus() == 200) {JSONObject result = JSONObject.parseObject(postResponse.getData());String statusText = result.getString(KEY_STATUS_TEXT);if (STATUS_SUCCESS.equals(statusText)) {taskId = result.getString(KEY_TASK_ID);}}} catch (ClientException e) {e.printStackTrace();}return taskId;}public String getFileTransResult(String taskId) {/*** 1. 创建CommonRequest 设置任务ID*/CommonRequest getRequest = new CommonRequest();// 设置域名getRequest.setDomain(DOMAIN);// 设置API版本getRequest.setVersion(API_VERSION);// 设置actiongetRequest.setAction(GET_REQUEST_ACTION);// 设置产品名称getRequest.setProduct(PRODUCT);// 设置任务ID为查询参数getRequest.putQueryParameter(KEY_TASK_ID, taskId);// 设置为GET方式的请求getRequest.setMethod(MethodType.GET);/*** 2. 提交录音文件识别结果查询请求* 以轮询的方式进行识别结果的查询,直到服务端返回的状态描述为“SUCCESS”,或者为错误描述,则结束轮询.*/String result = null;while (true) {try {CommonResponse getResponse = client.getCommonResponse(getRequest);System.err.println("识别查询结果:" + getResponse.getData());if (getResponse.getHttpStatus() != 200) {break;}JSONObject rootObj = JSONObject.parseObject(getResponse.getData());String statusText = rootObj.getString(KEY_STATUS_TEXT);if (STATUS_RUNNING.equals(statusText) || STATUS_QUEUEING.equals(statusText)) {// 继续轮询,注意设置轮询时间间隔Thread.sleep(3000);}else {// 状态信息为成功,返回识别结果;状态信息为异常,返回空if (STATUS_SUCCESS.equals(statusText)) {result = rootObj.getString(KEY_RESULT);// 状态信息为成功,但没有识别结果,则可能是由于文件里全是静音、噪音等导致识别为空if(result == null) {result = "";}}break;}} catch (Exception e) {e.printStackTrace();}}return result;}public static void main(String args[]) throws Exception {if (args.length < 3) {System.err.println("FileTransJavaDemo need params: <AccessKey Id> <AccessKey Secret> <app-key>");}final String accessKeyId = args[0];final String accessKeySecret = args[1];final String appKey = args[2];String fileLink = "https://aliyun-nls.oss-cn-hangzhou.aliyuncs.com/asr/fileASR/examples/nls-sample-16k.wav";FileTransJavaDemo demo = new FileTransJavaDemo(accessKeyId, accessKeySecret);// 第一步:提交录音文件识别请求,获取任务ID用于后续的识别结果轮询String taskId = demo.submitFileTransRequest(appKey, fileLink);if (taskId != null) {System.out.println("录音文件识别请求成功,task_id: " + taskId);}else {System.out.println("录音文件识别请求失败!");return;}// 第二步:根据任务ID轮询识别结果String result = demo.getFileTransResult(taskId);if (result != null) {System.out.println("录音文件识别结果查询成功:" + result);}else {System.out.println("录音文件识别结果查询失败!");}} }
补充说明:如果使用回调方式,请在task字符串中设置“enable_callback”、“callback_url”参数:
taskObject.put("enable_callback", true); taskObject.put("callback_url", "回调地址");
回调服务示例:该服务用于回调方式获取转写结果,仅供参考,假设设置的回调地址是:http://ip:port/filetrans/callback/result
package com.example.filetrans; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; @RequestMapping("/filetrans/callback") @RestController public class FiletransCallBack {// 以4开头的状态码是客户端错误private static final Pattern PATTERN_CLIENT_ERR = Pattern.compile("4105[0-9]*");// 以5开头的状态码是服务端错误private static final Pattern PATTERN_SERVER_ERR = Pattern.compile("5105[0-9]*");// 必须是post的方式@RequestMapping(value = "result", method = RequestMethod.POST)public void GetResult(HttpServletRequest request) {byte [] buffer = new byte[request.getContentLength()];ServletInputStream in = null;try {in = request.getInputStream();in.read(buffer, 0 ,request.getContentLength());in.close();// 获取json格式的文件转写结果String result = new String(buffer);JSONObject jsonResult = JSONObject.parseObject(result);// 解析并输出相关结果内容System.out.println("获取文件中转写回调结果:" + result);System.out.println("TaskId: " + jsonResult.getString("TaskId"));System.out.println("StatusCode: " + jsonResult.getString("StatusCode"));System.out.println("StatusText: " + jsonResult.getString("StatusText"));Matcher matcherClient = PATTERN_CLIENT_ERR.matcher(jsonResult.getString("StatusCode"));Matcher matcherServer = PATTERN_SERVER_ERR.matcher(jsonResult.getString("StatusCode"));// 以2开头状态码为正常状态码,回调方式方式正常状态只返回"21050000"if("21050000".equals(jsonResult.getString("StatusCode"))) {System.out.println("RequestTime: " + jsonResult.getString("RequestTime"));System.out.println("SolveTime: " + jsonResult.getString("SolveTime"));System.out.println("BizDuration: " + jsonResult.getString("BizDuration"));System.out.println("Result.Sentences.size: " +jsonResult.getJSONObject("Result").getJSONArray("Sentences").size());for (int i = 0; i < jsonResult.getJSONObject("Result").getJSONArray("Sentences").size(); i++) {System.out.println("Result.Sentences[" + i + "].BeginTime: " +jsonResult.getJSONObject("Result").getJSONArray("Sentences").getJSONObject(i).getString("BeginTime"));System.out.println("Result.Sentences[" + i + "].EndTime: " +jsonResult.getJSONObject("Result").getJSONArray("Sentences").getJSONObject(i).getString("EndTime"));System.out.println("Result.Sentences[" + i + "].SilenceDuration: " +jsonResult.getJSONObject("Result").getJSONArray("Sentences").getJSONObject(i).getString("SilenceDuration"));System.out.println("Result.Sentences[" + i + "].Text: " +jsonResult.getJSONObject("Result").getJSONArray("Sentences").getJSONObject(i).getString("Text"));System.out.println("Result.Sentences[" + i + "].ChannelId: " +jsonResult.getJSONObject("Result").getJSONArray("Sentences").getJSONObject(i).getString("ChannelId"));System.out.println("Result.Sentences[" + i + "].SpeechRate: " +jsonResult.getJSONObject("Result").getJSONArray("Sentences").getJSONObject(i).getString("SpeechRate"));System.out.println("Result.Sentences[" + i + "].EmotionValue: " +jsonResult.getJSONObject("Result").getJSONArray("Sentences").getJSONObject(i).getString("EmotionValue"));}}else if(matcherClient.matches()) {System.out.println("状态码以4开头表示客户端错误......");}else if(matcherServer.matches()) {System.out.println("状态码以5开头表示服务端错误......");}else {}} catch (IOException e) {e.printStackTrace();}} }