HarmonyOS Next SDK

本文介绍了如何使用阿里云智能语音服务提供的HarmonyOS Next  NUI SDK,包括SDK下载安装、关键接口及代码示例。

前提条件

下载安装

  1. 下载harmony.zip

    重要

    请下载后在样例初始化代码中替换您的阿里云账号信息、Appkey和Token才可运行。

    类别

    兼容范围

    系统

    支持HarmonyOS Next 5.0 版本,API LEVEL 12, DevEco Studio版本号5.0.3.403

    架构

    arm64-v8a

    此SDK还包含如下功能:

    功能

    是否支持

    一句话识别

    实时语音识别

    语音合成

    实时长文本语音合成

    流式文本语音合成

    离线语音合成

    录音文件识别极速版

    唤醒及命令词

    听悟实时推流

  2. 以arkts HAR包的形式进行集成。解压ZIP包,其中nuisdk-release/neonui.har 是SDK生成的HAR包文件,在用户工程项目中导入调用即可。如果需要HarmonyOS Next CPP接入方式,可在ZIP包的harmonyos_libs和harmonyos_include中获得动态库和头文件。

  3. 使用DevEco Studio打开harmony_nlsdemo目录下工程查看参考代码实现,其中一句话识别示例代码为SpeechTranscriber.ets文件,替换UserKey.ets中 UserKey类的Appkey和Token后,即可直接运行。

SDK关键接口

initialize:初始化SDK。

/**
* 初始化SDK,SDK可多实例,请先释放后再次进行初始化。请勿在UI线程调用,可能会引起阻塞。
* @param callback:事件监听回调,参见下文具体回调。
* @param parameters:json string形式的初始化参数,参见下方说明或接口说明:https://help.aliyun.com/document_detail/173298.html。
* @param level:log打印级别,值越小打印越多。
* @param save_log:是否保存log为文件,存储目录为ticket中的debug_path字段值。注意,log文件无上限,请注意持续存储导致磁盘存满。
* @return:参见错误码:https://help.aliyun.com/document_detail/459864.html。
*/
public initialize(callback:INativeNuiCallback ,
			parameters:string ,
			level:number ,
			save_log:boolean=false ):number

其中,INativeNuiCallback接口类型包含如下回调。

  • onNuiAudioStateChanged:根据音频状态进行录音功能的开关。

    /**
     * 当start/stop/cancel等接口调用时,SDK通过此回调通知App进行录音的开关操作。
     * @param state:录音需要的状态(打开/停止/关闭)
     */
    onNuiAudioStateChanged:(state:Constants.AudioState)=>void
  • onNuiNeedAudioData:在回调中提供音频数据。注意:由于ArkTS中异步接口调用频繁,因此建议不使用此回调提供录音数据,用户应通过主动调用updateAudio()顺序地给SDK中传入录音数据。

    /**
     * 开始识别时,此回调被连续调用,App需要在回调中进行语音数据填充。
     * @param buffer:填充语音的存储区。
     * @return:实际填充的字节数。
     */
    
    onNuiNeedAudioData:(buffer:ArrayBuffer)=>number;
  • onNuiEventCallback:SDK事件回调。

    /**
     * SDK主要事件回调
     * @param event:回调事件,参见如下事件列表。
     * @param resultCode:参见错误码,在出现EVENT_ASR_ERROR事件时有效。
     * @param arg2:保留参数。
     * @param kwsResult:语音唤醒功能(暂不支持)。
     * @param asrResult:语音识别结果。
     */
    onNuiEventCallback:(event:Constants.NuiEvent, resultCode:number, arg2:number, kwsResult:KwsResult,
    							asrResult:AsrResult)=>void;
  • onNuiAudioRMSChanged:音频能量值回调。

    /**
     * 音频能量值回调
     * @param val: 音频数据能量值回调,范围-160至0,一般用于UI展示语音动效
     */
    onNuiAudioRMSChanged:(val:number)=>number;

    事件列表:

    名称

    说明

    EVENT_VAD_START

    检测到人声起点。

    EVENT_VAD_END

    检测到人声尾点。

    EVENT_ASR_PARTIAL_RESULT

    语音识别中间结果。

    EVENT_ASR_ERROR

    根据错误码信息判断出错原因。

    EVENT_MIC_ERROR

    录音错误,表示SDK连续2秒未收到任何音频,可检查录音系统是否正常。

    EVENT_SENTENCE_START

    实时语音识别事件,表示检测到一句话开始。

    EVENT_SENTENCE_END

    实时语音识别事件,表示检测到一句话结束,返回一句完整的结果。

    EVENT_SENTENCE_SEMANTICS

    暂不使用。

    EVENT_TRANSCRIBER_COMPLETE

    停止语音识别后最终事件。

  • setParams:以JSON格式设置SDK参数。

    /**
     * 以JSON格式设置参数
     * @param params:参见接口说明:https://help.aliyun.com/document_detail/173298.html。
     * @return:参见错误码:https://help.aliyun.com/document_detail/459864.html。
     */
    public setParams(params:string):number
  • startDialog:开始识别。

    /**
     * 开始识别
     * @param vad_mode:多种模式,对于识别场景,请使用P2T。
     * @param dialog_params:json string形式的对话参数,参见接口说明:https://help.aliyun.com/document_detail/173298.html。
     * @return:参见错误码:https://help.aliyun.com/document_detail/459864.html。
     */
    public startDialog(vad_mode:Constants.VadMode, dialog_params:string):number
  • stopDialog:结束识别。

    /**
     * 结束识别,调用该接口后,服务端将返回最终识别结果并结束任务。
     * @return:参见错误码:https://help.aliyun.com/document_detail/459864.html。
     */
    public stopDialog():number
  • cancelDialog:立即结束识别。

    /**
     * 立即结束识别,调用该接口后,不等待服务端返回最终识别结果就立即结束任务。
     * @return:参见错误码:https://help.aliyun.com/document_detail/459864.html。
     */
    public cancelDialog():number
  • release:释放SDK。

    /**
     * 释放SDK资源
     * @return:参见错误码:https://help.aliyun.com/document_detail/459864.html。
     */
    public release():number
  • GetVersion:获得当前SDK版本信息。

    /**
     * 获得当前SDK版本信息
     * @return: 字符串形式的SDK版本信息
     */
    public GetVersion():string

