前提条件
- 在使用SDK之前,请先阅读产品简介。 
- 鉴权密钥获取:获取Appkey,请参见管理项目一节;获取Token,请参见通过SDK获取Token。 
Java示例代码
下载安装
从Maven服务器下载最新版本的SDK。
<dependency>
    <groupId>com.alibaba.nls</groupId>
    <artifactId>nls-sdk-tts</artifactId>
    <version>2.2.14</version>
</dependency>
<dependency>
    <groupId>com.alibaba.nls</groupId>
    <artifactId>nls-sdk-common</artifactId>
    <version>2.2.14</version>
</dependency>Java SDK 从 2.1.7 版本开始(含2.1.7),语音合成SDK(包括实时长文本语音合成)SpeechSynthesizer的waitForComplete接口的超时时间单位从秒变更为毫秒。
调用示例
以下Java代码示例模拟了流式文本输入,请求语音合成,并使用扬声器进行音频播放的全过程。如您想将合成的音频保存到本地,请在onAudioData方法中将接收到的二进制音频流以追加模式保存到同一个文件中。
代码运行前需要替换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.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) {
        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;
                //流式文本语音合成开始
                @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));
                }
                //收到语音合成的增量音频时间戳
                @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[] textArray = {"流式文本语音合成SDK,", "可以将输入的文本", "合成为语音二进制数据,",
                "相比于非流式语音合成,", "流式合成的优势在于实时性", "更强。用户在输入文本的同时",
                "可以听到接近同步的语音输出,", "极大地提升了交互体验,", "减少了用户等待时间。",
                "适用于调用大规模", "语言模型(LLM),以", "流式输入文本的方式", "进行语音合成的场景。"};
        StreamInputTtsPlayableDemo demo = new StreamInputTtsPlayableDemo(appKey, token, url);
        demo.process(textArray);
        demo.shutdown();
    }
    public void process(String[] textArray) throws InterruptedException {
        StreamInputTts synthesizer = null;
        PlaybackRunnable playbackRunnable = new PlaybackRunnable(24000);
        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);
            //设置返回音频的编码格式。
            synthesizer.setFormat(OutputFormatEnum.PCM);
            //设置返回音频的采样率。
            synthesizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_24K);
            synthesizer.setVoice("longxiaochun");
            //音量,范围是0~100,可选,默认50。
            synthesizer.setVolume(50);
            //语调,范围是-500~500,可选,默认是0。
            synthesizer.setPitchRate(0);
            //语速,范围是-500~500,默认是0。
            synthesizer.setSpeechRate(0);
            //此方法将以上参数设置序列化为JSON发送给服务端,并等待服务端确认。
            long start = System.currentTimeMillis();
            synthesizer.startStreamInputTts();
            System.out.println("tts start latency " + (System.currentTimeMillis() - start) + " ms");
            StreamInputTtsPlayableDemo.startTime = System.currentTimeMillis();
            //设置连续两次发送文本的最小时间间隔(毫秒),如果当前调用send时距离上次调用时间小于此值,则会阻塞并等待直到满足条件再发送文本
            synthesizer.setMinSendIntervalMS(100);
            for (String text : textArray) {
                //发送流式文本数据。
                synthesizer.sendStreamInputTts(text);
            }
            //通知服务端流式文本数据发送完毕,阻塞等待服务端处理完成。
            synthesizer.stopStreamInputTts();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭连接
            if (null != synthesizer) {
                synthesizer.close();
                playbackThread.join();
            }
        }
    }
    public void shutdown() {
        client.shutdown();
    }
}
Python代码示例
下载安装
- 下载Python SDK。 
从Github获取Python SDK,或直接下载streamInputTts-github-python。
- 安装SDK依赖。 
进入SDK根目录使用如下命令安装SDK依赖:
python -m pip install -r requirements.txt- 安装SDK。 
依赖安装完成后使用如下命令安装SDK:
python -m pip install .- 安装完成后通过以下代码导入SDK。 
# -*- coding: utf-8 -*-
import nls上述命令均需要在SDK根目录中执行。
调用示例
代码运行前需要替换 your-appkey 以及 your-token。
# coding=utf-8
#
# Installation instructions for pyaudio:
# APPLE Mac OS X
#   brew install portaudio
#   pip install pyaudio
# Debian/Ubuntu
#   sudo apt-get install python-pyaudio python3-pyaudio
#   or
#   pip install pyaudio
# CentOS
#   sudo yum install -y portaudio portaudio-devel && pip install pyaudio
# Microsoft Windows
#   python -m pip install pyaudio
import nls
import time
# 设置打开日志输出
nls.enableTrace(False)
# 将音频保存进文件
SAVE_TO_FILE = True
# 将音频通过播放器实时播放,需要具有声卡。在服务器上运行请将此开关关闭
PLAY_REALTIME_RESULT = True
if PLAY_REALTIME_RESULT:
    import pyaudio
