DuerOS DCS Android SDK开发指南
DuerOS DCS Android SDK(以下简称“SDK”)是DuerOS推出的基于DCS协议的智能设备语音交互软件开发工具包。DCS协议是DuerOS服务端与设备端之间的通讯协议,是一套把DuerOS的智能语音交互能力向所有设备开放的API。SDK在设备端实现了DCS协议,为Android设备提供简单、便捷的开发接口,降低设备接入DuerOS的成本,让开发者快速接入并在Android设备上实现语音交互功能。
DCS Android SDK支持以下功能。
- 基本功能
支持语音输入、语音输出、闹钟、音乐、播放控制等。 - 唤醒功能
支持开发者使用唤醒词为【小度小度】的唤醒功能。 - 语音识别功能
支持在线语音识别功能和离线语音识别功能。 - 语音多轮交互功能
SDK支持多轮交互功能,在多轮交互中不需要再次唤醒。 - 语音合成和播报功能
支持本地离线的语音合成和播报。 - 自定义端能力功能
开发者根据需要,定义设备的指令,控制设备。
本文主要介绍SDK运行的环境、工程的配置及如何使用SDK提供的功能。开发者可以参考DuerOS提供的API使用指南及sample app工程进行开发。
DuerOS DCS Android SDK API使用指南
DuerOS DCS Android SDK & Sample App工程
SDK开发运行环境
建议使用Android Studio进行SDK开发,并且要求Android是4.1及以上版本。目前支持arm64-v8a、armeabi、armeabi-v7a三种CPU类型。
配置SDK工程
使用SDK功能前,需要对工程进行配置。请下载sample app工程,并参照sample app工程完成工程配置。
配置SDK所需的jar包和so库
配置jar包
请按照如下操作配置jar包。
-
首先将sample app工程中app/libs下的jar包复制到你工程对应的位置。你可以根据需要进行jar包的拷贝。
-
然后在build.gradle文件的dependencies模块里对需要的jar包进行配置。
dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') // 可选 百度url音频播放器 compile name: 'bdplayer-1.0.0', ext: 'aar' // 定位 compile project(':location') // 可选------- def jacksonVersion = '2.9.1' compile "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}" compile "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}" compile "com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}" compile files('libs/commons-fileupload-1.3.2.jar') compile files('libs/commons-lang3-3.4.jar') compile files('libs/commons-io-2.5.jar') compile files('libs/okhttp-3.8.1.jar') compile files('libs/okio-1.14.0.jar') compile files('libs/jlayer-1.0.1.jar') compile files('libs/turbonet.jar') compile files('libs/localtts-2.3.2.jar') compile files('libs/dcssdk-版本号.jar') compile files('libs/crablite2.1.jar') compile files('libs/speechv3.jar') compile files('libs/fastjson-1.2.46.jar') }
配置so库文件
将app/src/main/jniLibs目录下的so库文件拷贝到你的工程,完成so库文件的配置。
说明:
路径app/src/main/jniLibs为Android Studio默认的so库文件存放路径。如果你将so库文件放到其他路径下,需要对build.gradle文件中的jniLibs.srcDirs进行修改,如将so放到app/libs路径下,则jniLibs.srcDirs=['libs']。
声明SDK需要的权限
SDK在使用过程中需要用到麦克风、扬声器等设备,所以需要进行相应权限的声明。请在AndroidManifest.xml中添加下面的权限声明代码。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
PID和KEY配置
为了方便个人开发者的使用,SDK中提供了近场远场两套识别模型。开发者根据设备功能选择合适的模型,如使用手机设备时选择近场模型,使用音箱设备时选择远场模型。使用模型时请注意PID和KEY的一一对应关系,近场的PID匹配近场的KEY,远场的PID匹配远场的KEY。
近场:
pid:1703
key:com.baidu.dumi.open
远场:
pid:1704
key:com.baidu.dumi.open.far
Multi-Dex分包
开发者根据业务需要选择是否配置Multi-Dex分包功能,并按照下面的指导进行Multi-Dex分包配置。
-
首先在build.gradle的buildType中添加multidex-config.txt。
multiDexKeepFile file('multidex-config.txt')
-
然后在multidex-config.txt里面增加下面的配置。
com/baidu/duer/dcs/tts/TtsImpl.class com/alibaba/fastjson/JSON.class com/baidu/duer/dcs/util/http/callback/DcsCallback.class com/baidu/duer/dcs/util/http/callback/ResponseCallback.class com/baidu/duer/dcs/util/http/callback/SimpleCallback.class com/baidu/duer/dcs/util/util/ObjectMapperUtil.class org/apache/commons/io/output/StringBuilderWriter.class com/fasterxml/jackson/databind/introspect/BasicBeanDescription.class com/fasterxml/jackson/core/io/SegmentedStringWriter.class com/baidu/tts/g/a/a.class com/fasterxml/jackson/databind/deser/impl/FailingDeserializer.class java/lang.reflect.ParameterizedType.class com/baidu/duer/dcs/componentapi/AbsDcsClient.class com/baidu/duer/dcs/devicemodule/audioplayer/message/PlaybackStatePayload.class com/baidu/speech/core/BDSCoreJniInterface.class com/baidu/duer/dcs/basiclibs/turbonet/TurbonetRequestImpl.class com/fasterxml/jackson/databind/introspect/SimpleMixInResolver.class com/baidu/duer/dcs/basiclibs/turbonet/CallImpl.class
说明:
- 如果设备上的Android系统是5.0及以上版本时,因为Android系统自带分包功能,所以不用进行分包操作。
- 如果你的工程中还有其他的分包设置,请与DCS SDK的分包配置放在一起。
- 请确保multidex-config.txt文件与build.gradle在同一路径下。
混淆规则设置
SDK中的jar包使用了混淆规则,所以你的工程也需要进行相应混淆规则的配置。请参考sample app工程的app/proguard-rules.pro文件查看SDK使用的混淆规则,并完成配置。下面是部分混淆规则示例。
-dontwarn ai.kitt.snowboy.**
-keep class ai.kitt.snowboy.** {*;}
-dontwarn com.baidu.duer.**
-keep class com.baidu.duer.** {*;}
-dontwarn com.baidu.dcs.acl.**
-keep class com.baidu.dcs.acl.** {*;}
SDK功能应用
SDK功能应用包括如何运行SDK,以及如何使用SDK提供语音识别、唤醒、自定义扩展等功能。
运行SDK
SDK运行分三个阶段,初始化阶段、运行阶段及退出。
-
首先介绍如何初始化SDK,初始化过程需要配置以下信息。
- 将SDK中的CLIENT_ID,替换为注册设备的CLIENT_ID。 请在DuerOS设备控制台上注册智能设备,具体流程请参见控制台接入流程。注册成功后,可以获得设备的CLIENT_ID,然后替换SDK中的CLIENT_ID。
- 根据设备的功能及特点选择对应的PID和KEY,并在工程中进行配置。
- 设置设备ID。设备ID需要遵循以下要求。
- 设备ID根据开发者的需求进行填写。
- 设备ID不能超过64个字符,支持字母、数字、下划线(_)及中划线(-)。
- 设备ID必须满足如下条件:设备ID唯一不变,在刷机及升级等操作后,设备ID不能发生变化。
- SDK提供StandbyDeviceIdUtil.getStandbyDeviceId()方法来生成设备ID,但是不能保证生成的设备ID是唯一的。
初始化代码示例。
BaseAudioRecorder audioRecorder = new AudioRecordImpl(); IOauth oauth = new OauthCodeImpl(CLIENT_ID, this); // OauthCode登录,请确保CLIENT_ID, 已进行相关配置 DcsSdkBuilder builder = new DcsSdkBuilder(); SdkConfigProvider sdkConfigProvider = new DefaultSdkConfigProvider() { @Override public String clientId() { // CLIENT_ID 是申请产品时的client_id return CLIENT_ID; } @Override public int pid() { // 语音PID return PID; } @Override public String appKey() { //语音key return APP_KEY; } }; dcsSdk = builder.withSdkConfig(sdkConfigProvider) .withOauth(oauth) .withAudioRecorder(audioRecorder) //获取设备ID .withDeviceId(StandbyDeviceIdUtil.getStandbyDeviceId()) .build();
-
配置SDK运行代码,代码示例如下。
((DcsSdkImpl) dcsSdk).getInternalApi().login(new ILoginListener() { @Override public void onSucceed(String accessToken) { dcsSdk.run(); Toast.makeText(SDKBaseActivity.this.getApplicationContext(), "登录成功", Toast .LENGTH_SHORT).show(); } @Override public void onFailed(String errorMessage) { Toast.makeText(SDKBaseActivity.this.getApplicationContext(), "登录失败", Toast .LENGTH_SHORT).show(); Log.e(TAG, "login onFailed. "); finish(); } @Override public void onCancel() { Toast.makeText(SDKBaseActivity.this.getApplicationContext(), "登录被取消", Toast .LENGTH_SHORT).show(); Log.e(TAG, "login onCancel. "); finish(); } });
-
SDK退出,可以调用release退出SDK。
dcsSdk.release();
支持授权账号登录
目前SDK支持百度账号授权登录,需要遵守百度OAuth标准。
首先在DuerOS控制台上完成授权功能相应的配置。
-
在设备的基础信息中,点击OAUTH CONFIG URL地址,会跳转到授权回调页。
-
在授权回调页,点击安全设置。
-
在安全设置中添加授权回调页
https://xiaodu.baidu.com/saiya/device/oauthCallback?client_id=*********
(*********
表示开发者申请的client_id),需保留默认配置,以逗号分隔。在根域名绑定中添加xiaodu.baidu.com
。
然后在SDK工程中通过DcsSdkBuilder类的withOauth(IOauth oauth)接口设置授权,并调用InternalApi的login。代码示例如下,详细的过程请参考sample app工程。
DcsSdkBuilder builder = new DcsSdkBuilder();
dcsSdk = builder.withOauth(oauth);
protected void sdkRun() {
// 第三步,将sdk跑起来
((DcsSdkImpl) dcsSdk).getInternalApi().login(new ILoginListener() {
@Override
public void onSucceed(String accessToken) {
dcsSdk.run(null);
Toast.makeText(SDKBaseActivity.this.getApplicationContext(), "登录成功", Toast
.LENGTH_SHORT).show();
}
IOauth oauth = new OauthCodeImpl(CLIENT_ID, this)
语音识别功能
SDK提供了语音识别功能,包括在线语音识别和离线语音识别。
- 在线语音识别
在设备连接网络的情况下,通过语音请求,将语音识别成文本,并返回对应的请求结果。SDK提供了开始、结束、取消语音识别的接口,通过调用这些接口实现语音识别功能。 - 离线语音识别
离线识别是指在无网络情况下进行语音识别。
在线语音识别
在线语音识别可以通过SDK提供的以下接口实现在线识别功能。
-
发送语音请求:IVoiceRequest.beginVoiceRequest。
发送语音识别请求,表示需要进行语音识别。该接口在cancelVoiceRequest接口的回调中实现的,代码示例如下。
// 必须先调用cancel dcsSdk.getVoiceRequest().cancelVoiceRequest(false, new com.baidu.duer.dcs.api.IVoiceRequestListener() { @Override public void onSucceed() { dcsSdk.getVoiceRequest().beginVoiceRequest(vad); } });
-
结束语音请求:IVoiceRequest.endVoiceRequest。
当结束语音流请求时,向DuerOS发送该请求,并等待等待DuerOS返回结果。
dcsSdk.getVoiceRequest().endVoiceRequest(new IVoiceRequestListener() { @Override public void onSucceed() { } });
-
取消语音识别请求:IVoiceRequest.cancelVoiceRequest。
dcsSdk.getVoiceRequest().cancelVoiceRequest(true, new IVoiceRequestListener() { @Override public void onSucceed() { Log.d(TAG, "cancelVoiceRequest onSucceed"); } });
说明:
- 如果enable为true,DuerOS会进行语音尾点检测(说话结束),不需要调用endVoiceRequest()。
- 如果想提前结束语音识别,调用endVoiceRequest()或者cancelVoiceRequest()。
- 结束语音请求endVoiceRequest()和取消语音请求cancelVoiceRequest()的区别在于,前者有返回度秘结果,如语音结果的播报,后者没有。
- 发送语音请求beginVoiceRequest()不能重复调用,必须等在结果返回或者调用endVoiceRequest()、cancelVoiceRequest()调用的回调返回后才能继续调用。
会话状态回调监听
发送一次语音请求可以理解为一次对话。一次正常的语音交互状态流程为
IDLE
-->LISTENING
-->THINKING
-->SPEAKING
-->IDLE
。SDK提供了监听对话流程状态接口,让接入方监听整个会话的过程。开发者可以根据不同的会话状态进行界面相关的展示。通过addDialogStateListener接口可以监听对话流程中的状态,通过removeDialogStateListener接口取消监听对话流程中的状态。
dcsSdk.getVoiceRequest().addDialogStateListener(IDialogStateListener dialogStateListener);
dcsSdk.getVoiceRequest().removeDialogStateListener(IDialogStateListener dialogStateListener);
离线语音识别
使用离线语音识别功能时,首先需要进行以下配置。
-
设置离线资源文件绝对路径及离线识别模型数据库。
AsrParam.ASR_OFFLINE_ENGINE_DAT_FILE_PATH = Environment.getExternalStorageDirectory() + "/libbd_model_easr_dat.so";
-
设置离线标点形式,有以下三种配置。
- 0:表示不设置标点,缺省配置是0。
- 1:表示设置标点,句尾的标点是逗号。
- 2:表示设置标点,句尾的标点是句号。
AsrParam.ASR_OFFLINE_PUNCTUATION_SETTING_VALUE = 1;
-
设置为离线模式。
AsrParam.ASR_DECODER = 1; asrMode = DcsConfig.ASR_MODE_OFFLINE asrOnly = true
-
设置离线license,离线license没有设置,第一次离线识别需要通过联网进行在线鉴权。
AsrParam.ASR_OFFLINE_ENGINE_LICENSE_FILE_PATH = "assets://temp_wakeup_license";
接下来,进行离线识别功能的设置。离线功能实现过程与在线功能一样,也需要进行取消语音识别请求、发送语音请求、结束语音请求等,离线识别也支持会话状态回调监听功能。这里不再详述。
唤醒功能
SDK提供了本地唤醒的功能,该功能基于Kitt Snowboy的KittWakeUpImpl实现的,默认唤醒词为【小度小度】。SDK支持唤醒监听、单进程唤醒、已有唤醒模块的接入。下面从以几点详细地介绍如何使用唤醒功能。
- 如何启动唤醒功能
- 如何配置唤醒监听功能
- 如何配置已有唤醒模块接入SDK
- 如何配置单进程唤醒
启动唤醒功能
唤醒功能需要在SDK的初始化流程中进行配置才能生效。下面介绍相关内容的配置,详细的代码实现请参考sample app工程中的SDKBaseActivity.java文件。
-
初始化WakeUp实现类和IWakeupProvider。
final BaseWakeup wakeup = new KittWakeUpImpl(); IWakeupProvider wakeupProvider = new IWakeupProvider() {...}
-
在IWakeupProvider依次进行唤醒参数的配置。
- 配置唤醒参数。包括添加多唤醒词及索引,这里的索引要求与Snowboy的唤醒模型文件一样,例如模型文件中有5个唤醒词,分别为不同语速的“小度小度”,index分别为1-5,则需要按照以下格式进行添加。
@Override public WakeUpConfig wakeUpConfig() { // 添加多唤醒词和索引 // 此处传入的index需要和Snowboy唤醒模型文件一致 List<WakeUpWord> wakeupWordList = new ArrayList<>(); wakeupWordList.add(new WakeUpWord(1, "小度小度")); wakeupWordList.add(new WakeUpWord(2, "小度小度")); wakeupWordList.add(new WakeUpWord(3, "小度小度")); wakeupWordList.add(new WakeUpWord(4, "小度小度")); wakeupWordList.add(new WakeUpWord(5, "小度小度")); final List<String> paths = new ArrayList<>(); paths.add(WAKEUP_UMDL_PATH); return new WakeUpConfig.Builder() .resPath(WAKEUP_RES_PATH) .umdlPath(paths) .sensitivity(WAKEUP_SENSITIVITY) .highSensitivity(WAKEUP_HIGH_SENSITIVITY) .wakeUpWords(wakeupWordList) .build(); }
- 配置是否播放唤醒提示音,TRUE表示播放。
@Override public boolean enableWarning() { return ENABLE_PLAY_WARNING; }
- 配置唤醒提示音资源路径。
@Override public String warningSource() { // 每次在播放唤醒提示音前调用该方法 // assets目录下的以assets://开头 // sd文件为绝对路径 return "assets://ding.wav"; }
- 配置唤醒提示音的音量大小。
@Override public float volume() { // 每次在播放唤醒提示音前调用该方法 // [0-1] return 0.8f; }
-
配置是否启用唤醒。
@Override public boolean wakeAlways() { return SDKBaseActivity.this.enableWakeUp(); }
-
配置唤醒的实现类。
@Override public BaseWakeup wakeupImpl() { return wakeup; }
- 配置播放唤醒提示音的通道。
@Override public int audioType() { // 用户 自定义类型 return AudioManager.STREAM_SYSTEM; }
- 配置唤醒参数。包括添加多唤醒词及索引,这里的索引要求与Snowboy的唤醒模型文件一样,例如模型文件中有5个唤醒词,分别为不同语速的“小度小度”,index分别为1-5,则需要按照以下格式进行添加。
-
在SDK初始化中设置唤醒参数WakeUpProvider。
dcsSdk = builder.withWakeupProvider(wakeupProvider)
-
初始化唤醒功能。
getInternalApi().initWakeUp();
唤醒回调
唤醒回调功能是在设备被唤醒时,通过添加回调接口来监听设备唤醒成功,代码示例如下。
private void initWakeUpAgentListener() {
IWakeupAgent wakeupAgent = getInternalApi().getWakeupAgent();
if (wakeupAgent != null) {
wakeupAgentListener = new IWakeupAgent.SimpleWakeUpAgentListener() {
@Override
public void onWakeupSucceed(WakeUpWord wakeUpWord) {
Toast.makeText(SDKBaseActivity.this,
"唤醒成功,唤醒词是:" + wakeUpWord.getWord(),
Toast.LENGTH_LONG).show();
}
};
wakeupAgent.addWakeupAgentListener(wakeupAgentListener);
}
}
自定义唤醒
SDK支持开发者将自己的唤醒模块接入到SDK中,为了方便接入,SDK定义了BaseWakeup
的抽象类,具体实现过程如下。
-
抽象类BaseWakeup。
public class MyWakeUpImpl extends BaseWakeup { }
- 在BaseWakeup类中进行以下操作。
- 接收语音未压缩的pcm数据。
private BaseAudioRecorder.IRecorderListener recorderListener = new BaseAudioRecorder.SimpleRecorderListener() { @Override public void onData(byte[] data) { // 这里接收语音的未压缩的pcm数据 // ... } };
- 构造函数。
public MyWakeUpImpl(BaseAudioRecorder audioRecorder) { super(); this.audioRecorder = audioRecorder; }
- 配置唤醒初始化逻辑,如唤醒资源拷贝等。
@Override public void initWakeup(WakeUpConfig wakeUpConfig) { super.initWakeup(wakeUpConfig); // 这里处理唤醒的初始化逻辑,比如唤醒资源的拷贝等 // ... }
- 开始唤醒。
@Override public void startWakeup() { // 这里处理开始唤醒的逻辑,每次识别完后调用,(如果不自定义IInteractionStrategy接口的话) // ... }
- 停止唤醒。
@Override public void stopWakeup(IStopWakeupListener stopWakeupListener) { // 这里处理停止唤醒的逻辑,每次开始识别时调用 // ... }
- 释放唤醒资源。
@Override public void release() { super.release(); // 这里处理释放资源,比如调用底层库释放资源等 // ... }
- 接收语音未压缩的pcm数据。
单进程唤醒
SDK支持进行单进程唤醒,即唤醒功能在一个独立的进程中实现,具体操作如下。
-
在AndroidManifest.xml中进行单进程配置。
<!-- KITT 唤醒 单独进程 --> <service android:name="com.baidu.duer.kitt.KittWakeUpService" android:enabled="true" android:process=":kittwakeup" />
- 在唤醒初始化时设置单进程唤醒,代码示例如下。
final BaseWakeup wakeup = new KittWakeUpServiceImpl(audioRecorder);
语音合成与播报
SDK中提供了离线语音播报(TTS)功能,可以对本地文本进行合成播报。使用InternalApi的speakOfflineQuery(String text)
进行合成播报。
扩展端能力
SDK提供了BaseDeviceModule类用于支持用户开发自定义指令。开发者实现DeviceModule后,必须调用dcsSdk.putDeviceModule(BaseDeviceModule deviceModule)启用指令。BaseDeviceModule类相关接口使用说明。
接口 | 描述 |
---|---|
public abstract void handleDirective(Directive directive) throws HandleDirectiveException; | 云端下发的指令。 |
public abstract HashMap<String, Class<?>> supportPayload(); | 注册指令对应的Payload,所有指令对应的Payload都需要注册。 |
public abstract void release(); | 释放DeviceModule相关的资源。 |
public abstract ClientContext clientContext(); | 上传端状态,若该端能力不需要上传端状态,可返回null。 |
在BaseDeviceModudle实现过程需要注意以下两点:
-
DCS协议的一个namespace对应唯一一个DeviceModule。
- 每个namespace下的一个name对应唯一一个Payload。
代码示例如下。
IMessageSender messageSender = getInternalApi().getMessageSender();
screenDeviceModule = new ScreenDeviceModule(messageSender);
screenDeviceModule.addScreenListener(screenListener);
dcsSdk.putDeviceModule(screenDeviceModule);