Android SDK

更新时间:
复制 MD 格式

使用 Android NUI SDK 将文本合成为语音,并通过回调获取音频数据。本文介绍 SDK 集成、调用步骤、代码示例、关键接口和常见问题。

前提条件

  • 已开通智能语音交互服务,并获取项目 Appkey。项目配置请参见管理项目。

  • 已获取 NLS Token,获取方法请参见获取 Token。

重要

AccessKey ID 和 AccessKey Secret 应保存在服务端,不得写入 App 代码或存储在移动端。云端语音合成使用 Appkey 和 NLS Token 鉴权;由服务端获取 Token 后下发给 App,并在 Token 过期前更新。初始化参数包含凭证,不应将完整参数写入日志。

语音合成参数和支持的音色请参见移动端语音合成接口说明。

下载和集成 SDK

从移动端 SDK 选择与下载下载支持语音合成的 Android NUI SDK 包。该页提供版本、兼容性、隐私政策及合规使用说明。

以下示例使用 V2.7.4-01B-20260831_Android_TLS 包。

  1. 解压 SDK 包,将 Demo 的 app/libs/nuisdk-release.aar 添加到应用模块的 libs 目录,并在模块的 build.gradle 中添加依赖:

        dependencies {
            implementation files('libs/nuisdk-release.aar')
        }
  2. 在 AndroidManifest.xml 中声明网络访问权限:

        <uses-permission android:name="android.permission.INTERNET" />
  3. 使用 Android Studio 打开 Demo 工程。语音合成示例为 TtsBasicActivity.java,替换 Appkey 和 Token 后可运行。

Java 接口通过 AAR 集成;C++ 接口所需动态库和头文件位于包内的 android_libs、android_include 目录。SDK V2.6.2 起,纯云端功能不需要复制资源文件或设置 workspace。

调用步骤

  1. 创建 SDK 和播放器实例,设置回调并初始化 SDK。

  2. 调用 setparamTts 设置音色、音频格式、采样率等参数。

  3. 调用 startTts 启动合成。

  4. 在 onTtsDataCallback 中获取音频数据,写入播放器进行流式播放,或依次追加保存到同一个文件。

  5. 收到 TTS_EVENT_END 后,通知播放器音频数据已发送完毕,由播放器继续处理缓冲数据。该事件表示合成结束,不表示播放结束。

  6. 不再使用 SDK 时,调用 tts_release 释放 SDK,并释放播放器资源。

代码示例

以下示例使用 Demo 中的 AudioPlayer.java、AudioPlayerCallback.java,播放格式为 16 kHz、16-bit、单声道 PCM。将这两个类及 Demo app/libs 中的 fastjson-1.1.46.android.jar 加入应用模块;后者也需要在 build.gradle 中声明依赖。

SDK 支持通过 NativeNui.GetInstance() 获取单例,也支持通过 new NativeNui(Constants.ModeType.MODE_TTS) 创建实例。下面使用独立实例,同一实例同一时间运行一个合成任务。初始化和取消等控制接口为同步调用,应在非 UI 线程顺序执行。

配置网关

在下面的 TtsExampleActivity 中声明网关常量:

private static final String GATEWAY = "wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1";

初始化、合成和回调

import android.util.Log;
import com.alibaba.idst.nui.Constants;
import com.alibaba.idst.nui.INativeTtsCallback;
import com.alibaba.idst.nui.NativeNui;
import mit.alibaba.nuidemo.AudioPlayer;
import mit.alibaba.nuidemo.AudioPlayerCallback;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.UUID;

public class SpeechSynthesizer implements INativeTtsCallback {
    private final NativeNui nui = new NativeNui(Constants.ModeType.MODE_TTS);
    private final AudioPlayer player;
    private volatile boolean taskActive;

    public SpeechSynthesizer(Runnable onPlaybackFinished) {
        player = new AudioPlayer(new AudioPlayerCallback() {
            @Override public void playStart() {}
            @Override public void playOver() {
                if (onPlaybackFinished != null) onPlaybackFinished.run();
            }
            @Override public void playSoundLevel(int level) {}
        });
    }

