Android Speech Recognition as a service on Android 4.1 & 4.2

Posted

技术标签:

【中文标题】Android Speech Recognition as a service on Android 4.1 & 4.2【英文标题】: 【发布时间】:2013-02-03 03:22:39 【问题描述】:

我已经设法让连续语音识别工作(使用 SpeechRecognizer 类)作为所有 android 版本(最高 4.1)的服务。我的问题涉及让它在 4.1 和 4.2 版本上运行,因为众所周知,API 没有按照在语音识别开始后几秒钟内记录的那样执行,如果没有检测到语音输入,那么它就像如果语音识别器无声无息地死去。 (http://code.google.com/p/android/issues/detail?id=37883)

我发现了一个建议解决此问题的问题 (Voice Recognition stops listening after a few seconds),但我不确定如何实现此解决方案所需的处理程序。我知道这种解决方法每隔几秒钟就会发出“哔”声,但获得持续的语音识别对我来说更重要。

如果有人有任何其他替代解决方法,那么我也想听听。

【问题讨论】:

在我的安卓 4.1.1 的 Nexus S 上,语音识别器不会死,但与其他 4.0 版手机的行为不同。几秒钟后(比如 5 秒)4.0,我得到错误没有语音输入。使用 4.1.1 至少比 4.0 (15s) 长 3 倍,我得到了其他与网络相关的错误。因此,如果用户在说 5 秒后说话,那么语音识别器将不会拾取它,因为它仍然处理“无输入错误”。总之,在 4.1.1 版本中,“无语音输入”被视为“其他网络相关错误”,服务器返回此错误需要更长的时间。 直到调用 onError() 方法之前,4.0 都没有问题,语音识别器仍然处于活动状态,我可以简单地“重新启动”语音识别 - 允许连续识别.我知道在 4.1.1 上会发生什么,但是等待调用 onError() 并不能帮助我提供连续识别,因为在语音识别器变为非活动状态和等待调用 onError() 之间存在很长的延迟. (在某些情况下几乎是一分钟!) 作为一种解决方法,您可以在 onReadyForSpeech 上实现一个计时器,如果 onEndofSpeech 没有被调用 5 秒后,则再次调用 cancel 和 startListening。 这个我试过了,检测超时没问题。再次启动侦听器时出现问题。如果您查看 SpeechRecognizer 类 (github.com/android/platform_frameworks_base/blob/master/core/…) 的 Android 源代码,您将看到调用了 checkIsCalledFromMainThread() 方法,并且我不确定在使用计时解决方法时如何从主线程内部调用 startListening()。 我通过向我的服务消息处理程序发送一个开始监听消息来做到这一点。 【参考方案1】:

这是针对 android 版本 4.1.1 的解决方法。

public class MyService extends Service

    protected AudioManager mAudioManager; 
    protected SpeechRecognizer mSpeechRecognizer;
    protected Intent mSpeechRecognizerIntent;
    protected final Messenger mServerMessenger = new Messenger(new IncomingHandler(this));

    protected boolean mIsListening;
    protected volatile boolean mIsCountDownOn;
    private boolean mIsStreamSolo;

    static final int MSG_RECOGNIZER_START_LISTENING = 1;
    static final int MSG_RECOGNIZER_CANCEL = 2;

    @Override
    public void onCreate()
    
        super.onCreate();
        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 
        mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
        mSpeechRecognizer.setRecognitionListener(new SpeechRecognitionListener());
        mSpeechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        mSpeechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                                         RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        mSpeechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
                                         this.getPackageName());
    

    protected static class IncomingHandler extends Handler
    
        private WeakReference<MyService> mtarget;

        IncomingHandler(MyService target)
        
            mtarget = new WeakReference<MyService>(target);
        


        @Override
        public void handleMessage(Message msg)
        
            final MyService target = mtarget.get();

            switch (msg.what)
            
                case MSG_RECOGNIZER_START_LISTENING:

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
                    
                        // turn off beep sound  
                        if (!mIsStreamSolo)
                        
                            mAudioManager.setStreamSolo(AudioManager.STREAM_VOICE_CALL, true);
                            mIsStreamSolo = true;
                        
                    
                     if (!target.mIsListening)
                     
                         target.mSpeechRecognizer.startListening(target.mSpeechRecognizerIntent);
                         target.mIsListening = true;
                        //Log.d(TAG, "message start listening"); //$NON-NLS-1$
                     
                     break;

                 case MSG_RECOGNIZER_CANCEL:
                    if (mIsStreamSolo)
                   
                        mAudioManager.setStreamSolo(AudioManager.STREAM_VOICE_CALL, false);
                        mIsStreamSolo = false;
                   
                      target.mSpeechRecognizer.cancel();
                      target.mIsListening = false;
                      //Log.d(TAG, "message canceled recognizer"); //$NON-NLS-1$
                      break;
             
        
     

    // Count down timer for Jelly Bean work around
    protected CountDownTimer mNoSpeechCountDown = new CountDownTimer(5000, 5000)
    

        @Override
        public void onTick(long millisUntilFinished)
        
            // TODO Auto-generated method stub

        

        @Override
        public void onFinish()
        
            mIsCountDownOn = false;
            Message message = Message.obtain(null, MSG_RECOGNIZER_CANCEL);
            try
            
                mServerMessenger.send(message);
                message = Message.obtain(null, MSG_RECOGNIZER_START_LISTENING);
                mServerMessenger.send(message);
            
            catch (RemoteException e)
            

            
        
    ;

    @Override
    public void onDestroy()
    
        super.onDestroy();

        if (mIsCountDownOn)
        
            mNoSpeechCountDown.cancel();
        
        if (mSpeechRecognizer != null)
        
            mSpeechRecognizer.destroy();
        
    

    protected class SpeechRecognitionListener implements RecognitionListener
    

        @Override
        public void onBeginningOfSpeech()
        
            // speech input will be processed, so there is no need for count down anymore
            if (mIsCountDownOn)
            
                mIsCountDownOn = false;
                mNoSpeechCountDown.cancel();
                           
            //Log.d(TAG, "onBeginingOfSpeech"); //$NON-NLS-1$
        

        @Override
        public void onBufferReceived(byte[] buffer)
        

        

        @Override
        public void onEndOfSpeech()
        
            //Log.d(TAG, "onEndOfSpeech"); //$NON-NLS-1$
         

        @Override
        public void onError(int error)
        
            if (mIsCountDownOn)
            
                mIsCountDownOn = false;
                mNoSpeechCountDown.cancel();
            
             mIsListening = false;
             Message message = Message.obtain(null, MSG_RECOGNIZER_START_LISTENING);
             try
             
                    mServerMessenger.send(message);
             
             catch (RemoteException e)
             

             
            //Log.d(TAG, "error = " + error); //$NON-NLS-1$
        

        @Override
        public void onEvent(int eventType, Bundle params)
        

        

        @Override
        public void onPartialResults(Bundle partialResults)
        

        

        @Override
        public void onReadyForSpeech(Bundle params)
        
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
            
                mIsCountDownOn = true;
                mNoSpeechCountDown.start();

            
            Log.d(TAG, "onReadyForSpeech"); //$NON-NLS-1$
        

        @Override
        public void onResults(Bundle results)
        
            //Log.d(TAG, "onResults"); //$NON-NLS-1$

        

        @Override
        public void onRmsChanged(float rmsdB)
        

        

    

