Linux SDK 集成手册
1. DDS 概述
DDS是一个纯云端功能的sdk,必须连接网络才能正常使用,用来和dui平台进行交互,完成识别、语义、对话、合成等功能,使用之前,先要确保在dui平台上已经正确建立产品并发布。另外,压缩包中支持Ubuntu 16.04 x86_64架构的SDK , 并且含有集成手册、开发手册、FAQ,example,除此之外,设备必须连接网络才能正常使用,用来和dui平台进行交互。其它芯片所需要输出SDK,请联系商务。
2. 创建产品
DDS SDK是DUI平台针对嵌入式设备开发的对话定制服务SDK。开发者需要到DUI平台注册开发者账号,并熟悉DUI平台技能定制与产品发布。
关联阅读
注:分支号就是接口配置中的”aliasKey”。
产品ID
请记住产品ID,详见下图:
aliasKey
发布下载页面的分支号即是“aliasKey”,如下图:
profile
授权信息,每一个设备对应一个授权profile文件,产品发布之后到 “授权管理” 标签页下载,如下图:
在开发时,调用dds_start函数,需要将profile文件中的内容作为配置参数deviceProfile的值传给dds,dds会完成后续授权相关操作
3. 头文件与接口描述
#ifndef __DDS_H__ #define __DDS_H__
#ifdef __cplusplus extern "C" { #endif
#if (!(defined DDS_CALL) || !(defined DDS_IMPORT_OR_EXPORT)) #if defined _WIN32 #if defined _WIN64 #define DDS_CALL __stdcall//windos系统和linux 系统,函数参数压栈方向不一样 #else #define DDS_CALL #endif
#ifdef DDS_IMPLEMENTION #define DDS_IMPORT_OR_EXPORT __declspec(dllexport)//类似标准ANSI c的 export方法,供被调用 #else #define DDS_IMPORT_OR_EXPORT __declspec(dllimport) #endif #if 0 #elif defined __ANDROID__ #define DDS_CALL #define DDS_IMPORT_OR_EXPORT #undef JNIEXPORT #define JNIEXPORT __attribute ((visibility("default"))) #endif #elif defined __APPLE__ #define DDS_CALL #define DDS_IMPORT_OR_EXPORT #elif defined __unix__ #define DDS_CALL #define DDS_IMPORT_OR_EXPORT __attribute ((visibility("default"))) #else #define DDS_CALL #define DDS_IMPORT_OR_EXPORT #endif #endif
#define DDS_VERSION "DDS 0.2.26" #define DDS_VERSION_NUM 226
/* callback event */ #define DDS_EV_OUT_RECORD_AUDIO 1 //dds主动索要音频数据,一般100ms一次,在回调函数中,通过DDS_EV_IN_AUDIO_STREAM把data 上报给dds #define DDS_EV_OUT_NATIVE_CALL 2 //dds 查询本地数据,通过DDS_EV_IN_NATIVE_RESPONSE 消息返回给dds server #define DDS_EV_OUT_COMMAND 3 //向客户端发送控制命令,一般为json格式字符串,比如打开台灯 #define DDS_EV_OUT_MEDIA 4 //云端返回的json格式的播放列表 #define DDS_EV_OUT_STATUS 5 //云端返回dds状态,idle,listening,understanding #define DDS_EV_OUT_TTS 6 //语音合成,返回合成的音频的url,nlg文本可选 #define DDS_EV_OUT_ERROR 7 //dds 异常 #define DDS_EV_OUT_ASR_RESULT 8 //语音识别,返回识别后的文本和拼音 #define DDS_EV_OUT_DUI_RESPONSE 9 //dui 服务返回完整结果,json格式 #define DDS_EV_OUT_DUI_LOGIN 10 //返回login注册结果,设备名 #define DDS_EV_OUT_CINFO_RESULT 11 //获得get的操作结果,成功失败,tts或词库的返回结果 #define DDS_EV_OUT_OAUTH_RESULT 12 //第三方授权结果返回 #define DDS_EV_OUT_PRODUCT_CONFIG_RESULT 13 //获取产品配置信息 #define DDS_EV_OUT_WEB_CONNECT 14 //返回和dui平台端tcp连接成功与否的消息 #define DDS_EV_OUT_DUI_DEVICENAME 15 //返回当前设备名 #define DDS_EV_OUT_REFRESH_TOKEN 23 //refresh token更新结果 #define DDS_EV_OUT_LASR_RT_RESULT 24 //长语音实时结果返回 #define DDS_EV_OUT_LASR_RT_RAW 25 //长语音实时结果(服务端返回结果) #define DDS_EV_OUT_LASR_RAW 26 //长语音文件转写结果返回 #define DDS_EV_OUT_REQUEST_ID 27 //request id生成通知 #define DDS_EV_OUT_VAD_RESULT 28 //全链路云端vad结果 #define DDS_EV_OUT_TTS_MULTIPLE 29 //tts多路复刻结果
/* external event */ #define DDS_EV_IN_SPEECH 101 //语音输入开始事件,start,end #define DDS_EV_IN_WAKEUP 102 //唤醒事件,触发读取音频 #define DDS_EV_IN_NATIVE_RESPONSE 103 #define DDS_EV_IN_RESET 104 //重置对话,该接口阻塞实现 #define DDS_EV_IN_EXIT 105 // 退出dds #define DDS_EV_IN_CUSTOM_TTS_TEXT 106 // 需要合成的中文文本 #define DDS_EV_IN_AUDIO_STREAM 107 //发送音频流到dds server #define DDS_EV_IN_PLAYER_STATUS 108 //当前播放状态,播放结束后 dds server进入listening #define DDS_EV_IN_NLU_TEXT 109 //语义请求 #define DDS_EV_IN_WAKEUP_WORD 110 //配置单个唤醒词 #define DDS_EV_IN_CINFO_OPERATE 111 //删除终端自定义配置 #define DDS_EV_IN_OAUTH_OPERATE 112 #define DDS_EV_IN_DM_INTENT 113 #define DDS_EV_IN_COMMON_WAKEUP_WORD 114 #define DDS_EV_IN_PHRASE_HINTS 115 #define DDS_EV_IN_PRODUCT_CONFIG 116 //获取dui控制台产品配置 #define DDS_EV_IN_TTS_STOP 128 //停止tts多路复刻 #define DDS_EV_IN_LASR_RT_START 129 //长语音实时识别开始接口 #define DDS_EV_IN_LASR_RT_STOP 130 //长语音实时识别结束接口 #define DDS_EV_IN_LASR_FILE_UPLOAD 131 //长语音文件转写文件分片上传接口 #define DDS_EV_IN_LASR_TASK_CREATE 132 //长语音文件转写创建识别任务接口 #define DDS_EV_IN_LASR_TASK_QUERY 133 //长语音文件转写结果查询接口
/* error id */ #define DDS_ERROR_BASE 1000 #define DDS_ERROR_FATAL (DDS_ERROR_BASE + 1) #define DDS_ERROR_TIMEOUT (DDS_ERROR_BASE + 2) #define DDS_ERROR_NETWORK (DDS_ERROR_BASE + 3) #define DDS_ERROR_SERVER (DDS_ERROR_BASE + 4) #define DDS_ERROR_LOGIC (DDS_ERROR_BASE + 5) #define DDS_ERROR_INPUT (DDS_ERROR_BASE + 6)
struct dds_msg; typedef int (*dds_ev_callback)(void *userdata, struct dds_msg *msg);//dds 回调函数,所有out 事件在此接口处理 struct dds_opt { dds_ev_callback _handler; void *userdata; };
DDS_IMPORT_OR_EXPORT int DDS_CALL dds_start(struct dds_msg *conf, struct dds_opt *opt); //dds 服务启动接口,阻塞运行,需要传送产品配置参数,pID,aliaskey,profile必选和opt操作句柄,dds服务启动后会一直阻塞等待消息 DDS_IMPORT_OR_EXPORT int DDS_CALL dds_send(struct dds_msg *msg); //发送消息给dds 服务,dds回调函数收到回复消息并处理
/* message pack or unpack */ DDS_IMPORT_OR_EXPORT struct dds_msg * DDS_CALL dds_msg_new(); //新建一个消息,并分配保存消息的内存 DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_delete(struct dds_msg *msg); //删除一个消息,并回收内存 DDS_IMPORT_OR_EXPORT void DDS_CALL dds_msg_print(struct dds_msg *msg); //打印出start消息的地址和当前消息之间的长度
DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_set_type(struct dds_msg *msg, int value); //设置消息type 类型,DDS_EV_IN_SPEECH DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_set_integer(struct dds_msg *msg, const char *key, int value); //key 变量赋值为整形value DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_set_double(struct dds_msg *msg, const char *key, double value); //key 变量赋值为double 型value DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_set_boolean(struct dds_msg *msg, const char *key, int value); //key 变量赋值为boolean value DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_set_string(struct dds_msg *msg, const char *key, const char *value); //key 变量赋值为字符串 DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_set_bin(struct dds_msg *msg, const char *key, const char *value, int value_len); //设置 audio stream 数据流给key DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_set_bin_p(struct dds_msg *msg, const char *key, const char *value, int value_len); //设置 value 指向的数据流文件给key
DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_get_type(struct dds_msg *msg, int *value); DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_get_integer(struct dds_msg *msg, const char *key, int *value); DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_get_double(struct dds_msg *msg, const char *key, double *value); DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_get_boolean(struct dds_msg *msg, const char *key, int *value); DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_get_string(struct dds_msg *msg, const char *key, char **value); DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_get_bin(struct dds_msg *msg, const char *key, char **value, int *value_len); DDS_IMPORT_OR_EXPORT int DDS_CALL dds_msg_get_bin_p(struct dds_msg *msg, const char *key, char **value, int *value_len);
#ifdef __cplusplus } #endif #endif |
3.1 接口描述
dds包含两个功能接口和一系列消息接口
- 功能接口:dds_start和dds_send,其中dds_start用于启动dds服务,dds_send用于发送消息。注意:dds_send接口是线程安全的,可以在不同的线程中调用,但是不要在dds的回调函数中调用
- 消息接口:以dds_msg_xxx开头的函数,它们用于组合不同的消息类型,通过dds_send接口发送出去
3.2 输入和输出描述
dds在调用dds_start的时候会注册一个回调函数,在回调中返回各种消息,消息的类型定义是以DDS_EV_OUT_开头的宏
dds通过dds_send发送各种消息,驱动dds工作,dds支持的输入消息类型是以DDS_EV_IN_开头的宏
4. 启动与授权
dds_start用于启动dds服务,它是一个阻塞接口,里面是一个死循环,用于接收dds_send发送的消息,并进行处理;所以该接口要运行在一个独立的线程里面。
4.1 全链路产品启动与授权
示例代码如下
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include "dds.h"
static int dds_ev_ccb(void *userdata, struct dds_msg *msg) {//事件回调函数,dds云端返回的响应都会回调此函数 int type; if (!dds_msg_get_type(msg, &type)) {//获得消息对应的事件类型 switch (type) { case DDS_EV_OUT_DUI_LOGIN: { printf("\n"); char *value; if (!dds_msg_get_string(msg, "error", &value)) {//如果云端login失败,打印出错误码 printf("ERROR: %s\n", value); } else if (!dds_msg_get_string(msg, "deviceName", &value)) {//如果无法获得错误码,打印设备名字,表示授权成功 printf("deviceName: %s\n", value); } break; } default: break; } } return 0; }
void *_run(void *arg) {//dds 线程创建成功的回调函数 struct dds_msg *msg = dds_msg_new();//新建一个dds msg,设置对应的productId,aliasKey,savedProfile,等配置信息 dds_msg_set_string(msg, "productId", "123456"); dds_msg_set_string(msg, "aliasKey", "prod"); dds_msg_set_string(msg, "savedProfile", "./xxx.profile"); dds_msg_set_string(msg, "productKey", "1234567"); dds_msg_set_string(msg, "productSecret", "abcdefg"); dds_msg_set_string(msg, "devInfo", "{\"deviceName\": \"12345678\", \"platform\": \"linux\"}");
struct dds_opt opt; opt._handler = dds_ev_ccb;//ev事件处理的函数 opt.userdata = arg;//数据配置指针,一指向创建线程的时候传入的数据指针 dds_start(msg, &opt);//各种配置配置完毕后,开始启动dds,创建dds服务需要的环境,设置参数等。 dds_msg_delete(msg); return NULL; }
int main(int argc, char **argv) { struct dds_msg *msg = NULL; pthread_t tid;//创建一个线程的 id pthread_create(&tid, NULL, _run, NULL);//创建一个线程,创建完毕后立即执行回调_run
/*do something*/ sleep(5);
msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_EXIT); dds_send(msg); dds_msg_delete(msg); pthread_join(tid, NULL);//阻塞方式等待子线程执行结束,回收线程资源 return 0; } |
4.2 全链路产品全双工模式启动与授权
示例代码如下
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include "dds.h"
static int dds_ev_ccb(void *userdata, struct dds_msg *msg) {//事件回调函数,dds云端返回的响应都会回调此函数 int type; if (!dds_msg_get_type(msg, &type)) {//获得消息对应的事件类型 switch (type) { case DDS_EV_OUT_DUI_LOGIN: { printf("\n"); char *value; if (!dds_msg_get_string(msg, "error", &value)) {//如果云端login失败,打印出错误码 printf("ERROR: %s\n", value); } else if (!dds_msg_get_string(msg, "deviceName", &value)) {//如果无法获得错误码,打印设备名字,表示授权成功 printf("deviceName: %s\n", value); } break; } default: break; } } return 0; }
void *_run(void *arg) {//dds 线程创建成功的回调函数 struct dds_msg *msg = dds_msg_new();//新建一个dds msg,设置对应的productId,aliasKey,savedProfile,等配置信息 dds_msg_set_string(msg, "productId", "123456"); dds_msg_set_string(msg, "aliasKey", "prod"); dds_msg_set_string(msg, "savedProfile", "./xxx.profile"); dds_msg_set_string(msg, "productKey", "1234567"); dds_msg_set_string(msg, "productSecret", "abcdefg"); dds_msg_set_string(msg, "devInfo", "{\"deviceName\": \"12345678\", \"platform\": \"linux\"}"); dds_msg_set_boolean(msg, "fullDuplex", 1); //配置为全双工模式
struct dds_opt opt; opt._handler = dds_ev_ccb;//ev事件处理的函数 opt.userdata = arg;//数据配置指针,一指向创建线程的时候传入的数据指针 dds_start(msg, &opt);//各种配置配置完毕后,开始启动dds,创建dds服务需要的环境,设置参数等。 dds_msg_delete(msg); return NULL; }
int main(int argc, char **argv) { struct dds_msg *msg = NULL; pthread_t tid;//创建一个线程的 id pthread_create(&tid, NULL, _run, NULL);//创建一个线程,创建完毕后立即执行回调_run /*do something*/ sleep(5);
msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_EXIT); dds_send(msg); dds_msg_delete(msg); pthread_join(tid, NULL);//阻塞方式等待子线程执行结束,回收线程资源 return 0; } |
正如上面的示例,dds_start的时候需要传入两个参数:
- struct dds_msg
该参数用于设置SDK的一些配置信息,主要包含产品配置和授权配置
产品配置:
参数名称 | 是否必选 | 描述 |
productId |
必选 | 产品id,是在dui平台上新建的产品 |
asrRes | 语音识别产品必选 | 识别模型,取值可为“aihome”、"aicomm"、"aicar"、"airobot" |
aliasKey | 全链路产品必选 |
产品分支,即产品发布的分支,取值为"test"或"prod",单项基础技术产品不可选。 |
授权配置:针对不同的使用场景,dds支持两种授权方式,两种授权方式的比较如下表
授权方式 |
离线预烧录授权 |
在线productKey授权 |
使用场景 |
没有唯一id的设备,比如MCU |
有唯一id的设备,比如mac地址 |
使用方法 |
产线给每台设备烧录一个profile文件 |
设备启动时,上传唯一id,获取profile |
获取profile |
DUI平台上申请 |
设备运行时在线下载 |
配置项 |
savedProfile:profile文件的路径 |
productKey:DUI平台上生成的产品Key productSecret:DUI平台上生成的产品Secret savedProfile:下载的profile文件的保存路径和文件名 devInfo:JSON格式,其中deviceName为设备唯一id |
- struct dds_opt
该参数用于注册回调,传递需要透传的用户指针,dds的所有输出都通过该回调返回,并透传userdata
5. 功能点
5.1 全链路
全链路产品,包含识别、语义、对话、资源搜索等功能,输入为语音或文本或状态,输出为对话结果,对话结果包含webapi、command、native api这几种,服务返回的结果以json格式,通过DDS_EV_OUT_DUI_RESPONSE消息,回调给用户。
5.1.1 语音输入
语音输入,一般配合本地vad使用,分为三个阶段:
1、vad检测到开始说话,发送DDS_EV_IN_SPEECH start消息//本地的vad 检测到语音输入后,开始发送DDS_EV_IN_SPEECH start 消息通知dds server接受数据
2、vad抛出音频,通过DDS_EV_IN_AUDIO_STREAM,给dds发送音频数据//dds 开始发送vad处理后的数据
3、vad检测到说话结束,发送DDS_EV_IN_SPEECH end消息 // 说话结束消息
struct dds_msg *msg = NULL; msg = dds_msg_new();//新建 一个消息 dds_msg_set_type(msg, DDS_EV_IN_SPEECH);//一般是有vad 发送此消息, dds_msg_set_string(msg, "action", "start");//vad 检测到 开始说话, dds_send(msg);//通知 给dds server 开始说话了 dds_msg_delete(msg);//销毁此消息,释放内存 msg = NULL;
FILE *f = fopen(wav_name, "rb");//以二进制打开一个可读文件,该文件必须存在 if(f==NULL){ printf("cant open file ,err!"); return -1; } fseek(f, 44, SEEK_SET);//跳转到文件开始第45字节处,wav文件 前面44字节保存了音频配置相关信息 char data[3200];//16bit 16k采样率,3200个字节=100ms*2Byte*1channel*16000 int len; struct dds_msg *m; while (1) { len = fread(data, 1, sizeof(data), f); if (len <= 0) break; m = dds_msg_new(); dds_msg_set_type(m, DDS_EV_IN_AUDIO_STREAM);//通知dds server 拿数据 dds_msg_set_bin(m, "audio", data, len); dds_send(m); dds_msg_delete(m); usleep(100000);//因为fread 读取很快,所以等100ms 下次读,启动下次读取数据 } fclose(f);
/*告知DDS结束语音*/ msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_SPEECH); dds_msg_set_string(msg, "action", "end");//会话结束,发送end 消息 dds_send(msg); dds_msg_delete(msg); msg = NULL; |
5.1.2 文本输入
文本输入,即通过一段文本来触发请求,绕过识别,直接走语义、对话、资源请求部分
struct dds_msg *msg = NULL; msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_NLU_TEXT); dds_msg_set_string(msg, "text", "苏州今天天气怎么样"); dds_send(msg); dds_msg_delete(msg); msg = NULL; |
5.1.3 dm intent输入
dm intent是绕过识别、语义、对话,直接请求技能
struct dds_msg *msg = NULL; msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_DM_INTENT); dds_msg_set_string(msg, "skill", "dds的example");//该技能必须在dui上创建 dds_msg_set_string(msg, "task", "车载"); dds_msg_set_string(msg, "intent", "油量不足"); dds_msg_set_string(msg, "slots", "{\"油量值\":\"4\"}"); dds_send(msg); dds_msg_delete(msg); |
5.1.4 native api输入
该输入是在服务触发native api输出的场景下使用,客户端执行相应的动作,并将执行结果上报给dui
struct dds_msg *retmsg = dds_msg_new(); dds_msg_set_type(retmsg, DDS_EV_IN_NATIVE_RESPONSE); dds_msg_set_string(retmsg, "temperature", "35"); dds_send(retmsg); dds_msg_delete(retmsg); retmsg = NULL |
以上几种输入的输出结果是一致的,都会通过回调返回;输入请求发送完成之后,dds会等待dui平台返回结果,如果15没有结果返回,会报超时错误。
5.1.5 结果输出
dui的输出统一通过回调中的DDS_EV_OUT_DUI_RESPONSE消息返回
static int dds_ev_ccb(void *userdata, struct dds_msg *msg) {//dui 回调函数 int type; if (!dds_msg_get_type(msg, &type)) { switch (type) { case DDS_EV_OUT_DUI_RESPONSE: { char *resp = NULL; if (!dds_msg_get_string(msg, "response", &resp)) { printf("\n%s\n", resp); } break; } case DDS_EV_OUT_ERROR: { char *value; if (!dds_msg_get_string(msg, "error", &value)) { printf("DDS_EV_OUT_ERROR: %s\n", value); } break; } default: break; } } return 0; } |
1) web api输出示例
web api输出,一般输出speakurl,即回复一段合成音,一般用于查询类使用场景,比如:天气、百科,如果是音乐查询,还会返回资源列表
天气返回json
{ "dm": {//对话结果 "intentName": "天气",//意图名字,关于天气的 "intentId": "5b860fcbcdd7ba00016b5ec1",// "widget": { "recommendations": ["苏州天气好不好", "你好明天的天气怎么样", "看一下深圳的天气预报"], "url": "https:\/\/apis.dui.ai\/webhook\/tq\/?city=苏州&lon=120.585279&lat=31.299732",//返回内容的web url 地址 "extra": {//附加信息,由webhook或者localhook透传 "wind": "西北风,3级", "temperature": "-2~4℃", "city": "苏州", "weather": "多云转阴" }, "duiWidget": "web",//dui控件类型,web控件 "name": "default",//描述控件的名称,可以编译,默认default "widgetName": "default", "type": "web"//控件类型,web控件 }, "nlg": "今天苏州的天气是多云转阴-2~4℃西北风,3级。", "task": "天气", "status": 0, "taskId": "5b860fcbcdd7ba00016b5ea3", "shouldEndSession": false }, "skillId": "2017120200000013",//dui 服务器端 技能 id "recordId": "38416974bc7848d1acba45e676b5fa82",//标识一个客户端请求id,默认有dds server 生成,若dds server 请求没有 recordid,生成一个新的 "contextId": "7bddda4196aa4b88a75afbe2947d4916",//标识dispatcher与一个skill之间的上下文,ddsserver保存dispatcher返回的contextId与sessionId, 并返回给客户端, 客户端第二次请求时可以带上contextId与sessionId "sessionId": "7bddda4196aa4b88a75afbe2947d4916", "speakUrl": "https:\/\/dds.dui.ai\/runtime\/v1\/cache\/38416974bc7848d1acba45e676b5fa82"//音频播放id,一般为mp3格式 } |
音乐返回json
{ "dm":{ "intentName":"播放音乐", //客户意图 "intentId":"5c09664a951088000116586a", "widget":{ "count":18, "content":[ { "linkUrl":"http://47.98.45.59/298918.mp3", "title":"百年孤寂", "subTitle":"王菲", "extra":{ "resType":"mp3", "source":0 }, "label":"", "imageUrl":"" }, { "linkUrl":"http://47.98.36.22/528478901.mp3", "title":"无问西东", "subTitle":"王菲", "extra":{ "resType":"mp3", "source":0 }, "label":"", "imageUrl":"" }, { "linkUrl":"http://47.98.45.59/299445.mp3", "title":"流年", "subTitle":"王菲", "extra":{ "resType":"mp3", "source":0 }, "label":"", "imageUrl":"" } ], "name":"music", "widgetName":"music", "itemsPerPage":5, //每页5个音乐 "totalPages":4, ////总共4页 "duiWidget":"media",//媒体控件类型,返回音乐资源列表列表 "dataSource":"api", "currentPage":1, //当前页 "type":"media" }, "nlg":"好听的音乐,马上就来。为您播放王菲的百年孤寂", "task":"音乐", "status":1, "taskId":"5c09664a9510880001165845", "shouldEndSession":true }, "skillId":"2018112200000035", "recordId":"8849c8fa758b4250954313651fbd8805", "contextId":"3ed70560a230477889bb9ee6f5b43891", "sessionId":"3ed70560a230477889bb9ee6f5b43891", "speakUrl":"https://dds.dui.ai/runtime/v1/cache/8849c8fa758b4250954313651fbd8805"
} |
2) command输出示例
command一般的使用场景为,控制设备执行一个动作,比如:暂停、播放等指令
command 输出json
{ "dm": { "intentName": "暂停播放",//cmd 意图,暂停播放 "intentId": "5c096c50951088000116666c", "runSequence": "nlgFirst",//先播放nlg,比如下面的好的,即将暂停,然后在执行command里面的暂停方法 "widget": { "duiWidget": "text",//文本控件 "name": "default", "widgetName": "default", "type": "text" }, "command": { "api": "mediacontrol.pause"//???应该是native本地暂停播放方法 }, "nlg": "好的,即将暂停", "task": "播放控制",//任务,暂停播放 "status": 1, "taskId": "5c096c509510880001166660", "shouldEndSession": true }, "skillId": "2018111600000003", "recordId": "67c555d4bd3e4a51965b19a6a7e6f738", "contextId": "e3388149a1904c9db969fe8382c742e3", "sessionId": "e3388149a1904c9db969fe8382c742e3", "speakUrl": "https:\/\/dds.dui.ai\/runtime\/v1\/cache\/67c555d4bd3e4a51965b19a6a7e6f738" } |
3) native api输出示例
native api一般用于让设备,执行一个动作,并上报执行结果,dui形成一个合成音下发给设备
native api json
{ "dm": { "intentName": "下一个",//主要意图,然后dds会返回下一首歌歌名url,播放完歌名后,执行本地api切换到这首歌曲 "param": { "duiWidget": "text", "intent": "下一个" }, "intentId": "5c096c509510880001166670", "api": "mediacontrol.next",//本地接口 "task": "播放控制", "runSequence": "nlgFirst",// "dataFrom": "native", "status": 0, "taskId": "5c096c509510880001166660", "shouldEndSession": false }, "skillId": "2018111600000003", "recordId": "3c851ea51e2a49bb8e45d2fa5b09a54a", "contextId": "495b99dacebe403481ccf3884780dce9", "sessionId": "495b99dacebe403481ccf3884780dce9" } |
5.2 语音识别
语音识别,即只支持识别功能,支持单句识别和多句识别
5.2.1 语音输入
struct dds_msg *msg = NULL; /*告知DDS开始语音*/ msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_SPEECH); dds_msg_set_string(msg, "action", "start"); dds_msg_set_string(msg, "aiType", "asr"); dds_send(msg); dds_msg_delete(msg); msg = NULL;
FILE *f = fopen(wav_name, "rb"); fseek(f, 44, SEEK_SET); char data[3200]; int len; struct dds_msg *m; while (1) { len = fread(data, 1, sizeof(data), f); if (len <= 0) break; m = dds_msg_new(); dds_msg_set_type(m, DDS_EV_IN_AUDIO_STREAM); dds_msg_set_bin(m, "audio", data, len); dds_send(m); dds_msg_delete(m); usleep(100000); } fclose(f);
/*告知DDS结束语音*/ msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_SPEECH); dds_msg_set_string(msg, "action", "end"); dds_send(msg); dds_msg_delete(msg); msg = NULL; |
5.2.2 识别结果输出
static int dds_ev_ccb(void *userdata, struct dds_msg *msg) { int type; if (!dds_msg_get_type(msg, &type)) { switch (type) { case DDS_EV_OUT_ERROR: { char *value; if (!dds_msg_get_string(msg, "error", &value)) { printf("DDS_EV_OUT_ERROR: %s\n", value); } break; } case DDS_EV_OUT_ASR_RESULT: { char *value; if (!dds_msg_get_string(msg, "var", &value)) { printf("var: %s\n", value);//识别的中间结果 } if (!dds_msg_get_string(msg, "text", &value)) { printf("text: %s\n", value);//识别的最终结果 } if (!dds_msg_get_string(msg, "pinyin", &value)) { printf("pinyin: %s\n", value);//识别结果的拼音 } break; } default: break; } } return 0; } |
5.3 语音合成
语音合成用于将用户的合成文本转化成音频,输入文本,输出url。
5.3.1 合成文本输入
msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_CUSTOM_TTS_TEXT); dds_msg_set_string(msg, "text", "今天天气非常好");//需要合成的文本 dds_msg_set_string(msg, "voiceId", "zhilingfa");//发音人,必选 dds_send(msg); dds_msg_delete(msg); msg = NULL; |
5.3.2 合成结果输出
static int dds_ev_ccb(void *userdata, struct dds_msg *msg) { int type; if (!dds_msg_get_type(msg, &type)) { switch (type) { case DDS_EV_OUT_ERROR: { char *value; if (!dds_msg_get_string(msg, "error", &value)) { printf("DDS_EV_OUT_ERROR: %s\n", value); } break; } case DDS_EV_OUT_TTS: { char *value; if (!dds_msg_get_string(msg, "speakUrl", &value)) { printf("speakUrl: %s\n", value);//合成的结果以url的形式返回 is_get_tts_url = 1; } if (!dds_msg_get_string(msg, "nlg", &value)) { printf("nlg: %s\n", value); } break; } default: break; } } return 0; } |
5.4 全双工
全双工将用户的音频全部实时上传到dui服务,本地不做vad检测,云端实时检测有效语音,完成识别、语义、对话,并实时下发识对话结果,与全链路配置相比,dds启动时只要多加一个配置,就可以设置为全双工模式
5.4.1 语音输入
与全链路相比,音频输入,start之后,一直feed,不需要feed的时候,stop之后, 再reset引擎,结束对话
struct dds_msg *msg = NULL; msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_SPEECH); dds_msg_set_string(msg, "action", "start"); dds_send(msg); dds_msg_delete(msg); msg = NULL;
FILE *f = fopen(wav_name, "rb");//以二进制打开一个可读文件,该文件必须存在 if(f==NULL){ printf("cant open file ,err!"); return -1; } fseek(f, 44, SEEK_SET);//跳转到文件开始第45字节处,wav文件 前面44字节保存了音频配置相关信息 char data[3200];//16bit 16k采样率,3200个字节=100ms*2Byte*1channel*16000 int len; struct dds_msg *m; while (1) { len = fread(data, 1, sizeof(data), f); if (len <= 0) break; m = dds_msg_new(); dds_msg_set_type(m, DDS_EV_IN_AUDIO_STREAM);//通知dds server 拿数据 dds_msg_set_bin(m, "audio", data, len); dds_send(m); dds_msg_delete(m); usleep(100000);//因为fread 读取很快,所以等100ms 下次读,启动下次读取数据 } fclose(f);
//不需要上传音频的时候,告知DDS结束语音, 然后reset引擎 msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_SPEECH); dds_msg_set_string(msg, "action", "end"); dds_send(msg); dds_msg_delete(msg); msg = NULL;
msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_RESET); dds_send(msg); dds_msg_delete(msg); msg = NULL; |
5.4.2 结果返回
全双工和全链路一样,都从DDS_EV_OUT_DUI_RESPONSE事件中返回json结果,json中需要关注的字段及其含义如下
//dm.rt.output //返回的json里面,有topic字段,并且值为dm.rt.output //实时中间结果,主要关注dm.command.api字段 { "dm":{ "command":{ //全双工中间返回结果。interrupt:打断TTS; backchannel:中间接话; discardResponse:输入内容语义置信度低,服务端过滤改回复。 "api":"interrupt" }, "status":0, "runSequence":"commandFirst", "shouldEndSession":false }, "topic":"dm.rt.output", //dm.rt.output,表示改消息为全双工对话中的中间反馈消息。 "contextId":"5b82ff0f9e4448c891a282d64dcf2500", "recordList":{ //客户端实现和云端对齐策略的参数,beginTime和endTime表示改消息对应的一次会话的音频的时间偏移。 "beginTime":1380, "endTime":3000, "expireTime":2000, "recordIdList":[{ "beginTime":1380, "recordId":"b917dd4c0fcc4fb69c2a10a25abbae12", "endTime":0 }] }, "sessionId":"5b82ff0f9e4448c891a282d64dcf2500", "requestId":"5b49fc73b81f4944b71dc63c48780629" }
//dm.output //没有topic字段,正常对话结果 { "dm": {//对话结果 "intentName": "天气",//意图名字,关于天气的 "intentId": "5b860fcbcdd7ba00016b5ec1",// "widget": { "url": "https:\/\/apis.dui.ai\/webhook\/tq\/?city=苏州&lon=120.585279&lat=31.299732",//返回内容的web url 地址 "duiWidget": "web",//dui控件类型,web控件 "name": "default",//描述控件的名称,可以编译,默认default "widgetName": "default", "type": "web"//控件类型,web控件 }, "nlg": "今天苏州的天气是多云转阴-2~4℃西北风,3级。", "task": "天气", "status": 0, "taskId": "5b860fcbcdd7ba00016b5ea3", "shouldEndSession": false, //该字段为true,表示全双工对话结束,停止向云端送音频;开始对话需要重新唤醒开启识别。 "simulateHalfDuplex": true, //模拟半双工。在全双工模式下,客户端收到该字段,在播放TTS时暂停向云端送音频数据,实现全双工链路模拟半双工模式的效果。 "endSkillDm": true, 。 }, "skillId": "2017120200000013",//dui 服务器端 技能 id "recordId": "38416974bc7848d1acba45e676b5fa82",//标识一个客户端请求id,默认有dds server 生成,若dds server 请求没有 recordid,生成一个新的 "contextId": "7bddda4196aa4b88a75afbe2947d4916",//标识dispatcher与一个skill之间的上下文,ddsserver保存dispatcher返回的contextId与sessionId, 并返回给客户端, 客户端第二次请求时可以带上contextId与sessionId "sessionId": "7bddda4196aa4b88a75afbe2947d4916", "speakUrl": "https:\/\/dds.dui.ai\/runtime\/v1\/cache\/38416974bc7848d1acba45e676b5fa82"//音频播放id,一般为mp3格式 } |
5.5 TTS复刻
tts多路复刻接口
5.5.1 合成文本输入
msg = dds_msg_new(); dds_msg_set_type(msg, DDS_EV_IN_CUSTOM_TTS_TEXT); dds_msg_set_string(msg, "text", "关于使用细节,必须指定复刻标志位,因为普通合成服务和复刻差别较大,无法复用全部流程。"); dds_msg_set_string(msg, "voiceId", "770f0bf98014404a92f25875625e0915"); dds_msg_set_integer(msg, "volume", 91); dds_msg_set_double(msg, "speed", 1.0); dds_msg_set_integer(msg, "voiceCopy", 1); dds_send(msg); dds_msg_delete(msg); |
5.5.2 合成结果输出
static int dds_ev_ccb(void* userdata, struct dds_msg* msg) { int type; if (!dds_msg_get_type(msg, &type)) { case DDS_EV_OUT_TTS_MULTIPLE: { char *value = 0, *reqID; if (!dds_msg_get_string(msg, "requestId", &reqID)) { printf("requestId: %s\n", reqID); } if (!dds_msg_get_bin(msg, "stream", &value, &len)) { printf("stream len: %d\n", len); if(len == 0){ //TODO::合成结束 }else{ //TODO::stream数据指针,长度len } } break; } default: break; } } return 0; } |
6. 错误描述
dds的错误统一在回调中返回,消息类型是DDS_EV_OUT_ERROR编译宏,具体的错误码如下
6.1 DDS_ERROR_FATAL
致命错误,出现该错误,一般表示dds客户端处于不可用状态,原因是因为授权未通过
6.2 DDS_ERROR_TIMEOUT
超时错误,表示未在指定时间内(15秒)返回对话结果
6.3 DDS_ERROR_NETWORK
网络错误,在网络连接、数据传输、websocket 握手,过程中出错;具体错误原因,在error字段中描述,error的常见描述如下
1) tcp connect error ,网络连接失败
2)auth failed, websocket授权失败
3)bad request,websocket 参数错误
4)network error,网络异常断开
6.4 DDS_ERROR_SERVER
服务返回的结果异常,非json格式或缺少字段
7. 日志调试开关
dds_msg_set_boolean(msg, "logEnable", 1); dds_msg_set_string(msg, "logFile", "./dds.log"); |