    public int initialize(String appkey, String token, String gateway) throws JSONException {
        JSONObject ticket = new JSONObject()
                .put("app_key", appkey)
                .put("token", token)
                .put("url", gateway)
                .put("mode_type", "2")
                .put("device_id", UUID.randomUUID().toString());
        int code = nui.tts_initialize(this, ticket.toString(),
                Constants.LogLevel.LOG_LEVEL_NONE, false);
        if (code != 0) return code;
        code = nui.setparamTts("font_name", "xiaoyun");
        if (code != 0) return code;
        code = nui.setparamTts("sample_rate", "16000");
        if (code != 0) return code;
        code = nui.setparamTts("encode_type", "pcm");
        if (code != 0) return code;
        code = nui.setparamTts("enable_audio_decoder", "0");
        if (code != 0) return code;
        return nui.setparamTts("tts_version", "0");
    }

    public int setParameter(String name, String value) {
        return nui.setparamTts(name, value);
    }

    public int start(String text) {
        player.clearAudioQueue();
        taskActive = true;
        int code = nui.startTts("1", "", text);
        if (code != 0) taskActive = false;
        return code;
    }

    public int cancel() { return nui.cancelTts(""); }
    public int pause() { return nui.pauseTts(); }
    public int resume() { return nui.resumeTts(); }

    public int close() {
        if (taskActive) nui.cancelTts("");
        player.stop();
        int code = nui.tts_release();
        player.releaseAudioTrack();
        return code;
    }

    @Override public void onTtsEventCallback(TtsEvent event, String taskId, int code) {
        if (event == TtsEvent.TTS_EVENT_START) {
            player.play();
        } else if (event == TtsEvent.TTS_EVENT_END) {
            taskActive = false;
            player.isFinishSend(true);
        } else if (event == TtsEvent.TTS_EVENT_CANCEL || event == TtsEvent.TTS_EVENT_ERROR) {
            taskActive = false;
            player.stop();
            if (event == TtsEvent.TTS_EVENT_ERROR) Log.e("TTS", "Synthesis failed: " + code);
        } else if (event == TtsEvent.TTS_EVENT_PAUSE) {
            player.pause();
        } else if (event == TtsEvent.TTS_EVENT_RESUME) {
            player.resume();
        }
    }

    @Override public void onTtsDataCallback(String info, int infoLength, byte[] data) {
        if (info != null && !info.isEmpty()) Log.i("TTS", "Subtitle: " + info);
        if (data != null && data.length > 0) player.setAudioData(data);
    }

    @Override public void onTtsVolCallback(int volume) {}
}

下面的 Activity 在串行队列中初始化并启动合成,在页面销毁时释放资源。将 YOUR_APPKEY 和 YOUR_NLS_TOKEN 替换为服务端下发的值,并添加前面的网关常量。

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TtsExampleActivity extends Activity {
    private final ExecutorService ttsQueue = Executors.newSingleThreadExecutor();
    private SpeechSynthesizer synthesizer;

    @Override protected void onCreate(Bundle state) {
        super.onCreate(state);
        synthesizer = new SpeechSynthesizer(() -> Log.i("TTS", "Playback queue completed"));
        ttsQueue.execute(() -> {
            try {
                int code = synthesizer.initialize("YOUR_APPKEY", "YOUR_NLS_TOKEN", GATEWAY);
                if (code == 0) code = synthesizer.start("Hello, welcome to the speech synthesis example.");
                if (code != 0) Log.e("TTS", "Start failed: " + code);
            } catch (JSONException error) {
                Log.e("TTS", "Failed to build initialization parameters");
            }
        });
    }

    @Override protected void onDestroy() {
        ttsQueue.execute(() -> {
            int code = synthesizer.close();
            if (code != 0) Log.e("TTS", "Release failed: " + code);
        });
        ttsQueue.shutdown();
        super.onDestroy();
    }
}

