Android SDK

更新时间:
复制 MD 格式

Android NUI SDK 可将实时采集的音频转换为文字,并通过回调返回中间结果、句子结果和任务完成事件。

前提条件

  • 已创建项目并获取 Appkey,操作方法请参见管理项目。

  • 已获取有效的 NLS Token,操作方法请参见获取Token。

重要

在服务端获取 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 资源。

调用流程

  1. 初始化 SDK 和录音实例,设置识别参数。

  2. 调用 startDialog 开始识别,在 onNuiAudioStateChanged 中按状态启动录音。

  3. 在 onNuiNeedAudioData 中填充 PCM 音频,通过事件回调接收中间结果和句子结果。

  4. 调用 stopDialog 停止识别,并通过 EVENT_TRANSCRIBER_COMPLETE 确认任务完成;无需等待最终结果时,可调用 cancelDialog 取消任务。

  5. 不再使用 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);

事件列表

事件

说明

EVENT_VAD_START

VAD 启动事件;在本实时识别模式中,不用于判定句子起点。

EVENT_VAD_END

VAD 结束事件;在本实时识别模式中,不用于判定句子终点。

EVENT_ASR_PARTIAL_RESULT

当前句子的识别中间结果。

EVENT_ASR_ERROR

识别错误,根据错误码及响应中的任务 ID 排查。

EVENT_MIC_ERROR

录音错误,连续 2 秒没有收到音频时,检查录音代码、权限及录音模块是否被占用。

EVENT_SENTENCE_START

检测到一句话开始。

EVENT_SENTENCE_END

检测到一句话结束,并返回完整的句子结果。

EVENT_SENTENCE_SEMANTICS

暂不使用。

EVENT_TRANSCRIBER_COMPLETE

实时语音识别任务完成。

常见问题

选择 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);