test_text = [
    "流式文本语音合成SDK,",
    "可以将输入的文本",
    "合成为语音二进制数据,",
    "相比于非流式语音合成,",
    "流式合成的优势在于实时性",
    "更强。用户在输入文本的同时",
    "可以听到接近同步的语音输出,",
    "极大地提升了交互体验,",
    "减少了用户等待时间。",
    "适用于调用大规模",
    "语言模型(LLM),以",
    "流式输入文本的方式",
    "进行语音合成的场景。",
]
if __name__ == "__main__":
    if SAVE_TO_FILE:
        file = open("output.wav", "wb")
    if PLAY_REALTIME_RESULT:
        player = pyaudio.PyAudio()
        stream = player.open(
            format=pyaudio.paInt16, channels=1, rate=24000, output=True
        )
    # 创建SDK实例
    # 配置回调函数
    def test_on_data(data, *args):
        if SAVE_TO_FILE:
            file.write(data)
        if PLAY_REALTIME_RESULT:
            stream.write(data)
    def test_on_message(message, *args):
        print("on message=>{}".format(message))
    def test_on_close(*args):
        print("on_close: args=>{}".format(args))
    def test_on_error(message, *args):
        print("on_error message=>{} args=>{}".format(message, args))
    sdk = nls.NlsStreamInputTtsSynthesizer(
        # 由于目前阶段大模型音色只在北京地区服务可用,因此需要调整url到北京
        url="wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1",
        token="your-token",
        appkey="your-appkey",
        on_data=test_on_data,
        on_sentence_begin=test_on_message,
        on_sentence_synthesis=test_on_message,
        on_sentence_end=test_on_message,
        on_completed=test_on_message,
        on_error=test_on_error,
        on_close=test_on_close,
        callback_args=[],
    )
    # 发送文本消息
    sdk.startStreamInputTts(
        voice="longxiaochun",       # 语音合成说话人
        aformat="wav",              # 合成音频格式
        sample_rate=24000,          # 合成音频采样率
        volume=50,                  # 合成音频的音量
        speech_rate=0,              # 合成音频语速
        pitch_rate=0,               # 合成音频的音调
    )
    for text in test_text:
        sdk.sendStreamInputTts(text)
        time.sleep(0.05)
    sdk.stopStreamInputTts()
    if SAVE_TO_FILE:
        file.close()
    if PLAY_REALTIME_RESULT:
        stream.stop_stream()
        stream.close()
        player.terminate()
移动端示例代码
代码调用示例请详见Demo工程。
下载安装
- 下载Android SDK示例工程、iOS SDK示例工程或HarmonyNext SDK 示例工程。 重要- 下载后请在样例初始化代码中替换您的阿里云账号信息、Appkey和Token才可运行。 - 类别 - Android 兼容范围 - iOS兼容范围 - HarmonyNext兼容范围 - 系统 - 支持Android 4.0 以上版本,API LEVEL 14 - 最低支持iOS9。 - 支持IDE 5.0.3.600 - 架构 - armeabi-v7a,arm64-v8a,x86,x86_64 - arm64,x86_64 - arm64-v8a 
- 解压ZIP包,添加代码库。 - Android平台请在 - app/libs目录下获取AAR格式的SDK包,将AAR包集成到您的工程项目中进行依赖。
- iOS平台将zip包中的nuisdk.framework添加到您的工程中,并在工程Build Phases的Link Binary With Libraries中添加nuisdk.framework 
- HarmonyNext平台,压缩包中的entry/libs/neonui.har 是SDK生成的HAR包文件,在用户工程项目中导入调用即可。 
 
- 打开工程文件运行DEMO工程 - Android平台可使用Android Studio打开此工程查看参考代码实现。其中,流式文本语音合成示例代码为StreamInputTtsBasicActivity.java文件,替换Appkey和Token后可直接运行。 
- iOS平台可使用Xcode打开此工程,工程中提供了参考代码以及一些直接可使用的工具类,例如音频播放录制和文件操作,您可以直接复制源码到您的实际工程进行使用。流式文本语音合成示例代码在ViewController文件中。替换Appkey和Token后可直接运行。 
- HarmonyNext平台,使用DevEco Studio打开工程,其中cosyvoice示例代码为StreamTTSPage.ets文件,替换UserKey.ets中 UserKeyStreamTTS类的Appkey和Token后,即可直接运行。 
 
