[Android] 안드로이드에서 TTS 기능 구현하기


개요

넥스터즈 22기 활동에서 프로젝트를 진행하던 도중, 안드로이드 클라이언트에서 특정 텍스트를 읽어 음성으로 재생하는 TTS(Text To Speech) 기능이 필요했습니다. 이에 따라 Clean Architecture를 기반으로 문자열 형태의 텍스트를 전달하면 그대로 음성으로 변환하여 읽어주는 인터페이스를 Hilt를 이용해 구현하게 되었습니다.


공식 API 지원

이전 포스트에서 다뤘던 OCR 기능과는 다르게, TTS 기능의 경우 안드로이드 환경에서 공식적인 라이브러리를 지원하여 기본으로 탑재되어 있습니다. 따라서 별도의 의존성 추가 없이 바로 TTS 기능을 구현할 수 있습니다. 관련 API에 대한 문서는 아래의 Android Developer 사이트에서 확인하실 수 있습니다.

TextToSpeech - Android Developer


구현

다음은 Hilt를 이용하여 TTS 인스턴스를 주입해주는 모듈을 작성한 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Module
@InstallIn(SingletonComponent::class)
internal object TtsModule {

    @Provides
    @Singleton
    fun provideTextToSpeech(@ApplicationContext context: Context) = 
        TextToSpeech(context) { status ->
            if (status == TextToSpeech.SUCCESS) {
                with(textToSpeech) {
                    // 언어
                    language = Locale.getDefault()

                    // 음정 높낮이
                    setPitch(1f)

                    // 말하기 속도
                    setSpeechRate(1f)
                }
            }
        }
}

TTS 인스턴스는 내장 API를 사용하는 만큼 간단하게 생성할 수 있으며, 코드에서 볼 수 있듯이 언어, 음정 높낮이, 말하기 속도 등을 세팅할 수 있습니다. 언어의 경우 Locale.getDefault() 를 통해 기기의 세팅 값을 그대로 이용할 수도 있고, 한국어나 영어 등 특정한 언어를 직접 선택할 수도 있습니다. 한국어로 고정할 경우에도 영어를 읽을 수는 있으나, 소위 말하는 콩글리쉬 발음으로 읽어주기 때문에 상황에 맞게 설정할 필요가 있습니다. 그 외에 음정 높낮이 및 말하기 속도와 같은 경우에는 기본값을 이용하는 것이 일반적입니다.

다음은 실제 음성을 재생 및 정지하거나, 콜백을 설정하는 인터페이스 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

// presentation layer
interface TtsService {
    fun speakText(text: String)

    fun setCallback(
        onStart: () -> Unit,
        onDone: () -> Unit,
        onError: () -> Unit
    )

    fun stop()

    fun close()
}

// core-feature layer
internal class TtsServiceImpl @Inject constructor(
    private val textToSpeech: TextToSpeech
) : TtsService {

    // 음성 재생
    override fun speakText(text: String) {
        if (textToSpeech.isSpeaking) textToSpeech.stop()
        textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, text)
    }

    // 콜백 설정
    override fun setCallback(
        onStart: () -> Unit,
        onDone: () -> Unit,
        onError: () -> Unit
    ) {
        textToSpeech.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
            override fun onStart(utteranceId: String?) {
                onStart() // 텍스트 읽기 시작 시
            }

            override fun onDone(utteranceId: String?) {
                onDone() // 텍스트 읽기 완료 시
            }

            override fun onError(utteranceId: String?) {
                onError() // 에러 발생 시
            }
        })
    }

    // 텍스트 읽기 종료
    override fun stop() {
        if (textToSpeech.isSpeaking) textToSpeech.stop()
    }

    // 인스턴스 종료
    override fun close() {
        stop()
        textToSpeech.shutdown()
    }
}

TTS 인스턴스의 speak() 메서드를 통해 전달받은 텍스트를 음성으로 재생할 수 있습니다. 또한 isSpeaking 프로퍼티를 통해 현재 TTS가 음성을 재생 중인지 확인할 수 있으며, stop()으로 음성 읽기를 중단하거나 shutdown()으로 인스턴스 사용을 종료할 수 있습니다.

주의 깊게 볼만한 것은 콜백 설정인데, setOnUtteranceProgressListener()를 통해 다음과 같은 상황에 대한 콜백을 설정할 수 있습니다.

  • 음성 재생 시작 시
  • 음성 재생 완료 시
  • 음성 재생 오류 시

예를 들어 UI 상에서 TTS가 재생 중일 때만 바뀌는 아이콘이 있다면, ViewModel에서 음성 재생 시작/완료 시에 대한 콜백으로 UI state 변경 코드를 전달하면 이를 구현할 수 있습니다. 이 외에 TTS 오류 발생 시 Toast 메세지를 띄우는 기능도 같은 방법으로 구현할 수 있습니다.


마치며

공식적인 API를 지원하기 때문에 별도의 라이브러리 서칭 시간을 소요하지 않고도 충분히 자연스럽게 텍스트를 읽어주는 고성능 TTS 기능을 쉽게 구현할 수 있었습니다. 만약 부가 기능으로 TTS를 추가할 수 있는 상황이라면 어렵지 않게 추가할 수 있으니 한 번쯤 사용해보시는 것을 추천드립니다!

0%