■ 개발일지/2차프로젝트

[RN] (해결)음성인식정리(react-native-voice 활용)

J U N E 2024. 7. 23. 00:41

 

2024-7-25

드디어 음성인식 완성했다!

잊기 전에 정리

 

react-native-voice 모듈을 사용하기로 했다.

단, 이 모듈이 문제가 몇 가지가 있어서 우선 모듈 수정을 해줘야 함

node_modules > react-native-voice > android > src > main > java > com > wenkesj > voice > VoiceModule.java

 

public void onResults 함수 수정

    ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
    if (matches != null) {
      for (String result : matches) {
        arr.pushString(result);
      }

matches가 null일 경우에 대한 내용이 없어서 if문을 사용하여 확실하게 정리해 준다.

 

그리고 혹시 voiceEmitter warning이 뜰 수 있는데 그럴 때는

node_modules/react-native-voice/src\index.js

 

여기 최상단

const voiceEmitter = Platform.OS !== 'web' ? new NativeEventEmitter() : null;

로 수정한다.

 

더보기

- 컴포넌트 적용시

import Voice from 'react-native-voice';

const App = () => {
    const MINIMUM_LENGTH = 60000;
    const SILENCE_LENGTH = 3000;
    const entire = useRef('');
    const partial = useRef('');
    const [result, setResult] = useState('');

    useEffect(() => {
        Voice.onSpeechStart = onSpeechStart;
        Voice.onSpeechEnd = onSpeechEnd;
        Voice.onSpeechPartialResults = onSpeechPartialResults;
        Voice.onSpeechResults = onSpeechResults;
        Voice.onSpeechError = onSpeechError;

        return () => {
            Voice.destroy().then(Voice.removeAllListeners);
        };
    }, []);

    const onSpeechStart = (e) => {
        entire.current += partial.current;
        partial.current = '';
    };

    const onSpeechEnd = (e) => {
        partial.current = '';
    };

    const onSpeechPartialResults = (e) => {
        if (e.value != null && e.value.length > 0)
            partial.current = e.value[0];
    };

    const onSpeechResults = (e) => {
        if (e.value != null && e.value.length > 0) {
          setResult(e.value[0]);
        }
        else {
          entire.current += partial.current;
          setResult(entire.current);
        }
        setState(false);

        // 결과 처리 코드 입력

    };

    const onSpeechError = (e) => {
        console.log(JSON.stringify(e.error));
        setState(false);
    };

    const startRecognizing = async () => {
        setResult('');
        setState(true);
        entire.current = '';
        partial.current = '';
        try {
            await Voice.start('ko-KR', {
                EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS: MINIMUM_LENGTH,
                EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS: SILENCE_LENGTH,
            });
        } catch (e) {
            console.error(e);
        }
    };

    const stopRecognizing = async () => {
        try {
            await Voice.stop();
        } catch (e) {
            console.error(e);
        }
    };


    };	// App

이번 프로젝트에서는 딱히 크게 연관은 없는 코드지만

EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS

EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS

의 차이도 정리한다.

 

EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS
인식기가 음성을 듣는 것을 멈춘 후 입력이 완료되었다고 간주하여 인식 세션을 종료하는 데 걸리는 시간

EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS
인식 세션의 최소 길이를 나타내는 선택적 정수

 

암튼 처음에 인식시간 늘린다고 시작했다가 

거대한 풍선누르기 몇 판을 아주 그냥 1주일 동안 하고...

어쨌든 해결이 되어서 다행이다! 

같이 고민해 준 아빠 땡큐!


@react-native-community/voice 모듈을 사용해 개발 중이었는데

조금만 머뭇거리면 1초만에 음성인식이 종료되고 텍스트로 출력되어버리는 이슈가 발생. 

온갖 생쑈를 다해봐도 안되어서 찾아보니 안드로이드는 원래 그렇다는 의견이 대다수;;

아니 그럼 다들 이 불편함을 감수하고 그냥 쓰고 있다는 거야? 말도 안돼.

저 1초를 한 5초 정도로 늘려만 줘도 괜찮을텐데. 

 

그런데 EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS를

적용해도 작동을 안한다.

(사실 여기서 좀 더 파 봤으면 더 이상 쓸 데 없는 삽질은 안해도 됐을텐데)

어쨌든 여기서 추가로 onSpeechPartialResult 함수를 써서 해결해 보기로 했다.

부분부분 인식된 결과를 반환해 주는 함수인데 이걸 모두 이어붙여서

최종결과를 서버에 보내주면 되지 않겠나 싶어서 시도해봤으나 

결과는 여전히 failed.

 

대체 이 라이브러리가 뭐하는 녀석인가 싶어서 그제서야 나는 라이브러리를 까기 시작했다.

그런데 이게 웬일, 콘솔을 찍어봐도 무응답이고 함수전체를 아예 주석처리를 해봐도 딱히 달라지는 게 없다.

ㅎㅎㅎㅎ 몇 일동안 나는 대체 뭔 짓을 한 건가.

 

라이브러리 갖다 버렸다.

 

아예 차라리 record를 하고 google-speech-to-text API를 이용할까 싶어서 고민하던 차에

아빠가 라이브러리 이걸로 써 보라며 @react-native-voice/voice를 추천했다.

(그냥 @react-native-voice 아님. ...voice/voice 임)

그리고 중요한 건 node-modules > @react-native-voice > dist > index.js 을 열어

start함수에 구문을 추가해 준다.

    start(locale, options = {}) {
        if (!this._loaded && !this._listeners && voiceEmitter !== null) {
            this._listeners = Object.keys(this._events).map((key) => voiceEmitter.addListener(key, this._events[key]));
        }
        return new Promise((resolve, reject) => {
            const callback = (error) => {
                if (error) {
                    reject(new Error(error));
                }
                else {
                    resolve();
                }
            };
            if (react_native_1.Platform.OS === 'android') {
                Voice.startSpeech(locale, Object.assign({
                    EXTRA_LANGUAGE_MODEL: 'LANGUAGE_MODEL_FREE_FORM',
                    EXTRA_MAX_RESULTS: 5,
                    EXTRA_PARTIAL_RESULTS: true,
                    REQUEST_PERMISSIONS_AUTO: true,
                    EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS: 60000, // 1. 
                    EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS: 8000, // 2.
                }, options), callback);
            }
            else {
                Voice.startSpeech(locale, callback);
            }
        });
    }

 

오!! 된다!!! 

음성인식이 강제종료되지 않고 계속 유지됨!

이제 이놈의 partial기능 때문에 자꾸 중복단어가 저장되는 문제가 있어서

이걸 해결해야 한다. 

어쨌든 이제 뭔가 한 줄기 빛이 보이는 듯.