Android原生SpeechRecognizer(語音識別)

開篇先吐槽下睦柴,在Android 平臺開發(fā)原生的SpeechRecognizer真是難受的,不像ios洋侨,無比輕松,平臺統(tǒng)一倦蚪。
由于Android 平臺的碎片化問題比較嚴重希坚,各個廠商都有自己的實現,尤其是語音助手出來以后陵且,每家的語音服務肯定是不一樣的裁僧。

目前Android原生的SpeechRecognizer做法應該有兩種

  1. 默認調用原生SpeechRecognizer,并稍作修改
  2. 調用第三方慕购,科大訊飛聊疲,百度等

這兩種做法中

  • 1. 在Google原生系統(tǒng)是可以的,但是在國內的環(huán)境是需要修改沪悲,修改后能保證各個機型基本可以用获洲,至于識別效果就要看各個機型自己實現的怎么樣了
  • 2. 最簡單省心省力,如果你的項目可以這么做殿如,那么兄弟恭喜你贡珊,你是最幸福的

這里我們不講第三方的,大家可以自己去集成第三方sdk涉馁,主要討論原生的開發(fā)

? 首先權限不要忘記(記得6.0以后動態(tài)請求權限)

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

? 在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<ResolveInfo> list = context.getPackageManager().queryIntentServices(
                new Intent(RecognitionService.SERVICE_INTERFACE), 0);
        return list != null && list.size() != 0;
    }

? 該方法在使用語音識別前建議要調用下门岔,該方法是檢查當前系統(tǒng)有沒有語音識別服務,我相信絕大多數廠商都有這個服務烤送,但是都有自己特別的實現寒随,但是它至少有,有就可以用。但是妻往,你像oppo的7.0以后機器互艾,這個方法調用后就是false,這時候就是毫無辦法了蒲讯。oppo 7.0以后就是這樣調用完后返回false忘朝,對于 oppo 這種情況,可以在手機上裝一個訊飛語音+的app判帮,語音識別就可以了局嘁,但是這種方法我估計沒人會用,用戶體驗太差晦墙。

? 如果該方法返回false在我們調用SpeechRecognizer.startListening();方法的時候會日志中發(fā)現這行l(wèi)og

no selected voice recognition service

? 該日志在SpeechRecognizer.startListening(final Intent recognizerIntent)方法中悦昵,大家可以進源碼查看這里就不貼了。

? 檢查完如果語音識別可用晌畅,接下來有兩種做法我們一個個來


  1. 直接創(chuàng)建實例啟動服務
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);
mSpeechRecognizer.setRecognitionListener(this);

? 創(chuàng)建識別實例但指,并添加監(jiān)聽,這里沒有什么問題抗楔,在監(jiān)聽中我們可以拿到我們的想要的回調

