Unity3D - 메모장

quest3dkorea.com


통계 위젯 (화이트)

311
80
136178


안드로이드스튜디오에서 유니티 플러그인 제작 - 음성인식 Unity3D 스크립트

 ※ 이번글의 목적은 유니티에서 안드로이드폰의 구글 음성인식을 사용해서 인식된 결과값을 리턴 받는 것입니다.
======================================================================================================

■ 안드로이드 부분

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
//유니티
import com.unity3d.player.UnityPlayerActivity;
import com.unity3d.player.UnityPlayer;

import android.os.Handler;
import android.os.Message;
//음성인식
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;

import android.util.Log;

import java.util.ArrayList;


// 유니티에서 스크립트가 붙을 오브젝트 이름
private String UnityObjName = "pluginUnity";
// 유니티에서 에러메세지 받을 함수 이름
private String UnityMsg = "msgUnity";
//유니티에서 음성인식 결과받을 함수 이름
private String UnitySTTresult = "sttUnity";
// 핸들러 관련
private final int STTSTART = 1;
private final int STTREADY = 2;
private final int STTEND = 3;
private msgHandle mHandler = null;
// 음성인식 관련
private SpeechRecognizer mRecognizer;
private static Context context;
private String recogLang = "en-US";
// ★ 유니티에서 안드로이드 음성인식을 작동시킬려면 멀티쓰레딩을 해야 한다고 합니다. 
그래서 핸들러를 사용합니다.
// 
class msgHandle extends Handler{
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
switch (msg.what){
case STTSTART:
		//실제 음성인식을 작동시킬 것입니다.
StartSpeechRecoService();
break;
case STTREADY:
		// 유니티에 음성인식이 실제로 시작했다는 메세지를 보냅니다. 
                UnityPlayer.UnitySendMessage(UnityObjName, UnityMsg, "START");
break;
case STTEND:
		// 유니티에 음성인식을 종료했다는 메세지를 보냅니다.
UnityPlayer.UnitySendMessage(UnityObjName, UnityMsg, "END");
break;
}
}
}
// 다음은 음성인식 리스너입니다. OnCreate()에 등록해 주어야 합니다.
private RecognitionListener listener = new RecognitionListener() {
@Override
public void onReadyForSpeech(Bundle bundle) {
// 음성인식이 실제로 작동하면 여기가 호출됩니다.
        mHandler.sendEmptyMessage(STTREADY);
}

@Override
public void onBeginningOfSpeech() {

}

@Override
public void onRmsChanged(float v) {

}

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

}

@Override
public void onEndOfSpeech() {
// 음성인식이 끝났다는 거구요
        mHandler.sendEmptyMessage(STTEND);
}

// 음성인식이 실패하면 나오는 에러입니다.
// 스피치 타임아웃은 실제 음성 입력이 대기시간내에 없을 경우입니다.
// 노매치는 음성인식 결과가 없을 경우입니다.
// 레코그나이저비지는 음성인식 서버가 바쁘다고 합니다. 
    @Override
public void onError(int error) {
String errMsg = "";
switch (error) {
case 1: errMsg = "ERROR_NETWORK_TIMEOUT"; break;
case 2: errMsg = "ERROR_NETWORK"; break;
case 3: errMsg = "ERROR_AUDIO"; break;
case 4: errMsg = "ERROR_SERVER"; break;
case 5: errMsg = "ERROR_CLIENT"; break;
case 6: errMsg = "ERROR_SPEECH_TIMEOUT"; break;
case 7: errMsg = "ERROR_NO_MATCH"; break;
case 8: errMsg = "ERROR_RECOGNIZER_BUSY"; break;
}
try{
UnityPlayer.UnitySendMessage(UnityObjName, UnityMsg, errMsg);
} catch (Exception e) {
}
// 핸들러 메세지 초기화
        EndSpeechReco();
}

//음성인식 결과입니다. matches는 음성인식결과가 배열로 여러개 들어오는데 그중에 첫번째 것만 사용합니다.
    @Override
public void onResults(Bundle bundle) {
mHandler.removeMessages(0);
ArrayList matches = bundle.getStringArrayList("results_recognition");
if(matches != null){
try{
UnityPlayer.UnitySendMessage(UnityObjName, UnitySTTresult,((String)matches.get(0)));
}catch (Exception e){

}
}
}

@Override
public void onPartialResults(Bundle bundle) {

}

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

}
};
// 음성인식 리스너 여기까지입니다.