调用步骤

  1. 创建SDK类对象实例

  2. 初始化SDK、录音实例。

  3. 根据业务需求设置参数。

  4. 调用startDialog开始识别。

  5. 根据音频状态回调onNuiAudioStateChanged中的事件,打开录音机。

  6. 调用 updateAudio()提供录音数据给SDK。

  7. 在EVENT_SENTENCE_START事件回调中表示当前开始识别一个句子,在EVENT_ASR_PARTIAL_RESULT事件回调中获取识别中间结果,在EVENT_SENTENCE_END事件回调中获得这句话完整的识别结果和各相关信息。

  8. 调用stopDialog结束识别。并从EVENT_TRANSCRIBER_COMPLETE事件回调确认已停止识别。

  9. 结束调用,使用release接口释放SDK资源。

代码示例

说明

您如果有多个需求,也可以直接new对象进行使用。也可采用GetInstance获得单例。

NUI SDK初始化

//定义类NativeNuiCallbackHandle 实现回调接口INativeNuiCallback
class NativeNuiCallbackHandle implements INativeNuiCallback{
  //内部实现INativeNuiCallback中的5个接口函数
  //此处省略
}

let context = getContext(this) as common.UIAbilityContext;
this.filesDir = context.filesDir;
this.resourceDir = context.resourceDir;

//这里获得资源路径, 由于资源文件存放在工程的resfiles目录下,所以使用沙箱路径下的resfiles目录
let asset_path:string = this.resourceDir+"/resources_cloud"
//由于用户无法直接操作设备目录,因此调试路径设置为APP所在的沙箱路径下的公共目录filesDir
let debug_path:string = this.filesDir

//初始化SDK,注意用户需要在genInitParams中填入相关ID信息才可以使用。
cbhandle:NativeNuiCallbackHandle = new NativeNuiCallbackHandle()
g_asrinstance:NativeNui = new NativeNui(Constants.ModeType.MODE_DIALOG, "asr")
let ret:number = this.g_asrinstance.initialize(this.cbhandle, this.genInitParams(asset_path,debug_path), Constants.LogLevel.LOG_LEVEL_VERBOSE, false);
console.info("result = " + ret);
if (ret == Constants.NuiResultCode.SUCCESS) {
  console.error(`call g_asrinstance.initialize() return success`);
} else {
  //抛出错误异常信息。
  console.error(`call g_asrinstance.initialize() return error:${ret}`);
}