2013 年 2 月 16 日 - 如果您在应用中使用“文本转语音”,请修复蜂鸣声,确保关闭 onResults 中的 Solo 流

【讨论】:

这似乎可行,虽然我没有在服务内部实现静音方法,而是在其他地方实现。 我在 4.1.2 上运行它,只需创建一个主 Activity,它有一个启动服务的按钮,但在服务文件中的所有不同方法中,只有 onCreate() 和IncomingHandler(MyService target) 实际上被命中了。然后什么也没有发生。这部分是 4.1 和 4.2 的问题吗? 在onCreate中添加setRecognitionListener。 不,您可以覆盖 onStartCommand 并发送 MSG_RECOGNIZER_START_LISTENING 消息 @daleyjem 这仍然是错误的,因为设备的整个音频系统都会关闭!【参考方案2】:

如果你真的想在没有互联网连接的情况下实现连续收听,你需要考虑第三方包,其中之一是 CMUSphinx,查看Pocketsphinx android demo 例如如何在离线时有效地收听关键字并对特定命令做出反应,例如一个关键词“哦,强大的计算机”。代码很简单:

您创建一个识别器,然后添加关键字定位搜索:

recognizer = defaultSetup()
        .setAcousticModel(new File(modelsDir, "hmm/en-us-semi"))
        .setDictionary(new File(modelsDir, "lm/cmu07a.dic"))
        .setKeywordThreshold(1e-5f)
        .getRecognizer();

