Java SDK

本文介绍如何使用阿里云智能语音服务提供的Java SDK,包括SDK的安装方法及SDK代码示例。

前提条件

下载安装

Maven服务器下载最新版本的SDK。

<dependency>
    <groupId>com.alibaba.nls</groupId>
    <artifactId>nls-sdk-tts</artifactId>
    <version>2.2.19</version>
</dependency>
<dependency>
    <groupId>com.alibaba.nls</groupId>
    <artifactId>nls-sdk-common</artifactId>
    <version>2.2.19</version>
</dependency>
重要

Java SDK 从 2.1.7 版本开始(含2.1.7),语音合成SDK(包括实时长文本语音合成)SpeechSynthesizerwaitForComplete接口的超时时间单位从变更为毫秒

SDK调用注意事项

  • SDK版本2.2.18之后支持长文本语音合成接口。

  • NlsClient使用Netty框架,NlsClient对象的创建会消耗一定时间和资源,一经创建可以重复使用。建议调用程序将NlsClient的创建和关闭与程序本身的生命周期相结合。

  • StreamInputTts对象不可重复使用,一个语音合成任务对应一个StreamInputTts对象。例如,N个文本要进行N次语音合成任务,创建NStreamInputTts对象。

  • StreamInputTtsListener对象和StreamInputTts对象是一一对应的,不能将一个StreamInputTtsListener对象设置到多个StreamInputTts对象中,否则不能将各语音合成任务区分开。

  • Java SDK依赖Netty网络库,如果您的应用依赖Netty,其版本需更新至4.1.17.Final及以上。

关键接口

StreamInputTtsCosyVoice语音合成大模型的主类,提供了下面的关键接口:

startTts:与服务端进行websocket建连操作,并完成回调、参数设置等操作

  • /**
     * 发起长文本合成任务,同步接收服务端确认
     * @param text 需要合成的文本,支持SSML
     * @param milliSeconds 服务返回超时时间
     * @throws Exception
     */
    public void startTts(String text, long milliSeconds)
  • waitForComplete:以阻塞的方式等待语音合成结束,并与服务端断开websocket连接

    /**
     * 等待语音合成结束。
     */
    public void waitForComplete()

    回调函数说明

    StreamInputTtsListener是回调类,包含下面的回调函数:

  • 事件回调函数:用于响应回调事件

    /**
     * 服务端检测到了一句话的开始
     * @param response
     */
    abstract public void onSentenceBegin(
        StreamInputSpeechSynthesizerResponse response);
    
    /**
     * 服务端检测到了一句话的结束,并返回这句话的起止位置与所有时间戳
     * @param response
     */
    abstract public void onSentenceEnd(
        StreamInputSpeechSynthesizerResponse response);
    /**
     * 合成结束
     * @param response
     */
    abstract public void onSynthesisComplete(
        StreamInputSpeechSynthesizerResponse response);
    /**
     * 失败处理
     * @param response
     */
    abstract public void onFail(StreamInputSpeechSynthesizerResponse response);
    /**
     * 增量在response=>payload中返回时间戳
     * @param response
     */
    abstract public void onSentenceSynthesis(
        StreamInputSpeechSynthesizerResponse response);

    数据回调函数:用于语音合成数据返回

    /**
    * 接收到语音合成音频数据流
    * @param message 二进制音频数据
    */
    abstract public void onAudioData(ByteBuffer message);

调用示例

以下Java代码示例使用了带有SSML文本输入请求语音合成,使用扬声器进行音频播放并保存。

重要

代码运行前需要替换your-appkey以及your-token。

package org.example;

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.StreamInputTts;
import com.alibaba.nls.client.protocol.tts.StreamInputTtsListener;
import com.alibaba.nls.client.protocol.tts.StreamInputTtsResponse;

import javax.sound.sampled.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;


class PlaybackRunnable implements Runnable {
    // 设置音频格式,请根据实际自身设备,合成音频参数和平台选择配置
    // 这里选择24k、16bit、单通道,建议客户根据选用的模型采样率情况和自身设备兼容性选择其他采样率和格式
    private AudioFormat af;

    private DataLine.Info info;

    private SourceDataLine targetSource;

    private AtomicBoolean runFlag;

    private ConcurrentLinkedQueue<ByteBuffer> queue;

    public PlaybackRunnable(int sample_rate) throws FileNotFoundException {
        af = new AudioFormat(sample_rate, 16, 1, true, false);
        info = new DataLine.Info(SourceDataLine.class, af);
        targetSource = null;
        runFlag = new AtomicBoolean(true);
        queue = new ConcurrentLinkedQueue<>();
    }

    // 准备播放器
    public void prepare() throws LineUnavailableException {
        targetSource = (SourceDataLine) AudioSystem.getLine(info);
        targetSource.open(af, 4096);
        targetSource.start();
    }

    public void put(ByteBuffer buffer) {
        queue.add(buffer);
    }

    // 停止播放
    public void stop() {
        runFlag.set(false);
    }

    @Override
    public void run() {
        if (targetSource == null) {
            return;
        }
        while (runFlag.get()) {
            if (queue.isEmpty()) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
                continue;
            }
            ByteBuffer buffer = queue.poll();
            if (buffer == null) {
                continue;
            }
            byte[] data = buffer.array();
            targetSource.write(data, 0, data.length);
        }
        // 将缓存全部播放完
        if (!queue.isEmpty()) {
            ByteBuffer buffer = null;
            while ((buffer = queue.poll()) != null) {
                byte[] data = buffer.array();
                targetSource.write(data, 0, data.length);
            }
        }
        // 释放播放器
        targetSource.drain();
        targetSource.stop();
        targetSource.close();
    }
}


public class StreamInputTtsPlayableDemo {
    private static long startTime;
    NlsClient client;
    private String appKey;