调用示例
更多关于移动端SDK使用详情可参考移动端SDK当中提供的Android与iOS官方工程DEMO示例。
其中JavaScript页为鸿蒙Next系统示例。
- 编写自己的回调函数 - @Override public void onStreamInputTtsEventCallback( INativeStreamInputTtsCallback.StreamInputTtsEvent event, String task_id, String session_id, int ret_code, String error_msg, String timestamp, String all_response) { Log.i(TAG, "stream input tts event:" + event + " session id " + session_id + " session id " + task_id + " ret " + ret_code); switch (event) { case STREAM_INPUT_TTS_EVENT_SYNTHESIS_STARTED: // TODO: 处理SynthesisStarted指令 break; case STREAM_INPUT_TTS_EVENT_SENTENCE_BEGIN: // TODO: 处理SentenceBegin指令 break; case STREAM_INPUT_TTS_EVENT_SENTENCE_SYNTHESIS: // TODO: 处理SentenceSynthesis指令 break; case STREAM_INPUT_TTS_EVENT_SENTENCE_END: // TODO: 处理SynthesisEnd指令 break; case STREAM_INPUT_TTS_EVENT_SYNTHESIS_COMPLETE: // TODO: 处理SynthesisComplete指令 break; case STREAM_INPUT_TTS_EVENT_TASK_FAILED: // TODO: 处理TaskFailed指令 break; default: break; } } @Override public void onStreamInputTtsDataCallback(byte[] data) { if (data.length > 0) { if (mEncodeType.equals("pcm")) { mAudioTrack.setAudioData(data); } } }- /** * 事件回调,打印出服务返回的完整信息,可以根据需要处理对应的事件类型 */ - (void)onStreamInputTtsEventCallback:(StreamInputTtsCallbackEvent)event taskId:(char *)taskid sessionId:(char *)sessionId ret_code:(int)ret_code error_msg:(char *)error_msg timestamp:(char *)timestamp all_response:(char *)all_response { // [self logFormattedString:@"\n[事件回调] (event_code : %d), %s", event, // all_response]; NSLog(@"\n[事件回调] (event_code : %d), %s", event, all_response); // 可以处理各类型的事件 switch (event) { case TTS_EVENT_SYNTHESIS_STARTED: // TODO: 处理SynthesisStarted指令 break; case TTS_EVENT_SENTENCE_BEGIN: // TODO: 处理SentenceBegin指令 break; case TTS_EVENT_SENTENCE_SYNTHESIS: // TODO: 处理SentenceSynthesis指令 break; case TTS_EVENT_SENTENCE_END: // TODO: 处理SynthesisEnd指令 break; case TTS_EVENT_SYNTHESIS_COMPLETE: // TODO: 处理SynthesisComplete指令 break; case TTS_EVENT_TASK_FAILED: // TODO: 处理TaskFailed指令 break; default: break; } } /** * 音频回调函数,将收到的音频帧写入到音频播放器中 */ - (void)onStreamInputTtsDataCallback:(char *)buffer len:(int)len { NSLog(@"\n[收到音频] %d 字节", len); if (len > 0) { [_voicePlayer write:(char *)buffer Length:(unsigned int)len]; } }- function cb_tts_event_callback(event:StreamInputTtsEvent, task_id:string, session_id:string, ret_code:number, error_msg:string, timestamp:string, all_response:string):void{ console.info( "stream input tts event:" + event + " session id " + session_id + " session id " + task_id + " ret " + ret_code); if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_SYNTHESIS_STARTED) { console.info("STREAM_INPUT_TTS_EVENT_SYNTHESIS_STARTED"); console.info("start play"); } else if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_SENTENCE_SYNTHESIS) { console.info("STREAM_INPUT_TTS_EVENT_SENTENCE_SYNTHESIS:" + timestamp); } else if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_SYNTHESIS_COMPLETE || event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_TASK_FAILED) { /* * 提示: 表示TTS已经合成完并通过回调传回了所有音频数据, 而不是表示播放器已经播放完了所有音频数据。 */ console.info("play end"); //合成结束或者出错,如果保存了语音数据到本地文件,则可以关闭文件了。 // if (filesave){ // fs.closeSync(filesave) // filesave=undefined // } // 通知播放器,数据合成已经结束,等待播放器播放完所有已缓存的数据即可。 // playerVoiceEnd() if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_TASK_FAILED) { console.info("STREAM_INPUT_TTS_EVENT_TASK_FAILED error_code:" + ret_code + " errmsg:" + error_msg); //生成报错,直接停止播放器的播放 //playerVoiceStop(true) } else { console.info("STREAM_INPUT_TTS_EVENT_SYNTHESIS_COMPLETE" ); } } else if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_SENTENCE_BEGIN) { console.info("STREAM_INPUT_TTS_EVENT_SENTENCE_BEGIN" ); } else if (event == StreamInputTtsEvent.STREAM_INPUT_TTS_EVENT_SENTENCE_END) { console.info("STREAM_INPUT_TTS_EVENT_SENTENCE_END"); } } function cb_tts_user_data_callback(buffer:ArrayBuffer|null):void{ if (buffer){ //保存生成的语音数据到本地语音文件中 // if (filesave){ // fs.writeSync(filesave.fd, buffer) // } //把生成的语音数据送入播放器模块进行实时播放 if (buffer.byteLength > 0) { playerSetVoiceArrayBuffer(buffer as ArrayBuffer) } } else { console.info("womx cb_tts_user_data_callback undefined"); } } const g_ttscallback_instance:INativeStreamInputTtsCallback = { onStreamInputTtsEventCallback: cb_tts_event_callback, onStreamInputTtsDataCallback: cb_tts_user_data_callback };
- 进行鉴权、参数设置以及连网操作,并完成回调函数设置,开始进行流式TTS语音合成 - String ticket = genTicket(); String parameters = genParameters(); NativeNui stream_input_tts_instance = new NativeNui(Constants.ModeType.MODE_STREAM_INPUT_TTS); //其中callback为包含配置好回调的INativeTtsCallback对象 int ret = streamInput_tts_instance.startStreamInputTts( callback, ticket, parameters, "", 1, false);- // 获得鉴权信息和语音合成参数 NSString *ticket = [self genTicket]; NSString *parameters = [self genParameters]; // 获得流式TTS实例,并配置delegate _streamInputTtsSdk = [StreamInputTts get_instance]; _streamInputTtsSdk.delegate = self; // 建立连接并开始语音合成任务 int ret = [_streamInputTtsSdk startStreamInputTts:[ticket UTF8String] parameters:[parameters UTF8String] sessionId:nil logLevel:0 saveLog:NO];- stream_input_tts_instance:NativeNui = new NativeNui(Constants.ModeType.MODE_STREAM_INPUT_TTS, "streamtts") ticket:string = genTicketTTS(); parameters:string = genParameters(this.fontname); startTTS():number{ let ret:number = this.stream_input_tts_instance.startStreamInputTts( g_ttscallback_instance, this.ticket, this.parameters, "", Constants.LogLevel.toInt(Constants.LogLevel.LOG_LEVEL_VERBOSE), false ) if (Constants.NuiResultCode.SUCCESS != ret) { //start报错。 console.log("start tts failed"); // showToast("start tts failed") } else { //成功 } return ret }- 参数ticket为String JSON字符串,主要用于生成鉴权相关信息,包含如下字段用户信息。 - { "appkey": "your-app-key", //必须参数,AppKey,获取方法可参考相关文档 "token": "yout-token", //必须参数,Token,获取方法可参考相关文档 "url": "wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1", "complete_waiting_ms": "10000", }- 参数parameters为String JSON字符串,用于设置语音合成相关的参数,包含如下字段配置。 - { "voice": "longxiaochun", "format": "wav", "sample_rate": "24000", "volume": "50", "speech_rate": "0", "pitch_rate": "0", "enable_subtitle": "0", "session_id": "", }
- 发送流式文本(可以多次发送),如果有响应,则执行回调函数 - String text = "你好"; int ret = streamInput_tts_instance.sendStreamInputTts(text);- ret = [_streamInputTtsSdk sendStreamInputTts:[oneLine UTF8String]];- let ttstext:string = "你好" this.stream_input_tts_instance.sendStreamInputTts(ttstext)
- 停止发送文本,阻塞至所有音频返回,并结束本次流式语音合成 - int ret = streamInput_tts_instance.stopStreamInputTts();- ret = [_streamInputTtsSdk stopStreamInputTts];- //注意,对于HarmonyNext平台,stop接口中参数可以控制是阻塞stop还是异步stop,推荐使用异步stop。 let retcode: number = this.stream_input_tts_instance.stopStreamInputTts(true) if (retcode == 0) { console.info('womx stopStreamInputTts() and return success') } else { //出错 console.info('womx stopStreamInputTts() but return error:%d', retcode) showToast(this.errorinfo) }