Java SDK

更新时间:
复制 MD 格式

通过一句话识别Java SDK发送音频流并接收识别结果。本文提供SDK安装、客户端初始化和完整调用示例,以及发送音频、结束识别和排查依赖问题的说明。

使用前准备

  • 已开通智能语音交互服务并创建项目。操作方法,请参见快速开始。

  • 准备项目Appkey,以及用于获取NLS Token的AccessKey ID和AccessKey Secret。项目与模型配置,请参见管理项目;Token的获取与有效期说明,请参见获取Token。

  • 使用JDK 8或更高版本。音频编码、采样率和模型要求,请参见一句话识别接口说明。

安装SDK

在Maven项目的pom.xml中添加以下依赖。示例使用Java SDK 2.2.1。

<dependency>
    <groupId>com.alibaba.nls</groupId>
    <artifactId>nls-sdk-recognizer</artifactId>
    <version>2.2.1</version>
</dependency>
<!-- JDK 9及以上版本还需添加以下依赖。 -->
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

从2.1.0起,nls-sdk-short-asr更名为nls-sdk-recognizer。从旧版本升级时,移除nls-sdk-short-asr依赖,并按编译提示补齐回调方法。如果项目已依赖Netty,使用4.1.17.Final或更高版本。

代码示例

下载nls-sample-16k.wav,放到程序的工作目录。该文件为单声道、16 kHz WAV音频;项目中需选择与音频语种和采样率匹配的模型。使用其他音频时,同步调整代码中的文件名、编码格式和采样率。

配置以下环境变量。AccessKey与Appkey需属于用于调用服务的账号。

环境变量

含义

ALIYUN_AK_ID

AccessKey ID,用于申请NLS Token。

ALIYUN_AK_SECRET

AccessKey Secret,用于申请NLS Token。

NLS_APP_KEY

智能语音交互项目的Appkey。

重要

AccessKey用于身份认证,避免将其硬编码在程序中或写入日志。NLS Token有有效期,长期运行的应用需在到期前重新获取Token,并通过NlsClient.setToken()更新客户端。

import com.alibaba.nls.client.AccessToken;
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 java.io.FileInputStream;
import java.io.InputStream;

public class SpeechRecognizerDemo {
    public static void main(String[] args) throws Exception {
        String appKey = requireEnv("NLS_APP_KEY");
        String accessKeyId = requireEnv("ALIYUN_AK_ID");
        String accessKeySecret = requireEnv("ALIYUN_AK_SECRET");
        String audioFile = "nls-sample-16k.wav";

        // 获取NLS Token;在有效期结束前重新获取并更新客户端。
        AccessToken accessToken = new AccessToken(accessKeyId, accessKeySecret);
        accessToken.apply();
        NlsClient client = new NlsClient(
                "wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1", accessToken.getToken());
        SpeechRecognizer recognizer = null;
        try {
            recognizer = new SpeechRecognizer(client, createListener());
            recognizer.setAppKey(appKey);
            // 编码格式和采样率须与音频文件及项目模型一致。
            recognizer.setFormat(InputFormatEnum.WAV);
            recognizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);
            recognizer.setEnableIntermediateResult(true);
            recognizer.addCustomedParam("enable_voice_detection", true);

            recognizer.start();
            try (InputStream audio = new FileInputStream(audioFile)) {
                // 从文件模拟实时音频:每次发送3200字节,间隔100毫秒。
                recognizer.send(audio, 3200, 100);
            }
            recognizer.stop();
        } finally {
            if (recognizer != null) {
                recognizer.close();
            }
            client.shutdown();
        }
    }

    private static SpeechRecognizerListener createListener() {
        return new SpeechRecognizerListener() {
            @Override
            public void onStarted(SpeechRecognizerResponse response) {
                System.out.println("Task ID: " + response.getTaskId());
            }

            @Override
            public void onRecognitionResultChanged(SpeechRecognizerResponse response) {
                System.out.println("RecognitionResultChanged: " + response.getRecognizedText());
            }

            @Override
            public void onRecognitionCompleted(SpeechRecognizerResponse response) {
                System.out.println("RecognitionCompleted: " + response.getRecognizedText());
            }

            @Override
            public void onFail(SpeechRecognizerResponse response) {
                System.err.println("Task ID: " + response.getTaskId()
                        + ", status: " + response.getStatus()
                        + ", message: " + response.getStatusText());
            }
        };
    }

    private static String requireEnv(String name) {
        String value = System.getenv(name);
        if (value == null || value.isEmpty()) {
            throw new IllegalArgumentException("Missing environment variable: " + name);
        }
        return value;
    }
}

识别结果通过onRecognitionResultChanged和onRecognitionCompleted回调返回;请求失败时,通过onFail获取任务ID、状态码和错误信息。

说明

在阿里云上海地域ECS上通过内网访问时,将创建NlsClient时的服务地址替换为ws://nls-gateway-cn-shanghai-internal.aliyuncs.com/ws/v1。