其中,genInitParams生成String JSON字符串,包含资源目录和用户信息。其中用户信息包含如下字段。

genInitParams(workpath:string, debugpath:string):string {
    let str:string = "";
    //获取token方式:

    //使用Map类型实现JSON格式的数据存储。用户也可以使用自有的JSON实现。
    let object:Map<string, string|number|boolean|object> = new Map();

    //账号和项目创建
    //  ak_id ak_secret app_key如何获得,请查看https://help.aliyun.com/document_detail/72138.html
    object.set("app_key", "用户自己的app_key"); // 必填

    //方法1:
    //  首先ak_id ak_secret app_key如何获得,请查看https://help.aliyun.com/document_detail/72138.html
    //  然后请看 https://help.aliyun.com/document_detail/466615.html 使用其中方案一获取临时凭证
    //  此方案简介: 远端服务器生成具有有效时限的临时凭证, 下发给移动端进行使用, 保证账号信息ak_id和ak_secret不被泄露
    //  获得Token方法(运行在APP服务端): https://help.aliyun.com/document_detail/450255.html?spm=a2c4g.72153.0.0.79176297EyBj4k
    object.set("token", "用户自己的token"); // 必填

    //方法2:
    //  STS获取临时凭证方法暂不支持

    //方法3:(强烈不推荐,存在阿里云账号泄露风险)
    //  参考Auth类的实现在端上访问阿里云Token服务获取SDK进行获取。请勿将ak/sk存在本地或端侧环境。
    //  此方法优点: 端侧获得Token, 无需搭建APP服务器。
    //  此方法缺点: 端侧获得ak/sk账号信息, 极易泄露。
    //            JSONObject object = Auth.getAliYunTicket();

    object.set("device_id", "用户设备所具有的唯一ID信息"); // 必填, 推荐填入具有唯一性的id, 方便定位问题
    object.set("url", "wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1"); // 默认
    object.set("workspace", workpath); // 必填, 且需要有读写权限

    //当初始化SDK时的save_log参数取值为true时,该参数生效。表示是否保存音频debug,该数据保存在debug目录中,需要确保debug_path有效可写。
    //            object.put("save_wav", "true");
    //debug目录,当初始化SDK时的save_log参数取值为true时,该目录用于保存中间音频文件。
    object.set("debug_path", debugpath);

    // FullMix = 0   // 选用此模式开启本地功能并需要进行鉴权注册
    // FullCloud = 1
    // FullLocal = 2 // 选用此模式开启本地功能并需要进行鉴权注册
    // AsrMix = 3    // 选用此模式开启本地功能并需要进行鉴权注册
    // AsrCloud = 4
    // AsrLocal = 5  // 选用此模式开启本地功能并需要进行鉴权注册
    //一句话识别
    console.log("init asr for 实时语音识别")
    object.set("service_mode", Constants.ModeFullCloud); // 必填。 此处是实时语音识别功能与一句话识别功能配置3个差异之1

    str = MapToJson(object)  //JSON格式转为字符串

    console.info("configinfo genInitParams:" + str);
    return str;
  }
function MapToJson(map:Map<string, string|number|boolean|object>):string {
  let obj:object = Object({});
  map.forEach( (value, key) => {
    obj[key] = value;
  });
  return JSON.stringify(obj)
}

参数设置

以JSON字符串形式进行设置。

//设置相关识别参数,具体参考API文档
//  initialize()之后startDialog之前调用
nui_instance.setParams(genParams());
genParams():string {
    let params:string = "";
    let nls_config:Map<string, string|number|boolean|object> = new Map();
    nls_config.set("enable_intermediate_result", true);


    // 参数可根据实际业务进行配置
    // 接口说明可见https://help.aliyun.com/document_detail/173528.html
    // 查看 2.开始识别

    //  此处是实时语音识别功能与一句话识别功能配置3个差异之3,无需设置vad相关参数

    nls_config.set("enable_punctuation_prediction", true);
    nls_config.set("enable_inverse_text_normalization", true);
    // nls_config.set("customization_id", "test_id");
    // nls_config.set("vocabulary_id", "test_id");
    // nls_config.put("enable_words", false);
    // nls_config.set("sample_rate", 16000);
    // nls_config.set("sr_format", "opus");

    let parameters:Map<string, string|number|boolean|object> = new Map();
    parameters.set("nls_config", Object( JSON.parse(MapToJson(nls_config)) ) );
    
    //一句话识别
    console.log("start asr for 实时语音识别")
    parameters.set("service_type", Constants.kServiceTypeSpeechTranscriber); // 必填  此处是实时语音识别功能与一句话识别功能配置3个差异之2

    params = MapToJson(parameters);//parameters.toString();
    console.log("configinfo genParams" + params)
    return params;
  }