// 초기화입니다.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//핸들러 붙이고
mHandler = new msgHandle();

//음성인식 리스너등록합니다.
context = getApplicationContext();
if(mRecognizer == null){
mRecognizer = SpeechRecognizer.createSpeechRecognizer(context);
mRecognizer.setRecognitionListener(listener);
}
}

// 유니티에서 호출할 함수입니다.
public void StartSpeechReco(String Lang) {
// "en-US" : 미국영어 / "ko" : 한국어 /  "zh-CN" : 중국어 /  "ja" : 일본어
recogLang = Lang;
EndSpeechReco();
//핸들러에 시작을 알리고 있습니다.
try {
Message msg1 = new Message();
msg1.what = STTSTART;
mHandler.sendMessage(msg1);
}catch (Exception e) {
}
}

// 실제 음성인식을 호출합니다. recogLang은 음성인식할 언어로 주시면됩니다.
public void StartSpeechRecoService(){
Intent i = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); // 음성인식
i.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getPackageName());
i.putExtra(RecognizerIntent.EXTRA_LANGUAGE, recogLang);
mRecognizer.startListening(i);
}

//메세지 초기화
public void EndSpeechReco() {
this.mHandler.removeMessages(STTREADY);
this.mHandler.removeMessages(STTEND);
this.mHandler.removeMessages(STTSTART);
}
======================================================================================================
■ 그래들 설정은 이전의 포스트를 참조하면 됩니다. 
■ 그래들은 app그래들입니다. 프로젝트 그래들 고쳐서 하시면 안됩니다.
apply plugin: 'com.android.library'

