Skip to content

Commit 7ae73e7

Browse files
authored
Run TTS engine service without starting the app. (#553)
1 parent 4fbad6a commit 7ae73e7

File tree

6 files changed

+134
-34
lines changed

6 files changed

+134
-34
lines changed

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
22
project(sherpa-onnx)
33

4-
set(SHERPA_ONNX_VERSION "1.9.8")
4+
set(SHERPA_ONNX_VERSION "1.9.9")
55

66
# Disable warning about
77
#

android/SherpaOnnxTtsEngine/app/src/main/AndroidManifest.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
xmlns:tools="http://schemas.android.com/tools">
3+
xmlns:tools="http://schemas.android.com/tools"
4+
package="com.k2fsa.sherpa.onnx.tts.engine">
45

56
<application
67
android:allowBackup="true"

android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/MainActivity.kt

+20-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.util.Log
99
import android.widget.Toast
1010
import androidx.activity.ComponentActivity
1111
import androidx.activity.compose.setContent
12+
import androidx.activity.viewModels
1213
import androidx.compose.foundation.layout.Box
1314
import androidx.compose.foundation.layout.Column
1415
import androidx.compose.foundation.layout.Row
@@ -43,9 +44,13 @@ import java.lang.NumberFormatException
4344
const val TAG = "sherpa-onnx-tts-engine"
4445

4546
class MainActivity : ComponentActivity() {
47+
// TODO(fangjun): Save settings in ttsViewModel
48+
private val ttsViewModel: TtsViewModel by viewModels()
49+
50+
private var mediaPlayer: MediaPlayer? = null
4651
override fun onCreate(savedInstanceState: Bundle?) {
4752
super.onCreate(savedInstanceState)
48-
TtsEngine.createTts(this.application)
53+
TtsEngine.createTts(this)
4954
setContent {
5055
SherpaOnnxTtsEngineTheme {
5156
// A surface container using the 'background' color from the theme
@@ -132,11 +137,12 @@ class MainActivity : ComponentActivity() {
132137
audio.samples.size > 0 && audio.save(filename)
133138

134139
if (ok) {
135-
val mediaPlayer = MediaPlayer.create(
140+
stopMediaPlayer()
141+
mediaPlayer = MediaPlayer.create(
136142
applicationContext,
137143
Uri.fromFile(File(filename))
138144
)
139-
mediaPlayer.start()
145+
mediaPlayer?.start()
140146
} else {
141147
Log.i(TAG, "Failed to generate or save audio")
142148
}
@@ -162,4 +168,15 @@ class MainActivity : ComponentActivity() {
162168
}
163169
}
164170
}
171+
172+
override fun onDestroy() {
173+
stopMediaPlayer()
174+
super.onDestroy()
175+
}
176+
177+
private fun stopMediaPlayer() {
178+
mediaPlayer?.stop()
179+
mediaPlayer?.release()
180+
mediaPlayer = null
181+
}
165182
}

android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/TtsEngine.kt

+26-27
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.k2fsa.sherpa.onnx.tts.engine
22

3-
import android.app.Application
3+
import android.content.Context
44
import android.content.res.AssetManager
55
import android.util.Log
66
import androidx.compose.runtime.MutableState
@@ -21,7 +21,6 @@ object TtsEngine {
2121
var lang: String? = null
2222

2323

24-
2524
val speedState: MutableState<Float> = mutableStateOf(1.0F)
2625
val speakerIdState: MutableState<Int> = mutableStateOf(0)
2726

@@ -44,19 +43,7 @@ object TtsEngine {
4443
private var dataDir: String? = null
4544
private var assets: AssetManager? = null
4645

47-
private var application: Application? = null
48-
49-
fun createTts(application: Application) {
50-
Log.i(TAG, "Init Next-gen Kaldi TTS")
51-
if (tts == null) {
52-
this.application = application
53-
initTts()
54-
}
55-
}
56-
57-
private fun initTts() {
58-
assets = application?.assets
59-
46+
init {
6047
// The purpose of such a design is to make the CI test easier
6148
// Please see
6249
// https://github.com/k2-fsa/sherpa-onnx/blob/master/scripts/apk/generate-tts-apk-script.py
@@ -89,9 +76,21 @@ object TtsEngine {
8976
// ruleFsts = "vits-zh-aishell3/rule.fst"
9077
// lexicon = "lexicon.txt"
9178
// lang = "zho"
79+
}
80+
81+
82+
fun createTts(context: Context) {
83+
Log.i(TAG, "Init Next-gen Kaldi TTS")
84+
if (tts == null) {
85+
initTts(context)
86+
}
87+
}
88+
89+
private fun initTts(context: Context) {
90+
assets = context.assets
9291

9392
if (dataDir != null) {
94-
val newDir = copyDataDir(modelDir!!)
93+
val newDir = copyDataDir(context, modelDir!!)
9594
modelDir = newDir + "/" + modelDir
9695
dataDir = newDir + "/" + dataDir
9796
assets = null
@@ -107,39 +106,39 @@ object TtsEngine {
107106
}
108107

109108

110-
private fun copyDataDir(dataDir: String): String {
109+
private fun copyDataDir(context: Context, dataDir: String): String {
111110
println("data dir is $dataDir")
112-
copyAssets(dataDir)
111+
copyAssets(context, dataDir)
113112

114-
val newDataDir = application!!.getExternalFilesDir(null)!!.absolutePath
113+
val newDataDir = context.getExternalFilesDir(null)!!.absolutePath
115114
println("newDataDir: $newDataDir")
116115
return newDataDir
117116
}
118117

119-
private fun copyAssets(path: String) {
118+
private fun copyAssets(context: Context, path: String) {
120119
val assets: Array<String>?
121120
try {
122-
assets = application!!.assets.list(path)
121+
assets = context.assets.list(path)
123122
if (assets!!.isEmpty()) {
124-
copyFile(path)
123+
copyFile(context, path)
125124
} else {
126-
val fullPath = "${application!!.getExternalFilesDir(null)}/$path"
125+
val fullPath = "${context.getExternalFilesDir(null)}/$path"
127126
val dir = File(fullPath)
128127
dir.mkdirs()
129128
for (asset in assets.iterator()) {
130129
val p: String = if (path == "") "" else path + "/"
131-
copyAssets(p + asset)
130+
copyAssets(context, p + asset)
132131
}
133132
}
134133
} catch (ex: IOException) {
135134
Log.e(TAG, "Failed to copy $path. ${ex.toString()}")
136135
}
137136
}
138137

139-
private fun copyFile(filename: String) {
138+
private fun copyFile(context: Context, filename: String) {
140139
try {
141-
val istream = application!!.assets.open(filename)
142-
val newFilename = application!!.getExternalFilesDir(null).toString() + "/" + filename
140+
val istream = context.assets.open(filename)
141+
val newFilename = context.getExternalFilesDir(null).toString() + "/" + filename
143142
val ostream = FileOutputStream(newFilename)
144143
// Log.i(TAG, "Copying $filename to $newFilename")
145144
val buffer = ByteArray(1024)

android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/TtsService.kt

+11-2
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,18 @@ Failed to get default language from engine com.k2fsa.sherpa.chapter5
5656

5757
class TtsService : TextToSpeechService() {
5858
override fun onCreate() {
59+
Log.i(TAG, "onCreate tts service")
5960
super.onCreate()
6061

6162
// see https://github.com/Miserlou/Android-SDK-Samples/blob/master/TtsEngine/src/com/example/android/ttsengine/RobotSpeakTtsService.java#L68
6263
onLoadLanguage(TtsEngine.lang, "", "")
6364
}
6465

66+
override fun onDestroy() {
67+
Log.i(TAG, "onDestroy tts service")
68+
super.onDestroy()
69+
}
70+
6571
// https://developer.android.com/reference/kotlin/android/speech/tts/TextToSpeechService#onislanguageavailable
6672
override fun onIsLanguageAvailable(_lang: String?, _country: String?, _variant: String?): Int {
6773
val lang = _lang ?: ""
@@ -79,12 +85,15 @@ class TtsService : TextToSpeechService() {
7985

8086
// https://developer.android.com/reference/kotlin/android/speech/tts/TextToSpeechService#onLoadLanguage(kotlin.String,%20kotlin.String,%20kotlin.String)
8187
override fun onLoadLanguage(_lang: String?, _country: String?, _variant: String?): Int {
88+
Log.i(TAG, "onLoadLanguage: $_lang, $_country")
8289
val lang = _lang ?: ""
8390

8491
return if (lang == TtsEngine.lang) {
92+
Log.i(TAG, "creating tts, lang :$lang")
8593
TtsEngine.createTts(application)
8694
TextToSpeech.LANG_AVAILABLE
8795
} else {
96+
Log.i(TAG, "lang $lang not supported, tts engine lang: ${TtsEngine.lang}")
8897
TextToSpeech.LANG_NOT_SUPPORTED
8998
}
9099
}
@@ -118,7 +127,7 @@ class TtsService : TextToSpeechService() {
118127
return
119128
}
120129

121-
val ttsCallback = {floatSamples: FloatArray ->
130+
val ttsCallback = { floatSamples: FloatArray ->
122131
// convert FloatArray to ByteArray
123132
val samples = floatArrayToByteArray(floatSamples)
124133
val maxBufferSize: Int = callback.maxBufferSize
@@ -136,7 +145,7 @@ class TtsService : TextToSpeechService() {
136145
text = text,
137146
sid = TtsEngine.speakerId,
138147
speed = TtsEngine.speed,
139-
callback=ttsCallback,
148+
callback = ttsCallback,
140149
)
141150

142151
callback.done()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.k2fsa.sherpa.onnx.tts.engine
2+
3+
import android.app.Application
4+
import android.os.FileUtils.ProgressListener
5+
import android.speech.tts.TextToSpeech
6+
import android.speech.tts.TextToSpeech.OnInitListener
7+
import android.speech.tts.UtteranceProgressListener
8+
import android.util.Log
9+
import androidx.lifecycle.ViewModel
10+
import java.util.Locale
11+
12+
class TtsApp : Application() {
13+
companion object {
14+
lateinit var instance: TtsApp
15+
}
16+
17+
override fun onCreate() {
18+
super.onCreate()
19+
instance = this
20+
}
21+
22+
}
23+
24+
class TtsViewModel : ViewModel() {
25+
26+
// https://developer.android.com/reference/kotlin/android/speech/tts/TextToSpeech.OnInitListener
27+
private val onInitListener = object : OnInitListener {
28+
override fun onInit(status: Int) {
29+
when (status) {
30+
TextToSpeech.SUCCESS -> Log.i(TAG, "Init tts succeded")
31+
TextToSpeech.ERROR -> Log.i(TAG, "Init tts failed")
32+
else -> Log.i(TAG, "Unknown status $status")
33+
}
34+
}
35+
}
36+
37+
// https://developer.android.com/reference/kotlin/android/speech/tts/UtteranceProgressListener
38+
private val utteranceProgressListener = object : UtteranceProgressListener() {
39+
override fun onStart(utteranceId: String?) {
40+
Log.i(TAG, "onStart: $utteranceId")
41+
}
42+
43+
override fun onStop(utteranceId: String?, interrupted: Boolean) {
44+
Log.i(TAG, "onStop: $utteranceId, $interrupted")
45+
super.onStop(utteranceId, interrupted)
46+
}
47+
48+
override fun onError(utteranceId: String?, errorCode: Int) {
49+
Log.i(TAG, "onError: $utteranceId, $errorCode")
50+
super.onError(utteranceId, errorCode)
51+
}
52+
53+
override fun onDone(utteranceId: String?) {
54+
Log.i(TAG, "onDone: $utteranceId")
55+
}
56+
57+
@Deprecated("Deprecated in Java")
58+
override fun onError(utteranceId: String?) {
59+
Log.i(TAG, "onError: $utteranceId")
60+
}
61+
}
62+
63+
val tts = TextToSpeech(TtsApp.instance, onInitListener, "com.k2fsa.sherpa.onnx.tts.engine")
64+
65+
init {
66+
tts.setLanguage(Locale(TtsEngine.lang!!))
67+
tts.setOnUtteranceProgressListener(utteranceProgressListener)
68+
}
69+
70+
override fun onCleared() {
71+
super.onCleared()
72+
tts.shutdown()
73+
}
74+
}

0 commit comments

Comments
 (0)