Android SDK

更新时间:
复制 MD 格式

通过一句话识别 Android SDK,可将应用采集的语音转换为文字。本文介绍 SDK 的集成、识别调用、参数和回调,以及常见问题的处理方法。

前提条件

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

  • 已获取 NLS Token。具体操作,请参见获取Token。

    重要

    在服务端获取 Token,再通过安全通道下发给应用。不要在移动端保存 AccessKey,也不要将 Token 或完整鉴权参数写入日志。

    服务端参数和消息格式,请参见一句话识别接口说明。

下载和集成 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.* { *; }

调用流程

  1. 创建 NativeNui 和 AudioRecord 实例,调用 initialize 初始化 SDK。

  2. 调用 setParams 设置识别参数。

  3. 调用 startDialog 开始识别;收到 STATE_OPEN 回调后启动录音。

  4. 在 onNuiNeedAudioData 中提供音频数据,在 EVENT_ASR_PARTIAL_RESULT 回调中获取中间结果。

  5. 调用 stopDialog 结束输入,并等待 EVENT_ASR_RESULT 或错误回调。启用在线 VAD 时,服务端可自动判断语音结束。需要取消任务时调用 cancelDialog。

  6. 不再使用 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/v1
import 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 字符串。

参数

类型

说明

app_key

String

必填,项目 Appkey。

token

String

必填,NLS Token。

device_id

String

必填,设备标识,建议具有唯一性,便于定位问题。

url

String

服务地址,使用代码示例中的网关地址。

service_mode

Integer

必填,一句话识别使用 Constants.ModeAsrCloud。

workspace

String

SDK 工作目录,需有读写权限。SDK 2.7.4 的纯在线一句话识别可不配置;使用依赖资源文件的 SDK 功能时,需按对应功能要求准备资源。

debug_path

String

调试文件目录。启用日志或音频保存时,确保该目录可写,并及时清理文件。

save_wav

String

设为 "true" 可保存调试音频,需要同时将 initialize 的 save_log 参数设为 true,并配置可写的 debug_path。

initialize 的 level 指定日志级别,数值越小输出越多;save_log 指定是否保存日志文件。示例关闭日志保存,避免持续占用存储空间。

识别参数

在 initialize 之后、startDialog 之前调用 setParams。顶层 service_type 必须设为 Constants.kServiceTypeASR,识别参数放入 nls_config 对象。

参数

类型

说明

sample_rate

Integer

音频采样率,例如 16000。需与输入音频及项目模型匹配。

sr_format

String

音频传输格式,支持 pcm 和 opus。输入 SDK 的音频仍为 PCM;选择 opus 时由 SDK 压缩后传输。

enable_intermediate_result

Boolean

是否返回中间识别结果。

enable_punctuation_prediction

Boolean

是否添加标点。

enable_voice_detection

Boolean

是否开启在线 VAD,自动判断语音起止。

max_start_silence

Integer

在线 VAD 的最大起始静音时间,单位为毫秒。示例为 10000。

max_end_silence

Integer

在线 VAD 的最大结束静音时间,单位为毫秒。示例为 800。

01B SDK 不包含本地 VAD 模块。使用在线 VAD 时,将 enable_voice_detection 设为 true,startDialog 仍使用 Constants.VadMode.TYPE_P2T。

输入音频采样率支持 8 kHz 和 16 kHz,分别对应 sample_rate 的 8000 和 16000,需与项目模型匹配。

以下可选参数也放入 nls_config:

参数

类型

说明

enable_inverse_text_normalization

Boolean

是否进行逆文本标准化。

customization_id

String

自学习模型 ID。

vocabulary_id

String

热词表 ID。

extend_config

Object

为服务端支持、但 SDK 未单独列出的参数提供扩展配置。具体参数仍需符合服务端要求。

使用 HTTPDNS 时,可在 setParams 的顶层参数中设置 direct_ip。

更多参数和取值要求,请参见一句话识别接口说明。

SDK 接口和回调

控制接口

接口

说明

int initialize(INativeNuiCallback callback, String parameters, Constants.LogLevel level, boolean save_log)

初始化 SDK。不要在 UI 线程中调用;同一实例再次初始化前,先释放已有资源。

int setParams(String params)

设置 JSON 格式的参数。

int startDialog(Constants.VadMode vad_mode, String dialog_params)

开始识别。使用 TYPE_P2T;dialog_params 可传入新的 token。

int stopDialog()

结束音频输入,最终结果或错误通过回调返回。

int cancelDialog()

取消识别,不等待最终识别结果。

int release()

释放 SDK 资源。

String GetVersion()

获取 SDK 版本。

返回 int 的接口以 0 表示调用成功,非 0 表示错误。

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

回调接口

回调

说明

onNuiAudioStateChanged(AudioState state)

录音状态通知。STATE_OPEN 启动录音,STATE_PAUSE 暂停录音,STATE_CLOSE 释放录音资源。

int onNuiNeedAudioData(byte[] buffer, int len)

将音频填入 buffer,返回实际填充的字节数,不能超过 len。返回负数表示错误,0 表示暂时没有数据;连续 2 秒无音频会触发 EVENT_MIC_ERROR。

onNuiEventCallback(NuiEvent event, int resultCode, int arg2, KwsResult kwsResult, AsrResult asrResult)

识别事件通知。resultCode 在错误事件中表示错误码;arg2 为保留参数,kwsResult 为本功能不使用的唤醒结果,asrResult 为识别结果或错误信息。

onNuiAudioRMSChanged(float value)

音频能量值回调,范围为 -160~0,可用于语音动效。

onNuiVprEventCallback(NuiVprEvent event)

声纹相关回调,一句话识别示例保留空实现。

AsrResult.asrResult 包含识别结果或错误信息及 task_id,排查问题时可记录任务 ID。支持 allResponse 的 SDK 版本可通过该字段获取完整 JSON 响应;读取前检查结果对象和字段是否为空。

事件类型

事件

说明

EVENT_VAD_START

检测到语音起点。

EVENT_VAD_END

检测到语音终点。

EVENT_ASR_PARTIAL_RESULT

中间识别结果。

EVENT_ASR_RESULT

最终识别结果。

EVENT_ASR_ERROR

识别错误,根据错误码和错误信息排查。

EVENT_MIC_ERROR

连续 2 秒未收到音频数据。检查录音权限、录音状态,以及录音设备是否被其他应用占用。

EVENT_DIALOG_EX

本示例无需处理。

常见问题

是否可以向 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。