[Android Studio] 내장 API로 구글 STT 기능 구현

Jangmanbo·2022년 1월 9일
1

안드로이드 스튜디오에서는 구글 STT 기능을 내장 API로 지원한다.
내장 API를 이용해 별도의 사용자 입력이 들어올 때까지 음성을 텍스트로 변환하는 기능을 구현해보자!


1. manifest에 permission 추가

AndroidManifest.xml

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

manifest 태그 아래에 인터넷과 녹음 permission을 추가한다.



2. layout에 텍스트뷰와 버튼 추가

activity_addpost.xml


<TextView
            android:id="@+id/recordTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="음성 녹음 시작"
            android:textSize="22sp" />
<ImageButton
            android:id="@+id/recordBtn"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:background="@android:color/transparent"
            android:contentDescription="record"
            android:scaleType="fitCenter"
            app:srcCompat="@drawable/start_record" />

ImageButton을 클릭하면 녹음 시작, 다시 클릭하면 녹음을 중단하고 TextView는 녹음 중이면 "음성 녹음 중단"으로 텍스트를 변경할 것이다.



3. layout에 결과를 출력할 EditTextView 추가

activity_addpost.xml

<EditText
        android:id="@+id/contentsTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="5dp"
        android:layout_weight="1"
        android:background="@drawable/post_textview"
        android:ems="10"
        android:gravity="top"
        android:hint="내용"
        android:inputType="textMultiLine"
        android:lines="8"
        android:maxLines="100"
        android:padding="5dp" />

음성 녹음의 결과를 출력할 EditTextView이다.



4. 퍼미션 체크 및 이미지 버튼에 onclick event listener 등록

AddPostActivity.java


    Intent intent;
    SpeechRecognizer speechRecognizer;
    final int PERMISSION = 1;	//permission 변수
    
    boolean recording = false;  //현재 녹음중인지 여부
    TextView recordTextView;
    ImageButton recordBtn;
    
    EditText contents;	//음성을 텍스트로 변환한 결과를 출력할 텍스트뷰

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_addpost);

        CheckPermission();  //녹음 퍼미션 체크

        //UI
        recordBtn = findViewById(R.id.recordBtn);
        recordTextView=findViewById(R.id.recordTextView);
        contents=findViewById(R.id.contentsTextView);

        //버튼 클릭 이벤트 리스터 등록
        recordBtn.setOnClickListener(click);

        //RecognizerIntent 객체 생성
        intent=new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,getPackageName());
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE,"ko-KR");   //한국어

    }

    View.OnClickListener click = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                //녹음 버튼
                case R.id.recordBtn:
                    if (!recording) {   //녹음 시작
                        StartRecord();
                        Toast.makeText(getApplicationContext(), "지금부터 음성으로 기록합니다.", Toast.LENGTH_SHORT).show();
                    }
                    else {  //이미 녹음 중이면 녹음 중지
                        StopRecord();
                    }
                    break;
                default:
                    break;
            }
        }
    };

//퍼미션 체크
void CheckPermission() {
    //안드로이드 버전이 6.0 이상
    if ( Build.VERSION.SDK_INT >= 23 ){
        //인터넷이나 녹음 권한이 없으면 권한 요청
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET) == PackageManager.PERMISSION_DENIED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_DENIED ) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.INTERNET,
                            Manifest.permission.RECORD_AUDIO},PERMISSION);
        }
    }
}

안드로이드 버전 6.0이상에서는 디바이스 자원을 사용하기 위해 권한이 필요하다.

checkSelfPermission으로 인터넷이나 녹음 권한이 없는지 확인하고 없다면 권한을 요청한다.

//녹음 시작
void StartRecord() {
    recording = true;
    
    //마이크 이미지와 텍스트 변경
    recordBtn.setImageResource(R.drawable.stop_record);
    recordTextView.setText("음성 녹음 중지");
    
    speechRecognizer=SpeechRecognizer.createSpeechRecognizer(getApplicationContext());
    speechRecognizer.setRecognitionListener(listener);
    speechRecognizer.startListening(intent);
}

