帶有android懸浮窗的語(yǔ)音識(shí)別語(yǔ)義理解demo
轉(zhuǎn)載請(qǐng)注明CSDN博文地址:http://blog.csdn.net/ls0609/article/details/77162417
在線聽書demo:http://blog.csdn.net/ls0609/article/details/71519203
語(yǔ)音記賬demo:http://blog.csdn.net/ls0609/article/details/72765789
Android桌面懸浮窗實(shí)現(xiàn)比較簡(jiǎn)單萍诱,本篇以一個(gè)語(yǔ)音識(shí)別悬嗓,語(yǔ)義理解的demo來(lái)演示如何實(shí)現(xiàn)android懸浮窗。
桌面上待機(jī)的時(shí)候裕坊,懸浮窗吸附在邊上
拖動(dòng)遠(yuǎn)離屏幕邊緣時(shí)圖標(biāo)變大包竹,松開自動(dòng)跑到屏幕邊緣,距離屏幕左右邊緣靠近哪邊吸附哪邊
點(diǎn)擊懸浮圖標(biāo)時(shí)籍凝,啟動(dòng)錄音
說(shuō)完后可以點(diǎn)擊左button周瞎,上傳錄音給服務(wù)器等待處理返回結(jié)果
服務(wù)器返回結(jié)果后自動(dòng)跳轉(zhuǎn)到應(yīng)用界面,本例用的是在線聽書饵蒂,跳轉(zhuǎn)到在線聽書的界面
2.FloatViewIdle與FloatViewIdleService
1.FloatViewIdle
定義一個(gè)FloatViewIdle類声诸,如下是該類的單例模式
publicstaticsynchronizedFloatViewIdlegetInstance(Context context)
{
if(floatViewManager ==null)
{
mContext =context.getApplicationContext();;
winManager = (WindowManager)
mContext.getSystemService(Context.WINDOW_SERVICE);
displayWidth =winManager.getDefaultDisplay().getWidth();
displayHeight =winManager.getDefaultDisplay().getHeight();
floatViewManager =newFloatViewIdle();
}
returnfloatViewManager;
}
利用winManager的addview方法,把自定義的floatview添加到屏幕中退盯,那么就會(huì)在任何界面顯示該floatview彼乌,然后再屏蔽非待機(jī)界面隱藏floatview,這樣就只有待機(jī)顯示懸浮窗了渊迁。
定義兩個(gè)自定義view慰照,分別是FloatIconView和FloatRecordView,前者就是待機(jī)看到的小icon圖標(biāo)宫纬,后者是點(diǎn)擊這個(gè)icon圖標(biāo)后展示的錄音的那個(gè)界面焚挠。
下面來(lái)看下怎么定義的FloatIconView
classFloatIconView extends LinearLayout{
privateintmWidth;
privateintmHeight;
privateintpreX;
privateintpreY;
privateintx;
privateinty;
publicboolean isMove;
publicboolean isMoveToEdge;
privateFloatViewIdle manager;
publicImageView imgv_icon_left;
publicImageView imgv_icon_center;
publicImageView imgv_icon_right;
publicintmWidthSide;
publicFloatIconView(Context context) {
super(context);
View view = LayoutInflater.from(mContext).
inflate(R.layout.layout_floatview_icon,this);
LinearLayout layout_content =
(LinearLayout)view.findViewById(R.id.layout_content);
imgv_icon_left = (ImageView)view.findViewById(R.id.imgv_icon_left);
imgv_icon_center = (ImageView)view.findViewById(R.id.imgv_icon_center);
imgv_icon_right = (ImageView)view.findViewById(R.id.imgv_icon_right);
imgv_icon_left.setVisibility(View.GONE);
imgv_icon_center.setVisibility(View.GONE);
mWidth = layout_content.getWidth();
mHeight =layout_content.getHeight();
if((mWidth ==0)||(mHeight ==0))
{
inttemp = DensityUtil.dip2px(mContext,icon_width);
mHeight = temp;
icon_width_side_temp =DensityUtil.dip2px(mContext, icon_width_side);
mWidth = icon_width_side_temp;
}
manager =FloatViewIdle.getInstance(mContext);
if(params!=null)
{
params.x = displayWidth -icon_width_side_temp;
params.y = displayHeight/2;
}
}
publicintgetFloatViewWidth()
{
returnmWidth;
}
publicintgetFloatViewHeight()
{
returnmHeight;
}
@Override
publicboolean onTouchEvent(MotionEventevent)
{
switch(event.getAction())
{
caseMotionEvent.ACTION_DOWN:
preX = (int)event.getRawX();
preY = (int)event.getRawY();
isMove =false;
if(params.width == icon_width_side_temp)
handler.sendMessage(handler.obtainMessage(
MSG_UPDATE_FLOAT_VIEW_AFTER_CHANGED,3,0));
break;
caseMotionEvent.ACTION_UP:
if(isMoveToEdge ==true)
{
if(params.width == icon_width_side_temp)
handler.sendMessage(handler.obtainMessage(
MSG_UPDATE_FLOAT_VIEW_AFTER_CHANGED,3,0));
handler.sendMessage(handler.obtainMessage(
MSG_FLOAT_VIEW_MOVE_TO_EDGE,this));
}
break;
caseMotionEvent.ACTION_MOVE:
x = (int)event.getRawX();
y = (int)event.getRawY();
if(Math.abs(x-preX)>1||Math.abs(y-preY)>1)
{
isMoveToEdge =true;
}
if(Math.abs(x-preX)>5||Math.abs(y-preY)>5)
isMove =true;
if(params.width == icon_width_side_temp)
handler.sendMessage(handler.obtainMessage(
MSG_UPDATE_FLOAT_VIEW_AFTER_CHANGED,3,0));
manager.move(this, x-preX,y-preY);
preX = x;
preY = y;
break;
}
returnsuper.onTouchEvent(event);
}
}
通過(guò)layout文件生成一個(gè)FloatIconView,在onTouchEvent函數(shù)中當(dāng)按下的時(shí)候漓骚,發(fā)送消息更新懸浮view,抬起即up事件時(shí)先更新懸浮view榛泛,然后再顯示吸附到邊上的動(dòng)畫蝌蹂。當(dāng)move的時(shí)候,判斷每次位移至少5和像素則更新view位置曹锨,這樣不斷move不斷更新就會(huì)形成連續(xù)的畫面孤个。
另一個(gè)FloatRecordView(錄音的懸浮窗)道理相同,這里就不貼代碼了沛简,有興趣可以下載demo自己編譯跑一下齐鲤。
在FloatIconView中定義一個(gè)handler斥废,用于接收消息處理懸浮窗更新位置和吸附的動(dòng)畫
privatevoidinitHandler(){
handler =newHandler(){
@Override
publicvoidhandleMessage(Message msg)
{
switch(msg.what)
{
caseMSG_REFRESH_VOLUME:
if(floatRecordView
!=null)
floatRecordView.updateVolume((int)msg.arg1);
break;
caseMSG_FLOAT_VIEW_MOVE_TO_EDGE:
//更新懸浮窗位置的動(dòng)畫
moveAnimation((View)msg.obj);
break;
caseMSG_REMOVE_FLOAT_VIEW:
if(msg.arg1 ==1)
{//此時(shí)已有floatview是floatIconView
if(floatIconView
!=null)
{//先移除一個(gè)floatview
winManager.removeView(floatIconView);
floatIconView =null;
floatRecordView= getFloatRecordView();
if(floatRecordView
!=null)
{
if(floatRecordView.getParent()
==null)
{//再加入一個(gè)新的floatview
winManager.addView(floatRecordView,params);
floatViewType =FLOAT_RECORD_VIEW_TYPE;
}
if(mHandler !=null)
{
mHandler.sendMessage(mHandler.obtainMessage(
MessageConst.CLIENT_ACTION_START_CAPTURE));
IS_RECORD_FROM_FLOAT_VIEW_IDLE =true;
}
}
}
}
else
{//此時(shí)已有floatview是floatRecordView即錄音的floatview
if(floatRecordView
!=null)
{//先移除一個(gè)floatview
winManager.removeView(floatRecordView);
floatRecordView=null;
}
floatIconView =getFloatIconView();
if(floatIconView
!=null)
{
if(floatIconView.getParent()
==null)
{/再加入一個(gè)新的floatview
winManager.addView(floatIconView,params);
floatViewType =FLOAT_ICON_VIEW_TYPE;
setViewOnClickListener(floatIconView);
}
//可能需要有吸附動(dòng)畫
moveAnimation(floatIconView);
}
}
break;
caseMSG_UPDATE_VIEW_SENDING_TO_SERVER:
if(floatRecordView
!=null)
{
floatRecordView.updateSendingToServerView();
floatRecordView.setTitle("努力識(shí)別中");
}
break;
caseMSG_UPDATE_ROTATE_VIEW:
if(floatRecordView
!=null)
{
floatRecordView.rotateview.startRotate();
}
break;
caseMSG_UPDATE_FLOAT_VIEW_AFTER_CHANGED:
//1,2是吸附到左邊還是右邊给郊,3是拖動(dòng)到中間顯示放大的懸浮窗icon
if(msg.arg1 ==1)
changeFloatIconToSide(false);
elseif(msg.arg1 ==2)
changeFloatIconToSide(true);
elseif(msg.arg1 ==3)
changeFloatIconToNormal();
break;
caseMSG_UPDATE_FLOAT_VIEW_ON_SIDE:
if(msg.arg1 ==1)
updateFloatIconOnSide(true);
elseif(msg.arg1 ==2)
updateFloatIconOnSide(false);
break;
caseMSG_START_ACTIVITY:
hide();
Intent intent =newIntent(mContext,MusicActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(START_FROM_FLOAT_VIEW,true);
IS_START_FROM_FLOAT_VIEW_IDLE =true;
mContext.startActivity(intent);
break;
}
}
};
}
那么牡肉,怎樣做到點(diǎn)擊吸附屏幕邊緣的懸浮按鈕,切換成錄音的懸浮窗呢淆九?
publicvoidshow()
{
isHide =false;
floatIconView = getFloatIconView();
if(floatIconView !=null)
{
if(floatIconView.getParent() ==null)
{
winManager.addView(floatIconView,params);
floatViewType =FLOAT_ICON_VIEW_TYPE;
}
if(floatRecordView !=null)
{
handler.sendMessage(handler.obtainMessage(
MSG_REMOVE_FLOAT_VIEW,2,0));
}
floatIconView.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(View v) {
if(floatIconView.isMove ||floatIconView.isMoveToEdge)
{
floatIconView.isMove =false;
return;
}
winManager.removeView(floatIconView);
floatIconView =null;
floatRecordView =getFloatRecordView();
if(floatRecordView !=null)
{
if(floatRecordView.getParent()
==null)
{
winManager.addView(floatRecordView,params);
floatViewType =FLOAT_RECORD_VIEW_TYPE;
}
if(mHandler !=null)
{
mHandler.sendMessage(mHandler.obtainMessage(
MessageConst.CLIENT_ACTION_START_CAPTURE));
IS_RECORD_FROM_FLOAT_VIEW_IDLE =true;
}
}
}
});
}
}
在show函數(shù)中统锤,設(shè)置了floatIconView的點(diǎn)擊事件,移除小的懸浮吸附按鈕炭庙,加入錄音的懸浮窗view并啟動(dòng)錄音饲窿。
2.FloatViewIdleService
為什么要定義這個(gè)service?
這個(gè)service用途是焕蹄,定時(shí)掃描是否在待機(jī)桌面逾雄,如果是待機(jī)桌面則顯示floatview,否則隱藏腻脏。
publicclassFloatViewIdleServiceextendsService{
privatestaticHandler mHandler;
privateFloatViewIdle floatViewIdle;
privatefinalstaticintREFRESH_FLOAT_VIEW =1;
privatebooleanis_vertical =true;
@Override
publicvoidonCreate() {
super.onCreate();
initHandler();
}
@Override
publicintonStartCommand(Intent intent,intflags,intstartId) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(REFRESH_FLOAT_VIEW),500);
FloatViewIdle.IS_START_FROM_FLOAT_VIEW_IDLE =false;
is_vertical =true;
returnSTART_STICKY;
}
protectedvoidinitHandler() {
mHandler =newHandler() {
@Override
publicvoidhandleMessage(Message msg) {
switch(msg.what) {
caseREFRESH_FLOAT_VIEW://1s發(fā)送一次更新floatview消息
updateFloatView();
mHandler.sendMessageDelayed(
mHandler.obtainMessage(REFRESH_FLOAT_VIEW),1000);
break;
}
}
};
}
privatevoidupdateFloatView()
{
booleanisOnIdle = isHome();//判斷是否在待機(jī)界面
floatViewIdle =FloatViewIdle.getInstance(FloatViewIdleService.this);
if(isOnIdle)
{//待機(jī)界面則顯示floatview
if(floatViewIdle.getFloatViewType() ==0)
{
floatViewIdle.show();
}
elseif(floatViewIdle.getFloatViewType() ==
floatViewIdle.FLOAT_ICON_VIEW_TYPE||
floatViewIdle.getFloatViewType() ==
floatViewIdle.FLOAT_RECORD_VIEW_TYPE)
{
if(this.getResources().getConfiguration().orientation==
Configuration.ORIENTATION_LANDSCAPE)
{
if(is_vertical ==true)
{
floatViewIdle.swapWidthAndHeight();
is_vertical =false;
}
}
elseif(this.getResources().getConfiguration().orientation==
Configuration.ORIENTATION_PORTRAIT)
{
if(is_vertical ==false)
{
floatViewIdle.swapWidthAndHeight();
is_vertical =true;
}
}
}
}
else
{//否則隱藏floatview
floatViewIdle.hide();
}
}
privatebooleanisHome()
{
ActivityManager mActivityManager =(ActivityManager)
getSystemService(Context.ACTIVITY_SERVICE);
List rti =mActivityManager.getRunningTasks(1);
try{
if(rti.size() ==0)
{
returntrue;
}else
{
if(rti.get(0).topActivity.getPackageName().
equals("com.olami.floatviewdemo"))
returnfalse;
else
returngetHomes().contains(rti.get(0).topActivity.getPackageName());
}
}
catch(Exception e)
{
returntrue;
}
}
privateList getHomes()
{
List names =newArrayList();
PackageManager packageManager =this.getPackageManager();
Intent intent =newIntent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List resolveInfo =packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
for(ResolveInfo ri : resolveInfo) {
names.add(ri.activityInfo.packageName);
}
returnnames;
}
@Override
publicvoidonDestroy() {
super.onDestroy();
if(floatViewIdle !=null)
floatViewIdle.setFloatViewType(0);
}
@Override
publicIBinder onBind(Intent intent) {
returnnull;
}
}
3.啟動(dòng)語(yǔ)音識(shí)別
在另一個(gè)VoiceSdkService(另一個(gè)處理錄音服務(wù)業(yè)務(wù)的service)中鸦泳,當(dāng)接收到懸浮窗按鈕點(diǎn)擊事件消息時(shí),則啟動(dòng)錄音服務(wù)迹卢,錄音結(jié)束后會(huì)在onResult回調(diào)中收到服務(wù)器返回的結(jié)果辽故。
本例用的是olami語(yǔ)音識(shí)別,語(yǔ)義理解引擎腐碱,olami支持強(qiáng)大的用戶自定義語(yǔ)義誊垢,能更好的解決語(yǔ)義理解。
比如同義理解的時(shí)候症见,我要聽三國(guó)演義喂走,我想聽三國(guó)演義,聽三國(guó)演義這本書谋作,類似的說(shuō)法有很多芋肠,olmai就可以為你解決這類的語(yǔ)義理解,olami語(yǔ)音識(shí)別引擎使用比較簡(jiǎn)單遵蚜,只需要簡(jiǎn)單的初始化帖池,然后設(shè)置好回調(diào)listener,在回調(diào)的時(shí)候處理服務(wù)器返回的json字符串即可吭净,當(dāng)然語(yǔ)義還是要用戶自己定義的睡汹。
publicvoidinit()
{
initHandler();
mOlamiVoiceRecognizer =newOlamiVoiceRecognizer(VoiceSdkService.this);
TelephonyManagertelephonyManager=(TelephonyManager)this.getSystemService(
(this.getBaseContext().TELEPHONY_SERVICE);
String imei=telephonyManager.getDeviceId();
mOlamiVoiceRecognizer.init(imei);//設(shè)置身份標(biāo)識(shí),可以填null
mOlamiVoiceRecognizer.setListener(mOlamiVoiceRecognizerListener);//設(shè)置識(shí)別結(jié)果回調(diào)listener
mOlamiVoiceRecognizer.setLocalization(
OlamiVoiceRecognizer.LANGUAGE_SIMPLIFIED_CHINESE);//設(shè)置支持的語(yǔ)音類型寂殉,優(yōu)先選擇中文簡(jiǎn)體
mOlamiVoiceRecognizer.setAuthorization("51a4bb56ba954655a4fc834bfdc46af1",
"asr","68bff251789b426896e70e888f919a6d","nli");
//注冊(cè)Appkey囚巴,在olami官網(wǎng)注冊(cè)應(yīng)用后生成的appkey
//注冊(cè)api,請(qǐng)直接填寫“asr”,標(biāo)識(shí)語(yǔ)音識(shí)別類型
//注冊(cè)secret彤叉,在olami官網(wǎng)注冊(cè)應(yīng)用后生成的secret
//注冊(cè)seq庶柿,請(qǐng)?zhí)顚憽皀li”
mOlamiVoiceRecognizer.setVADTailTimeout(2000);//錄音時(shí)尾音結(jié)束時(shí)間,建議填//2000ms
//設(shè)置經(jīng)緯度信息秽浇,不愿上傳位置信息浮庐,可以填0
mOlamiVoiceRecognizer.setLatitudeAndLongitude(31.155364678184498,121.34882432933009);
在VoiceSdkService中定義OlamiVoiceRecognizerListener用于處理錄音時(shí)的回調(diào)
onError(int errCode)//出錯(cuò)回調(diào),可以對(duì)比官方文檔錯(cuò)誤碼看是什么錯(cuò)誤
onEndOfSpeech()//錄音結(jié)束
onBeginningOfSpeech()//錄音開始
onResult(String result, int type)//result是識(shí)別結(jié)果JSON字符串
onCancel()//取消識(shí)別兼呵,不會(huì)再返回識(shí)別結(jié)果
onUpdateVolume(int volume)//錄音時(shí)的音量兔辅,1-12個(gè)級(jí)別大小音量
本文用的是在線聽書的例子,當(dāng)收到服務(wù)器返回的消息是击喂,進(jìn)入如下函數(shù):
在下面的函數(shù)中维苔,通過(guò)解析服務(wù)器返回的json字符串,提取用戶需要的語(yǔ)義理解字段進(jìn)行處理
privatevoidprocessServiceMessage(Stringmessage)
{
Stringinput=null;
StringserverMessage=null;
try{
JSONObject jsonObject=newJSONObject(message);
JSONArray jArrayNli=jsonObject.optJSONObject("data").optJSONArray("nli");
JSONObject jObj=jArrayNli.optJSONObject(0);
JSONArray jArraySemantic=null;
if(message.contains("semantic"))
jArraySemantic=jObj.getJSONArray("semantic");
else{
input=jsonObject.optJSONObject("data").optJSONObject("asr").
optString("result");
sendMessageToActivity(MessageConst.
CLIENT_ACTION_UPDATA_INPUT_TEXT,0,0,null, input);
serverMessage=jObj.optJSONObject("desc_obj").opt("result").toString();
sendMessageToActivity(MessageConst.
CLIENT_ACTION_UPDATA_SERVER_MESSAGE,0,0,null,serverMessage);
return;
}
JSONObject jObjSemantic;
JSONArray jArraySlots;
JSONArray jArrayModifier;
Stringtype=null;
StringsongName=null;
Stringsinger=null;
if(jObj!=null) {
type=jObj.optString("type");
if("musiccontrol".equals(type))
{
jObjSemantic=jArraySemantic.optJSONObject(0);
input=jObjSemantic.optString("input");
jArraySlots=jObjSemantic.optJSONArray("slots");
jArrayModifier=jObjSemantic.optJSONArray("modifier");
Stringmodifier=(String)jArrayModifier.opt(0);
if((jArrayModifier!=null)&&("play".equals(modifier)))
{
if(jArraySlots!=null)
for(int i=0,k=jArraySlots.length(); i
{
JSONObject obj=jArraySlots.getJSONObject(i);
Stringname=obj.optString("name");
if("singer".equals(name))
singer=obj.optString("value");
elseif("songname".equals(name))
songName=obj.optString("value");
}
}elseif((modifier!=null)&&("stop".equals(modifier)))
{
if(mBookUtil!=null)
if(mBookUtil.isPlaying())
mBookUtil.stop();
}elseif((modifier!=null)&&("pause".equals(modifier)))
{
if(mBookUtil!=null)
if(mBookUtil.isPlaying())
mBookUtil.pause();
}elseif((modifier!=null)&&("resume_play".equals(modifier)))
{
if(mBookUtil!=null)
mBookUtil.resumePlay();
}elseif((modifier!=null)&&("add_volume".equals(modifier)))
{
if(mBookUtil!=null)
mBookUtil.addVolume();
}elseif((modifier!=null)&&("del_volume".equals(modifier)))
{
if(mBookUtil!=null)
mBookUtil.delVolume();
}elseif((modifier!=null)&&("next".equals(modifier)))
{
if(mBookUtil!=null)
mBookUtil.next();
}elseif((modifier!=null)&&("previous".equals(modifier)))
{
if(mBookUtil!=null)
mBookUtil.prev();
}elseif((modifier!=null)&&("play_index".equals(modifier)))
{
int position=0;
if(jArraySlots!=null)
for(int i=0,k=jArraySlots.length(); i
{
JSONObjectobj=jArraySlots.getJSONObject(i);
JSONObjectjNumDetial=obj.getJSONObject("num_detail");
Stringindex=jNumDetial.optString("recommend_value");
position=Integer.parseInt(index)-1;
}
if(mBookUtil!=null)
mBookUtil.skipTo(position);
}
}
}
if(songName!=null)
{
if(singer!=null)
{
}else{
mBookUtil.searchBookAndPlay(songName,0,0);
}
}elseif(singer!=null)
{
mBookUtil.searchBookAndPlay(songName,0,0);
}
serverMessage=jObj.optJSONObject("desc_obj").opt("result").toString();
}
catch (Exception e)
{
e.printStackTrace();
}
//發(fā)送消息更新語(yǔ)音識(shí)別的文字
sendMessageToActivity(MessageConst.CLIENT_ACTION_UPDATA_INPUT_TEXT,0,0,null, input);
//發(fā)送消息更新服務(wù)器返回的結(jié)果字符串
sendMessageToActivity(MessageConst.CLIENT_ACTION_UPDATA_SERVER_MESSAGE,
0,0,null,serverMessage);
}
以我要聽三國(guó)演義這句語(yǔ)音懂昂,服務(wù)器返回的數(shù)據(jù)如下:
{
"data": {
"asr": {
"result":"我要聽三國(guó)演義",
"speech_status":0,
"final":true,
"status":0
},
"nli": [
{
"desc_obj": {
"result":"正在努力搜索中介时,請(qǐng)稍等",
"status":0
},
"semantic": [
{
"app":"musiccontrol",
"input":"我要聽三國(guó)演義",
"slots": [
{
"name":"songname",
"value":"三國(guó)演義"
}
],
"modifier": [
"play"
],
"customer":"58df512384ae11f0bb7b487e"
}
],
"type":"musiccontrol"
}
]
},
"status":"ok"
}
1)解析出nli中type類型是musiccontrol,這是語(yǔ)法返回app的類型,而這個(gè)在線聽書的demo只關(guān)心musiccontrol這個(gè)app類型凌彬,其他的忽略沸柔。
2)用戶說(shuō)的話轉(zhuǎn)成文字是在asr中的result中獲取
3)在nli中的semantic中,input值是用戶說(shuō)的話铲敛,同asr中的result褐澎。
modifier代表返回的行為動(dòng)作,此處可以看到是play就是要求播放伐蒋,slots中的數(shù)據(jù)表示歌曲名稱是三國(guó)演義工三。
那么動(dòng)作是play,內(nèi)容是歌曲名稱是三國(guó)演義先鱼,在這個(gè)demo中調(diào)用
mBookUtil.searchBookAndPlay(songName,0,0);會(huì)先查詢俭正,查詢到結(jié)果會(huì)再發(fā)播放消息要求播放,我要聽三國(guó)演義這個(gè)流程就走完了焙畔。
關(guān)于在線聽書請(qǐng)看博文:http://blog.csdn.net/ls0609/article/details/71519203
http://pan.baidu.com/s/1o8OELdC
語(yǔ)音記賬demo:http://blog.csdn.net/ls0609/article/details/72765789
olami開放平臺(tái)語(yǔ)法編寫簡(jiǎn)介:http://blog.csdn.net/ls0609/article/details/71624340
olami開放平臺(tái)語(yǔ)法官方介紹:https://cn.olami.ai/wiki/?mp=nli&content=nli2.html