开始识别

通过startDialog接口开启监听。

//默认使用Constants.VadMode.TYPE_P2T。
//Constants.VadMode.TYPE_VAD只在具有离线功能的SDK中支持,若想要启动VAD,请设置参数enable_voice_detection。
nui_instance.startDialog(Constants.VadMode.TYPE_P2T, genDialogParams());

genDialogParams():string {
    let params:string = "";
    let dialog_param:Map<string, string|number|boolean|object> = new Map();
    // 运行过程中可以在startDialog时更新临时参数,尤其是更新过期token
    // 注意: 若下一轮对话不再设置参数,则继续使用初始化时传入的参数
    //            dialog_param.put("app_key", "");
    //            dialog_param.put("token", "");
    params = MapToJson(dialog_param);

    console.info("configinfo dialog params: " + params);
    return params;
  }

推送录音数据

  • updateAudio:在AudioCapturer的on('readData',)注册的回调函数中,直接调用updateAudio接口把录音数据送入SDK内部。

    //g_asrinstance.updateAudio(buffer,false)
    /*AudioCapturer中注册的'readData'接口是AudioCapturer.readDataCallback
     *AudioCapturer.audioCapturer.on('readData', AudioCapturer.readDataCallback);
    */
    class AudioCapturer{
      static  readDataCallback = (buffer: ArrayBuffer) => {
        console.log(`${TAG} read data bytelength is ${buffer.byteLength}. uid[${process.uid}] pid[${process.pid}] tid[${process.tid}]`);
        AudioCapturer.g_asrinstance.updateAudio(buffer,false)
      }
    }

