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

说明:

  1. 如果设备上的Android系统是5.0及以上版本时,因为Android系统自带分包功能,所以不用进行分包操作。
  2. 如果你的工程中还有其他的分包设置,请与DCS SDK的分包配置放在一起。
  3. 请确保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控制台上完成授权功能相应的配置。

  1. 在设备的基础信息中,点击OAUTH CONFIG URL地址,会跳转到授权回调页。

  2. 在授权回调页,点击安全设置。

  3. 在安全设置中添加授权回调页 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提供的以下接口实现在线识别功能。

  1. 发送语音请求:IVoiceRequest.beginVoiceRequest

    发送语音识别请求,表示需要进行语音识别。该接口在cancelVoiceRequest接口的回调中实现的,代码示例如下。

    // 必须先调用cancel
    dcsSdk.getVoiceRequest().cancelVoiceRequest(false, new com.baidu.duer.dcs.api.IVoiceRequestListener() {
        @Override
        public void onSucceed() {
            dcsSdk.getVoiceRequest().beginVoiceRequest(vad);
        }
    });
  2. 结束语音请求:IVoiceRequest.endVoiceRequest

    当结束语音流请求时,向DuerOS发送该请求,并等待等待DuerOS返回结果。

    dcsSdk.getVoiceRequest().endVoiceRequest(new IVoiceRequestListener() {
        @Override
        public void onSucceed() {
        }
    });
  3. 取消语音识别请求:IVoiceRequest.cancelVoiceRequest

    dcsSdk.getVoiceRequest().cancelVoiceRequest(true, new IVoiceRequestListener() {
        @Override
        public void onSucceed() {
            Log.d(TAG, "cancelVoiceRequest onSucceed");
        }
    });    

说明:

  1. 如果enable为true,DuerOS会进行语音尾点检测(说话结束),不需要调用endVoiceRequest()。
  2. 如果想提前结束语音识别,调用endVoiceRequest()或者cancelVoiceRequest()。
  3. 结束语音请求endVoiceRequest()和取消语音请求cancelVoiceRequest()的区别在于,前者有返回度秘结果,如语音结果的播报,后者没有。
  4. 发送语音请求beginVoiceRequest()不能重复调用,必须等在结果返回或者调用endVoiceRequest()、cancelVoiceRequest()调用的回调返回后才能继续调用。

会话状态回调监听

发送一次语音请求可以理解为一次对话。一次正常的语音交互状态流程为 IDLE-->LISTENING-->THINKING-->SPEAKING-->IDLE。SDK提供了监听对话流程状态接口,让接入方监听整个会话的过程。开发者可以根据不同的会话状态进行界面相关的展示。通过addDialogStateListener接口可以监听对话流程中的状态,通过removeDialogStateListener接口取消监听对话流程中的状态。

dcsSdk.getVoiceRequest().addDialogStateListener(IDialogStateListener dialogStateListener);
dcsSdk.getVoiceRequest().removeDialogStateListener(IDialogStateListener dialogStateListener);

离线语音识别

使用离线语音识别功能时,首先需要进行以下配置。

  1. 设置离线资源文件绝对路径及离线识别模型数据库。

    AsrParam.ASR_OFFLINE_ENGINE_DAT_FILE_PATH = Environment.getExternalStorageDirectory() + "/libbd_model_easr_dat.so";
  2. 设置离线标点形式,有以下三种配置。

    • 0:表示不设置标点,缺省配置是0。
    • 1:表示设置标点,句尾的标点是逗号。
    • 2:表示设置标点,句尾的标点是句号。
    AsrParam.ASR_OFFLINE_PUNCTUATION_SETTING_VALUE = 1;
  3. 设置为离线模式。

    AsrParam.ASR_DECODER = 1;
    asrMode = DcsConfig.ASR_MODE_OFFLINE
    asrOnly = true
  4. 设置离线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;
      }     
  • 在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();
          // 这里处理释放资源,比如调用底层库释放资源等
          // ...
      }

单进程唤醒

SDK支持进行单进程唤醒,即唤醒功能在一个独立的进程中实现,具体操作如下。

  1. 在AndroidManifest.xml中进行单进程配置。

    <!-- KITT 唤醒 单独进程 -->
    <service
        android:name="com.baidu.duer.kitt.KittWakeUpService"
        android:enabled="true"
        android:process=":kittwakeup" />
  2. 在唤醒初始化时设置单进程唤醒,代码示例如下。
    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);