//녹음 중지
void StopRecord() {
    recording = false;
    
    //마이크 이미지와 텍스트 변경
    recordBtn.setImageResource(R.drawable.start_record);
    recordTextView.setText("음성 녹음 시작");
    
    speechRecognizer.stopListening();   //녹음 중지
    Toast.makeText(getApplicationContext(), "음성 기록을 중지합니다.", Toast.LENGTH_SHORT).show();
}

speechRecognizer.setRecognitionListener(listener);

SpeechRecognizer에 RecognitionListener를 할당하여 RecognitionListener가 recognition 관련 이벤트를 수신하도록 한다.
startListening(), stopListening() 호출 전에 setRecognitionListener()가 선행되어야 한다.



5. RecognitionListener 정의

RecognitionListener는 사용자가 말을 그만두면 자동으로 녹음이 중단된다. 버튼을 다시 클릭하기 전까지 녹음하고 싶다면 onResults 메소드를 수정해야 한다.


참고로 SpeechRecognizer와 RecognitionListener의 동작은 메인 스레드에서 이루어져야 한다.

RecognitionListener listener = new RecognitionListener() {
    @Override
    public void onReadyForSpeech(Bundle bundle) {

    }

    @Override
    public void onBeginningOfSpeech() {
        //사용자가 말하기 시작
    }

    @Override
    public void onRmsChanged(float v) {

    }

    @Override
    public void onBufferReceived(byte[] bytes) {

    }

    @Override
    public void onEndOfSpeech() {
        //사용자가 말을 멈추면 호출
        //인식 결과에 따라 onError나 onResults가 호출됨
    }

    @Override
    public void onError(int error) {    //토스트 메세지로 에러 출력
        String message;
        switch (error) {
            case SpeechRecognizer.ERROR_AUDIO:
                message = "오디오 에러";
                break;
            case SpeechRecognizer.ERROR_CLIENT:
                //message = "클라이언트 에러";
                //speechRecognizer.stopListening()을 호출하면 발생하는 에러
                return; //토스트 메세지 출력 X
            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 = "찾을 수 없음";
                //녹음을 오래하거나 speechRecognizer.stopListening()을 호출하면 발생하는 에러
                //speechRecognizer를 다시 생성하여 녹음 재개
                if (recording)
                    StartRecord();
                return; //토스트 메세지 출력 X
            case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
                message = "RECOGNIZER가 바쁨";
                break;
            case SpeechRecognizer.ERROR_SERVER:
                message = "서버가 이상함";
                break;
            case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
                message = "말하는 시간초과";
                break;
            default:
                message = "알 수 없는 오류임";
                break;
        }
        Toast.makeText(getApplicationContext(), "에러가 발생하였습니다. : " + message, Toast.LENGTH_SHORT).show();
    }

    //인식 결과가 준비되면 호출
    @Override
    public void onResults(Bundle bundle) {
        ArrayList<String> matches = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);	//인식 결과를 담은 ArrayList
        String originText = contents.getText().toString();  //기존 text
        
        //인식 결과
        String newText="";
        for (int i = 0; i < matches.size() ; i++) {
            newText += matches.get(i);
        }
        
        contents.setText(originText + newText + " ");	//기존의 text에 인식 결과를 이어붙임
        speechRecognizer.startListening(intent);    //녹음버튼을 누를 때까지 계속 녹음해야 하므로 녹음 재개
    }

    @Override
    public void onPartialResults(Bundle bundle) {

    }

    @Override
    public void onEvent(int i, Bundle bundle) {

    }
};

사용자가 말을 멈추면 onEndOfSpeech() 메소드가 호출되고 에러가 발생했다면 onError(), 음성인식 결과가 제대로 나왔다면 onResults() 메소드가 호출된다.

onError에서는 토스트 메세지로 에러를 출력하고 onResults에서는 기존 text에 인식결과를 이어붙인 text를 출력한다.


전체 코드

1개의 댓글

comment-user-thumbnail
2022년 1월 10일

STT API를 기본 라이브러리로 지원하고 있는 건 몰랐네요!

답글 달기