需要取消、暂停或恢复时,在同一队列中调用 synthesizer.cancel()、synthesizer.pause() 或 synthesizer.resume()。close() 会停止播放器并释放资源;SDK 资源释放放在控制队列中执行,不在 SDK 回调中直接调用。

设置合成参数

以下片段放在 Activity 的 initialize 调用之后、start 之前,沿用其中的 code 变量。可根据需要设置语速、语调和音量:

if (code == 0) code = synthesizer.setParameter("speed_level", "0.6");
if (code == 0) code = synthesizer.setParameter("pitch_level", "100");
if (code == 0) code = synthesizer.setParameter("volume", "0.6");

选择支持时间戳的音色并启用 enable_subtitle,在 onTtsDataCallback 的 info 中获取结果。也可以通过 custom_params 传入交互协议支持的参数;其值为 JSON 字符串:

if (code == 0) code = synthesizer.setParameter("font_name", "siyue");
if (code == 0) code = synthesizer.setParameter("enable_subtitle", "1");

// Alternatively, pass the setting in a JSON string.
String params = new JSONObject().put("enable_subtitle", true).toString();
// if (code == 0) code = synthesizer.setParameter("custom_params", params);

上述片段完成后,将返回值为 0 的情况继续连接到 start 调用。调整合成采样率时,还需通过 AudioPlayer.setSampleRate 设置相同的播放器采样率。

合成长文本

短文本模式每次支持不超过 300 个字符,超过部分会被截断。一个汉字、英文字母或标点各计为一个字符。长文本语音合成需单独开通服务,计费与短文本不同。

以下片段用于普通文本,替换 Activity 中 initialize 成功后的 start 调用:

StringBuilder builder = new StringBuilder();
for (int i = 0; i < 12; i++) builder.append("This is a long text synthesis example. ");
String text = builder.toString();
if (code == 0) code = synthesizer.setParameter("tts_version", "1");
if (code == 0) code = synthesizer.start(text);

NativeNui 实例的 getUtf8CharsNum 方法可用于普通文本字符计数。该方法按传入字符串计数,包括 SSML 标签字符;使用 SSML 时,根据实际正文长度选择合成模式。

关键接口与回调

初始化参数

ticket 是传给 tts_initialize 的 JSON 字符串。

参数

类型

说明

app_key

String

必填,控制台项目的 Appkey。

token

String

必填,有效且未过期的 NLS Token。可在初始化时设置,也可通过 setparamTts("token", newToken) 更新。

device_id

String

必填,调用方设置的标识,推荐使用具有唯一性的值,便于定位问题。

mode_type

Integer 或 String

必填,纯在线合成设置为 2 或 "2",数值常量为 Constants.TtsModeTypeCloud。

url

String

服务网关,示例中显式指定。

workspace

String

资源目录。V2.6.2 起纯云端功能不需要设置;使用依赖本地资源的功能时,目录需要有读写权限。

debug_path

String

日志保存目录,启用 save_log 时设置为可写目录,并关注持续保存日志的存储占用。

合成参数

合成参数通过 setparamTts 设置,参数值为字符串。

参数

示例值

说明

font_name

"xiaoyun"

音色。

sample_rate

"16000"

合成采样率,需与播放器采样率一致。

encode_type

"pcm"

输出格式,示例使用 PCM。

enable_subtitle

"1"

启用时间戳,仅对支持该功能的音色有效;"0" 表示关闭。

speed_level

"0.6"

语速。

pitch_level

"100"

语调。

volume

"0.6"

音量。

tts_version

"0"

合成模式,默认 "0" 为短文本。

custom_params

JSON 字符串

配置交互协议支持的其他参数,使用 custom_params 作为参数名、JSON 字符串作为参数值。

tts_version="1" 用于长文本语音合成。更多参数及音色支持范围请参见移动端语音合成接口说明。

控制接口

除 getparamTts 返回参数值外,下列接口均返回整数状态码,0 表示调用成功。异步合成错误通过 TTS_EVENT_ERROR 通知。

