네이버가 제공하는 API중에 '음성합성'을 앱개발에 적용하여 간단한 예제를 만들어 보기로 하자. 이것은 간단히 말해서 텍스트를 음성으로 재생해 주는 것이다. 구글에도 있는 서비스로 네이버에서는 현재 한국어, 영어, 일본어, 중국어 4개국어를 제공하고 있고 하루 10,000글자로 처리한도가 정해져 있다. 그게 어느 정도인지는 감이 안오네... 

https://developers.naver.com/products/tts/

위 링크에서 이용신청을 해야 한다.

 

이미지_039.pngimage_6992693121490092442803.jpg

 

애플리케이션 등록으로 Client ID와 Clinet Secret를 확보했다. 이제 안드로이드 스튜디오에서 작업을 하자.

 

 

안드로이스 스튜디오

안드로이드 스튜디오에서 사전에 특별히 실치하거나 하는 것은 없는것 같다. 단지 POST방식으로 네이버 서버에 요청만 하면 텍스트를 음성으로 바꿔서 보내 주는 방식인듯 하다. 앱에서는 그 결과 데이터(음성)를 받아서 재생하도록 해주면 될것 같다.

 

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context="com.js.naverstt.MainActivity">

 

    <TextView

        android:id="@+id/tv_title"

        android:gravity="center"

        android:layout_marginTop="36dp"

        android:layout_marginBottom="50dp"

        android:textColor="@color/colorPrimaryDark"

        android:textSize="28dp"

        android:layout_centerHorizontal="true"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Naver Text to Speech!"/>

 

    <EditText

        android:id="@+id/et_text"

        android:hint="텍스트를 입력하세요."

        android:layout_centerHorizontal="true"

        android:layout_below="@+id/tv_title"

        android:layout_width="300dp"

        android:layout_height="wrap_content" />

 

    <LinearLayout

        android:layout_below="@+id/et_text"

        android:gravity="center"

        android:layout_margin="16dp"

        android:layout_width="match_parent"

        android:layout_height="wrap_content">

 

        <Button

            android:id="@+id/bt_tts"

            android:text="미진씨 읽어줘!"

            android:layout_centerHorizontal="true"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content" />

 

        <Button

            android:id="@+id/bt_reset"

            android:text="새로작성"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content" />

 

    </LinearLayout>

 

</RelativeLayout>

 

레이아웃은 간단히 텍스트를 입력하는 뷰와 재생버튼등으로 구성했다. 버튼을 누르면 입력된 텍스트를 네이버 서버로 보내서 음성으로 변환 mp3파일을 폰으로 받아 온다. 그리고 즉시 재생을 한다.

 

 

네이버 API 클래스

 


// 네이버 음성합성 Open API 예제
//아주 아주 아주 중요!!!! 네트워크 접속을 사용시 AsyncTask(즉 쓰레드)를 사용해야 한다. 아 미쳐~

import android.media.MediaPlayer;
import android.os.Environment;
import android.util.Log;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;

public class APIExamTTS {

    private static String TAG = "APIExamTTS";

