음성 인식은 마이크로 들리는 소리를 바탕으로 단어를 만들어 인터넷 검색 등에 사용할 수 있도록 기술입니다. 특히 구글의 음성 인식은 처음 나왔을 때 국내에서도 그 정확도에 많은 관심을 받았습니다. 그래봤자 지금은 시리가 인기짱이죠
하여튼 안드로이드는 직접 만든 앱에서 간편하게 구글의 음성 인식 서비스를 쓸 수 있도록 SpeechRecognizer 클래스를 제공합니다. 이 클래스는 사용자에게 인식을 시작한다는 신호를 받으면 계속해서 소리를 녹음하다가 음량이 낮아지거나 하면 얻은 음성을 처리해 적당한 문자열들로 바꾸어 결과를 내놓습니다. 소리를 녹음하기 때문에 당연히 RECORD_AUDIO 퍼미션이 필요합니다. (그리고 확인은 못해봤지만 핸드폰에 있는 음성 인식 앱을 쓰는 듯해서, 만약 음성인식 앱이 없을 때는 ActivityNotFoundException이 발생하므로 예외처리를 해줘야 하는 듯 합니다) 그리고 폰이 인터넷에 연결되어 있지 않으면 녹음 조차 안 합니다.
인스턴스를 얻기 위해서는 생성자 대신 다음의 메소드를 호출합니다.
SpeechRecognizer createSpeechRecognizer (Context context)
그리고 반환된 인스턴스에 콜백 리스너를 등록해야 합니다. 메소드는 다음과 같습니다.
void setRecognitionListener (RecognitionListener listener)
콜백 리스너는 RecognitionListener 인터페이스를 구현하는 인스턴스를 하나 전달해 주면 됩니다. RecognitionListener는 다음의 메소드를 재정의 해줘야 합니다.
void onReadyForSpeech (Bundle params)
준비가 다 된 SpeechRecognizer의 startListening(Intent recognizerIntent)을 호출하면 실행됩니다. 말을 들을 준비가 되었다는 콜백입니다.
void onEndOfSpeech()
음성이 끝났을 때 호출됩니다. 음성 인식이 성공했다는 건 아니고 이 콜백 다음에 인식 결과에 따라 onError나 onResults가 호출됩니다.
void onBufferReceived(byte[] buffer)
새 소리가 들어왔을 때 호출됩니다. 직접 확인해 보면 onReadyForSpeech과 onEndOfSpeech 사이에서 무수히 호출되는 것을 볼 수 있습니다.
void onRmsChanged(float rmsdB)
들리는 소리 크기가 변경되었을 때 호출됩니다. 소리 크기는 dB단위로 전달됩니다. (RMS(root mean square)는 제곱 평균 제곱근으로 음수 양수를 오가는 값들의 평균을 구할 때 좋다고 인터넷에서 찾아보니까 그러네요)
확인해보면 onBufferReceived하고 비슷하게 호출됩니다.
void onError (int error)
에러가 발생했을 때 호출됩니다. 전달인자 error은 SpeechRecognizer 에서 미리 정의된 다음의 상수들입니다.
1 ERROR_NETWORK_TIMEOUT : 네트워크 타임아웃
2 ERROR_NETWORK : 그 외 네트워크 에러
3 ERROR_AUDIO : 녹음 에러
4 ERROR_SERVER : 서버에서 에러를 보냄
5 ERROR_CLIENT : 클라이언트 에러
6 ERROR_SPEECH_TIMEOUT : 아무 음성도 듣지 못했을 때
7 ERROR_NO_MATCH : 적당한 결과를 찾지 못했을 때
8 ERROR_RECOGNIZER_BUSY : RecognitionService가 바쁠 때
9 ERROR_INSUFFICIENT_PERMISSIONS: uses-permission(즉 RECORD_AUDIO) 이 없을 때
void onResults(Bundle results)
음성 인식이 끝나고 결과가 나왔을 때 호출됩니다. 전달인자는 무려 Bundle인데요, results.get(SpeechRecognizer.RESULTS_RECOGNITION)해 주면 ArrayList<String>가 나오므로 걱정하지 않으셔도 됩니다.
void onPartialResults (Bundle partialResults)
부분적인 결과를 잡아 냈을 때 호출됩니다.
저로선 정확히 어떨 때 호출되는지 모르겠습니다
그리고 이렇게 구현된 RecognitionListener을 setRecognitionListener (RecognitionListener listener)로 등록하면 일단 준비는 끝납니다.
음성인식을 시작할 때는 SpeechRecognizer의 startListening(Intent recognizerIntent)을 호출하면 됩니다. 전달인자는 RecognizerIntent 클래스를 이용해서 만들어 전달해 주면 됩니다.(그냥 new Intent() 해서 넘겨주면 적당히 디폴트로 돌아갑니다)
우선 RecognizerIntent.ACTION_WEB_SEARCH를 주면서 Intent를 만듭니다
Intent recognizerIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH)
웹 서치가 아니라도 SpeechRecognizer를 쓸때는 이 액션을 주어야 합니다. ACTION_RECOGNIZE_SPEECH은 암시적 인텐트로 음성인식 액티비티를 실행하고 결과만 받을 때 쓰는 액션입니다. (http://developer.android.com/resources/articles/speech-input.html 을 참고해 주세요)
그리고 putExtra메소드로 LANGUAGE MODEL를 설정해 줍니다.
음성 인식 결과를 웹 검색에 쓰려면 LANGUAGE_MODEL_WEB_SEARCH을, 그외에 용도에는 LANGUAGE_MODEL_FREE_FORM을 주면 됩니다.
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
혹은
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
그리고 필요에 따라 여러 엑스트라를 줄 수 있습니다
EXTRA_LANGUAGE
EXTRA_MAX_RESULTS
EXTRA_PARTIAL_RESULTS
EXTRA_WEB_SEARCH_ONLY
양이 꽤 많으니 http://developer.android.com/reference/android/speech/RecognizerIntent.html#ACTION_WEB_SEARCH 를 참고해 주세요
마지막으로, 제때에 destroy()를 호출하지 않으면 폰 안에 있는 다른 음성인식이 다 죽습니다.
제 경우에는 SpeechRecognizer 생성 자체를 onResume에서 하고 onPause에서 destroy하는 식으로 처리했습니다.
이하 제가 만든 예제입니다
레이아웃은 소리 크기를 표시할 ProgressBar, 상태를 보여줄 TextView와 결과를 보여줄 TextView, 그리고 startListening()을 호출할 버튼을 하나 만들어놨습니다.
public class Ex_talkingActivity extends Activity {
ArrayList<String> arDump = new ArrayList<String>();
TextView statusView,resultsView;
Intent recognizerIntent;
SpeechRecognizer mSpeechRecognizer;
ProgressBar volumeView;
String str;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
statusView = (TextView)findViewById(R.id.status);
resultsView = (TextView)findViewById(R.id.results);
volumeView = (ProgressBar)findViewById(R.id.volumeView);
volumeView.setMax(100);
recognizerIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
}
@Override
public void onResume() {
// TODO Auto-generated method stub
super.onResume();
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
mSpeechRecognizer.setRecognitionListener(mRecognitionListener);
}
@Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();
mSpeechRecognizer.destroy();
}
public void onClick(View v) {
mSpeechRecognizer.startListening(recognizerIntent);
arDump.clear();
resultsView.setText("");
}
RecognitionListener mRecognitionListener = new RecognitionListener() {
@Override
public void onBeginningOfSpeech() {
// TODO Auto-generated method stub
AppendText("onBeginningOfSpeech");
}
@Override
public void onBufferReceived(byte[] buffer) {
// TODO Auto-generated method stub
AppendText("onBufferReceived "+buffer[0]);
}
@Override
public void onEndOfSpeech() {
// TODO Auto-generated method stub
AppendText("onEndOfSpeech");
}
@Override
public void onError(int error) {
// TODO Auto-generated method stub
AppendText("onError "+error);
}
@Override
public void onEvent(int eventType, Bundle params) {
// TODO Auto-generated method stub
AppendText("onEvent");
}
@Override
public void onPartialResults(Bundle partialResults) {
// TODO Auto-generated method stub
AppendText("onPartialResults");
}
@Override
public void onReadyForSpeech(Bundle params) {
// TODO Auto-generated method stub
AppendText("onReadyForSpeech");
}
@SuppressWarnings("unchecked")
@Override
public void onResults(Bundle results) {
// TODO Auto-generated method stub
AppendText("onResults");
resultsView.setText("");
ArrayList<String> rsts = (ArrayList<String>) results.get(SpeechRecognizer.RESULTS_RECOGNITION);
for(int i=0;i<rsts.size();i++)
resultsView.setText(rsts.get(i)+"\n");
}
@Override
public void onRmsChanged(float rmsdB) {
// TODO Auto-generated method stub
volumeView.setProgress((int)rmsdB);
}
};
void AppendText(String text) {
if(arDump.size()>17)
arDump.remove(0);
arDump.add(text);
StringBuilder result = new StringBuilder();
for(String s : arDump)
result.append(s+"\n");
statusView.setText(result.toString());
}
}
실행 결과