android {
compileSdkVersion 24
buildToolsVersion "24.0.2"

defaultConfig {
minSdkVersion 15
targetSdkVersion 24
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.1'
compile files('libs/classes.jar')
}

task deleteOldJar(type: Delete){
delete 'release/forUnity'
}

task exportJar(type: Copy){
from('build/intermediates/bundles/release/')
into('build') // 요기 경로는 APP아래 build폴더입니다.
into('jar파일 저장할 경로 적어주세요 / 선택사항입니다.');
include('classes.jar')
rename('classes.jar', 'forUnity.jar')
}

exportJar.dependsOn(deleteOldJar, build)

■ 여기까지 안드로이드 스튜디오를 무사히 끝마치고 빌드했다면 Jar파일을 
유니티 Assets - Plugins - Android 폴더에 넣으세요.
매니페스트 파일도 같이요.
======================================================================================================
■ 유니티 파트입니다. 여기는 살짝 헷갈리는 부분들도 있을수 있습니다. 
좌절이나 절망하지 마시고 천천히 하면 금방 이해가 됩니다. 
  ● 일단 스크립트는 두개를 작성할 것입니다. 하나는 플러그인 매니저고 또 다른하나는 
씬에서 사용하게될 스크립트입니다.
    ※ 플러그인 매니저를 따로 두는 이유는 이렇게 만들어 두고 쓰면 편해서 입니다. 뭐 별다른 것은 없습니다.
  ● 안드로이드 스튜디오에서 유니티에서 쓰는 함수라고 변수자리에 작성한것을 보셨을겁니다. 
대소문자 다 구분하니 오타내시면 안됩니다.
 "pluginUnity", "msgUnity", "sttUnity" 요놈들이죠.

======================================================================================================

  ● 일단 플러그인 매니저입니다. 윗줄에 있는 "msgUnity", "sttUnity" 요놈들을 델리게이트를 통해서 
실제사용하는 스크립트에 내용을 전달하게 됩니다.
//클래스 바깥에 정의했습니다.
public delegate void delegateSTTresult(string result);
public delegate void delegateMSG(string msg);
//다음은 클래스 내부
// 싱글톤 설정입니다.
private static pluginmanager instance;
private static GameObject content;
public static pluginmanager Getinstance() {
if (instance == null) {
content = new GameObject();
content.name = "pluginUnity"; // 오브젝트 이름 틀리면 안됩니다.
instance = content.AddComponent(typeof(pluginmanager))as pluginmanager;
}
return instance;
}
// 안드로이드와 델리게이트 선언입니다.
private AndroidJavaObject AJO = null;
private AndroidJavaClass AJC = null;

delegateSTTresult _STTresult;
delegateMSG _MSG;
//안드로이드 연결은 Awake()에서 해줍니다.
void Awake() {
#if UNITY_ANDROID && !UNITY_EDITOR
AJC = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AJO = AJC.GetStatic<AndroidJavaObject>("currentActivity");
#endif
}
//안드로이드에 있는 함수를 호출합니다. string lang은 "en-US" 넣어주면 됩니다.
public void call_androidSTT(string lang) {
AJO.Call("StartSpeechReco", lang);
}
// 안드로이드에서 호출할 유니티 메소드 두개입니다. 에러메세지 받을 것과 음성인식 결과 받을 것입니다.
public void msgUnity(string msg) {
if (_MSG != null) {
_MSG(msg);
}
}

public void sttUnity(string result) {
if (_STTresult != null) {
_STTresult(result);
}
}
//받을 것을 콜백으로 실제 사용하는 스크립트로 넘겨줍니다.
public void setcallbackSTTresult(delegateSTTresult callback) { this._STTresult = callback; }
public void setcallbackMSG(delegateMSG callback) { this._MSG = callback; }

======================================================================================================

■ 플러그인 메니저는 별탈 없이 작성했다면 실제 사용하게 될 스크립트를 작성합니다.
■ 유니티에서 사용할때는 이번작성한 코드만 적당한곳에 붙여서 사용하면 됩니다. 매니저는 붙이지 마세요.
// Awake()에서 메니저를 부르고
void Awake() {
pluginmanager.Getinstance();
}
// Start()에서 콜백받을 메서드를 설정합니다.
void Start () {
pluginmanager.Getinstance().setcallbackSTTresult(new delegateSTTresult(STTresult));
pluginmanager.Getinstance().setcallbackMSG(new delegateMSG(androidMSG));
}
// 전달 받을 애들입니다.
void STTresult(string result) {
makelog("STT : " + result);
}
void androidMSG(string msg) {
makelog("LOG : " + msg);
}
void makelog(string logmsg){
//// 적당히
}
// 음성인식 명령을 보낼 메소드, 여기서 호출하면 안드로이드에서 실제 음성인식 호출하고 등록된 리스너가 
처리를 해서 위에 콜백으로 결과가 들어오게 됩니다.
public void Call_STTstart() {
pluginmanager.Getinstance().call_androidSTT("en-US");
}

======================================================================================================

■ 다음은 매니패스트 세팅입니다.
   ※ //처리된 주석부분은 지우세요.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="패키지명 넣으세요">
//녹음을 떠야되서 오디오 래코딩을 허가해줘야합니다.
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:debuggable="true"
android:allowBackup="true"
android:icon="@drawable/app_icon"
android:label="@string/app_name">
//테마는 삭제했습니다. 
<activity android:name="패키지명 넣으세요.MainActivity"
//다음의 android:configChanges 는 꼭 넣어주세요.
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|
// 여기부분 주석 지우고 윗줄이랑 아랫줄이랑 한줄로 해주세요. 화면이 짤려서 내려놓은거에요.
screenLayout|uiMode|screenSize|smallestScreenSize|fontScale">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
======================================================================================================
■ 여기까지 했다면 유니티에서 안드로이드 플러그인을 사용하는 것에 큰 문제가 없을 것입니다.
■ 음성인식에 핸들러를 사용했는데 AsyncTask를 사용해도 될 거 같습니다. 해보지는 않았는데...
■ 혹시 코드는 제대로 작성했는데 음성인식 안되는 분들은 폰에 google.inc라는 앱이 활성화 되어있는지 확인하세요.
■ 없으면 플레이스토어에서 다운받고, 비활성화 되어있으면 활성화 시키세요. LG폰을 다해본건 아니지만 자주 비활성화 됩니다.
■ 다중 음성인식 언어 사용할려면 폰에 로그인이 되어있어야 합니다. 로그인 안하면 1개언어밖에 안됩니다.
■ 비로그인폰은 환경설정에서 기본언어를 영어로 하시면 영어 인식 됩니다.
■ 음성인식 시작하면 딩동하면서 시작하는데 이소리는 끌수 있습니다. -- 이건 다음 포스트에.
■ 폰 종류에 따라서 음성인식 대기시간이 차이날수 있습니다. StartSpeechRecoService()에 아래와 같은 걸로 추가해서 
■ 조절할 수 있다고 했는데 이게 잘되는지는 저도 모르겠네요. 
//        i.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, 500);
// i.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, 2000);
// i.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, 1000);
G
M
T
음성 기능은 100자로 제한됨
G
M
T
음성 기능은 100자로 제한됨
G
M
T
음성 기능은 100자로 제한됨
G
M
T
음성 기능은 100자로 제한됨
G
M
T
음성 기능은 100자로 제한됨
G
M
T
음성 기능은 100자로 제한됨
G
M
T
음성 기능은 100자로 제한됨

덧글

  • 코딩고자 2016/09/26 19:21 # 삭제 답글

    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <application
    android:debuggable="true"
    android:allowBackup="true"
    android:icon="@drawable/app_icon"
    android:label="@string/app_name">

    안녕하세요 이부분이 레코딩 부분인데
    음성인식할때 같이 레코딩호출할때 그 정보들을 저장하고 듣고 싶은데
    설명좀 해주시면 감사하겠습니다...
    감사합니다. 수고하십시오!
  • 오 미니 오 미니 2016/09/27 12:27 # 답글

    바로 추출하는건 잘 안되는거 같고 녹음을 먼저 뜬 후에 음성인식을 시작하고 녹음한 파일을 재생시켜서 입력해야 될거 같습니다. 뭐 가정이기는 하지만 스피커에 나오는 소리도 마이크로 들어가니까요.
  • 제론미 2016/12/29 22:15 # 삭제 답글

    안녕하세요. 플러그인 정보를 찾다가 들어오게됬습니다.
    유니티 공부한지 얼마 되지 않았는데, 덕분에 유니티에서 플러그인하는 것 까지 쉽게 갔어요. 정말 감사합니다.
    그런데 해당 게시물을 쭉 따라 실습을 했는데 핸드폰에 빌드를 한 후 딩동- 하지 않아여..
    빌드 오류는 없구요. 혹시 해당 게시물을 쭉 따라하고 빌드하면 바로 딩동 하며 STT 기능이 실행되는건가요?
    아니면 추가적으로 무언가 작업을 더 해야하는건가요?
  • 제론미 2016/12/29 22:21 # 삭제

    UI-TEXT를 하나 만들어서
    makelog(string logmsg) 함수에 WinText.text = logmsg; 로 확인용으로 작성했는데,
    음성인식 시작이 안되는 것 같아요..
  • 오 미니 오 미니 2017/01/03 10:35 #

    음성인식 안되는 부분에 대해서는 글 하단부에 적어놓았습니다.
    이부분외의 상황은 별로 격어 보지 않아서 하단부 글내용에 대부분 포함된다고 보고 있습니다.

    이부분외의 상황은 오타나 Activity 경로가 잘못됬거나 하는 경우가 많습니다.

    일단 유니티로 에러메세지가 제대로 넘어오는지 확인해야 할거 같습니다. 안넘어온다면 플러그인 자체가 안붙었을수도 있습니다.
  • YK 2017/05/09 22:27 # 삭제 답글

    로그캣으로 확인하다보니 Java.lang.NoSuchMethodError: no non-static method with name='StaticSpeechReco' signature-'(Ljava/lang/String;)V' in class Ljava.lang.Object; 에러가 납니다. 해결방법이 있을까요?
  • 오 미니 오 미니 2017/05/11 11:49 #

    단순 오타같은데요. StaticSpeechReco --> StartSpeechReco
  • YK 2017/05/15 21:13 # 삭제 답글

    아차.. Java.lang.NoSuchMethodError: no non-static method with name='StartSpeechReco' signature-'(Ljava/lang/String;)V' in class Ljava.lang.Object; 에러입니다ㅠ
    제 생각으로는 플러그인 만들때 release폴더가 없어서 debug폴더로 지정을 해 줬는데 그게 문제일까요?
  • 오 미니 오 미니 2017/05/16 11:50 #

    메소드를 못찾는건데 Public이 빠진거 아니라면 클래스 불러올때 AndroidJavaClass unityPlayeyclass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); 이부분이 잘못되어서 그럴수도 있습니다. "com.unity3d.player.UnityPlayer" 이부분은 별 다른거 없으면 꼭 이렇게 써주어야 합니다.
  • 오 미니 오 미니 2017/05/16 11:54 #

    #if UNITY_ANDROID && !UNITY_EDITOR
    currAJC = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    currAJO = currAJC.GetStatic<AndroidJavaObject>("currentActivity");
    currAJO.Call("UnityGameObjectName",this.gameObject.name);
    #endif

    "com.unity3d.player.UnityPlayer", "currentActivity" 이부분은 별 다른거 없으면 고정입니다.
  • YK 2017/05/16 21:48 # 삭제 답글

    덕분에 오류해결은 됐습니다. 그런데 소스코드를 그대로 옮겨적어서 그런지 SendMessage: object ~ does not have receiver for function sttUnity! 가 나오네요^^.. 유니티가 처음이라 많이 어렵습니다..
  • 오 미니 오 미니 2017/05/17 11:50 #

    아마 androidobject 에서 안드로이드쪽 메소드 콜할때 static이 있고 없고 차이일거에요. 그냥 public은 AndroidJavaObject.Call(); 이고 static이 있으면 AndroidJavaObject.CallStatic();이렇게 해줘야 할겁니다. 안드로이드쪽에서 나는 에러라면 Unity쪽에 Static이 포함되서 발생하는 거 같네요.
  • 바니카 2018/01/30 11:27 # 삭제 답글

    안녕하세요 유니티 음성인식 플러그인 공부중인 사람입니다.
    올려주신 내용 도움이 많이 되었습니다. 감사합니다.

    다름이아니라 음성인식 중 일정시간 이상 음성을 받아드리려 노력중입니다.

    올려주신 내용중

    intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS, 15000);
    intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS, 150000);
    intent.putExtra(RecognizerIntent.EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS, 150000);

    이부분을 적용해보려고 하는데 어떻게 해야하는지 혹시 도움을 주실 수 있나요?
  • 오 미니 오 미니 2018/05/03 19:23 #

    이유는 모르겠으나 그부분은 정상적인 동작이 되는거 같지 않습니다. 아마도 음성인식 서버와 통신할 데이타량 때문에라도 적용은 안될거 같습니다.
  • 드루킹 2018/04/23 16:00 # 삭제 답글

    유니티에서 안드로이드 빌드해서 핸드폰에서 실행을 시키면 계속 앱이 중지된다고 나오는데 이거 무슨 오류인가요..?
  • 오 미니 오 미니 2018/05/03 19:25 #

    본글 아랫부분에 있는 오류사항에 해당되지 않았으면 오탈자일 경우가 많습니다. 본문에 구체적인 예외처리부분이 없습니다. 실제로 적용하실때는 가능한 모든 오류에 대해서 예외처리를 하셔야 할 것입니다.
  • yg 2018/09/23 17:05 # 삭제 답글

    안녕하세요!! 정말 졸업작품 얼마 전에 이 글을 보고 정말 큰 도움을 받았어요! 정말 감사합니다. 이 글을 제 블로그에 공유해도 될까요?? 수익성(광고 등)은 전혀 없는 순수 포트폴리오 용이요~ 링크 걸어놔도 될까요?? 정말 도움 많이 받았어요!!
  • 오 미니 오 미니 2018/10/08 16:30 # 답글

    네 도움되셨다니 다행이네요. 링크거는거야뭐 상관없습니다.
  • SDL 2018/11/12 16:48 # 삭제 답글

    저기 혹시 음성인식 팝업창에 관한 설정이 어딘지 알려 주실 수 있나요??ㅠㅠ
  • 오 미니 오 미니 2018/11/12 19:50 #

    음성인식 팝업창은 없고 음성인식 기능이 백그라운드에서 도는걸 유니티가 받아오는 구조입니다.
  • 미루미루 2019/01/10 15:32 # 삭제 답글

    위 내용들은 안드로이드 스튜디오에서 전부 처리하는 건가요?
  • 오 미니 오 미니 2019/01/11 09:15 #

    아니요. 중간부분에 유니티에서 처리하는 부분있습니다. 유니티 - 안드로이드 플러그인 - 안드로이드 기능제어 이런 구조입니다.
  • 또로롱 2019/04/06 17:10 # 삭제 답글

    안드로이드 스튜디오에서 STT라는 이름으로 java 클래스를 만들었습니다. 기본 코드 (public class STT {})를 모두 지우고 그대로 복붙하면 되나요?? 저걸 안지운다면 class STT{ } 중괄호 안에 복붙하면 되나요??ㅠㅠ
    둘다 해봤는데도 jar 파일 추출 오류가 나네요 ..ㅠㅠㅠ. 어떻게 해야할까요?
  • 오 미니 오 미니 2019/04/15 16:34 #

    jar파일 만들때 생기는 거라면 뭔가 다른 문제인거 같은데요.
댓글 입력 영역