    public static void main(String[] args) {
        String clientId = "유어클라이언트아이디";//애플리케이션 클라이언트 아이디값";
        String clientSecret = "유어시크릿";//애플리케이션 클라이언트 시크릿값";
        try {
            String text = URLEncoder.encode(args[0], "UTF-8"); // 13자
            String apiURL = "https://openapi.naver.com/v1/voice/tts.bin";
            URL url = new URL(apiURL);
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setRequestMethod("POST");
            con.setRequestProperty("X-Naver-Client-Id", clientId);
            con.setRequestProperty("X-Naver-Client-Secret", clientSecret);
            // post request
            String postParams = "speaker=mijin&speed=0&text=" + text;
            con.setDoOutput(true);
            con.setDoInput(true);
            DataOutputStream wr = new DataOutputStream(con.getOutputStream());///여기서 에러 난다?
            Log.d(TAG, String.valueOf(wr));
            wr.writeBytes(postParams);
            wr.flush();
            wr.close();
            int responseCode = con.getResponseCode();
            BufferedReader br;
            if(responseCode==200) { // 정상 호출
                InputStream is = con.getInputStream();
                int read = 0;
                byte[] bytes = new byte[1024];
                //폴더를 만들어 줘야 겠다. 없으면 새로 생성하도록 해야 한다. 일단 Naver폴더에 저장하도록 하자.
                File dir = new File(Environment.getExternalStorageDirectory()+"/", "Naver");
                if(!dir.exists()){
                    dir.mkdirs();
                }
                // 랜덤한 이름으로 mp3 파일 생성
                //String tempname = Long.valueOf(new Date().getTime()).toString();
                String tempname = "naverttstemp"; //하나의 파일명으로 덮어쓰기 하자.
                File f = new File(Environment.getExternalStorageDirectory() + File.separator + "Naver/" + tempname + ".mp3");
                f.createNewFile();
                OutputStream outputStream = new FileOutputStream(f);
                while ((read =is.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, read);
                }
                is.close();

                //여기서 바로 재생하도록 하자. mp3파일 재생 어떻게 하지? 구글링!
                String Path_to_file = Environment.getExternalStorageDirectory()+File.separator+"Naver/"+tempname+".mp3";
                MediaPlayer audioPlay = new MediaPlayer();
                audioPlay.setDataSource(Path_to_file);
                audioPlay.prepare();//이걸 해줘야 하는군. 없으면 에러난다.
                audioPlay.start();
                //재생하고 나서 파일을 지워줘야 하나? 이거참 고민이네... if문으로 분기 시켜야 하나?
                //아니면 유니크한 파일로 만들지 말고 하나의 파일명으로 저장하게 할수도 있을듯...



            } else {  // 에러 발생
                br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
                String inputLine;
                StringBuffer response = new StringBuffer();
                while ((inputLine = br.readLine()) != null) {
                    response.append(inputLine);
                }
                br.close();
            }
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

 

네이버 API예제에 몇군데 추가하거나 수정했다. http통신하는 분은 뭐 손댈게 없다. 어려워서 뭔지 잘 모르기도 하고 ... 흐름만 파악하면 될것 같다. 처음엔 뭔가 에러가 나고 작동이 안되었다. 몇시간 골머리를 않다가 알게 된게 네트워크 접속을 하려는 경우 쓰레드를 사용해야 한다고 한다. 요즘은 AsyncTask를 사용해야 한다고 한다. 이런... 저번에도 이것 때문에 당하고도 그새 까먹고 헤메고 있었다. 

 

 

메인액티비티

마지막으로 메인액티비티에서 AsyncTask를 작성해 보자.

 


public class MainActivity extends AppCompatActivity {

    private NaverTTSTask mNaverTTSTask;
    String[] mTextString;
    EditText etText;
    Button btTTS, btReset;

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

        etText = (EditText) findViewById(R.id.et_text);
        btTTS = (Button) findViewById(R.id.bt_tts);
        btReset = (Button) findViewById(R.id.bt_reset);

        //버튼 클릭이벤트 - 클릭하면 에디터뷰에 있는 글자를 가져와서 네이버에 보낸다. MP3로 바꿔 달라고 ...
        btTTS.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //사용자가 입력한 텍스트를 이 배열변수에 담는다.
                String mText;
                if (etText.getText().length() > 0) { //한글자 이상 1
                    mText = etText.getText().toString();
                    mTextString = new String[]{mText};

                    //AsyncTask 실행
                    mNaverTTSTask = new NaverTTSTask();
                    mNaverTTSTask.execute(mTextString);
                } else {
                    Toast.makeText(MainActivity.this, "텍스트를 입력하세요.", Toast.LENGTH_SHORT).show();
                    return;
                }
            }
        });