关键接口

接口

用途与生命周期

NlsClient

语音处理客户端,支持一句话识别、实时语音识别和语音合成。线程安全,建议全局复用一个实例,在应用退出时调用shutdown()释放资源。

SpeechRecognizer

设置一句话识别参数、发送音频和结束识别。非线程安全,每个识别任务创建一个对象,不重复用于其他任务;任务结束后调用close()。

SpeechRecognizerListener

接收开始、中间结果、完成和失败事件。非线程安全,与SpeechRecognizer一一对应,不将同一个监听器绑定到多个识别对象。

NlsClient使用Netty,创建实例会消耗时间和资源。客户端的创建和关闭可与应用生命周期保持一致;识别任务则分别创建和关闭SpeechRecognizer。更多方法,请参见Java API接口说明。

运行完整示例包

也可以下载完整示例工程,使用其中的调用程序和压测程序。

下载nls-sdk-java-demo。

解压后,在工程根目录运行mvn package。生成的可执行文件位于nls-example-recognizer/target/nls-example-recognizer-2.0.0-jar-with-dependencies.jar。将JAR包和配套音频复制到目标服务器后,运行以下命令,并按提示提供Appkey、NLS Token和可选的服务地址。程序会在工作目录生成logs/nls.log。

java -cp nls-example-recognizer-2.0.0-jar-with-dependencies.jar com.alibaba.nls.client.SpeechRecognizerDemo

需要压测时,使用以下命令,并按提示设置并发数:

java -jar nls-example-recognizer-2.0.0-jar-with-dependencies.jar

服务地址为wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1。

压测使用16 kHz PCM音频,并发数需在已开通服务的并发额度内。压测调用同样适用服务的计费规则,运行前确认服务版本、计费方式和并发额度。

常见问题

何时调用stop()?

音频发送完成后,调用recognizer.stop()发送StopRecognition指令,并等待识别完成。开启语音检测(VAD)时,服务端也可以在检测到语音结束后返回RecognitionCompleted事件。

语音发送期间需保持连续、实时的数据流;长时间未发送数据会触发空闲超时。需要中间结果时,在start()前调用setEnableIntermediateResult(true)。

如何识别本地音频文件?

本页示例从本地文件读取音频,并按实时速度发送给服务端。也可以通过RESTful API上传音频文件,调用方式请参见一句话识别RESTful API。

如何使用send()发送音频?

一句话识别提供以下四个send()重载:

public void send(byte[] data);
public void send(byte[] data, int length);
public void send(InputStream ins);
public void send(InputStream ins, int batchSize, int sleepInterval);

send(byte[])、send(byte[], int)和send(InputStream)按数据到达或读取的速度发送,不自动按音频时长设置间隔。实时采集音频时,按采集速度连续发送;从文件模拟实时流时,可使用带batchSize和sleepInterval的方法控制发送速度。

16位、单声道PCM音频在16 kHz采样率下,每100毫秒对应3200字节;8 kHz时,3200字节对应200毫秒。其他编码格式需按压缩后的数据量调整每批字节数和发送间隔。发送间隔过大可能增加延迟或导致断连,过小会增加服务端和网络资源消耗。

基于前面的完整示例,发送16 kHz音频文件流可使用:

recognizer.send(audio, 3200, 100);

自行循环读取文件时,也可以使用send(byte[], int),或为完整的100毫秒音频数据调用send(byte[]):

recognizer.send(data);
Thread.sleep(100);

上述data表示16 kHz、16位、单声道PCM的3200字节音频;最后一批不足3200字节时,使用send(data, length)传入实际长度。

如何通过日志分析延迟?

一句话识别的结束延迟,可以用发送StopRecognition到收到RecognitionCompleted的时间差计算。例如,两条日志的时间分别为14:24:44.844和14:24:44.984,对应延迟为140毫秒。

同样的方法也适用于其他语音处理任务:

  • 实时语音识别使用StopTranscription和TranscriptionCompleted的时间差。

  • 语音合成的首包延迟使用发送StartSynthesis到收到首个音频包的时间差。例如,14:32:13.813到14:32:14.035对应222毫秒。

完整示例包已配置日志。自行集成SDK时,按应用的日志框架配置日志输出。通过RESTful API调用时,可在客户端记录请求和响应时间来计算延迟。

找不到com.alibaba的JAR包怎么办?

通过Alibaba Cloud SDK for Java获取Token时,需安装相应依赖。安装方法,请参见V1.0 Java SDK。

org.json.JSONArray.iterator()报错怎么办?

旧工程通过Alibaba Cloud SDK for Java获取客户端或Token时,若出现org.json.JSONArray.iterator()Ljava/util/Iterator方法不存在的错误,检查运行时加载的JSON库与SDK所需版本是否一致,并排除重复依赖。检查org.json:json和com.google.code.gson:gson等相关依赖是否完整,使用与当前Alibaba Cloud SDK兼容的版本。