阿里云智能语音交互

  • 时间:
  • 来源:互联网
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_42228694/article/details/103289916

智能语音交互

智能语音交互(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 {
    // 您的地域ID
    private 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算法进行对称加密.其工作流程如下:

  1. 请求端根据API请求内容(包括HTTP 请求参数和Body)生成签名字符串

  2. 请求端使用阿里云账号的AccessKeyId 和 AccessKeySecret对第一步生成的签名字符串进行签名,获得该API请求的数字签名

  3. 请求端把API请求内容和数字签名一同发送给服务端

  4. 服务端在接收到请求后会重复如上的第1、2步工作(服务端会在后台获取该请求使用的用户秘钥)并在服务端计算出该请求期望的数字签名

  5. 服务端用期望的数字签名和请求端发送过来的数字签名做对比,如果完全一致则认为该请求通过验证.否则直接拒绝该请求

2.生成请求的签名字符串

1. 构造规范化的请求字符串

将HTTP的请求参数(不包括Signature)构造成规范化的请求字符串.规范化步骤:

  1. 参数排序.按参数名的字典顺序,对请求参数进行排序,严格按照大小写敏感排序

  2. 编码参数,对排序后的请求参数进行规范化设置. 请求参数的名称和值都要使用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替换为波浪号~,即可得到上述规则描述的编码字符串

  3. 使用等号=连接URL编码后的参数名和参数值:percentEncode(参数Key) + “=” + percentEncode(参数值)

  4. 使用与号&连接第4步URL编码后的请求参数对,例如Action=CreateToken&Format=JSON

  5. 返回规范化的请求字符串(注意:字符串中第一个参数名前面不需要 & 符号)

构造规范化的请求字符串代码示例:

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();

构造的签名字符串:strToSign

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 方法,GET
        String 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 调用注意事项

  1. NlsClient对象创建一次可以重复使用,每次创建消耗性能.NlsClient使用了netty的框架,创建时比较消耗时间和资源,但创建之后可以重复利用.建议调用程序将NlsClient的创建和关闭与程序本身的生命周期结合

  2. SpeechRecognizer对象不能重复使用,一个识别任务对应一个SpeechRecognizer对象.例如有N个音频文件,则要进行N次识别任务,创建N个SpeechRecognizer对象.

  3. 实现的SpeechRecognizerListener对象和SpeechRecognizer对象是一一对应的,不能将一个SpeechRecognizerListener对象设置到多个SpeechRecognizer对象中,否则不能区分是哪个识别任务

  4. 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()过期前再次获取token
        AccessToken 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)时,才会有此类消息返回
            @Override
            public void onRecognitionResultChanged(SpeechRecognizerResponse response) {
                //事件名称 RecognitionResultChanged、 状态码(20000000 表示识别成功)、语音识别文本
                System.out.println("name: " + response.getName() + ", status: " + response.getStatus() + ", result: " + response.getRecognizedText());
            }
            //识别完毕
            @Override
            public void onRecognitionCompleted(SpeechRecognizerResponse response) {
                //事件名称 RecognitionCompleted, 状态码 20000000 表示识别成功, getRecognizedText是识别结果文本
                System.out.println("name: " + response.getName() + ", status: " + response.getStatus() + ", result: " + response.getRecognizedText());
            }
            @Override
            public void onStarted(SpeechRecognizerResponse response) {
                System.out.println("myOrder: " + myOrder + "; myParam: " + userParam + "; task_id: " + response.getTaskId());
            }
            @Override
            public 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或16000
    public 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.OPUS
            recognizer.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采样率语音,第二个参数改为8000
                int 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/v1
        if (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 调用注意事项

  1. NlsClient对象创建一次可以重复使用,每次创建消耗性能.NlsClient使用了netty的框架,创建时比较消耗时间和资源,但创建之后可以重复利用.建议调用程序将NlsClient的创建和关闭与程序本身的生命周期结合

  2. SpeechTranscriber对象不能重复使用,一个识别任务对应一个SpeechTranscriber对象.例如有N个音频文件,则要进行N次识别任务,创建N个SpeechTranscriber对象

  3. 实现的SpeechTranscriberListener对象和SpeechTranscriber对象是一一对应的,不能将一个SpeechTranscriberListener对象设置到多个SpeechTranscriber对象中,否则不能区分是哪个识别任务

  4. 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()过期前再次获取token
        AccessToken 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)时,才会有此类消息返回
            @Override
            public 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());
            }
            @Override
            public void onTranscriberStart(SpeechTranscriberResponse response) {
                // TODO 重要提示: task_id很重要,是调用方和服务端通信的唯一ID标识,当遇到问题时,需要提供此task_id以便排查
                System.out.println("task_id: " + response.getTaskId() + ", name: " + response.getName() + ", status: " + response.getStatus());
            }
            @Override
            public void onSentenceBegin(SpeechTranscriberResponse response) {
                System.out.println("task_id: " + response.getTaskId() + ", name: " + response.getName() + ", status: " + response.getStatus());
            }
            //识别出一句话.服务端会智能断句,当识别到一句话结束时会返回此消息
            @Override
            public 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());
            }
            //识别完毕
            @Override
            public void onTranscriptionComplete(SpeechTranscriberResponse response) {
                System.out.println("task_id: " + response.getTaskId() + ", name: " + response.getName() + ", status: " + response.getStatus());
            }
            @Override
            public 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或16000
    public 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);
            //是否将返回结果规整化,比如将一百返回为100
            transcriber.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采样率语音,第二个参数改为8000
                int 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/v1
        if (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 调用注意事项

  1. NlsClient对象创建一次可以重复使用,每次创建消耗性能.NlsClient使用了netty的框架,创建时比较消耗时间和资源,但创建之后可以重复利用.建议调用程序将NlsClient的创建和关闭与程序本身的生命周期结合

  2. SpeechSynthesizer对象不能重复使用,一个语音合成任务对应一个SpeechSynthesizer对象.例如有N个文本需要语音合成,则要进行N次语音合成任务,创建N个SpeechSynthesizer对象

  3. 实现的SpeechSynthesizerListener对象和SpeechSynthesizer对象是一一对应的,不能将一个SpeechSynthesizerListener对象设置到多个SpeechSynthesizer对象中,否则不能区分是哪个语音合成任务

  4. 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()过期前再次获取token
        AccessToken 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()过期前再次获取token
        AccessToken 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;
                //语音合成结束
                @Override
                public void onComplete(SpeechSynthesizerResponse response) {
                    // TODO 当onComplete时表示所有TTS数据已经接收完成,因此这个是整个合成延迟,该延迟可能较大,未必满足实时场景
                    System.out.println("name: " + response.getName() +
                        ", status: " + response.getStatus()+
                        ", output file :"+f.getAbsolutePath()
                    );
                }
                //语音合成的语音二进制数据
                @Override
                public 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();
                    }
                }
                @Override
                public 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,可选,默认是0
            synthesizer.setPitchRate(100);
            //语速,范围是-500~500,默认是0
            synthesizer.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/v1
        if (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";
    // 请求参数key
    public 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";
    // 响应参数key
    public 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";
    // 阿里云鉴权client
    IAcsClient client;
    public FileTransJavaDemo(String accessKeyId, String accessKeySecret) {
        // 设置endpoint
        try {
            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-DD
        postRequest.setVersion(API_VERSION);
        // 设置action
        postRequest.setAction(POST_REQUEST_ACTION);
        // 设置产品名称
        postRequest.setProduct(PRODUCT);
        /**
         * 2. 设置录音文件识别请求参数,以JSON字符串的格式设置到请求的Body中
         */
        JSONObject taskObject = new JSONObject();
        // 设置appkey
        taskObject.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);
        // 设置action
        getRequest.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();
        }
    }
}

本文链接http://element-ui.cn/news/show-324.html