/**
 * 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的話拍棕,即使注冊了監(jiān)聽mSpeechRecognizer.setRecognitionListener(this);回調方法不會走的勺良,因為沒有該服務的

? 下面就是樣板代碼了,都一樣的

// 啟動服務需要一個 Intent
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);
// mLocale 是一個語音種類尚困,可以根據自己的需求去設置
mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, mLocale);

// 開始語音識別 結果在 mSpeechRecognizer.setRecognitionListener(this);回調中
mSpeechRecognizer.startListening(mRecognitionIntent);

// 停止監(jiān)聽
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;

? 這里特別說明下呵萨,如果你所有操作都正常,可是在監(jiān)聽回調中一直出現 ERROR_INSUFFICIENT_PERMISSIONS 即錯誤碼返回一直是 9,這時候可以嘗試晨缈纾看各個廠商的語音助手有沒有在這里做處理潮峦,方法就是在說話的時候,這時打開語音助手嘱腥,會有語音助手提示賦予應用權限拘悦,這種情況我在小米手機上遇到過,小愛同學需要打開權限础米,打開就好了。

? 以上就是一般的做法医寿,但是不一定有用蘑斧,廠商會做什么事,我們是不知道滴竖瘾,具體問題需要具體對待,下面我們來討論另外一種實現


  1. 照舊在啟動服務前需要檢查服務是否存在 SpeechRecognizer.isRecognitionAvailable(context);如果返回false,要么歇菜(絕大多數不會出現)事扭,要么自己實現乐横,我自己實現不了今野。

    如果返回true,說明有語音識別服務可以用催什,這時候我們需要記錄下當前系統(tǒng)內置的是哪個服務

String serviceComponent = Settings.Secure.getString(context.getContentResolver(),
                                                            "voice_recognition_service");

? serviceComponent就是我們的服務名稱宰睡,eg:華為手機返回"com.huawei.vassistant/com.huawei.ziri.service.FakeRecognitionService"從名字看就是 FakeRecognitionService偽造的語音識別服務蒲凶,就是說這個是不用的。這里多說下旋圆,華為使用的訊飛的語音識別服務麸恍。

? 組裝成組件

// 當前系統(tǒng)內置語音識別服務
ComponentName component = ComponentName.unflattenFromString(serviceComponent);

? 組裝成一個Component組件搀矫,后面我們需要用到


// 內置語音識別服務是否可用
boolean isRecognizerServiceValid = false;
ComponentName currentRecognitionCmp = null;
// 查找得到的 "可用的" 語音識別服務
List<ResolveInfo> 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);
                
        // 這里拿系統(tǒng)使用的語音識別服務和內置的語音識別比較瓤球,如果相同敏弃,OK我們直接直接使用
        // 如果相同就可以直接使用mSpeechRecognizer =              SpeechRecognizer.createSpeechRecognizer(context);來創(chuàng)建實例,因為內置的可以使用
        if (info.serviceInfo.packageName.equals(component.getPackageName())) {
            isRecognizerServiceValid = true;
            break;
        } else {
         // 如果服務不同绿饵,說明 內置服務 和 系統(tǒng)使用 不是同一個隅要,那么我們需要使用系統(tǒng)使用的
         // 因為內置的系統(tǒng)不用,我們用了也沒有用
            currentRecognitionCmp = new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
        }

    }
} else {
    // 這里既是查不到可用的語音識別服務步清,可以歇菜了
    debugLog(TAG, "No recognition services installed");
    return false;
}

? 根據判斷結果創(chuàng)建實例

        // 當前系統(tǒng)內置語音識別服務可用
        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专缠,系統(tǒng)沒有服務可用淑仆,那是沒有辦法的。

? 以下是自己的實現嘁圈,可以根據自己使用修改

        // 查找當前系統(tǒng)的內置使用的語音識別服務
        // 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<ResolveInfo> 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;

到這里之后就是監(jiān)聽回調最住,回到了第一種方法的實現。就不貼代碼了轧粟。


如有錯誤脓魏,不吝賜教

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市混蔼,隨后出現的幾起案子珊燎,更是在濱河造成了極大的恐慌,老刑警劉巖悔政,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谋国,死亡現場離奇詭異,居然都是意外死亡芦瘾,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門缅糟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逃贝,你說我怎么就攤上這事∧啻樱” “怎么了沪摄?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵纱烘,是天一觀的道長祈餐。 經常有香客問我,道長哺壶,這世上最難降的妖魔是什么蜒谤? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮资锰,結果婚禮上阶祭,老公的妹妹穿的比我還像新娘。我一直安慰自己胖翰,他們只是感情好,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布懊缺。 她就那樣靜靜地躺著培他,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俊扳。 梳的紋絲不亂的頭發(fā)上猛遍,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音梯醒,去河邊找鬼腌紧。 笑死,一個胖子當著我的面吹牛壁肋,可吹牛的內容都是我干的籽慢。 我是一名探鬼主播猫胁,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼杜漠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了驾茴?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤晨缴,失蹤者是張志新(化名)和其女友劉穎峡捡,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體稍途,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡砚婆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年装盯,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片埂奈。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡账磺,死狀恐怖,靈堂內的尸體忽然破棺而出垮抗,到底是詐尸還是另有隱情,我是刑警寧澤借宵,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布壤玫,位于F島的核電站,受9級特大地震影響欲间,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一叛氨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沉唠,春花似錦苛败、人聲如沸满葛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鄙币,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間因惭,已是汗流浹背绩衷。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留勿决,地道東北人招盲。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親讳推。 傳聞我的和親對象是個殘疾皇子玩般,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容