通过一句话识别 Android SDK,可将应用采集的语音转换为文字。本文介绍 SDK 的集成、识别调用、参数和回调,以及常见问题的处理方法。
前提条件
下载和集成 SDK
SDK 和示例工程下载,请参见移动端SDK选择与下载。
集成 SDK
Java
解压下载包,将示例工程 app/libs 下的 AAR 文件复制到应用的 app/libs 目录,在应用模块的 build.gradle 中添加依赖:
dependencies {
implementation files('libs/nuisdk-release.aar')
}使用 Android Studio 打开下载包中的示例工程,可查看 SpeechRecognizerActivity.java 中的一句话识别实现。运行前替换项目 Appkey 和 Token。
C++
下载包的 android_libs 和 android_include 目录分别提供动态库和头文件,可用于 Android C++ 接入。
配置权限
在 AndroidManifest.xml 的 manifest 元素中声明网络和录音权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />Android 6.0 及以上版本还需在运行时申请录音权限,获得授权后再创建录音实例。
配置代码混淆
如果启用了代码混淆,在 proguard-rules.pro 中添加:
-keep class com.alibaba.idst.nui.* { *; }调用流程
创建
NativeNui和AudioRecord实例,调用initialize初始化 SDK。调用
setParams设置识别参数。调用
startDialog开始识别;收到STATE_OPEN回调后启动录音。在
onNuiNeedAudioData中提供音频数据,在EVENT_ASR_PARTIAL_RESULT回调中获取中间结果。调用
stopDialog结束输入,并等待EVENT_ASR_RESULT或错误回调。启用在线 VAD 时,服务端可自动判断语音结束。需要取消任务时调用cancelDialog。不再使用 SDK 时,调用
release释放资源。
初始化、参数设置和识别控制必须使用同一个 NativeNui 实例。可通过 new NativeNui() 创建实例,也可通过 NativeNui.GetInstance() 获取单例。
代码示例
以下 Java 示例使用 SDK 2.7.4,以 16 kHz、16 bit、单声道 PCM 录音进行一句话识别。获得录音权限后,使用项目 Appkey、有效 Token 和设备标识初始化 ShortSentenceRecognizer,通过 start、stop、cancel 和 release 控制识别。
在非 UI 线程串行调用这些方法;回调中如需更新界面,切换到 UI 线程。不要在 SDK 回调中直接调用 SDK 接口,以免发生死锁。
endpoint 使用以下地址:
wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
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;
public final class ShortSentenceRecognizer implements INativeNuiCallback {
public interface Listener {
void onResult(String result, boolean isFinal);
void onError(int code, String detail);
}
private static final int SAMPLE_RATE = 16000;
private final NativeNui nui = new NativeNui();
private final Listener listener;
private AudioRecord recorder;
public ShortSentenceRecognizer(Listener listener) {
this.listener = listener;
}
public void initialize(String appkey, String token, String deviceId,
String endpoint, boolean onlineVad) throws JSONException {
int minSize = AudioRecord.getMinBufferSize(SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (minSize <= 0) {
throw new IllegalStateException("Unsupported recording configuration");
}
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT,
Math.max(minSize * 2, 6400));
if (recorder.getState() != AudioRecord.STATE_INITIALIZED) {
closeRecorder();
throw new IllegalStateException("AudioRecord initialization failed");
}
JSONObject init = new JSONObject();
init.put("app_key", appkey);
init.put("token", token);
init.put("device_id", deviceId);
init.put("url", endpoint);
init.put("service_mode", Constants.ModeAsrCloud);
try {
check(nui.initialize(this, init.toString(),
Constants.LogLevel.LOG_LEVEL_NONE, false));
JSONObject nls = new JSONObject();
nls.put("sample_rate", SAMPLE_RATE);
nls.put("sr_format", "pcm");
nls.put("enable_intermediate_result", true);
nls.put("enable_voice_detection", onlineVad);
if (onlineVad) {
nls.put("max_start_silence", 10000);
nls.put("max_end_silence", 800);
}
JSONObject params = new JSONObject();
params.put("service_type", Constants.kServiceTypeASR);
params.put("nls_config", nls);
check(nui.setParams(params.toString()));
} catch (RuntimeException | JSONException e) {
nui.release();
closeRecorder();
throw e;
}
}
public void start(String refreshedToken) throws JSONException {
JSONObject dialog = new JSONObject();
if (refreshedToken != null && !refreshedToken.isEmpty()) {
dialog.put("token", refreshedToken);
}
check(nui.startDialog(Constants.VadMode.TYPE_P2T, dialog.toString()));
}
public void stop() {
check(nui.stopDialog());
}
public void cancel() {
check(nui.cancelDialog());
}
public void release() {
nui.release();
closeRecorder();
}
private static void check(int code) {
if (code != 0) {
throw new IllegalStateException("NUI error: " + code);
}
}
private void closeRecorder() {
if (recorder != null) {
if (recorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
recorder.stop();
}
recorder.release();
recorder = null;
}
}
@Override
public void onNuiAudioStateChanged(Constants.AudioState state) {
if (recorder == null) {
return;
}
if (state == Constants.AudioState.STATE_OPEN) {
recorder.startRecording();
} else if (state == Constants.AudioState.STATE_PAUSE) {
if (recorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
recorder.stop();
}
} else if (state == Constants.AudioState.STATE_CLOSE) {
closeRecorder();
}
}
@Override
public int onNuiNeedAudioData(byte[] buffer, int len) {
if (recorder == null || recorder.getState() != AudioRecord.STATE_INITIALIZED) {
return -1;
}
return recorder.read(buffer, 0, len);
}
@Override
public void onNuiEventCallback(Constants.NuiEvent event, int resultCode,
int arg2, KwsResult kwsResult, AsrResult asrResult) {
if (event == Constants.NuiEvent.EVENT_ASR_RESULT
|| event == Constants.NuiEvent.EVENT_ASR_PARTIAL_RESULT) {
if (asrResult != null) {
listener.onResult(asrResult.asrResult,
event == Constants.NuiEvent.EVENT_ASR_RESULT);
}
} else if (event == Constants.NuiEvent.EVENT_ASR_ERROR
|| event == Constants.NuiEvent.EVENT_MIC_ERROR) {
listener.onError(resultCode, asrResult == null ? "" : asrResult.asrResult);
}
}
@Override
public void onNuiAudioRMSChanged(float value) {}
@Override
public void onNuiVprEventCallback(Constants.NuiVprEvent event) {}
}start(null) 不传入新的 Token;需要更新 Token 时,将新 Token 传入 start。正常结束音频输入后,等待最终结果或错误回调,再开始下一次识别。stop 暂停录音以便后续复用,release 释放 SDK 和录音资源。
参数说明
初始化参数
initialize 的 parameters 为 JSON 字符串。
参数 | 类型 | 说明 |
| String | 必填,项目 Appkey。 |
| String | 必填,NLS Token。 |
| String | 必填,设备标识,建议具有唯一性,便于定位问题。 |
| String | 服务地址,使用代码示例中的网关地址。 |
| Integer | 必填,一句话识别使用 |
| String | SDK 工作目录,需有读写权限。SDK 2.7.4 的纯在线一句话识别可不配置;使用依赖资源文件的 SDK 功能时,需按对应功能要求准备资源。 |
| String | 调试文件目录。启用日志或音频保存时,确保该目录可写,并及时清理文件。 |
| String | 设为 |
initialize 的 level 指定日志级别,数值越小输出越多;save_log 指定是否保存日志文件。示例关闭日志保存,避免持续占用存储空间。
识别参数
在 initialize 之后、startDialog 之前调用 setParams。顶层 service_type 必须设为 Constants.kServiceTypeASR,识别参数放入 nls_config 对象。
参数 | 类型 | 说明 |
| Integer | 音频采样率,例如 |
| String | 音频传输格式,支持 |
| Boolean | 是否返回中间识别结果。 |
| Boolean | 是否添加标点。 |
| Boolean | 是否开启在线 VAD,自动判断语音起止。 |
| Integer | 在线 VAD 的最大起始静音时间,单位为毫秒。示例为 |
| Integer | 在线 VAD 的最大结束静音时间,单位为毫秒。示例为 |
01B SDK 不包含本地 VAD 模块。使用在线 VAD 时,将 enable_voice_detection 设为 true,startDialog 仍使用 Constants.VadMode.TYPE_P2T。
输入音频采样率支持 8 kHz 和 16 kHz,分别对应 sample_rate 的 8000 和 16000,需与项目模型匹配。
以下可选参数也放入 nls_config:
参数 | 类型 | 说明 |
| Boolean | 是否进行逆文本标准化。 |
| String | 自学习模型 ID。 |
| String | 热词表 ID。 |
| Object | 为服务端支持、但 SDK 未单独列出的参数提供扩展配置。具体参数仍需符合服务端要求。 |
使用 HTTPDNS 时,可在 setParams 的顶层参数中设置 direct_ip。
更多参数和取值要求,请参见一句话识别接口说明。
SDK 接口和回调
控制接口
接口 | 说明 |
| 初始化 SDK。不要在 UI 线程中调用;同一实例再次初始化前,先释放已有资源。 |
| 设置 JSON 格式的参数。 |
| 开始识别。使用 |
| 结束音频输入,最终结果或错误通过回调返回。 |
| 取消识别,不等待最终识别结果。 |
| 释放 SDK 资源。 |
| 获取 SDK 版本。 |
返回 int 的接口以 0 表示调用成功,非 0 表示错误。
错误码及处理方法,请参见错误码查询。
回调接口
回调 | 说明 |
| 录音状态通知。 |
| 将音频填入 |
| 识别事件通知。 |
| 音频能量值回调,范围为 -160~0,可用于语音动效。 |
| 声纹相关回调,一句话识别示例保留空实现。 |
AsrResult.asrResult 包含识别结果或错误信息及 task_id,排查问题时可记录任务 ID。支持 allResponse 的 SDK 版本可通过该字段获取完整 JSON 响应;读取前检查结果对象和字段是否为空。
事件类型
事件 | 说明 |
| 检测到语音起点。 |
| 检测到语音终点。 |
| 中间识别结果。 |
| 最终识别结果。 |
| 识别错误,根据错误码和错误信息排查。 |
| 连续 2 秒未收到音频数据。检查录音权限、录音状态,以及录音设备是否被其他应用占用。 |
| 本示例无需处理。 |
常见问题
是否可以向 SDK 输入 OPUS 音频?
一句话识别和实时语音识别的 SDK 音频输入为 PCM 编码、16 bit、单声道数据。sr_format 控制传输格式:设为 opus 时,SDK 将输入的 PCM 音频编码压缩后发送,以减少网络带宽占用。
录音文件识别极速版支持 OPUS 音频文件,其文件输入要求与一句话识别不同。
Demo 在模拟器上语音合成正常,但开始一句话识别时闪退,如何处理?
模拟器环境可能存在兼容性问题,可在真机上排查。
持续请求音频数据但没有识别结果,随后返回 50000000,如何处理?
50000000 表示服务错误,可能与 SDK 运行状态异常有关。排查时收集从 SDK 初始化到错误发生期间的完整 Logcat,并在提供日志前移除凭证等敏感信息。
出现“audio recoder not init”提示,如何处理?
检查录音权限和 AudioRecord 初始化状态,并确认音频模块未被其他应用占用。读取数据前必须调用 startRecording();初始化成功不代表已经开始录音。可将录音部分单独运行,检查是否能够取得音频数据。代码示例在 STATE_OPEN 回调中启动录音,在 STATE_PAUSE 中停止录音。
已授予录音权限,但初始化返回 240021,如何处理?
240021(FILE_ACCESS_FAIL)表示文件访问失败。检查配置目录的读写权限;如果所用功能需要 SDK 资源文件,还需将资源复制到对应工作目录。示例工程使用以下方式复制资源:
if (!CommonUtils.copyAssetsData(this)) {
Log.e(TAG, "Failed to copy SDK assets");
return;
}
CommonUtils 是示例工程中的辅助类。使用资源文件时,可通过 CommonUtils.getModelPath(this) 获取工作目录,并将其传入初始化参数 workspace。