recognizer.addListener(this);
recognizer.addKeywordSearch(KWS_SEARCH_NAME, KEYPHRASE);
switchSearch(KWS_SEARCH_NAME);

并定义一个监听器:

@Override
public void onPartialResult(Hypothesis hypothesis) 
    String text = hypothesis.getHypstr();
    if (text.equals(KEYPHRASE))
      //  do something
 

【讨论】:

对不起,但是这样到底发生了什么?只有当我说出 tts 开始的关键字时,您才能将关键字确定为“hello”,然后在 onPartialResult 中? 即使屏幕关闭也能正常工作吗?另外,KEYPHRASE 是字符串吗? 1) 是的,它在屏幕关闭时工作,但您需要添加唤醒锁以防止手机进入睡眠状态。 2) 是的,它是任意字符串。 嘿,我正在尝试在我的服务中做一个简单的关键字定位,这样我就可以听到“你好”这个词。不幸的是,它不起作用。请帮助我:***.com/questions/35388720/… @NikolayShmyrev。我可以请你回答这个问题 - ***.com/q/37225500/4568864 - 谢谢【参考方案3】:

对于任何试图使哔哔声静音的人,重新评分@HoanNguyen 答案非常好,但要小心,如 api set setStreamSolo 中所说的那样是累积的,所以如果语音识别出错并且出错被调用(例如没有互联网连接)然后 setStremSolo true 被一次又一次地调用,这将导致您的应用程序静音整个手机(非常糟糕)!解决方案是将 setStremMute(false) 添加到 SpeechRecognizer onError。

【讨论】:

很好的收获。我实现了一个我在 onError 中调用的 cancel() 函数,除此之外,它还取消了单独的流。所以我从来没有问题。【参考方案4】:

查看我的演示应用程序: https://github.com/galrom/ContinuesVoiceRecognition

我建议同时使用 PockeySphix 和 SpeechRecognizer。

【讨论】:

嘿,我正在尝试在我的服务中做一个简单的关键字定位,这样我就可以听到“你好”这个词。不幸的是,它不起作用。请帮助我:***.com/questions/35388720/… @RuchirBaronia ,你检查过我的 Github 项目了吗?尝试比较并查看您的项目中缺少什么。 嗨,Gal Rom,我确实看到了你的 Github 项目,但我无法找出我的项目有什么问题。这是我的stack trace。出于某种原因,我无法简单地识别“你好”这个词。 :( 非常感谢您的帮助,您似乎是语音识别专家! @gal-rom 你的 github 代码有效!您是否可以在您的 github 中编写代码,以便它可以在后台录制音频(即使屏幕关闭或应用程序被杀死)?像这样的东西:fabcirablog.weebly.com/blog/… @GalRom 非常感谢!我发现了一个错误:在 SpeechRecognizerManager 的 onDestroy 中,您检查 mIsStreamSolo 是否为假,但您需要检查它是否为真,以重新建立音量设置!

以上是关于Android Speech Recognition as a service on Android 4.1 & 4.2的主要内容,如果未能解决你的问题,请参考以下文章

Android Speech Recognition as a service on Android 4.1 & 4.2

Android 简单的语音播报

Web Speech API - iOS 中的语音识别

在使用 Bot Framework 时,如何将 Skype 音频附件与 Bing Speech API 一起使用?

Speech语音播报

如何在 Unity 中使用 System.Speech?