错误码及处理方法请参见错误码查询。

初始化和参数设置

tts_initialize:初始化 SDK。再次初始化前先释放 SDK 资源;初始化可能阻塞,应在非 UI 线程执行。

public synchronized int tts_initialize(INativeTtsCallback callback,
        String ticket, Constants.LogLevel level, boolean save_log);
  • callback:INativeTtsCallback 事件及数据回调。

  • ticket:初始化参数 JSON 字符串。

  • level:日志级别,枚举值越小,日志越详细。

  • save_log:是否保存日志文件,目录由 ticket 中的 debug_path 指定。启用时应关注存储容量并定期清理日志。

setparamTts:按参数名和值设置合成参数。

public synchronized int setparamTts(String param, String value);

param 为参数名,value 为参数值。

合成任务控制

startTts:启动合成,通过回调向 App 输出音频,由 App 播放或保存。

public synchronized int startTts(String priority, String taskid, String text);
  • priority:任务优先级,使用 "1"。

  • taskid:任务 ID,可传入 32 字节的 UUID,或传入空字符串由 SDK 自动生成。

  • text:待合成文本。

cancelTts:取消合成。taskid 为待取消的任务 ID;传入空字符串取消所有任务。

public synchronized int cancelTts(String taskid);

pauseTts / resumeTts:暂停或恢复合成任务。播放器的暂停和恢复由 App 控制,示例根据对应事件调用播放器接口。

public synchronized int pauseTts();
public synchronized int resumeTts();

参数查询和资源释放

getparamTts:查询参数,param 为参数名,返回值为参数值。可查询 "error_msg" 获取详细错误信息。

public String getparamTts(String param);

tts_release:释放 SDK 资源。

public synchronized int tts_release();

数据和音量回调

INativeTtsCallback 包含以下回调。onTtsDataCallback 返回合成音频;启用时间戳且音色支持时,还会返回 JSON 格式的时间戳信息。

void onTtsDataCallback(String info, int info_len, byte[] data);
void onTtsVolCallback(int volume);
  • info:时间戳信息。时间戳信息和音频数据可在不同回调中返回,分别检查后处理。

  • info_len:info 数据长度。

  • data:合成音频数据,可写入播放器或文件。

  • volume:音量回调值。不使用音量回调时,也需要提供该方法的实现。

事件回调

void onTtsEventCallback(TtsEvent event, String task_id, int ret_code);

event 为事件类型,task_id 为任务 ID;ret_code 为错误码,在 TTS_EVENT_ERROR 时有效。

事件

说明

TTS_EVENT_START

合成开始,准备播放。

TTS_EVENT_END

合成结束,音频已全部通过回调输出,但播放器可能仍有缓冲数据。

TTS_EVENT_CANCEL

合成任务已取消。

TTS_EVENT_PAUSE

合成任务已暂停。

TTS_EVENT_RESUME

合成任务已恢复。

TTS_EVENT_ERROR

合成发生错误,可通过 getparamTts("error_msg") 获取详细信息。

常见问题

调用取消接口时出现 ANR,如何处理?

SDK 控制接口为同步调用。将初始化、取消等接口放在非 UI 线程中顺序执行,避免阻塞主线程。

返回 140002,如何处理?

该错误表示合成文本非法,例如文本为空。检查传入文本是否有效、非空,并通过 getparamTts("error_msg") 获取详细错误信息。

无法正常使用语音合成,如何排查?

确认已满足前提条件,并核对所用服务的开通状态和可用额度。试用期结束或商用请求未开通对应服务时,需要开通商用版。

模拟器运行时闪退,如何排查?

确认 SDK 包包含与模拟器匹配的 ABI,并检查系统兼容性及崩溃日志。设备相关问题可在真机中复核。

返回 240021 文件访问错误,如何处理?

检查所配置资源或日志目录的读写权限。使用依赖配置文件的 SDK 功能时,还需确认资源文件已完成复制;V2.6.2 起纯云端合成不需要复制资源文件。更多错误码说明请参见错误码查询。