回调处理

  • onNuiAudioStateChanged:录音状态回调,SDK内部维护录音状态,调用时根据该状态的回调进行录音机的开关操作。

    /*
    对于鸿蒙开发环境IDE版本5.0.3.403 以前的版本,AudioCapturer模块如果使用注册回调[on("readData",)]的方式读取录音数据,
    存在AudioCapturer.stop后直接start不会触发回调的情况。此时必须按照 (stop,realease)再(createAudioCapturer,start)的流程才能正常工作。
    升级为 IDE版本5.0.3.403版本后,以上问题已经解决。所以以下示例代码中注释掉了create/release相关接口的调用。
    */
    onNuiAudioStateChanged(state:Constants.AudioState):void {
        console.info(`womx onUsrNuiAudioStateChanged(${state})`)
        if (state === Constants.AudioState.STATE_OPEN){
          console.info(`womx onUsrNuiAudioStateChanged(${state}) audio recorder start`)
          //AudioCapturer.init(g_asrinstance)
          AudioCapturer.start()
          console.info(`womx onUsrNuiAudioStateChanged(${state}) audio recorder start done`)
        } else if (state === Constants.AudioState.STATE_CLOSE){
          console.info(`womx onUsrNuiAudioStateChanged(${state}) audio recorder close`)
          AudioCapturer.stop()
          //AudioCapturer.release()
          console.info(`womx onUsrNuiAudioStateChanged(${state}) audio recorder close done`)
        } else if (state === Constants.AudioState.STATE_PAUSE){
          console.info(`womx onUsrNuiAudioStateChanged(${state}) audio recorder pause`)
          AudioCapturer.stop()
          //AudioCapturer.release()
          console.info(`womx onUsrNuiAudioStateChanged(${state}) audio recorder pause done`)
        }
      }
  • onNuiNeedAudioData:录音数据回调,在该回调中填充录音数据。

    public int onNuiNeedAudioData(byte[] buffer, int len) {
      console.info(`warning,this callback should not be called in HarmonyOS Next`)
      return 0;
    }
  • onNuiEventCallback:NUI SDK事件回调,请勿在事件回调中调用SDK的接口,可能引起死锁。

    onNuiEventCallback(event:Constants.NuiEvent, resultCode:number, arg2:number, kwsResult:KwsResult,
        asrResult:AsrResult):void {
        console.log("onUsrNuiEventCallback event is " + event);
        // asrResult包含task_id,task_id有助于排查问题,请用户进行记录保存。
        //
        // 新版本新增asrResult.allResponse,若为非nullptr和非空,则给出json格式字符串的完整信息。
        if (event == Constants.NuiEvent.EVENT_TRANSCRIBER_COMPLETE) {
            // 例如展示识别结果
            showText(asrView, asrResult.asrResult);
        } else if (event == Constants.NuiEvent.EVENT_ASR_PARTIAL_RESULT || event === Constants.NuiEvent.EVENT_SENTENCE_END) {   
            if (event === Constants.NuiEvent.EVENT_ASR_PARTIAL_RESULT ) {
              // 例如展示当前句子的识别中间结果
              this.message = "EVENT_ASR_PARTIAL_RESULT"
            } else if(event === Constants.NuiEvent.EVENT_SENTENCE_END){
              // 例如展示当前句子的完整识别结果
              this.message = "EVENT_SENTENCE_END"
            }
            showText(asrView, asrResult.asrResult);
        } else if (event == Constants.NuiEvent.EVENT_ASR_ERROR) {
            // asrResult在EVENT_ASR_ERROR中为错误信息,搭配错误码resultCode和其中的task_id更易排查问题,请用户进行记录保存。
        } else if (event == Constants.NuiEvent.EVENT_MIC_ERROR) {
            // EVENT_MIC_ERROR表示2s未传入音频数据,请检查录音相关代码、权限或录音模块是否被其他应用占用。
        } else if (event == Constants.NuiEvent.EVENT_DIALOG_EX) { /* unused */
            // 此事件可不用关注
        }
    
        //解析asr识别结果
        if (asrResult) {
          let asrinfo:string = ""
          asrinfo = asrResult.asrResult
          if (asrinfo) {
            try {
              let asrresult_json:object|null = JSON.parse(asrResult.asrResult)
              if (asrresult_json) {
                let payload:object|null = asrresult_json["payload"];
                if (payload) {
                  //console.log(JSON.stringify(payload))
                  let asrmessage:string =  payload["result"]; //解析到云端返回的识别结果
                }
              }
            } catch (e){
              console.error("got asrinfo not json, so donot fresh asrinfo." + JSON.stringify(e))
            }
          }
        }
    }

结束识别

nui_instance.stopDialog();

释放SDK

nui_instance.release();

常见问题

SDK是否可以上传OPUS音频数据,实现实时语音转文字?

ASR中一句话识别和录音文件极速版支持OPUS数据,实时语音转文字仅支持PCM编码、16 bit采样位数、单声道(mono)。具体详情请参见接口说明

针对送录音数据到SDK内部这一功能,如果已经保证了多线程数据共享的加锁保护工作,是否可以使用回调函数而不使用用户主动updateAudio()推送数据的方式?

可以。如果能够通过共享数据、加锁保证录音数据生产、消费之间的有序无误,可以使用SDK回调的方式传输录音数据到SDK内部。 此时需要修改资源文件nui.json中 nui_config里的 "enable_callback_recording" 为true即可。

实时语音识别和一句话识别的调用区别有哪些?

  1. 实时语音识别和一句话识别的调用区别主要有三个:

    1. 在初始化参数中的"service_mode"字段【见函数genInitParams(...)】,实时语音识别是 Constants.ModeFullCloud, 一句话识别的是 Constants.ModeAsrCloud

    2. 在startDialog前 setParams()参数中的"service_type"字段【见函数genParams()】, 实时语音识别是Constants.kServiceTypeSpeechTranscriber, 一句话识别的是 Constants.kServiceTypeASR

    3. 在startDialog前 setParams()参数中【见函数genParams()】,一句话识别需要按需设置是否开启云端VAD功能以及设置VAD相关参数,实时语音识别保持默认即可。

  2. 在回调事件分类上,对识别结果相关事件,一句话识别关注 EVENT_ASR_PARTIAL_RESULT 和 EVENT_ASR_RESULT, 实时语音识别需要关注 EVENT_ASR_PARTIAL_RESULT、EVENT_SENTENCE_END、EVENT_TRANSCRIBER_COMPLETE。