Android原生SpeechRecognizer(语音识别)
开篇先吐槽下,在Android 平台开发原生的SpeechRecognizer真是难受的,不像ios,无比轻松,平台统一。 由于Android 平台的碎片化问题比较严重,各个厂商都有自己的实现,尤其是语音助手出来以后,每家的语音服务肯定是不一样的。
目前Android原生的SpeechRecognizer做法应该有两种
- 默认调用原生SpeechRecognizer,并稍作修改
- 调用第三方,科大讯飞,百度等
这两种做法中
- 1. 在Google原生系统是可以的,但是在国内的环境是需要修改,修改后能保证各个机型基本可以用,至于识别效果就要看各个机型自己实现的怎么样了
- 2. 最简单省心省力,如果你的项目可以这么做,那么兄弟恭喜你,你是最幸福的
这里我们不讲第三方的,大家可以自己去集成第三方sdk,主要讨论原生的开发
首先权限不要忘记(记得6.0以后动态请求权限)
复制代码
在SpeechRecognizer.class有这样SpeechRecognizer .isRecognitionAvailable一个方法
/** * Checks whether a speech recognition service is available on the system. If this method * returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will * fail. * * @param context with which {@code SpeechRecognizer} will be created * @return {@code true} if recognition is available, {@code false} otherwise */ public static boolean isRecognitionAvailable(final Context context) { final List list = context.getPackageManager().queryIntentServices( new Intent(RecognitionService.SERVICE_INTERFACE), 0); return list != null && list.size() != 0; }复制代码
该方法在使用语音识别前建议要调用下,该方法是检查当前系统有没有语音识别服务,我相信绝大多数厂商都有这个服务,但是都有自己特别的实现,但是它至少有,有就可以用。但是,你像oppo的7.0以后机器,这个方法调用后就是false,这时候就是毫无办法了。oppo 7.0以后就是这样调用完后返回false,对于 oppo 这种情况,可以在手机上装一个**讯飞语音+**的app,语音识别就可以了,但是这种方法我估计没人会用,用户体验太差。
如果该方法返回false在我们调用*SpeechRecognizer.startListening();*方法的时候会日志中发现这行log
no selected voice recognition service复制代码
该日志在SpeechRecognizer.startListening(final Intent recognizerIntent)方法中,大家可以进源码查看这里就不贴了。
检查完如果语音识别可用,接下来有两种做法我们一个个来
- 直接创建实例启动服务
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);mSpeechRecognizer.setRecognitionListener(this);复制代码
创建识别实例,并添加监听,这里没有什么问题,在监听中我们可以拿到我们的想要的回调
/** * Used for receiving notifications from the SpeechRecognizer when the * recognition related events occur. All the callbacks are executed on the * Application main thread. * 值的注意的是,所有的回调都在主线程 */public interface RecognitionListener { // 实例准备就绪 void onReadyForSpeech(Bundle params); // 开始语音识别 void onBeginningOfSpeech(); // 聆听分贝值 可能会有负数哦 void onRmsChanged(float rmsdB); void onBufferReceived(byte[] buffer); // 识别结束 void onEndOfSpeech(); // 错误码 void onError(int error); // 识别的结果,在某些国产机上,这个结果会是空 void onResults(Bundle results); // 识别的部分结果 有些过程机上 [onResults] 方法为空,可以在这里拿到结果 void onPartialResults(Bundle partialResults); void onEvent(int eventType, Bundle params);}复制代码
指的注意的是,如果 SpeechRecognizer .isRecognitionAvailable 方法返回false的话,即使注册了监听*mSpeechRecognizer.setRecognitionListener(this);*回调方法不会走的,因为没有该服务的
下面就是样板代码了,都一样的
// 启动服务需要一个 IntentmRecognitionIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3);// mLocale 是一个语音种类,可以根据自己的需求去设置mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, mLocale);// 开始语音识别 结果在 mSpeechRecognizer.setRecognitionListener(this);回调中mSpeechRecognizer.startListening(mRecognitionIntent);// 停止监听mSpeechRecognizer.stopListening();// 取消服务mSpeechRecognizer.cancel();复制代码
在识别过程中,如果出错,错误码很有用
// 错误码 void onError(int error);复制代码
这里是错误码的原因,可以做参考去排查,错误码在SpeechRecognizer.class中,可以自行查阅
/** Network operation timed out. */ public static final int ERROR_NETWORK_TIMEOUT = 1; /** Other network related errors. */ public static final int ERROR_NETWORK = 2; /** Audio recording error. */ public static final int ERROR_AUDIO = 3; /** Server sends error status. */ public static final int ERROR_SERVER = 4; /** Other client side errors. */ public static final int ERROR_CLIENT = 5; /** No speech input */ public static final int ERROR_SPEECH_TIMEOUT = 6; /** No recognition result matched. */ public static final int ERROR_NO_MATCH = 7; /** RecognitionService busy. */ public static final int ERROR_RECOGNIZER_BUSY = 8; /** Insufficient permissions */ public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;复制代码
这里特别说明下,如果你所有操作都正常,可是在监听回调中一直出现 ERROR_INSUFFICIENT_PERMISSIONS 即错误码返回一直是 9,这时候可以尝试常看各个厂商的语音助手有没有在这里做处理,方法就是在说话的时候,这时打开语音助手,会有语音助手提示赋予应用权限,这种情况我在小米手机上遇到过,小爱同学需要打开权限,打开就好了。
以上就是一般的做法,但是不一定有用,厂商会做什么事,我们是不知道滴,具体问题需要具体对待,下面我们来讨论另外一种实现
-
照旧在启动服务前需要检查服务是否存在 *SpeechRecognizer.isRecognitionAvailable(context);*如果返回false,要么歇菜(绝大多数不会出现),要么自己实现,我自己实现不了。
如果返回true,说明有语音识别服务可以用,这时候我们需要记录下当前系统内置的是哪个服务
String serviceComponent = Settings.Secure.getString(context.getContentResolver(), "voice_recognition_service");复制代码
serviceComponent就是我们的服务名称,eg:华为手机返回"com.huawei.vassistant/com.huawei.ziri.service.FakeRecognitionService"从名字看就是 FakeRecognitionService伪造的语音识别服务,就是说这个是不用的。这里多说下,华为使用的讯飞的语音识别服务。
组装成组件
// 当前系统内置语音识别服务ComponentName component = ComponentName.unflattenFromString(serviceComponent);复制代码
组装成一个Component组件,后面我们需要用到
// 内置语音识别服务是否可用boolean isRecognizerServiceValid = false;ComponentName currentRecognitionCmp = null;// 查找得到的 "可用的" 语音识别服务List list = context.getPackageManager().queryIntentServices(new Intent(RecognitionService.SERVICE_INTERFACE), MATCH_ALL);if (list != null && list.size() != 0) { for (ResolveInfo info : list) { debugLog(TAG, "\t" + info.loadLabel(context.getPackageManager()) + ": " + info.serviceInfo.packageName + "/" + info.serviceInfo.name); // 这里拿系统使用的语音识别服务和内置的语音识别比较,如果相同,OK我们直接直接使用 // 如果相同就可以直接使用mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);来创建实例,因为内置的可以使用 if (info.serviceInfo.packageName.equals(component.getPackageName())) { isRecognizerServiceValid = true; break; } else { // 如果服务不同,说明 内置服务 和 系统使用 不是同一个,那么我们需要使用系统使用的 // 因为内置的系统不用,我们用了也没有用 currentRecognitionCmp = new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name); } }} else { // 这里既是查不到可用的语音识别服务,可以歇菜了 debugLog(TAG, "No recognition services installed"); return false;}复制代码
根据判断结果创建实例
// 当前系统内置语音识别服务可用 if (isRecognizerServiceValid) { mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context); } else { // 内置不可用,需要我们使用查找到的可用的 mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context, currentRecognitionCmp); } mSpeechRecognizer.setRecognitionListener(this);复制代码
关于*SpeechRecognizer createSpeechRecognizer(final Context context,final ComponentName serviceComponent)*方法源码如下
/** * Factory method to create a new {@code SpeechRecognizer}. Please note that * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any * command to the created {@code SpeechRecognizer}, otherwise no notifications will be * received. * * Use this version of the method to specify a specific service to direct this * {@link SpeechRecognizer} to. Normally you would not use this; use * {@link #createSpeechRecognizer(Context)} instead to use the system default recognition * service. * * @param context in which to create {@code SpeechRecognizer} * @param serviceComponent the {@link ComponentName} of a specific service to direct this * {@code SpeechRecognizer} to * @return a new {@code SpeechRecognizer} */ public static SpeechRecognizer createSpeechRecognizer(final Context context, final ComponentName serviceComponent) { if (context == null) { throw new IllegalArgumentException("Context cannot be null)"); } checkIsCalledFromMainThread(); return new SpeechRecognizer(context, serviceComponent); }复制代码
注释写的很明白,不建议我们使用,但是没办法,我们也不想折腾,各大厂商自己有实现,只能这样了。
使用该方法来做基本能满足大多数手机的功能实现,但是这种方法的前提有一个*SpeechRecognizer.isRecognitionAvailable(final Context context)*该方法要返回true,系统没有服务可用,那是没有办法的。
以下是自己的实现,可以根据自己使用修改
// 查找当前系统的内置使用的语音识别服务 // com.huawei.vassistant/com.huawei.ziri.service.FakeRecognitionService String serviceComponent = Settings.Secure.getString(context.getContentResolver(), "voice_recognition_service"); debugLog(TAG, "voice_recognition_service : " + serviceComponent); if (TextUtils.isEmpty(serviceComponent)) { return false; } ComponentName component = ComponentName.unflattenFromString(serviceComponent); if (component == null) { debugLog(TAG, "voice_recognition_service component == null"); return false; } debugLog(TAG, "serviceComponent : " + component.toShortString()); boolean isRecognizerServiceValid = false; ComponentName currentRecognitionCmp = null; // 查找得到的 "可用的" 语音识别服务 List list = context.getPackageManager().queryIntentServices(new Intent(RecognitionService.SERVICE_INTERFACE), MATCH_ALL); if (list != null && list.size() != 0) { for (ResolveInfo info : list) { debugLog(TAG, "\t" + info.loadLabel(context.getPackageManager()) + ": " + info.serviceInfo.packageName + "/" + info.serviceInfo.name); if (info.serviceInfo.packageName.equals(component.getPackageName())) { isRecognizerServiceValid = true; break; } else { currentRecognitionCmp = new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name); } } } else { debugLog(TAG, "No recognition services installed"); return false; } if (mSpeechRecognizer != null) { return true; } debugLog(TAG, "isRecognitionAvailable: " + SpeechRecognizer.isRecognitionAvailable(context)); if (isRecognizerServiceValid) { mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context); } else { mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context, currentRecognitionCmp); } mSpeechRecognizer.setRecognitionListener(this); if (mRecognitionIntent == null) { mRecognitionIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true); mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3); } return true;复制代码
到这里之后就是监听回调,回到了第一种方法的实现。就不贴代码了。
如有错误,不吝赐教
转载自:https://juejin.im/post/5cfa1a866fb9a07eab687234
更多相关文章
- Android 日志系统(Logcat)的实现分析
- Android在代码中设置drawableLeft(Right/Top/Bottom)
- 关于Android studio 使用fastjson报错的解决方法
- Android中常用的框架(从GitHub开源库中代码量来排名)
- Android禁止EditText自动弹出软键盘的方法
- Android SDK23以上(包括23)无法使用httpclient的处理方法
- android 属性android:visibility及 view的setVisibility方法值的
- Android图形显示系统——一张图片的显示流程