摘要:本例用的是语音识别,语义理解引擎,支持强大的用户自定义语义,能更好的解决语义理解。代表返回的行为动作,此处可以看到是就是要求播放,中的数据表示歌曲名称是三国演义。
带有android悬浮窗的语音识别语义理解demo
如发现代码排版问题,请访问CSDN博客
转载请注明CSDN博文地址:http://blog.csdn.net/ls0609/a...
在线听书demo:http://blog.csdn.net/ls0609/a...
语音记账demo:http://blog.csdn.net/ls0609/a...
Android桌面悬浮窗实现比较简单,本篇以一个语音识别,语义理解的demo来演示如何实现android悬浮窗。
1.悬浮窗效果
桌面上待机的时候,悬浮窗吸附在边上
拖动远离屏幕边缘时图标变大,松开自动跑到屏幕边缘,距离屏幕左右边缘靠近哪边吸附哪边
点击悬浮图标时,启动录音
说完后可以点击左button,上传录音给服务器等待处理返回结果
服务器返回结果后自动跳转到应用界面,本例用的是在线听书,跳转到在线听书的界面
2.FloatViewIdle与FloatViewIdleService
1.FloatViewIdle
定义一个FloatViewIdle类,如下是该类的单例模式
public static synchronized FloatViewIdle getInstance(Context context)
{
if(floatViewManager == null) { mContext = context.getApplicationContext();; winManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); displayWidth = winManager.getDefaultDisplay().getWidth(); displayHeight = winManager.getDefaultDisplay().getHeight(); floatViewManager = new FloatViewIdle(); } return floatViewManager;
}
利用winManager 的addview方法,把自定义的floatview添加到屏幕中,那么就会在任何界面显示该floatview,然后再屏蔽非待机界面隐藏floatview,这样就只有待机显示悬浮窗了。
定义两个自定义view,分别是FloatIconView和FloatRecordView,前者就是待机看到的小icon图标,后者是点击这个icon图标后展示的录音的那个界面。
下面来看下怎么定义的FloatIconView
class FloatIconView extends LinearLayout{
private int mWidth; private int mHeight; private int preX; private int preY; private int x; private int y; public boolean isMove; public boolean isMoveToEdge; private FloatViewIdle manager; public ImageView imgv_icon_left; public ImageView imgv_icon_center; public ImageView imgv_icon_right; public int mWidthSide; public FloatIconView(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)) { int temp = 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; } } public int getFloatViewWidth() { return mWidth; } public int getFloatViewHeight() { return mHeight; } @Override public boolean onTouchEvent(MotionEvent event) { switch(event.getAction()) { case MotionEvent.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; case MotionEvent.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; case MotionEvent.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; } return super.onTouchEvent(event); }
}
通过layout文件生成一个FloatIconView,在onTouchEvent函数中当按下的时候,发送消息更新悬浮view,抬起即up事件时先更新悬浮view,然后再显示吸附到边上的动画。 当move的时候,判断每次位移至少5和像素则更新view位置,这样不断move不断更新就会形成连续的画面。
另一个FloatRecordView(录音的悬浮窗)道理相同,这里就不贴代码了,有兴趣可以下载demo自己编译跑一下。
在FloatIconView中定义一个handler,用于接收消息处理悬浮窗更新位置和吸附的动画
private void initHandler(){
handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_REFRESH_VOLUME: if(floatRecordView != null) floatRecordView.updateVolume((int)msg.arg1); break; case MSG_FLOAT_VIEW_MOVE_TO_EDGE: //更新悬浮窗位置的动画 moveAnimation((View)msg.obj); break; case MSG_REMOVE_FLOAT_VIEW: if(msg.arg1 == 1) {//此时已有floatview是floatIconView if(floatIconView != null) {//先移除一个floatview winManager.removeView(floatIconView); floatIconView = null; floatRecordView = getFloatRecordView(); if(floatRecordView != null) { if(floatRecordView.getParent() == null) {//再加入一个新的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 {//此时已有floatview是floatRecordView即录音的floatview if(floatRecordView != null) {//先移除一个floatview winManager.removeView(floatRecordView); floatRecordView = null; } floatIconView = getFloatIconView(); if(floatIconView != null) { if(floatIconView.getParent() == null) {/再加入一个新的floatview winManager.addView(floatIconView, params); floatViewType = FLOAT_ICON_VIEW_TYPE; setViewOnClickListener(floatIconView); } //可能需要有吸附动画 moveAnimation(floatIconView); } } break; case MSG_UPDATE_VIEW_SENDING_TO_SERVER: if(floatRecordView != null) { floatRecordView.updateSendingToServerView(); floatRecordView.setTitle("努力识别中"); } break; case MSG_UPDATE_ROTATE_VIEW: if(floatRecordView != null) { floatRecordView.rotateview.startRotate(); } break; case MSG_UPDATE_FLOAT_VIEW_AFTER_CHANGED: //1,2是吸附到左边还是右边,3是拖动到中间显示放大的悬浮窗icon if(msg.arg1 == 1) changeFloatIconToSide(false); else if(msg.arg1 == 2) changeFloatIconToSide(true); else if(msg.arg1 == 3) changeFloatIconToNormal(); break; case MSG_UPDATE_FLOAT_VIEW_ON_SIDE: if(msg.arg1 == 1) updateFloatIconOnSide(true); else if(msg.arg1 == 2) updateFloatIconOnSide(false); break; case MSG_START_ACTIVITY: hide(); Intent intent = new Intent(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; } } };
}
那么,怎样做到点击吸附屏幕边缘的悬浮按钮,切换成录音的悬浮窗呢?
public void show()
{
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(new OnClickListener(){ @Override public void onClick(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函数中,设置了floatIconView的点击事件,移除小的悬浮吸附按钮,加入录音的悬浮窗view并启动录音。
2.FloatViewIdleService
为什么要定义这个service?
这个service用途是,定时扫描是否在待机桌面,如果是待机桌面则显示floatview,否则隐藏。
public class FloatViewIdleService extends Service {
private static Handler mHandler; private FloatViewIdle floatViewIdle; private final static int REFRESH_FLOAT_VIEW = 1; private boolean is_vertical = true; @Override public void onCreate() { super.onCreate(); initHandler(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { mHandler.sendMessageDelayed(mHandler.obtainMessage(REFRESH_FLOAT_VIEW), 500); FloatViewIdle.IS_START_FROM_FLOAT_VIEW_IDLE = false; is_vertical = true; return START_STICKY; } protected void initHandler() { mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case REFRESH_FLOAT_VIEW://1s发送一次更新floatview消息 updateFloatView(); mHandler.sendMessageDelayed( mHandler.obtainMessage(REFRESH_FLOAT_VIEW), 1000); break; } } }; } private void updateFloatView() { boolean isOnIdle = isHome();//判断是否在待机界面 floatViewIdle = FloatViewIdle.getInstance(FloatViewIdleService.this); if(isOnIdle) { //待机界面则显示floatview if(floatViewIdle.getFloatViewType() == 0) { floatViewIdle.show(); } else if(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; } } else if(this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { if(is_vertical == false) { floatViewIdle.swapWidthAndHeight(); is_vertical = true; } } } } else {//否则隐藏floatview floatViewIdle.hide(); } } private boolean isHome() { ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); Listrti = mActivityManager.getRunningTasks(1); try{ if(rti.size() == 0) { return true; }else { if(rti.get(0).topActivity.getPackageName(). equals("com.olami.floatviewdemo")) return false; else return getHomes().contains(rti.get(0).topActivity.getPackageName()); } } catch(Exception e) { return true; } } private List getHomes() { List names = new ArrayList (); PackageManager packageManager = this.getPackageManager(); Intent intent = new Intent(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); } return names; } @Override public void onDestroy() { super.onDestroy(); if(floatViewIdle != null) floatViewIdle.setFloatViewType(0); } @Override public IBinder onBind(Intent intent) { return null; }
}
3.启动语音识别
在另一个VoiceSdkService(另一个处理录音服务业务的service)中,当接收到悬浮窗按钮点击事件消息时,则启动录音服务,录音结束后会在onResult回调中收到服务器返回的结果。
本例用的是olami语音识别,语义理解引擎,olami支持强大的用户自定义语义,能更好的解决语义理解。
比如同义理解的时候,我要听三国演义,我想听三国演义,听三国演义这本书,类似的说法有很多,olmai就可以为你解决这类的语义理解,olami语音识别引擎使用比较简单,只需要简单的初始化,然后设置好回调listener,在回调的时候处理服务器返回的json字符串即可,当然语义还是要用户自己定义的。
public void init()
{
initHandler(); mOlamiVoiceRecognizer = new OlamiVoiceRecognizer(VoiceSdkService.this); TelephonyManager telephonyManager=(TelephonyManager) this.getSystemService( (this.getBaseContext().TELEPHONY_SERVICE); String imei=telephonyManager.getDeviceId(); mOlamiVoiceRecognizer.init(imei);//设置身份标识,可以填null mOlamiVoiceRecognizer.setListener(mOlamiVoiceRecognizerListener);//设置识别结果回调listener mOlamiVoiceRecognizer.setLocalization( OlamiVoiceRecognizer.LANGUAGE_SIMPLIFIED_CHINESE);//设置支持的语音类型,优先选择中文简体 mOlamiVoiceRecognizer.setAuthorization("51a4bb56ba954655a4fc834bfdc46af1", "asr","68bff251789b426896e70e888f919a6d","nli"); //注册Appkey,在olami官网注册应用后生成的appkey //注册api,请直接填写“asr”,标识语音识别类型 //注册secret,在olami官网注册应用后生成的secret //注册seq ,请填写“nli” mOlamiVoiceRecognizer.setVADTailTimeout(2000);//录音时尾音结束时间,建议填//2000ms //设置经纬度信息,不愿上传位置信息,可以填0 mOlamiVoiceRecognizer.setLatitudeAndLongitude(31.155364678184498,121.34882432933009);
在VoiceSdkService中定义OlamiVoiceRecognizerListener用于处理录音时的回调
onError(int errCode)//出错回调,可以对比官方文档错误码看是什么错误
onEndOfSpeech()//录音结束
onBeginningOfSpeech()//录音开始
onResult(String result, int type)//result是识别结果JSON字符串
onCancel()//取消识别,不会再返回识别结果
onUpdateVolume(int volume)//录音时的音量,1-12个级别大小音量
本文用的是在线听书的例子,当收到服务器返回的消息是,进入如下函数:
在下面的函数中,通过解析服务器返回的json字符串,提取用户需要的语义理解字段进行处理
private void processServiceMessage(String message)
{ String input = null; String serverMessage = null; try{ JSONObject jsonObject = new JSONObject(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; String type = null; String songName = null; String singer = 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"); String modifier = (String)jArrayModifier.opt(0); if((jArrayModifier != null) && ("play".equals(modifier))) { if(jArraySlots != null) for(int i=0,k=jArraySlots.length(); i}
以我要听三国演义这句语音,服务器返回的数据如下:
{
"data": { "asr": { "result": "我要听三国演义", "speech_status": 0, "final": true, "status": 0 }, "nli": [ { "desc_obj": { "result": "正在努力搜索中,请稍等", "status": 0 }, "semantic": [ { "app": "musiccontrol", "input": "我要听三国演义", "slots": [ { "name": "songname", "value": "三国演义" } ], "modifier": [ "play" ], "customer": "58df512384ae11f0bb7b487e" } ], "type": "musiccontrol" } ] }, "status": "ok"}
1)解析出nli中type类型是musiccontrol,这是语法返回app的类型,而这个在线听书的demo只关心musiccontrol这 个app类型,其他的忽略。
2)用户说的话转成文字是在asr中的result中获取
3)在nli中的semantic中,input值是用户说的话,同asr中的result。
modifier代表返回的行为动作,此处可以看到是play就是要求播放,slots中的数据表示歌曲名称是三国演义。
那么动作是play,内容是歌曲名称是三国演义,在这个demo中调用
mBookUtil.searchBookAndPlay(songName,0,0);会先查询,查询到结果会再发播放消息要求播放,我要听三国演义这个流程就走完了。关于在线听书请看博文:http://blog.csdn.net/ls0609/a...
4.源码下载链接
http://pan.baidu.com/s/1o8OELdC
5.相关链接
语音记账demo:http://blog.csdn.net/ls0609/a...
olami开放平台语法编写简介:http://blog.csdn.net/ls0609/a...
olami开放平台语法官方介绍:https://cn.olami.ai/wiki/?mp=...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/67617.html
摘要:可以针对笔者常用的数独本文的实现都基于该,实现数独的识别求解并把答案自动填入。专家级别的平均秒完成求解包括图像数字提取,识别过程,完成全部操作。步骤四数独求解,生成答案,并生成需要填充的数字序列。 1 序 数独是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3...
摘要:模仿的功能掘金本模仿了的功能。国内曾经出现的团购类网站有多家,到四年多以后的现在,美团已经是成为国内最大的本地生活服务平台,不管怎饿了么移动的架构演进掘金引言时代演进,技术也随之发展。 模仿 Smartisan OS 的 BigBang 功能 ??? - Android - 掘金 本 Demo 模仿了 Smartisan OS 的 BigBang 功能。App 打开会从剪切板读取文字并...
摘要:会议主要是加深开发者对的了解,从而帮助开发者做好的兼容工作。因此本篇我会选择性说明一些在上你需要兼容的一些事情。那么现在有哪些会用到这种呢举一个大家熟悉的。另外目前可以通过在清单文件设置是否启用。因此强烈建议将这个工作排上兼容行程。showImg(https://user-gold-cdn.xitu.io/2019/5/27/16af6c9ce913040d); 5 月 20 号参加了 An...
阅读 3236·2021-11-22 14:44
阅读 1085·2021-11-16 11:53
阅读 1224·2021-11-12 10:36
阅读 669·2021-10-14 09:43
阅读 3637·2019-08-30 15:55
阅读 3372·2019-08-30 14:14
阅读 1710·2019-08-26 18:37
阅读 3367·2019-08-26 12:12