Android NUI SDK 可将实时采集的音频转换为文字,并通过回调返回中间结果、句子结果和任务完成事件。
前提条件
在服务端获取 NLS Token,并通过安全通道下发给应用。不要将 AccessKey ID、AccessKey Secret 保存在移动端,也不要输出包含 Token 的完整初始化参数。
下载和集成
SDK 下载入口及平台要求,请参见移动端SDK选择与下载。
解压 SDK 整合包后,按应用的开发语言集成对应文件。整合包的 example 目录提供示例工程压缩包,其中实时语音识别的 Java 示例为 SpeechTranscriberActivity.java。运行示例前,配置项目 Appkey 和有效的 NLS Token。
Java/Kotlin
将整合包中的 nuisdk-release.aar 复制到应用模块的 libs 目录;也可使用示例工程 app/libs 中的 AAR。在 app/build.gradle 的 dependencies 中添加本地依赖:
dependencies {
implementation files("libs/nuisdk-release.aar")
// 也可引入 libs 目录中的所有 AAR 文件:
// implementation fileTree(include: ["*.aar"], dir: "libs")
}
C++
使用整合包中 android_libs 的动态库和 android_include 的头文件,按应用的目标 ABI 集成对应动态库。
录音与网络权限
在 AndroidManifest.xml 中声明网络和录音权限,并在开始录音前取得录音权限的运行时授权。
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
ProGuard 配置
如果启用了代码混淆,在 proguard-rules.pro 中保留 NUI 类:
-keep class com.alibaba.idst.nui.* { *; }
调用流程和代码示例
以下片段使用 SDK 2.7.4,展示 Java 实时语音识别的主要调用逻辑。将成员和方法放入实现 INativeNuiCallback 的 Activity 中;录音权限申请、界面展示和调用入口可参考随包示例工程。初始化和任务控制方法在工作线程调用,避免阻塞 UI 线程。
可通过 new NativeNui() 创建对象,或通过 NativeNui.GetInstance() 获取单例。初始化、设置参数、启动、停止和释放均使用同一个实例;再次初始化前先释放原实例的 SDK 资源。
调用流程
初始化 SDK 和录音实例,设置识别参数。
调用
startDialog开始识别,在onNuiAudioStateChanged中按状态启动录音。在
onNuiNeedAudioData中填充 PCM 音频,通过事件回调接收中间结果和句子结果。调用
stopDialog停止识别,并通过EVENT_TRANSCRIBER_COMPLETE确认任务完成;无需等待最终结果时,可调用cancelDialog取消任务。不再使用 SDK 时,调用
release释放资源。
初始化
导入 SDK、录音和 JSON 类,并声明供后续方法使用的成员。示例使用 16 kHz、16 bit、单声道 PCM。
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;
import com.alibaba.idst.nui.AsrResult;
import com.alibaba.idst.nui.Constants;
import com.alibaba.idst.nui.INativeNuiCallback;
import com.alibaba.idst.nui.KwsResult;
import com.alibaba.idst.nui.NativeNui;
import org.json.JSONException;
import org.json.JSONObject;
private static final String TAG = "RealtimeRecognizer";
private static final int SAMPLE_RATE = 16000;
private final NativeNui nui_instance = new NativeNui();
private AudioRecord mAudioRecorder;
初始化参数中的 app_key 和 token 分别填写项目 Appkey 和服务端下发的 NLS Token。纯云端识别使用 Constants.ModeFullCloud;SDK 2.7.4 的纯云端模式无需设置本地资源目录 workspace。
private String genInitParams(String appkey, String token) throws JSONException {
JSONObject parameters = new JSONObject();
parameters.put("app_key", appkey);
parameters.put("token", token);
parameters.put("service_mode", Constants.ModeFullCloud);
parameters.put("url", gatewayUrl());
return parameters.toString();
}
private String gatewayUrl() {
return "wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1";
}
以下方法创建录音实例并初始化 SDK。调用前已取得录音权限;返回 0 后再设置参数和启动任务。可按需在初始化参数中设置唯一的 device_id,用于定位问题。
private int initializeRecognizer(String appkey, String token) throws JSONException {
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (minBufferSize <= 0) {
throw new IllegalStateException("Invalid recording configuration");
}
mAudioRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT,
minBufferSize * 2);
if (mAudioRecorder.getState() != AudioRecord.STATE_INITIALIZED) {
mAudioRecorder.release();
mAudioRecorder = null;
throw new IllegalStateException("AudioRecord initialization failed");
}
int ret = nui_instance.initialize(this, genInitParams(appkey, token),
Constants.LogLevel.LOG_LEVEL_ERROR, false);
if (ret != 0) {
mAudioRecorder.release();
mAudioRecorder = null;
}
return ret;
}
设置参数
初始化成功后、开始识别前调用 setParams。service_type 设置为 Constants.kServiceTypeSpeechTranscriber,sample_rate 与录音采样率保持一致。
private int configureRecognizer() throws JSONException {
JSONObject nls_config = new JSONObject();
nls_config.put("enable_intermediate_result", true);
nls_config.put("sample_rate", SAMPLE_RATE);
nls_config.put("sr_format", "pcm");
// 按需配置识别选项:
// nls_config.put("enable_punctuation_prediction", true);
// nls_config.put("enable_inverse_text_normalization", true);
// nls_config.put("max_sentence_silence", 800);
// nls_config.put("enable_words", false);
JSONObject parameters = new JSONObject();
parameters.put("nls_config", nls_config);
parameters.put("service_type", Constants.kServiceTypeSpeechTranscriber);
return nui_instance.setParams(parameters.toString());
}
初始化、识别和任务参数的详细定义,请参见移动端接口说明。如果某个参数未列在说明中,但服务支持该参数,可通过 nls_config 中的 extend_config 对象设置;使用 HttpDNS 时,可按需设置 direct_ip。
开始识别
参数设置成功后,以 TYPE_P2T 模式开始识别。Token 更新后,可在下一次 startDialog 中传入新的有效 Token。
private int startRecognizer(String token) throws JSONException {
JSONObject dialog_params = new JSONObject();
dialog_params.put("token", token);
return nui_instance.startDialog(Constants.VadMode.TYPE_P2T,
dialog_params.toString());
}
处理回调
在录音状态回调中启动、停止或释放 AudioRecord,在音频数据回调中向 SDK 提供录音数据。
@Override
public void onNuiAudioStateChanged(Constants.AudioState state) {
if (mAudioRecorder == null) {
return;
}
if (state == Constants.AudioState.STATE_OPEN) {
mAudioRecorder.startRecording();
} else if (state == Constants.AudioState.STATE_PAUSE) {
if (mAudioRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
mAudioRecorder.stop();
}
} else if (state == Constants.AudioState.STATE_CLOSE) {
mAudioRecorder.release();
mAudioRecorder = null;
}
}
@Override
public int onNuiNeedAudioData(byte[] buffer, int len) {
if (mAudioRecorder == null
|| mAudioRecorder.getState() != AudioRecord.STATE_INITIALIZED) {
return -1;
}
return mAudioRecorder.read(buffer, 0, len);
}
不要在事件回调中调用 SDK 接口,以免引起死锁。需要操作界面时,将更新交给 UI 线程处理。
事件回调中的 asrResult.asrResult 提供识别结果;allResponse 非空时提供完整 JSON 响应,其中的任务 ID 可用于排查问题。以下示例保留结果和错误处理入口,可替换为应用的界面展示与错误处理逻辑。
@Override
public void onNuiEventCallback(Constants.NuiEvent event, int resultCode, int arg2,
KwsResult kwsResult, AsrResult asrResult) {
if (event == Constants.NuiEvent.EVENT_ASR_PARTIAL_RESULT
|| event == Constants.NuiEvent.EVENT_SENTENCE_END) {
if (asrResult != null) {
Log.i(TAG, "Recognition result: " + asrResult.asrResult);
}
} else if (event == Constants.NuiEvent.EVENT_TRANSCRIBER_COMPLETE) {
Log.i(TAG, "Transcription completed");
} else if (event == Constants.NuiEvent.EVENT_ASR_ERROR
|| event == Constants.NuiEvent.EVENT_MIC_ERROR) {
Log.e(TAG, "Recognition error: " + resultCode);
}
}
@Override
public void onNuiAudioRMSChanged(float val) {
// 可用于展示音量动效。
}
@Override
public void onNuiVprEventCallback(Constants.NuiVprEvent event) {
// 本示例不使用声纹事件。
}
停止或取消识别
调用 stopDialog 停止识别后,通过 EVENT_TRANSCRIBER_COMPLETE 确认任务完成。有尚未返回的有效语音结果时,继续从句子结果回调接收结果;没有有效语音时,不会因此产生新的句子结果。
int ret = nui_instance.stopDialog();
无需等待服务端最终结果时,调用 nui_instance.cancelDialog() 取消任务。
释放资源
不再使用 SDK 时释放资源。释放后,如需再次使用,重新创建录音实例并初始化 SDK。
int ret = nui_instance.release();
if (mAudioRecorder != null) {
mAudioRecorder.release();
mAudioRecorder = null;
}
关键接口与事件
以下签名为接口声明。返回整型状态码的接口,返回 0 表示成功;其他返回值需按错误码处理。
错误码含义和处理方法,请参见错误码查询。
初始化与参数配置
initialize 设置回调、初始化参数、日志级别及是否保存日志文件。参数以 JSON 字符串传入,日志级别数值越小,记录越详细。启用日志保存时,设置可写的 debug_path;需要保存调试音频时,同时设置 save_wav。持续保存调试文件会占用存储空间,应及时清理。
public synchronized int initialize(INativeNuiCallback callback, String parameters,
Constants.LogLevel level, boolean save_log);
setParams 以 JSON 字符串配置识别参数,须在初始化成功后、开始识别前调用。
public synchronized int setParams(String params);
任务控制与资源管理
startDialog 开始识别;实时识别使用 Constants.VadMode.TYPE_P2T,任务参数为 JSON 字符串。
public synchronized int startDialog(Constants.VadMode vad_mode, String dialog_params);
stopDialog 发送停止识别请求,通过事件回调接收剩余结果和任务完成通知。
public synchronized int stopDialog();
cancelDialog 取消任务,不等待服务端最终识别结果。
public synchronized int cancelDialog();
release 释放 SDK 资源。
public synchronized int release();
GetVersion 返回 SDK 版本字符串。
public synchronized String GetVersion();
回调接口
INativeNuiCallback 的主要回调如下。实时识别未使用的声纹回调 onNuiVprEventCallback 也需实现,可保留空方法。
onNuiAudioStateChanged 通知应用打开、暂停或关闭录音。
void onNuiAudioStateChanged(Constants.AudioState state);
onNuiNeedAudioData 持续请求音频数据:将数据写入 buffer,最多写入 len 字节,并返回实际写入的字节数。返回负值表示录音出错,返回 0 表示暂无数据;连续 2 秒没有音频数据时,SDK 触发 EVENT_MIC_ERROR。
int onNuiNeedAudioData(byte[] buffer, int len);
onNuiEventCallback 返回识别事件。resultCode 提供错误码,arg2 为保留参数,kwsResult 不用于本实时识别示例,asrResult 提供识别结果或错误响应。
void onNuiEventCallback(Constants.NuiEvent event, int resultCode, int arg2,
KwsResult kwsResult, AsrResult asrResult);
onNuiAudioRMSChanged 返回音频能量值,范围为 -160~0,可用于音量动效。
void onNuiAudioRMSChanged(float val);
事件列表
|
事件 |
说明 |
|
|
VAD 启动事件;在本实时识别模式中,不用于判定句子起点。 |
|
|
VAD 结束事件;在本实时识别模式中,不用于判定句子终点。 |
|
|
当前句子的识别中间结果。 |
|
|
识别错误,根据错误码及响应中的任务 ID 排查。 |
|
|
录音错误,连续 2 秒没有收到音频时,检查录音代码、权限及录音模块是否被占用。 |
|
|
检测到一句话开始。 |
|
|
检测到一句话结束,并返回完整的句子结果。 |
|
|
暂不使用。 |
|
|
实时语音识别任务完成。 |
常见问题
选择 8 kHz 模型时,如何设置音频参数?
项目选择 8 kHz 模型,AudioRecord 的采样率及 nls_config.sample_rate 均设置为 8000,并将 sr_format 设置为小写的 pcm。示例中将 SAMPLE_RATE 改为 8000,使录音与请求参数保持一致。
断网后恢复网络,SDK 会自动继续识别吗?
SDK 2.7.4 默认关闭自动重连。需要自动重连时,在初始化参数中设置 enable_reconnection;开启后,SDK 会在连接中断时尝试重新连接。关闭时,由应用处理错误并重新启动任务。
在 genInitParams 返回参数前添加:
parameters.put("enable_reconnection", "true");
对 Android 版本有什么要求?
当前 SDK 的最低版本要求为 Android 5.0(API 21)。
具体 SDK 版本的平台要求,请参见移动端SDK选择与下载。
可以直接向 SDK 输入 OPUS 音频吗?
实时语音识别输入为 PCM 编码、16 bit、单声道音频,支持 8 kHz 或 16 kHz 采样率。sr_format 控制音频传输格式,取值为小写的 pcm 或 opus。设置为 opus 时,SDK 会将输入的 PCM 数据编码压缩后传输,以节省带宽。
提示“audio recorder not init”如何处理?
检查应用是否已取得录音权限,以及 AudioRecord 是否成功初始化。录音配置须使用单声道、PCM 16 bit,采样率与识别配置一致;读取前调用 startRecording(),暂停时调用 stop(),不再使用时调用 release()。已释放的录音实例须重新创建后才能继续录音。
音频回调可按以下方式检查实例状态并读取数据:
public int onNuiNeedAudioData(byte[] buffer, int len) {
if (mAudioRecorder == null
|| mAudioRecorder.getState() != AudioRecord.STATE_INITIALIZED) {
Log.e(TAG, "Audio recorder not initialized");
return -1;
}
return mAudioRecorder.read(buffer, 0, len);
}
初始化返回 240021 如何处理?
240021 表示 FILE_ACCESS_FAIL 文件访问错误。检查 SDK 配置中涉及的目录是否具有读写权限;使用本地资源时,检查资源和配置文件是否已正确复制到 SDK 工作目录。
需要从 AAR 复制本地资源时,可使用 CommonUtils.copyAssetsData,并将工作目录路径设置到初始化参数的 workspace 中;纯云端示例不需要执行此操作。
if (!com.alibaba.idst.nui.CommonUtils.copyAssetsData(this)) {
Log.e(TAG, "Failed to copy SDK assets");
return;
}
String workspace = com.alibaba.idst.nui.CommonUtils.getModelPath(this);