如何实现永久运行仅响应“OK app”的Android SpeechRecognizer
Android 永久语音唤醒功能实现方案
问题根因
原生SpeechRecognizer本身定位为短语音识别组件,默认检测到2秒左右静默、或识别完成就会自动结束监听。你当前实现中每次识别结束都销毁并重建SpeechRecognizer实例,这段重建耗时就是识别空白期,自然会出现唤醒词漏响应的问题。
优化方案
方案1:优化现有SpeechRecognizer实现(快速修复,适合测试)
- 复用
SpeechRecognizer实例,不要每次结果/错误回调就销毁重建,仅重启监听即可,大幅降低空白期时长 - 调整识别参数,延长静默超时阈值,开启部分结果回调,无需等语音完全结束即可检测唤醒词
- 补全错误回调的重启逻辑,除了错误码7(无匹配结果)外,静默超时、识别器繁忙等可恢复错误都要触发重启监听
- 将服务改为前台服务,Android 8.0+后台服务会被系统自动回收,必须展示常驻通知保证服务存活,同时申请录音权限、后台麦克风权限、忽略电池优化权限。
方案2:改用专业唤醒词引擎(生产环境推荐)
全量语音识别做永久监听功耗高、准确率低,生产环境建议使用专门的唤醒词方案:
- Android 12及以上可使用系统自带
AlwaysOnHotwordDetector,系统级低功耗永久唤醒,无识别间隙,不需要自行维护后台服务 - 低版本可集成离线唤醒SDK,比如开源的Porcupine、PocketSphinx,或国内百度、科大讯飞的离线唤醒组件,仅监听你设置的"OK app"关键词,命中后再拉起全量语音识别做后续交互,功耗和准确率远高于原生SpeechRecognizer实现。
优化后核心代码示例
public class SttService extends Service implements RecognitionListener { private static final String TAG = "SstService"; SpeechRecognizer mSpeech; // 初始化一次实例,复用 private void initSpeechRecognizer() { if (mSpeech == null) { mSpeech = SpeechRecognizer.createSpeechRecognizer(SttService.this); mSpeech.setRecognitionListener(SttService.this); } } private void startListening() { Intent intent1 = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); intent1.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, "en"); // 开启部分结果回调,提前检测唤醒词 intent1.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true); // 静默超时改为5秒 intent1.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, 5000); // 最小语音长度设为1秒 intent1.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, 1000); mSpeech.startListening(intent1); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.e(TAG, "onStartCommand: I am running"); // 改成前台服务,这里需要自行添加通知创建逻辑 // startForeground(NOTIFICATION_ID, notification); initSpeechRecognizer(); startListening(); return START_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } // 其他默认回调省略... @Override public void onError(int error) { Log.e(TAG, "onError: " + error); // 所有可恢复错误都触发重启 if (error == SpeechRecognizer.ERROR_NO_MATCH || error == SpeechRecognizer.ERROR_SPEECH_TIMEOUT || error == SpeechRecognizer.ERROR_RECOGNIZER_BUSY) { startListening(); } } @Override public void onPartialResults(Bundle partialResults) { ArrayList<String> partials = partialResults.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); if (partials != null && !partials.isEmpty()) { for (String text : partials) { if (text.equalsIgnoreCase("OK app")) { // 提前命中唤醒词,执行对应逻辑 Log.e(TAG, "唤醒成功:" + text); return; } } } } @Override public void onResults(Bundle results) { ArrayList<String> resultsStringArrayList = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); if (resultsStringArrayList == null || resultsStringArrayList.isEmpty()) { startListening(); return; } String mainResult = resultsStringArrayList.get(0); Log.e(TAG, "onResults: " + mainResult); if (mainResult.equalsIgnoreCase("OK app")) { System.out.println("What's up?"); } // 直接重启监听,不销毁实例 startListening(); } }
内容的提问来源于stack exchange,提问作者xRay
相关产品推荐
相关产品推荐