    public StreamInputTtsPlayableDemo(String appKey, String token, String url) {
        this.appKey = appKey;
        //创建NlsClient实例应用全局创建一个即可。生命周期可和整个应用保持一致,默认服务地址为阿里云线上服务地址。
        if (url.isEmpty()) {
            client = new NlsClient(token);
        } else {
            client = new NlsClient(url, token);
        }
    }

    private static StreamInputTtsListener getSynthesizerListener(final PlaybackRunnable audioPlayer) {
        StreamInputTtsListener listener = null;
        try {
            listener = new StreamInputTtsListener() {
                private boolean firstRecvBinary = true;
                File f=new File("ssml_test.pcm");
                FileOutputStream fout = new FileOutputStream(f);


                //流式文本语音合成开始
                @Override
                public void onSynthesisStart(StreamInputTtsResponse response) {
                    System.out.println("name: " + response.getName() +
                            ", status: " + response.getStatus());
                }

                //服务端检测到了一句话的开始
                @Override
                public void onSentenceBegin(StreamInputTtsResponse response) {
                    System.out.println("name: " + response.getName() +
                            ", status: " + response.getStatus());
                    System.out.println("Sentence Begin");
                }

                //服务端检测到了一句话的结束,获得这句话的起止位置和所有时间戳
                @Override
                public void onSentenceEnd(StreamInputTtsResponse response) {
                    System.out.println("name: " + response.getName() +
                            ", status: " + response.getStatus() + ", subtitles: " + response.getObject("subtitles"));

                }

                //流式文本语音合成结束
                @Override
                public void onSynthesisComplete(StreamInputTtsResponse response) {
                    // 调用onSynthesisComplete时,表示所有TTS数据已经接收完成,所有文本都已经合成音频并返回。
                    System.out.println("name: " + response.getName() + ", status: " + response.getStatus());
                    audioPlayer.stop();
                }

                //收到语音合成的语音二进制数据
                @Override
                public void onAudioData(ByteBuffer message) {
                    if (firstRecvBinary) {
                        // 此处计算首包语音流的延迟,收到第一包语音流时,即可以进行语音播放,以提升响应速度(特别是实时交互场景下)。
                        firstRecvBinary = false;
                        long now = System.currentTimeMillis();
                        System.out.println("tts first latency : " + (now - StreamInputTtsPlayableDemo.startTime) + " ms");
                    }
                    byte[] bytesArray = new byte[message.remaining()];
                    message.get(bytesArray, 0, bytesArray.length);
                    System.out.println("recv audio bytes:" + bytesArray.length);
                    audioPlayer.put(ByteBuffer.wrap(bytesArray));
                    try {
                        fout.write(bytesArray);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                //收到语音合成的增量音频时间戳
                @Override
                public void onSentenceSynthesis(StreamInputTtsResponse response) {
                    System.out.println("name: " + response.getName() +
                            ", status: " + response.getStatus() + ", subtitles: " + response.getObject("subtitles"));
                }

                @Override
                public void onFail(StreamInputTtsResponse response) {
                    // task_id是调用方和服务端通信的唯一标识,当遇到问题时,需要提供此task_id以便排查。
                    System.out.println(
                            "session_id: " + getStreamInputTts().getCurrentSessionId() +
                                    ", task_id: " + response.getTaskId() +
                                    //状态码
                                    ", status: " + response.getStatus() +
                                    //错误信息
                                    ", status_text: " + response.getStatusText());
                    audioPlayer.stop();
                }
            };
        } catch (Exception e) {
            e.printStackTrace();
        }
        return listener;
    }

    public static void main(String[] args) throws Exception {
        String appKey = "your-appkey";
        String token = "your-token";
        // url取默认值
        String url = "wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1";
        String text = "<speak bgm=\"http://nls.alicdn.com/bgm/2.wav\">今天天气怎么样</speak>";
        StreamInputTtsPlayableDemo demo = new StreamInputTtsPlayableDemo(appKey, token, url);
        demo.process(text);
        demo.shutdown();
    }

    public void process(String text) throws InterruptedException, FileNotFoundException {
        StreamInputTts synthesizer = null;
        PlaybackRunnable playbackRunnable = new PlaybackRunnable(8000);
        try {
            playbackRunnable.prepare();
        } catch (LineUnavailableException e) {
            throw new RuntimeException(e);
        }
        Thread playbackThread = new Thread(playbackRunnable);
        // 启动播放线程
        playbackThread.start();
        try {
            //创建实例,建立连接。
            synthesizer = new StreamInputTts(client, getSynthesizerListener(playbackRunnable));
            synthesizer.setAppKey(appKey);
            //设置返回音频的编码格式。可选:PCM, WAV, MP3, OPUS
            synthesizer.setFormat(OutputFormatEnum.PCM);
            //设置返回音频的采样率。
            synthesizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_8K);
            synthesizer.setVoice("longxiaochun_v2");
            //音量,范围是0~100,可选,默认50。
            synthesizer.setVolume(50);
            //语调,范围是-500~500,可选,默认是0。
            synthesizer.setPitchRate(0);
            //语速,范围是-500~500,默认是0。
            synthesizer.setSpeechRate(0);
            //此方法将以上参数设置序列化为JSON发送给服务端,并等待服务端确认。
            // synthesizer.setBitRate(64); 码率,仅对opus编码有效。
            long start = System.currentTimeMillis();
            synthesizer.startTts(text, 1000);
            synthesizer.waitForComplete();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭连接
            if (null != synthesizer) {
                synthesizer.close();
                playbackThread.join();
            }
        }
    }

    public void shutdown() {
        client.shutdown();
    }
}

常见SDK错误码

更多错误码详情参见错误码查询