Day 25 - [Android APP] 03-Android 的 STT 与 TTS

用键盘输入讯息,对年轻人或许稀松平常,但对长者而言,使用语音的方式或许更轻松。所以除了画面字体放大外,我们也使用语音识别与输出技术,提供长者更友善的工具。

上次的文章 [Zenbo开发系列] 06-安装DDE语料到Zenbo 有讲到 DDE 的问题和使用 Android 语音识别与输出的原因。那麽,今天就要来介绍 Android 这边的实作罗!

STT 语音转文字

STT (Speach to Text)

完整程序码: https://gitlab.com/graduate_lab415/chatbot/-/blob/master/app/src/main/java/com/cmrdb/app/chatbot/view/ChatActivity.java

语音转文字的功能用在左下的麦克风按钮,点击之後会有个对话框请使用者说出问句。

对话页面

我们让麦克风按钮被点击後,执行一个 Intent。

ImageButton btnSpeak = findViewById(R.id.speak);
btnSpeak.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent sttIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        sttIntent.putExtra(
                RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
        );
        sttIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
        sttIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.start_to_speak));

        try {
            startActivityForResult(sttIntent, REQUEST_CODE_STT);
        } catch (ActivityNotFoundException e) {
            Log.d(TAG, "onClick: " + e.getLocalizedMessage());
            Toast.makeText(ChatActivity.this, "Your device does not support STT.", Toast.LENGTH_LONG).show();
        }
    }
});

之後我们就能在 onActivityResult 取得语音辨识的结果,并把文字显示到 EditText 上。
这样设计的原因是想解决 DDE 常辨识不到文字的状况,使用者可以确认语音识别的内容是否正确,再点选送出纽。

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_STT) {
        if (resultCode == Activity.RESULT_OK && data != null) {
            ArrayList<String> result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
            if (result != null) {
                String recognizedText = result.get(0);
                mUserInput.setText(recognizedText);
            }
        }
    }
}

TTS 文字转语音

TTS (Text to Speach)

我是在 ViewModel 的呼叫使用 TTSSpeaker。

ChatViewModel

完整程序码: https://gitlab.com/graduate_lab415/chatbot/-/blob/master/app/src/main/java/com/cmrdb/app/chatbot/viewmodel/ChatViewModel.java

这边参数需要传两个 Callback 给 TTSSpeaker。

  • 第一个 TTSCallback 是我自己写的,主要用来确认 TTS 初始化是否成功,onReady 才能执行将句子念出来的动作。
  • 第二个 UtteranceProgressListener 是要确认 TTS 目前的状况,onStart(开始)、onDone(念完)、onError(错误)。
private void speak(String message) {
    TTSCallback ttsCallback = new TTSCallback() {
        @Override
        public void onReady() {
            mSpeaker.speak(message);
        }

        @Override
        public void onFail() {
            Log.d(TAG, "ttsCallback onFail");
        }
    };
    UtteranceProgressListener speakingStatus = new UtteranceProgressListener() {
        @Override
        public void onStart(String utteranceId) {
            Log.d(TAG, "speakingStatus onStart");
        }

        @Override
        public void onDone(String utteranceId) {
            Log.d(TAG, "speakingStatus onDone");
            mSpeaker.destroy();
        }

        @Override
        public void onError(String utteranceId) {
            Log.d(TAG, "speakingStatus onError");
            mSpeaker.destroy();
        }
    };

    mSpeaker = new TTSSpeaker(mApplication, ttsCallback, speakingStatus);
}

TTSSpeaker

完整程序码: https://gitlab.com/graduate_lab415/chatbot/-/blob/master/app/src/main/java/com/cmrdb/app/chatbot/controller/TTSSpeaker.java

public class TTSSpeaker {
    private TextToSpeech mTTS;
    private TTSCallback mCallback;
    private Application mApplication;

    public TTSSpeaker(Application application, TTSCallback callback, UtteranceProgressListener utteranceProgressListener) {
        mApplication = application;
        mCallback = callback;
        mTTS = new TextToSpeech(mApplication, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int status) {
                if (status == TextToSpeech.SUCCESS) {
                    int result = mTTS.setLanguage(Locale.TAIWAN);    //设定语言为中文
                    if (result == TextToSpeech.LANG_MISSING_DATA
                            || result == TextToSpeech.LANG_NOT_SUPPORTED) {
                        Log.e("TTS", "This Language is not supported");
                    } else {
                        mTTS.setPitch(1);    //语调(1为正常语调;0.5比正常语调低一倍;2比正常语调高一倍)
                        mTTS.setSpeechRate(1);    //速度(1为正常速度;0.5比正常速度慢一倍;2比正常速度快一倍)
                    }
                    mTTS.setOnUtteranceProgressListener(utteranceProgressListener);
                    mCallback.onReady();
                } else {
                    Log.e("TTS", "Initialization Failed!");
                    mCallback.onFail();
                }
            }

        });
    }

    public void speak(String message) {
        mTTS.speak(message, TextToSpeech.QUEUE_FLUSH, null, "tts1");
//        destroy();
    }

    public void destroy() {
            mTTS.shutdown();
    }
}

Demo



<<:  【把玩Azure DevOps】Day27 Build Pipeline的YAML结构描述:多个Agent Job

>>:  [Day 24] Edge Impulse + BLE Sense实现手势动作辨识(上)

追求JS小姊姊系列 Day9 -- 如果时间能重来,我不想跟工具人聊天(上)

前情提要 上一集让人等到很崩溃的,终於..郑列终於吹嘘完了 阿物件:我跟你说... 我:... (接...

【在厨房想30天的演算法】Day 10 资料结构:树 Tree

Aloha~!又是我少女人妻Uerica!这阵子家人住院,从急诊到加护病房再到普通病房,看到形形色色...

深呼吸、喘口气,所以你这麽努力是要往哪里去?(拼命努力,不好吗? )

周慕姿x邓惠文 聊《过度努力》、《我想看你变老的样子》 今天看到《过度努力》这本书,一路找到这个访谈...

#17 Automation (5)

今天处里剩下的部分:checker 函式和它注入页面的辅助函式。 checker checker 函...

【Git】 发 PR 是什麽?

第一次遇到发PR,原来就是档案修改後,发通知请原作来拉回去(Pull)的请求(Request) 1....