使用 Android NUI SDK 将文本合成为语音,并通过回调获取音频数据。本文介绍 SDK 集成、调用步骤、代码示例、关键接口和常见问题。
前提条件
AccessKey ID 和 AccessKey Secret 应保存在服务端,不得写入 App 代码或存储在移动端。云端语音合成使用 Appkey 和 NLS Token 鉴权;由服务端获取 Token 后下发给 App,并在 Token 过期前更新。初始化参数包含凭证,不应将完整参数写入日志。
语音合成参数和支持的音色请参见移动端语音合成接口说明。
下载和集成 SDK
从移动端 SDK 选择与下载下载支持语音合成的 Android NUI SDK 包。该页提供版本、兼容性、隐私政策及合规使用说明。
以下示例使用 V2.7.4-01B-20260831_Android_TLS 包。
-
解压 SDK 包,将 Demo 的
app/libs/nuisdk-release.aar添加到应用模块的libs目录,并在模块的build.gradle中添加依赖:dependencies { implementation files('libs/nuisdk-release.aar') } -
在
AndroidManifest.xml中声明网络访问权限:<uses-permission android:name="android.permission.INTERNET" /> 使用 Android Studio 打开 Demo 工程。语音合成示例为
TtsBasicActivity.java,替换 Appkey 和 Token 后可运行。
Java 接口通过 AAR 集成;C++ 接口所需动态库和头文件位于包内的 android_libs、android_include 目录。SDK V2.6.2 起,纯云端功能不需要复制资源文件或设置 workspace。
调用步骤
创建 SDK 和播放器实例,设置回调并初始化 SDK。
调用
setparamTts设置音色、音频格式、采样率等参数。调用
startTts启动合成。在
onTtsDataCallback中获取音频数据,写入播放器进行流式播放,或依次追加保存到同一个文件。收到
TTS_EVENT_END后,通知播放器音频数据已发送完毕,由播放器继续处理缓冲数据。该事件表示合成结束,不表示播放结束。不再使用 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 字符串。
|
参数 |
类型 |
说明 |
|
|
String |
必填,控制台项目的 Appkey。 |
|
|
String |
必填,有效且未过期的 NLS Token。可在初始化时设置,也可通过 |
|
|
String |
必填,调用方设置的标识,推荐使用具有唯一性的值,便于定位问题。 |
|
|
Integer 或 String |
必填,纯在线合成设置为 |
|
|
String |
服务网关,示例中显式指定。 |
|
|
String |
资源目录。V2.6.2 起纯云端功能不需要设置;使用依赖本地资源的功能时,目录需要有读写权限。 |
|
|
String |
日志保存目录,启用 |
合成参数
合成参数通过 setparamTts 设置,参数值为字符串。
|
参数 |
示例值 |
说明 |
|
|
|
音色。 |
|
|
|
合成采样率,需与播放器采样率一致。 |
|
|
|
输出格式,示例使用 PCM。 |
|
|
|
启用时间戳,仅对支持该功能的音色有效; |
|
|
|
语速。 |
|
|
|
语调。 |
|
|
|
音量。 |
|
|
|
合成模式,默认 |
|
|
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 时有效。
|
事件 |
说明 |
|
|
合成开始,准备播放。 |
|
|
合成结束,音频已全部通过回调输出,但播放器可能仍有缓冲数据。 |
|
|
合成任务已取消。 |
|
|
合成任务已暂停。 |
|
|
合成任务已恢复。 |
|
|
合成发生错误,可通过 |
常见问题
调用取消接口时出现 ANR,如何处理?
SDK 控制接口为同步调用。将初始化、取消等接口放在非 UI 线程中顺序执行,避免阻塞主线程。
返回 140002,如何处理?
该错误表示合成文本非法,例如文本为空。检查传入文本是否有效、非空,并通过 getparamTts("error_msg") 获取详细错误信息。
无法正常使用语音合成,如何排查?
确认已满足前提条件,并核对所用服务的开通状态和可用额度。试用期结束或商用请求未开通对应服务时,需要开通商用版。
模拟器运行时闪退,如何排查?
确认 SDK 包包含与模拟器匹配的 ABI,并检查系统兼容性及崩溃日志。设备相关问题可在真机中复核。
返回 240021 文件访问错误,如何处理?
检查所配置资源或日志目录的读写权限。使用依赖配置文件的 SDK 功能时,还需确认资源文件已完成复制;V2.6.2 起纯云端合成不需要复制资源文件。更多错误码说明请参见错误码查询。