개요
이 튜토리얼은 Google Cloud Speech API 같은 외부 서비스에 의존하지 않고, 안드로이드에 기본 내장된 SpeechRecognizer 클래스만으로 음성 인식 기능을 구현하는 방법을 단계별로 설명합니다. 마이크 권한 요청부터 실시간 음량 시각화, 인식 결과 출력, 오류 처리까지 완전히 동작하는 예제 코드를 함께 다룹니다.
1단계 — 새 프로젝트 생성
Android Studio에서 File ⇒ New Project를 선택하고 새 프로젝트를 생성한 뒤, 필요한 모든 세부 정보를 입력합니다.
2단계 — 레이아웃 작성 (res/layout/activity_main.xml)
아래 코드를 activity_main.xml에 추가합니다. 화면 중앙에는 마이크 아이콘, 상단에는 인식 시작/중지를 제어하는 토글 버튼, 그 아래에는 음량(RMS)을 표시하는 프로그레스바와 인식 결과를 보여주는 TextView가 배치됩니다.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
android:src="@drawable/ic_mic" />
<ProgressBar
android:id="@+id/progressBar1" style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/toggleButton1"
android:layout_marginTop="28dp"
android:paddingLeft="10dp"
android:paddingRight="10dp" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/progressBar1"
android:layout_centerHorizontal="true"
android:layout_marginTop="47dp" />
<ToggleButton
android:id="@+id/toggleButton1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="26dp"
android:text="ToggleButton" />
</RelativeLayout>3단계 — 메인 액티비티 작성 (src/MainActivity.java)
다음 코드를 MainActivity.java에 추가합니다. 핵심 동작 흐름은 다음과 같습니다.
- SpeechRecognizer 생성: createSpeechRecognizer()로 인식기를 만들고 RecognitionListener를 등록합니다.
- 권한 요청: 토글 버튼을 켜면 RECORD_AUDIO 권한을 요청하고, 승인되면 startListening()으로 음성 인식을 시작합니다.
- 실시간 피드백: onRmsChanged() 콜백에서 입력 음량을 받아 프로그레스바에 표시합니다.
- 결과 및 오류 처리: onResults()에서 인식된 텍스트를 화면에 출력하고, onError()에서는 오류 코드에 맞는 한글 안내 메시지를 표시합니다.
package app.com.sample;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity implements
RecognitionListener {
private static final int REQUEST_RECORD_PERMISSION = 100;
private TextView returnedText;
private ToggleButton toggleButton;
private ProgressBar progressBar;
private SpeechRecognizer speech = null;
private Intent recognizerIntent;
private String LOG_TAG = "VoiceRecognitionActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
returnedText = findViewById(R.id.textView1);
progressBar = findViewById(R.id.progressBar1);
toggleButton = findViewById(R.id.toggleButton1);
progressBar.setVisibility(View.INVISIBLE);
speech = SpeechRecognizer.createSpeechRecognizer(this);
Log.i(LOG_TAG, "isRecognitionAvailable: " + SpeechRecognizer.isRecognitionAvailable(this));
speech.setRecognitionListener(this);
recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE,"US-en");
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
recognizerIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3);
toggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
progressBar.setVisibility(View.VISIBLE);
progressBar.setIndeterminate(true);
ActivityCompat.requestPermissions (MainActivity.this, new String[]{Manifest.permission.RECORD_AUDIO},
REQUEST_RECORD_PERMISSION);
} else {
progressBar.setIndeterminate(false);
progressBar.setVisibility(View.INVISIBLE);
speech.stopListening();
}
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_RECORD_PERMISSION:
if (grantResults.length > 0 && grantResults[0]== PackageManager.PERMISSION_GRANTED) {
speech.startListening(recognizerIntent);
} else {
Toast.makeText(MainActivity.this, "Permission Denied!", Toast .LENGTH_SHORT).show();
}
}
}
@Override
public void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
if (speech != null) {
speech.destroy();
Log.i(LOG_TAG, "destroy");
}
}
@Override
public void onBeginningOfSpeech() {
Log.i(LOG_TAG, "onBeginningOfSpeech");
progressBar.setIndeterminate(false);
progressBar.setMax(10);
}
@Override
public void onBufferReceived(byte[] buffer) {
Log.i(LOG_TAG, "onBufferReceived: " + buffer);
}
@Override
public void onEndOfSpeech() {
Log.i(LOG_TAG, "onEndOfSpeech");
progressBar.setIndeterminate(true);
toggleButton.setChecked(false);
}
@Override
public void onError(int errorCode) {
String errorMessage = getErrorText(errorCode);
Log.d(LOG_TAG, "FAILED " + errorMessage);
returnedText.setText(errorMessage);
toggleButton.setChecked(false);
}
@Override
public void onEvent(int arg0, Bundle arg1) {
Log.i(LOG_TAG, "onEvent");
}
@Override
public void onPartialResults(Bundle arg0) {
Log.i(LOG_TAG, "onPartialResults");
}
@Override
public void onReadyForSpeech(Bundle arg0) {
Log.i(LOG_TAG, "onReadyForSpeech");
}
@Override
public void onResults(Bundle results) {
Log.i(LOG_TAG, "onResults");
ArrayList<String> matches = results .getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
String text = "";
for (String result : matches)
text = result + "\n";
returnedText.setText(text);
}
@Override
public void onRmsChanged(float rmsdB) {
Log.i(LOG_TAG, "onRmsChanged: " + rmsdB);
progressBar.setProgress((int) rmsdB);
}
public static String getErrorText(int errorCode) {
String message;
switch (errorCode) {
case SpeechRecognizer.ERROR_AUDIO:
message = "오디오 녹음 오류";
break;
case SpeechRecognizer.ERROR_CLIENT:
message = "클라이언트 측 오류";
break;
case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
message = "권한이 부족합니다";
break;
case SpeechRecognizer.ERROR_NETWORK:
message = "네트워크 오류";
break;
case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
message = "네트워크 시간 초과";
break;
case SpeechRecognizer.ERROR_NO_MATCH:
message = "일치하는 결과가 없습니다";
break;
case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
message = "RecognitionService 사용 중입니다";
break;
case SpeechRecognizer.ERROR_SERVER:
message = "서버 오류";
break;
case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
message = "음성 입력이 감지되지 않았습니다";
break;
default:
message = "이해하지 못했습니다. 다시 시도해 주세요.";
break;
}
return message;
}
}4단계 — 매니페스트 설정 (AndroidManifest.xml)
마이크 접근을 위해 RECORD_AUDIO 권한, 그리고 인식 서비스 통신을 위해 INTERNET 권한을 반드시 선언해야 합니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android" package="app.com.sample">
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>앱 실행하기
이제 애플리케이션을 실행해 보겠습니다. 실제 안드로이드 기기가 컴퓨터에 연결되어 있다고 가정합니다. Android Studio에서 프로젝트의 액티비티 파일 중 하나를 연 뒤, 툴바의 Run 아이콘을 클릭하세요. 실행 옵션 목록에서 자신의 모바일 기기를 선택하면, 해당 기기에 앱이 설치되어 아래와 같은 기본 화면이 표시됩니다.


마무리 팁
토글 버튼을 켜면 마이크 권한을 묻는 대화상자가 나타나고, 권한을 허용하면 즉시 음성 인식이 시작됩니다. 말하는 동안 프로그레스바가 입력 음량에 따라 움직이며, 인식이 끝나면 결과 텍스트가 화면에 표시됩니다. 참고로 SpeechRecognizer는 기기에 설치된 음성 인식 서비스(대부분 Google 앱)를 통해 동작하므로, 에뮬레이터보다는 마이크가 있는 실제 기기에서 테스트하는 것이 좋습니다. 한국어 인식을 원한다면 EXTRA_LANGUAGE_PREFERENCE 값을 "ko-KR"로 변경하면 됩니다.