        //리셋버튼
        btReset.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                etText.setText("");
                etText.setHint("텍스트를 입력하세요.");
            }
        });
    }

    
    private class NaverTTSTask extends AsyncTask{

        @Override
        protected String doInBackground(String[]... strings) {
            //여기서 서버에 요청
            APIExamTTS.main(mTextString);
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            //방금 받은 파일명의 mp3가 있으면 플레이 시키자. 맞나 여기서 하는거?
            //아닌가 파일을 만들고 바로 실행되게 해야 하나? AsyncTask 백그라운드 작업중에...?

        }
    }
}

   

Screenshot_2017-03-22-00-12-05.png  Screenshot_2017-03-22-00-41-40.png

 

 

서버로 요청을 보내면 음성으로 변환해서 결과를 보내주는데 mp3 파일이다. 지정된 디렉토리에 파일이 저장되는것을 확인 할 수 있다. 이 예제에서는 하나의 파일을 덮어쓰도록 수정했다. 파일을 저장하고 싶다면 설정을 만들어서 선택하게 해주면 될것 같다.

 

 

 

잘된다. 구글 TTS보다 부드럽게 잘한다. 실제 성우의 목소리를 사용했다는 건가? 만족스럽다. 쓸일이 있으면 네이버 TTS를 사용하도록 하자. 여러가지 설정을 더해 주면 좀 더 다양한 기능의 앱을 만들 수 있을것 같다. 

다음에는 '음성인식' API도 알아 보기로 하자.

매니페스트 파일에 인터넷과 스토리지 읽기, 쓰기 퍼미션을 주는것도 잊지 말자!

번호 제목 글쓴이 날짜 조회 수
128 PC를 위한 최고의 안드로이드 에뮬레이터 황제낙엽 2023.01.18 3132
127 [Jetpack Compose UI / 상추님] GDG Jetpack compose 코드랩 수료 후기 (feat. 굿즈) 황제낙엽 2022.12.24 2370
126 [Jetpack Compose UI / kyj1991719] compose - Compose 이해 황제낙엽 2022.12.24 4025
125 [Jetpack Compose UI / 꾸준] Compose 가 뭔데...? 황제낙엽 2022.12.24 4035
124 [Jetpack Compose UI / IIVIKI] Compose 학습과 분석 황제낙엽 2022.12.24 4050
123 [Jetpack Compose UI] 스터디 시작하기 황제낙엽 2022.12.24 4001
122 [개발하는 정대리] 취준생을 위한 안드로이드 앱만들기 - 바텀네비게이션 뷰, 네비게이션 AAC file 황제낙엽 2022.12.22 3882
121 [개발하는 정대리] 취준생을 위한 안드로이드 앱만들기 - 뷰바인딩 file 황제낙엽 2022.12.22 3850
120 [개발하는 정대리] 취준생을 위한 안드로이드 앱만들기 - 라이브데이터 뷰모델 file 황제낙엽 2022.12.21 3826
119 [대왕놀] ImageButton in Compose (IconButton) 황제낙엽 2022.12.20 4105
118 [대왕놀] compose instrumentation test tutorial 황제낙엽 2022.12.20 3985
117 [대왕놀] Switch in Compose 황제낙엽 2022.12.20 4106
116 [대왕놀] EditText in Compose (TextField) 황제낙엽 2022.12.20 4021
115 [대왕놀] margin in Compose 황제낙엽 2022.12.20 4105
114 [대왕놀] modifier in compose 황제낙엽 2022.12.20 4180
113 [대왕놀] Button in Compose file 황제낙엽 2022.12.20 4224
112 [대왕놀] TextView in Compose (Text) 황제낙엽 2022.12.20 4083
111 [대왕놀] ImageView in Compose (Image) 황제낙엽 2022.12.20 4017
110 [대왕놀] LinearLayout in Compose (Row, Column) 황제낙엽 2022.12.20 4113
109 [대왕놀] Introduction to Jetpack compose 황제낙엽 2022.12.20 3964