正在显示
57 个修改的文件
包含
1488 行增加
和
0 行删除
| @@ -13,3 +13,8 @@ for usage. | @@ -13,3 +13,8 @@ for usage. | ||
| 13 | 13 | ||
| 14 | - [SherpaOnnxVadAsr](./SherpaOnnxVadAsr) It uses a VAD with a non-streaming | 14 | - [SherpaOnnxVadAsr](./SherpaOnnxVadAsr) It uses a VAD with a non-streaming |
| 15 | ASR model. | 15 | ASR model. |
| 16 | + | ||
| 17 | +- [SherpaOnnxTts](./SherpaOnnxTts) It is for standalone text-to-speech. | ||
| 18 | + | ||
| 19 | +- [SherpaOnnxTtsEngine](./SherpaOnnxTtsEngine) It is for text-to-speech engine; | ||
| 20 | + you can use it to replace the system TTS engine. |
| @@ -56,6 +56,8 @@ class OfflineTts( | @@ -56,6 +56,8 @@ class OfflineTts( | ||
| 56 | 56 | ||
| 57 | fun sampleRate() = getSampleRate(ptr) | 57 | fun sampleRate() = getSampleRate(ptr) |
| 58 | 58 | ||
| 59 | + fun numSpeakers() = getNumSpeakers(ptr) | ||
| 60 | + | ||
| 59 | fun generate( | 61 | fun generate( |
| 60 | text: String, | 62 | text: String, |
| 61 | sid: Int = 0, | 63 | sid: Int = 0, |
| @@ -113,6 +115,7 @@ class OfflineTts( | @@ -113,6 +115,7 @@ class OfflineTts( | ||
| 113 | 115 | ||
| 114 | private external fun delete(ptr: Long) | 116 | private external fun delete(ptr: Long) |
| 115 | private external fun getSampleRate(ptr: Long): Int | 117 | private external fun getSampleRate(ptr: Long): Int |
| 118 | + private external fun getNumSpeakers(ptr: Long): Int | ||
| 116 | 119 | ||
| 117 | // The returned array has two entries: | 120 | // The returned array has two entries: |
| 118 | // - the first entry is an 1-D float array containing audio samples. | 121 | // - the first entry is an 1-D float array containing audio samples. |
android/SherpaOnnxTtsEngine/.gitignore
0 → 100644
android/SherpaOnnxTtsEngine/app/.gitignore
0 → 100644
| 1 | +/build |
| 1 | +plugins { | ||
| 2 | + id("com.android.application") | ||
| 3 | + id("org.jetbrains.kotlin.android") | ||
| 4 | +} | ||
| 5 | + | ||
| 6 | +android { | ||
| 7 | + namespace = "com.k2fsa.sherpa.onnx.tts.engine" | ||
| 8 | + compileSdk = 34 | ||
| 9 | + | ||
| 10 | + defaultConfig { | ||
| 11 | + applicationId = "com.k2fsa.sherpa.onnx.tts.engine" | ||
| 12 | + minSdk = 21 | ||
| 13 | + targetSdk = 34 | ||
| 14 | + versionCode = 1 | ||
| 15 | + versionName = "1.0" | ||
| 16 | + | ||
| 17 | + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" | ||
| 18 | + vectorDrawables { | ||
| 19 | + useSupportLibrary = true | ||
| 20 | + } | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + buildTypes { | ||
| 24 | + release { | ||
| 25 | + isMinifyEnabled = false | ||
| 26 | + proguardFiles( | ||
| 27 | + getDefaultProguardFile("proguard-android-optimize.txt"), | ||
| 28 | + "proguard-rules.pro" | ||
| 29 | + ) | ||
| 30 | + } | ||
| 31 | + } | ||
| 32 | + compileOptions { | ||
| 33 | + sourceCompatibility = JavaVersion.VERSION_1_8 | ||
| 34 | + targetCompatibility = JavaVersion.VERSION_1_8 | ||
| 35 | + } | ||
| 36 | + kotlinOptions { | ||
| 37 | + jvmTarget = "1.8" | ||
| 38 | + } | ||
| 39 | + buildFeatures { | ||
| 40 | + compose = true | ||
| 41 | + } | ||
| 42 | + composeOptions { | ||
| 43 | + kotlinCompilerExtensionVersion = "1.5.1" | ||
| 44 | + } | ||
| 45 | + packaging { | ||
| 46 | + resources { | ||
| 47 | + excludes += "/META-INF/{AL2.0,LGPL2.1}" | ||
| 48 | + } | ||
| 49 | + } | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +dependencies { | ||
| 53 | + | ||
| 54 | + implementation("androidx.core:core-ktx:1.12.0") | ||
| 55 | + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") | ||
| 56 | + implementation("androidx.activity:activity-compose:1.8.2") | ||
| 57 | + implementation(platform("androidx.compose:compose-bom:2023.08.00")) | ||
| 58 | + implementation("androidx.compose.ui:ui") | ||
| 59 | + implementation("androidx.compose.ui:ui-graphics") | ||
| 60 | + implementation("androidx.compose.ui:ui-tooling-preview") | ||
| 61 | + implementation("androidx.compose.material3:material3") | ||
| 62 | + implementation("androidx.appcompat:appcompat:1.6.1") | ||
| 63 | + implementation("com.google.android.material:material:1.9.0") | ||
| 64 | + testImplementation("junit:junit:4.13.2") | ||
| 65 | + androidTestImplementation("androidx.test.ext:junit:1.1.5") | ||
| 66 | + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") | ||
| 67 | + androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00")) | ||
| 68 | + androidTestImplementation("androidx.compose.ui:ui-test-junit4") | ||
| 69 | + debugImplementation("androidx.compose.ui:ui-tooling") | ||
| 70 | + debugImplementation("androidx.compose.ui:ui-test-manifest") | ||
| 71 | +} |
| 1 | +# Add project specific ProGuard rules here. | ||
| 2 | +# You can control the set of applied configuration files using the | ||
| 3 | +# proguardFiles setting in build.gradle. | ||
| 4 | +# | ||
| 5 | +# For more details, see | ||
| 6 | +# http://developer.android.com/guide/developing/tools/proguard.html | ||
| 7 | + | ||
| 8 | +# If your project uses WebView with JS, uncomment the following | ||
| 9 | +# and specify the fully qualified class name to the JavaScript interface | ||
| 10 | +# class: | ||
| 11 | +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||
| 12 | +# public *; | ||
| 13 | +#} | ||
| 14 | + | ||
| 15 | +# Uncomment this to preserve the line number information for | ||
| 16 | +# debugging stack traces. | ||
| 17 | +#-keepattributes SourceFile,LineNumberTable | ||
| 18 | + | ||
| 19 | +# If you keep the line number information, uncomment this to | ||
| 20 | +# hide the original source file name. | ||
| 21 | +#-renamesourcefileattribute SourceFile |
| 1 | +package com.k2fsa.sherpa.onnx.tts.engine | ||
| 2 | + | ||
| 3 | +import androidx.test.platform.app.InstrumentationRegistry | ||
| 4 | +import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
| 5 | + | ||
| 6 | +import org.junit.Test | ||
| 7 | +import org.junit.runner.RunWith | ||
| 8 | + | ||
| 9 | +import org.junit.Assert.* | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * Instrumented test, which will execute on an Android device. | ||
| 13 | + * | ||
| 14 | + * See [testing documentation](http://d.android.com/tools/testing). | ||
| 15 | + */ | ||
| 16 | +@RunWith(AndroidJUnit4::class) | ||
| 17 | +class ExampleInstrumentedTest { | ||
| 18 | + @Test | ||
| 19 | + fun useAppContext() { | ||
| 20 | + // Context of the app under test. | ||
| 21 | + val appContext = InstrumentationRegistry.getInstrumentation().targetContext | ||
| 22 | + assertEquals("com.k2fsa.sherpa.onnx.tts.engine", appContext.packageName) | ||
| 23 | + } | ||
| 24 | +} |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | + xmlns:tools="http://schemas.android.com/tools"> | ||
| 4 | + | ||
| 5 | + <application | ||
| 6 | + android:allowBackup="true" | ||
| 7 | + android:dataExtractionRules="@xml/data_extraction_rules" | ||
| 8 | + android:fullBackupContent="@xml/backup_rules" | ||
| 9 | + android:icon="@mipmap/ic_launcher" | ||
| 10 | + android:label="@string/app_name" | ||
| 11 | + android:roundIcon="@mipmap/ic_launcher_round" | ||
| 12 | + android:supportsRtl="true" | ||
| 13 | + android:theme="@style/Theme.SherpaOnnxTtsEngine" | ||
| 14 | + tools:targetApi="31"> | ||
| 15 | + <activity | ||
| 16 | + android:name=".GetSampleText" | ||
| 17 | + android:exported="true" | ||
| 18 | + android:theme="@android:style/Theme.Translucent.NoTitleBar"> | ||
| 19 | + <intent-filter> | ||
| 20 | + <action android:name="android.speech.tts.engine.GET_SAMPLE_TEXT" /> | ||
| 21 | + | ||
| 22 | + <category android:name="android.intent.category.DEFAULT" /> | ||
| 23 | + </intent-filter> | ||
| 24 | + </activity> | ||
| 25 | + <activity | ||
| 26 | + android:name=".CheckVoiceData" | ||
| 27 | + android:exported="true"> | ||
| 28 | + <intent-filter> | ||
| 29 | + <action android:name="android.speech.tts.engine.CHECK_TTS_DATA" /> | ||
| 30 | + | ||
| 31 | + <category android:name="android.intent.category.DEFAULT" /> | ||
| 32 | + </intent-filter> | ||
| 33 | + </activity> | ||
| 34 | + <activity | ||
| 35 | + android:name=".InstallVoiceData" | ||
| 36 | + android:exported="true"> | ||
| 37 | + <intent-filter> | ||
| 38 | + <action android:name="android.speech.tts.engine.INSTALL_TTS_DATA" /> | ||
| 39 | + | ||
| 40 | + <category android:name="android.intent.category.DEFAULT" /> | ||
| 41 | + </intent-filter> | ||
| 42 | + </activity> | ||
| 43 | + | ||
| 44 | + <service | ||
| 45 | + android:name=".TtsService" | ||
| 46 | + android:enabled="true" | ||
| 47 | + android:exported="true" | ||
| 48 | + android:label="@string/app_name"> | ||
| 49 | + <intent-filter> | ||
| 50 | + <action android:name="android.intent.action.TTS_SERVICE" /> | ||
| 51 | + | ||
| 52 | + <category android:name="android.intent.category.DEFAULT" /> | ||
| 53 | + </intent-filter> | ||
| 54 | + | ||
| 55 | + <meta-data | ||
| 56 | + android:name="android.speech.tts" | ||
| 57 | + android:resource="@xml/tts_engine" /> | ||
| 58 | + </service> | ||
| 59 | + | ||
| 60 | + <activity | ||
| 61 | + android:name=".MainActivity" | ||
| 62 | + android:exported="true" | ||
| 63 | + android:label="@string/app_name" | ||
| 64 | + android:theme="@style/Theme.SherpaOnnxTtsEngine"> | ||
| 65 | + <intent-filter> | ||
| 66 | + <action android:name="android.intent.action.MAIN" /> | ||
| 67 | + | ||
| 68 | + <category android:name="android.intent.category.LAUNCHER" /> | ||
| 69 | + </intent-filter> | ||
| 70 | + <intent-filter> | ||
| 71 | + <action android:name="android.speech.tts.engine.CONFIGURE_ENGINE" /> | ||
| 72 | + | ||
| 73 | + <category android:name="android.intent.category.DEFAULT" /> | ||
| 74 | + </intent-filter> | ||
| 75 | + </activity> | ||
| 76 | + </application> | ||
| 77 | + | ||
| 78 | +</manifest> |
android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/CheckVoiceData.kt
0 → 100644
| 1 | +package com.k2fsa.sherpa.onnx.tts.engine | ||
| 2 | + | ||
| 3 | +import android.content.Intent | ||
| 4 | +import androidx.appcompat.app.AppCompatActivity | ||
| 5 | +import android.os.Bundle | ||
| 6 | +import android.speech.tts.TextToSpeech | ||
| 7 | + | ||
| 8 | +class CheckVoiceData : AppCompatActivity() { | ||
| 9 | + override fun onCreate(savedInstanceState: Bundle?) { | ||
| 10 | + super.onCreate(savedInstanceState) | ||
| 11 | + val intent = Intent().apply { | ||
| 12 | + putStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES, arrayListOf(TtsEngine.lang)) | ||
| 13 | + putStringArrayListExtra(TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES, arrayListOf()) | ||
| 14 | + } | ||
| 15 | + setResult(TextToSpeech.Engine.CHECK_VOICE_DATA_PASS, intent) | ||
| 16 | + finish() | ||
| 17 | + } | ||
| 18 | +} |
android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/GetSampleText.kt
0 → 100644
| 1 | +package com.k2fsa.sherpa.onnx.tts.engine | ||
| 2 | + | ||
| 3 | +import android.app.Activity | ||
| 4 | +import android.content.Intent | ||
| 5 | +import androidx.appcompat.app.AppCompatActivity | ||
| 6 | +import android.os.Bundle | ||
| 7 | +import android.speech.tts.TextToSpeech | ||
| 8 | + | ||
| 9 | +class GetSampleText : Activity() { | ||
| 10 | + override fun onCreate(savedInstanceState: Bundle?) { | ||
| 11 | + super.onCreate(savedInstanceState) | ||
| 12 | + var result = TextToSpeech.LANG_AVAILABLE | ||
| 13 | + var text: String = "" | ||
| 14 | + when(TtsEngine.lang) { | ||
| 15 | + "eng" -> { | ||
| 16 | + text = "This is a text-to-speech engine with next generation Kaldi" | ||
| 17 | + } | ||
| 18 | + "zho", "cmn" -> { | ||
| 19 | + text = "使用新一代 Kaldi 进行语音合成" | ||
| 20 | + } | ||
| 21 | + else -> { | ||
| 22 | + result = TextToSpeech.LANG_NOT_SUPPORTED | ||
| 23 | + } | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + val intent = Intent().apply{ | ||
| 27 | + if(result == TextToSpeech.LANG_AVAILABLE) { | ||
| 28 | + putExtra(TextToSpeech.Engine.EXTRA_SAMPLE_TEXT, text) | ||
| 29 | + } else { | ||
| 30 | + putExtra("sampleText", text) | ||
| 31 | + } | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + setResult(result, intent) | ||
| 35 | + finish() | ||
| 36 | + } | ||
| 37 | +} |
android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/InstallVoiceData.kt
0 → 100644
| 1 | +package com.k2fsa.sherpa.onnx.tts.engine | ||
| 2 | + | ||
| 3 | +import android.app.Activity | ||
| 4 | +import android.os.Bundle | ||
| 5 | +import android.view.Window | ||
| 6 | + | ||
| 7 | +class InstallVoiceData : Activity() { | ||
| 8 | + override fun onCreate(savedInstanceState: Bundle?) { | ||
| 9 | + requestWindowFeature(Window.FEATURE_NO_TITLE) | ||
| 10 | + super.onCreate(savedInstanceState) | ||
| 11 | + } | ||
| 12 | +} |
android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/MainActivity.kt
0 → 100644
| 1 | +@file:OptIn(ExperimentalMaterial3Api::class) | ||
| 2 | + | ||
| 3 | +package com.k2fsa.sherpa.onnx.tts.engine | ||
| 4 | + | ||
| 5 | +import android.media.MediaPlayer | ||
| 6 | +import android.net.Uri | ||
| 7 | +import android.os.Bundle | ||
| 8 | +import android.util.Log | ||
| 9 | +import android.widget.Toast | ||
| 10 | +import androidx.activity.ComponentActivity | ||
| 11 | +import androidx.activity.compose.setContent | ||
| 12 | +import androidx.compose.foundation.layout.Box | ||
| 13 | +import androidx.compose.foundation.layout.Column | ||
| 14 | +import androidx.compose.foundation.layout.Row | ||
| 15 | +import androidx.compose.foundation.layout.fillMaxSize | ||
| 16 | +import androidx.compose.foundation.layout.fillMaxWidth | ||
| 17 | +import androidx.compose.foundation.layout.padding | ||
| 18 | +import androidx.compose.foundation.layout.wrapContentHeight | ||
| 19 | +import androidx.compose.material3.Button | ||
| 20 | +import androidx.compose.material3.ExperimentalMaterial3Api | ||
| 21 | +import androidx.compose.material3.MaterialTheme | ||
| 22 | +import androidx.compose.material3.OutlinedTextField | ||
| 23 | +import androidx.compose.material3.Scaffold | ||
| 24 | +import androidx.compose.material3.Slider | ||
| 25 | +import androidx.compose.material3.Surface | ||
| 26 | +import androidx.compose.material3.Text | ||
| 27 | +import androidx.compose.material3.TextField | ||
| 28 | +import androidx.compose.material3.TopAppBar | ||
| 29 | +import androidx.compose.runtime.Composable | ||
| 30 | +import androidx.compose.runtime.getValue | ||
| 31 | +import androidx.compose.runtime.mutableStateOf | ||
| 32 | +import androidx.compose.runtime.remember | ||
| 33 | +import androidx.compose.runtime.setValue | ||
| 34 | +import androidx.compose.ui.Modifier | ||
| 35 | +import androidx.compose.ui.tooling.preview.Preview | ||
| 36 | +import androidx.compose.ui.unit.dp | ||
| 37 | +import com.k2fsa.sherpa.onnx.tts.engine.ui.theme.SherpaOnnxTtsEngineTheme | ||
| 38 | +import java.io.File | ||
| 39 | + | ||
| 40 | +const val TAG = "sherpa-onnx-tts-engine" | ||
| 41 | + | ||
| 42 | +class MainActivity : ComponentActivity() { | ||
| 43 | + override fun onCreate(savedInstanceState: Bundle?) { | ||
| 44 | + super.onCreate(savedInstanceState) | ||
| 45 | + TtsEngine.createTts(this.application) | ||
| 46 | + setContent { | ||
| 47 | + SherpaOnnxTtsEngineTheme { | ||
| 48 | + // A surface container using the 'background' color from the theme | ||
| 49 | + Surface( | ||
| 50 | + modifier = Modifier.fillMaxSize(), | ||
| 51 | + color = MaterialTheme.colorScheme.background | ||
| 52 | + ) { | ||
| 53 | + Scaffold(topBar = { | ||
| 54 | + TopAppBar(title = { Text("Next-gen Kaldi: TTS") }) | ||
| 55 | + }) { | ||
| 56 | + Box(modifier = Modifier.padding(it)) { | ||
| 57 | + Column { | ||
| 58 | + Row { | ||
| 59 | + Text("Speed") | ||
| 60 | + Slider( | ||
| 61 | + value = TtsEngine.speedState.value, | ||
| 62 | + onValueChange = { TtsEngine.speed = it }, | ||
| 63 | + valueRange = 0.2F..3.0F | ||
| 64 | + ) | ||
| 65 | + } | ||
| 66 | + var testText by remember { mutableStateOf("") } | ||
| 67 | + | ||
| 68 | + OutlinedTextField(value = testText, | ||
| 69 | + onValueChange = { testText = it }, | ||
| 70 | + label = { Text ("Test text") }, | ||
| 71 | + modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(16.dp), | ||
| 72 | + singleLine = false, | ||
| 73 | + ) | ||
| 74 | + | ||
| 75 | + val numSpeakers = TtsEngine.tts!!.numSpeakers() | ||
| 76 | + if (numSpeakers > 1) { | ||
| 77 | + Row { | ||
| 78 | + Text("Speaker ID: (0-${numSpeakers - 1})") | ||
| 79 | + Slider( | ||
| 80 | + value = TtsEngine.speakerIdState.value.toFloat(), | ||
| 81 | + onValueChange = { TtsEngine.speakerId = it.toInt() }, | ||
| 82 | + valueRange = 0.0f..(numSpeakers - 1).toFloat(), | ||
| 83 | + steps = 1 | ||
| 84 | + ) | ||
| 85 | + } | ||
| 86 | + } | ||
| 87 | + Row { | ||
| 88 | + Button( | ||
| 89 | + modifier = Modifier.padding(20.dp), | ||
| 90 | + onClick = { | ||
| 91 | + Log.i(TAG, "Clicked, text: ${testText}") | ||
| 92 | + if (testText.isBlank() || testText.isEmpty()) { | ||
| 93 | + Toast.makeText( | ||
| 94 | + applicationContext, | ||
| 95 | + "Please input a test sentence", | ||
| 96 | + Toast.LENGTH_SHORT | ||
| 97 | + ).show() | ||
| 98 | + } else { | ||
| 99 | + val audio = TtsEngine.tts!!.generate( | ||
| 100 | + text = testText, | ||
| 101 | + sid = TtsEngine.speakerId, | ||
| 102 | + speed = TtsEngine.speed, | ||
| 103 | + ) | ||
| 104 | + | ||
| 105 | + val filename = | ||
| 106 | + application.filesDir.absolutePath + "/generated.wav" | ||
| 107 | + val ok = | ||
| 108 | + audio.samples.size > 0 && audio.save(filename) | ||
| 109 | + | ||
| 110 | + if (ok) { | ||
| 111 | + val mediaPlayer = MediaPlayer.create( | ||
| 112 | + applicationContext, | ||
| 113 | + Uri.fromFile(File(filename)) | ||
| 114 | + ) | ||
| 115 | + mediaPlayer.start() | ||
| 116 | + } else { | ||
| 117 | + Log.i(TAG, "Failed to generate or save audio") | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + }) { | ||
| 121 | + Text("Test") | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + Button( | ||
| 125 | + modifier = Modifier.padding(20.dp), | ||
| 126 | + onClick = { | ||
| 127 | + TtsEngine.speakerId = 0 | ||
| 128 | + TtsEngine.speed = 1.0f | ||
| 129 | + testText = "" | ||
| 130 | + }) { | ||
| 131 | + Text("Reset") | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | + } | ||
| 137 | + } | ||
| 138 | + } | ||
| 139 | + } | ||
| 140 | + } | ||
| 141 | +} |
| 1 | +../../../../../../../../../../../SherpaOnnxTts/app/src/main/java/com/k2fsa/sherpa/onnx/Tts.kt |
android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/TtsEngine.kt
0 → 100644
| 1 | +package com.k2fsa.sherpa.onnx.tts.engine | ||
| 2 | + | ||
| 3 | +import android.app.Application | ||
| 4 | +import android.content.res.AssetManager | ||
| 5 | +import android.util.Log | ||
| 6 | +import androidx.compose.runtime.MutableState | ||
| 7 | +import androidx.compose.runtime.mutableStateOf | ||
| 8 | +import com.k2fsa.sherpa.onnx.* | ||
| 9 | +import java.io.File | ||
| 10 | +import java.io.FileOutputStream | ||
| 11 | +import java.io.IOException | ||
| 12 | + | ||
| 13 | +object TtsEngine { | ||
| 14 | + var tts: OfflineTts? = null | ||
| 15 | + | ||
| 16 | + // https://en.wikipedia.org/wiki/ISO_639-3 | ||
| 17 | + // Example: | ||
| 18 | + // eng for English, | ||
| 19 | + // deu for German | ||
| 20 | + // cmn for Mandarin | ||
| 21 | + var lang: String? = null | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + val speedState: MutableState<Float> = mutableStateOf(1.0F) | ||
| 26 | + val speakerIdState: MutableState<Int> = mutableStateOf(0) | ||
| 27 | + | ||
| 28 | + var speed: Float | ||
| 29 | + get() = speedState.value | ||
| 30 | + set(value) { | ||
| 31 | + speedState.value = value | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + var speakerId: Int | ||
| 35 | + get() = speakerIdState.value | ||
| 36 | + set(value) { | ||
| 37 | + speakerIdState.value = value | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + private var modelDir: String? = null | ||
| 41 | + private var modelName: String? = null | ||
| 42 | + private var ruleFsts: String? = null | ||
| 43 | + private var lexicon: String? = null | ||
| 44 | + private var dataDir: String? = null | ||
| 45 | + private var assets: AssetManager? = null | ||
| 46 | + | ||
| 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 | + | ||
| 60 | + // The purpose of such a design is to make the CI test easier | ||
| 61 | + // Please see | ||
| 62 | + // https://github.com/k2-fsa/sherpa-onnx/blob/master/scripts/apk/generate-tts-apk-script.py | ||
| 63 | + modelDir = null | ||
| 64 | + modelName = null | ||
| 65 | + ruleFsts = null | ||
| 66 | + lexicon = null | ||
| 67 | + dataDir = null | ||
| 68 | + lang = null | ||
| 69 | + | ||
| 70 | + // Please enable one and only one of the examples below | ||
| 71 | + | ||
| 72 | + // Example 1: | ||
| 73 | + // modelDir = "vits-vctk" | ||
| 74 | + // modelName = "vits-vctk.onnx" | ||
| 75 | + // lexicon = "lexicon.txt" | ||
| 76 | + // lang = "eng" | ||
| 77 | + | ||
| 78 | + // Example 2: | ||
| 79 | + // https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models | ||
| 80 | + // https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-piper-en_US-amy-low.tar.bz2 | ||
| 81 | + // modelDir = "vits-piper-en_US-amy-low" | ||
| 82 | + // modelName = "en_US-amy-low.onnx" | ||
| 83 | + // dataDir = "vits-piper-en_US-amy-low/espeak-ng-data" | ||
| 84 | + // lang = "eng" | ||
| 85 | + | ||
| 86 | + // Example 3: | ||
| 87 | + // modelDir = "vits-zh-aishell3" | ||
| 88 | + // modelName = "vits-aishell3.onnx" | ||
| 89 | + // ruleFsts = "vits-zh-aishell3/rule.fst" | ||
| 90 | + // lexcion = "lexicon.txt" | ||
| 91 | + // lang = "zho" | ||
| 92 | + | ||
| 93 | + if (dataDir != null) { | ||
| 94 | + val newDir = copyDataDir(modelDir!!) | ||
| 95 | + modelDir = newDir + "/" + modelDir | ||
| 96 | + dataDir = newDir + "/" + dataDir | ||
| 97 | + assets = null | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + val config = getOfflineTtsConfig( | ||
| 101 | + modelDir = modelDir!!, modelName = modelName!!, lexicon = lexicon ?: "", | ||
| 102 | + dataDir = dataDir ?: "", | ||
| 103 | + ruleFsts = ruleFsts ?: "" | ||
| 104 | + )!! | ||
| 105 | + | ||
| 106 | + tts = OfflineTts(assetManager = assets, config = config) | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + | ||
| 110 | + private fun copyDataDir(dataDir: String): String { | ||
| 111 | + println("data dir is $dataDir") | ||
| 112 | + copyAssets(dataDir) | ||
| 113 | + | ||
| 114 | + val newDataDir = application!!.getExternalFilesDir(null)!!.absolutePath | ||
| 115 | + println("newDataDir: $newDataDir") | ||
| 116 | + return newDataDir | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + private fun copyAssets(path: String) { | ||
| 120 | + val assets: Array<String>? | ||
| 121 | + try { | ||
| 122 | + assets = application!!.assets.list(path) | ||
| 123 | + if (assets!!.isEmpty()) { | ||
| 124 | + copyFile(path) | ||
| 125 | + } else { | ||
| 126 | + val fullPath = "${application!!.getExternalFilesDir(null)}/$path" | ||
| 127 | + val dir = File(fullPath) | ||
| 128 | + dir.mkdirs() | ||
| 129 | + for (asset in assets.iterator()) { | ||
| 130 | + val p: String = if (path == "") "" else path + "/" | ||
| 131 | + copyAssets(p + asset) | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + } catch (ex: IOException) { | ||
| 135 | + Log.e(TAG, "Failed to copy $path. ${ex.toString()}") | ||
| 136 | + } | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + private fun copyFile(filename: String) { | ||
| 140 | + try { | ||
| 141 | + val istream = application!!.assets.open(filename) | ||
| 142 | + val newFilename = application!!.getExternalFilesDir(null).toString() + "/" + filename | ||
| 143 | + val ostream = FileOutputStream(newFilename) | ||
| 144 | + // Log.i(TAG, "Copying $filename to $newFilename") | ||
| 145 | + val buffer = ByteArray(1024) | ||
| 146 | + var read = 0 | ||
| 147 | + while (read != -1) { | ||
| 148 | + ostream.write(buffer, 0, read) | ||
| 149 | + read = istream.read(buffer) | ||
| 150 | + } | ||
| 151 | + istream.close() | ||
| 152 | + ostream.flush() | ||
| 153 | + ostream.close() | ||
| 154 | + } catch (ex: Exception) { | ||
| 155 | + Log.e(TAG, "Failed to copy $filename, ${ex.toString()}") | ||
| 156 | + } | ||
| 157 | + } | ||
| 158 | +} |
android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/TtsService.kt
0 → 100644
| 1 | +package com.k2fsa.sherpa.onnx.tts.engine | ||
| 2 | + | ||
| 3 | +import android.media.AudioFormat | ||
| 4 | +import android.speech.tts.SynthesisCallback | ||
| 5 | +import android.speech.tts.SynthesisRequest | ||
| 6 | +import android.speech.tts.TextToSpeech | ||
| 7 | +import android.speech.tts.TextToSpeechService | ||
| 8 | +import android.util.Log | ||
| 9 | +import com.k2fsa.sherpa.onnx.* | ||
| 10 | + | ||
| 11 | +/* | ||
| 12 | +https://developer.android.com/reference/java/util/Locale#getISO3Language() | ||
| 13 | +https://developer.android.com/reference/java/util/Locale#getISO3Country() | ||
| 14 | + | ||
| 15 | +eng, USA, | ||
| 16 | +eng, USA, POSIX | ||
| 17 | +eng, | ||
| 18 | +eng, GBR | ||
| 19 | +afr, | ||
| 20 | +afr, NAM | ||
| 21 | +afr, ZAF | ||
| 22 | +agq | ||
| 23 | +agq, CMR | ||
| 24 | +aka, | ||
| 25 | +aka, GHA | ||
| 26 | +amh, | ||
| 27 | +amh, ETH | ||
| 28 | +ara, | ||
| 29 | +ara, 001 | ||
| 30 | +ara, ARE | ||
| 31 | +ara, BHR, | ||
| 32 | +deu | ||
| 33 | +deu, AUT | ||
| 34 | +deu, BEL | ||
| 35 | +deu, CHE | ||
| 36 | +deu, ITA | ||
| 37 | +deu, ITA | ||
| 38 | +deu, LIE | ||
| 39 | +deu, LUX | ||
| 40 | +spa, | ||
| 41 | +spa, 419 | ||
| 42 | +spa, ARG, | ||
| 43 | +spa, BRA | ||
| 44 | +fra, | ||
| 45 | +fra, BEL, | ||
| 46 | +fra, FRA, | ||
| 47 | + | ||
| 48 | +E Failed to check TTS data, no activity found for Intent | ||
| 49 | +{ act=android.speech.tts.engine.CHECK_TTS_DATA pkg=com.k2fsa.sherpa.chapter5 }) | ||
| 50 | + | ||
| 51 | +E Failed to get default language from engine com.k2fsa.sherpa.chapter5 | ||
| 52 | +Engine failed voice data integrity check (null return)com.k2fsa.sherpa.chapter5 | ||
| 53 | +Failed to get default language from engine com.k2fsa.sherpa.chapter5 | ||
| 54 | + | ||
| 55 | +*/ | ||
| 56 | + | ||
| 57 | +class TtsService : TextToSpeechService() { | ||
| 58 | + override fun onCreate() { | ||
| 59 | + super.onCreate() | ||
| 60 | + | ||
| 61 | + // see https://github.com/Miserlou/Android-SDK-Samples/blob/master/TtsEngine/src/com/example/android/ttsengine/RobotSpeakTtsService.java#L68 | ||
| 62 | + onLoadLanguage(TtsEngine.lang, "", "") | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + // https://developer.android.com/reference/kotlin/android/speech/tts/TextToSpeechService#onislanguageavailable | ||
| 66 | + override fun onIsLanguageAvailable(_lang: String?, _country: String?, _variant: String?): Int { | ||
| 67 | + val lang = _lang ?: "" | ||
| 68 | + | ||
| 69 | + if (lang == TtsEngine.lang) { | ||
| 70 | + return TextToSpeech.LANG_AVAILABLE | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + return TextToSpeech.LANG_NOT_SUPPORTED | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + override fun onGetLanguage(): Array<String> { | ||
| 77 | + return arrayOf(TtsEngine.lang!!, "", "") | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + // https://developer.android.com/reference/kotlin/android/speech/tts/TextToSpeechService#onLoadLanguage(kotlin.String,%20kotlin.String,%20kotlin.String) | ||
| 81 | + override fun onLoadLanguage(_lang: String?, _country: String?, _variant: String?): Int { | ||
| 82 | + val lang = _lang ?: "" | ||
| 83 | + | ||
| 84 | + if (lang == TtsEngine.lang) { | ||
| 85 | + TtsEngine.createTts(application) | ||
| 86 | + return TextToSpeech.LANG_AVAILABLE | ||
| 87 | + } else { | ||
| 88 | + return TextToSpeech.LANG_NOT_SUPPORTED | ||
| 89 | + } | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + override fun onStop() {} | ||
| 93 | + | ||
| 94 | + override fun onSynthesizeText(request: SynthesisRequest?, callback: SynthesisCallback?) { | ||
| 95 | + if (request == null || callback == null) { | ||
| 96 | + return | ||
| 97 | + } | ||
| 98 | + val language = request.language | ||
| 99 | + val country = request.country | ||
| 100 | + val variant = request.variant | ||
| 101 | + val text = request.charSequenceText.toString() | ||
| 102 | + | ||
| 103 | + val ret = onIsLanguageAvailable(language, country, variant) | ||
| 104 | + if (ret == TextToSpeech.LANG_NOT_SUPPORTED) { | ||
| 105 | + callback.error() | ||
| 106 | + return | ||
| 107 | + } | ||
| 108 | + Log.i(TAG, "text: $text") | ||
| 109 | + val tts = TtsEngine.tts!! | ||
| 110 | + | ||
| 111 | + // Note that AudioFormat.ENCODING_PCM_FLOAT requires API level >= 24 | ||
| 112 | + // callback.start(tts.sampleRate(), AudioFormat.ENCODING_PCM_FLOAT, 1) | ||
| 113 | + | ||
| 114 | + callback.start(tts.sampleRate(), AudioFormat.ENCODING_PCM_16BIT, 1) | ||
| 115 | + | ||
| 116 | + if (text.isBlank() || text.isEmpty()) { | ||
| 117 | + callback.done() | ||
| 118 | + return | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + val ttsCallback = {floatSamples: FloatArray -> | ||
| 122 | + // convert FloatArray to ByteArray | ||
| 123 | + val samples = floatArrayToByteArray(floatSamples) | ||
| 124 | + val maxBufferSize: Int = callback.maxBufferSize | ||
| 125 | + var offset = 0 | ||
| 126 | + while (offset < samples.size) { | ||
| 127 | + val bytesToWrite = Math.min(maxBufferSize, samples.size - offset) | ||
| 128 | + callback.audioAvailable(samples, offset, bytesToWrite) | ||
| 129 | + offset += bytesToWrite | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + Log.i(TAG, "text: $text") | ||
| 135 | + tts.generateWithCallback( | ||
| 136 | + text = text, | ||
| 137 | + sid = TtsEngine.speakerId, | ||
| 138 | + speed = TtsEngine.speed, | ||
| 139 | + callback=ttsCallback, | ||
| 140 | + ) | ||
| 141 | + | ||
| 142 | + callback.done() | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + private fun floatArrayToByteArray(audio: FloatArray): ByteArray { | ||
| 146 | + // byteArray is actually a ShortArray | ||
| 147 | + val byteArray = ByteArray(audio.size * 2) | ||
| 148 | + for (i in audio.indices) { | ||
| 149 | + val sample = (audio[i] * 32767).toInt() | ||
| 150 | + byteArray[2 * i] = sample.toByte() | ||
| 151 | + byteArray[2 * i + 1] = (sample shr 8).toByte() | ||
| 152 | + } | ||
| 153 | + return byteArray | ||
| 154 | + } | ||
| 155 | +} |
android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/ui/theme/Color.kt
0 → 100644
| 1 | +package com.k2fsa.sherpa.onnx.tts.engine.ui.theme | ||
| 2 | + | ||
| 3 | +import androidx.compose.ui.graphics.Color | ||
| 4 | + | ||
| 5 | +val Purple80 = Color(0xFFD0BCFF) | ||
| 6 | +val PurpleGrey80 = Color(0xFFCCC2DC) | ||
| 7 | +val Pink80 = Color(0xFFEFB8C8) | ||
| 8 | + | ||
| 9 | +val Purple40 = Color(0xFF6650a4) | ||
| 10 | +val PurpleGrey40 = Color(0xFF625b71) | ||
| 11 | +val Pink40 = Color(0xFF7D5260) |
android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/ui/theme/Theme.kt
0 → 100644
| 1 | +package com.k2fsa.sherpa.onnx.tts.engine.ui.theme | ||
| 2 | + | ||
| 3 | +import android.app.Activity | ||
| 4 | +import android.os.Build | ||
| 5 | +import androidx.compose.foundation.isSystemInDarkTheme | ||
| 6 | +import androidx.compose.material3.MaterialTheme | ||
| 7 | +import androidx.compose.material3.darkColorScheme | ||
| 8 | +import androidx.compose.material3.dynamicDarkColorScheme | ||
| 9 | +import androidx.compose.material3.dynamicLightColorScheme | ||
| 10 | +import androidx.compose.material3.lightColorScheme | ||
| 11 | +import androidx.compose.runtime.Composable | ||
| 12 | +import androidx.compose.runtime.SideEffect | ||
| 13 | +import androidx.compose.ui.graphics.toArgb | ||
| 14 | +import androidx.compose.ui.platform.LocalContext | ||
| 15 | +import androidx.compose.ui.platform.LocalView | ||
| 16 | +import androidx.core.view.WindowCompat | ||
| 17 | + | ||
| 18 | +private val DarkColorScheme = darkColorScheme( | ||
| 19 | + primary = Purple80, | ||
| 20 | + secondary = PurpleGrey80, | ||
| 21 | + tertiary = Pink80 | ||
| 22 | +) | ||
| 23 | + | ||
| 24 | +private val LightColorScheme = lightColorScheme( | ||
| 25 | + primary = Purple40, | ||
| 26 | + secondary = PurpleGrey40, | ||
| 27 | + tertiary = Pink40 | ||
| 28 | + | ||
| 29 | + /* Other default colors to override | ||
| 30 | + background = Color(0xFFFFFBFE), | ||
| 31 | + surface = Color(0xFFFFFBFE), | ||
| 32 | + onPrimary = Color.White, | ||
| 33 | + onSecondary = Color.White, | ||
| 34 | + onTertiary = Color.White, | ||
| 35 | + onBackground = Color(0xFF1C1B1F), | ||
| 36 | + onSurface = Color(0xFF1C1B1F), | ||
| 37 | + */ | ||
| 38 | +) | ||
| 39 | + | ||
| 40 | +@Composable | ||
| 41 | +fun SherpaOnnxTtsEngineTheme( | ||
| 42 | + darkTheme: Boolean = isSystemInDarkTheme(), | ||
| 43 | + // Dynamic color is available on Android 12+ | ||
| 44 | + dynamicColor: Boolean = true, | ||
| 45 | + content: @Composable () -> Unit | ||
| 46 | +) { | ||
| 47 | + val colorScheme = when { | ||
| 48 | + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { | ||
| 49 | + val context = LocalContext.current | ||
| 50 | + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + darkTheme -> DarkColorScheme | ||
| 54 | + else -> LightColorScheme | ||
| 55 | + } | ||
| 56 | + val view = LocalView.current | ||
| 57 | + if (!view.isInEditMode) { | ||
| 58 | + SideEffect { | ||
| 59 | + val window = (view.context as Activity).window | ||
| 60 | + window.statusBarColor = colorScheme.primary.toArgb() | ||
| 61 | + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme | ||
| 62 | + } | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + MaterialTheme( | ||
| 66 | + colorScheme = colorScheme, | ||
| 67 | + typography = Typography, | ||
| 68 | + content = content | ||
| 69 | + ) | ||
| 70 | +} |
android/SherpaOnnxTtsEngine/app/src/main/java/com/k2fsa/sherpa/onnx/tts/engine/ui/theme/Type.kt
0 → 100644
| 1 | +package com.k2fsa.sherpa.onnx.tts.engine.ui.theme | ||
| 2 | + | ||
| 3 | +import androidx.compose.material3.Typography | ||
| 4 | +import androidx.compose.ui.text.TextStyle | ||
| 5 | +import androidx.compose.ui.text.font.FontFamily | ||
| 6 | +import androidx.compose.ui.text.font.FontWeight | ||
| 7 | +import androidx.compose.ui.unit.sp | ||
| 8 | + | ||
| 9 | +// Set of Material typography styles to start with | ||
| 10 | +val Typography = Typography( | ||
| 11 | + bodyLarge = TextStyle( | ||
| 12 | + fontFamily = FontFamily.Default, | ||
| 13 | + fontWeight = FontWeight.Normal, | ||
| 14 | + fontSize = 16.sp, | ||
| 15 | + lineHeight = 24.sp, | ||
| 16 | + letterSpacing = 0.5.sp | ||
| 17 | + ) | ||
| 18 | + /* Other default text styles to override | ||
| 19 | + titleLarge = TextStyle( | ||
| 20 | + fontFamily = FontFamily.Default, | ||
| 21 | + fontWeight = FontWeight.Normal, | ||
| 22 | + fontSize = 22.sp, | ||
| 23 | + lineHeight = 28.sp, | ||
| 24 | + letterSpacing = 0.sp | ||
| 25 | + ), | ||
| 26 | + labelSmall = TextStyle( | ||
| 27 | + fontFamily = FontFamily.Default, | ||
| 28 | + fontWeight = FontWeight.Medium, | ||
| 29 | + fontSize = 11.sp, | ||
| 30 | + lineHeight = 16.sp, | ||
| 31 | + letterSpacing = 0.5.sp | ||
| 32 | + ) | ||
| 33 | + */ | ||
| 34 | +) |
| 1 | +<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 2 | + xmlns:aapt="http://schemas.android.com/aapt" | ||
| 3 | + android:width="108dp" | ||
| 4 | + android:height="108dp" | ||
| 5 | + android:viewportWidth="108" | ||
| 6 | + android:viewportHeight="108"> | ||
| 7 | + <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> | ||
| 8 | + <aapt:attr name="android:fillColor"> | ||
| 9 | + <gradient | ||
| 10 | + android:endX="85.84757" | ||
| 11 | + android:endY="92.4963" | ||
| 12 | + android:startX="42.9492" | ||
| 13 | + android:startY="49.59793" | ||
| 14 | + android:type="linear"> | ||
| 15 | + <item | ||
| 16 | + android:color="#44000000" | ||
| 17 | + android:offset="0.0" /> | ||
| 18 | + <item | ||
| 19 | + android:color="#00000000" | ||
| 20 | + android:offset="1.0" /> | ||
| 21 | + </gradient> | ||
| 22 | + </aapt:attr> | ||
| 23 | + </path> | ||
| 24 | + <path | ||
| 25 | + android:fillColor="#FFFFFF" | ||
| 26 | + android:fillType="nonZero" | ||
| 27 | + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" | ||
| 28 | + android:strokeWidth="1" | ||
| 29 | + android:strokeColor="#00000000" /> | ||
| 30 | +</vector> |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | + android:width="108dp" | ||
| 4 | + android:height="108dp" | ||
| 5 | + android:viewportWidth="108" | ||
| 6 | + android:viewportHeight="108"> | ||
| 7 | + <path | ||
| 8 | + android:fillColor="#3DDC84" | ||
| 9 | + android:pathData="M0,0h108v108h-108z" /> | ||
| 10 | + <path | ||
| 11 | + android:fillColor="#00000000" | ||
| 12 | + android:pathData="M9,0L9,108" | ||
| 13 | + android:strokeWidth="0.8" | ||
| 14 | + android:strokeColor="#33FFFFFF" /> | ||
| 15 | + <path | ||
| 16 | + android:fillColor="#00000000" | ||
| 17 | + android:pathData="M19,0L19,108" | ||
| 18 | + android:strokeWidth="0.8" | ||
| 19 | + android:strokeColor="#33FFFFFF" /> | ||
| 20 | + <path | ||
| 21 | + android:fillColor="#00000000" | ||
| 22 | + android:pathData="M29,0L29,108" | ||
| 23 | + android:strokeWidth="0.8" | ||
| 24 | + android:strokeColor="#33FFFFFF" /> | ||
| 25 | + <path | ||
| 26 | + android:fillColor="#00000000" | ||
| 27 | + android:pathData="M39,0L39,108" | ||
| 28 | + android:strokeWidth="0.8" | ||
| 29 | + android:strokeColor="#33FFFFFF" /> | ||
| 30 | + <path | ||
| 31 | + android:fillColor="#00000000" | ||
| 32 | + android:pathData="M49,0L49,108" | ||
| 33 | + android:strokeWidth="0.8" | ||
| 34 | + android:strokeColor="#33FFFFFF" /> | ||
| 35 | + <path | ||
| 36 | + android:fillColor="#00000000" | ||
| 37 | + android:pathData="M59,0L59,108" | ||
| 38 | + android:strokeWidth="0.8" | ||
| 39 | + android:strokeColor="#33FFFFFF" /> | ||
| 40 | + <path | ||
| 41 | + android:fillColor="#00000000" | ||
| 42 | + android:pathData="M69,0L69,108" | ||
| 43 | + android:strokeWidth="0.8" | ||
| 44 | + android:strokeColor="#33FFFFFF" /> | ||
| 45 | + <path | ||
| 46 | + android:fillColor="#00000000" | ||
| 47 | + android:pathData="M79,0L79,108" | ||
| 48 | + android:strokeWidth="0.8" | ||
| 49 | + android:strokeColor="#33FFFFFF" /> | ||
| 50 | + <path | ||
| 51 | + android:fillColor="#00000000" | ||
| 52 | + android:pathData="M89,0L89,108" | ||
| 53 | + android:strokeWidth="0.8" | ||
| 54 | + android:strokeColor="#33FFFFFF" /> | ||
| 55 | + <path | ||
| 56 | + android:fillColor="#00000000" | ||
| 57 | + android:pathData="M99,0L99,108" | ||
| 58 | + android:strokeWidth="0.8" | ||
| 59 | + android:strokeColor="#33FFFFFF" /> | ||
| 60 | + <path | ||
| 61 | + android:fillColor="#00000000" | ||
| 62 | + android:pathData="M0,9L108,9" | ||
| 63 | + android:strokeWidth="0.8" | ||
| 64 | + android:strokeColor="#33FFFFFF" /> | ||
| 65 | + <path | ||
| 66 | + android:fillColor="#00000000" | ||
| 67 | + android:pathData="M0,19L108,19" | ||
| 68 | + android:strokeWidth="0.8" | ||
| 69 | + android:strokeColor="#33FFFFFF" /> | ||
| 70 | + <path | ||
| 71 | + android:fillColor="#00000000" | ||
| 72 | + android:pathData="M0,29L108,29" | ||
| 73 | + android:strokeWidth="0.8" | ||
| 74 | + android:strokeColor="#33FFFFFF" /> | ||
| 75 | + <path | ||
| 76 | + android:fillColor="#00000000" | ||
| 77 | + android:pathData="M0,39L108,39" | ||
| 78 | + android:strokeWidth="0.8" | ||
| 79 | + android:strokeColor="#33FFFFFF" /> | ||
| 80 | + <path | ||
| 81 | + android:fillColor="#00000000" | ||
| 82 | + android:pathData="M0,49L108,49" | ||
| 83 | + android:strokeWidth="0.8" | ||
| 84 | + android:strokeColor="#33FFFFFF" /> | ||
| 85 | + <path | ||
| 86 | + android:fillColor="#00000000" | ||
| 87 | + android:pathData="M0,59L108,59" | ||
| 88 | + android:strokeWidth="0.8" | ||
| 89 | + android:strokeColor="#33FFFFFF" /> | ||
| 90 | + <path | ||
| 91 | + android:fillColor="#00000000" | ||
| 92 | + android:pathData="M0,69L108,69" | ||
| 93 | + android:strokeWidth="0.8" | ||
| 94 | + android:strokeColor="#33FFFFFF" /> | ||
| 95 | + <path | ||
| 96 | + android:fillColor="#00000000" | ||
| 97 | + android:pathData="M0,79L108,79" | ||
| 98 | + android:strokeWidth="0.8" | ||
| 99 | + android:strokeColor="#33FFFFFF" /> | ||
| 100 | + <path | ||
| 101 | + android:fillColor="#00000000" | ||
| 102 | + android:pathData="M0,89L108,89" | ||
| 103 | + android:strokeWidth="0.8" | ||
| 104 | + android:strokeColor="#33FFFFFF" /> | ||
| 105 | + <path | ||
| 106 | + android:fillColor="#00000000" | ||
| 107 | + android:pathData="M0,99L108,99" | ||
| 108 | + android:strokeWidth="0.8" | ||
| 109 | + android:strokeColor="#33FFFFFF" /> | ||
| 110 | + <path | ||
| 111 | + android:fillColor="#00000000" | ||
| 112 | + android:pathData="M19,29L89,29" | ||
| 113 | + android:strokeWidth="0.8" | ||
| 114 | + android:strokeColor="#33FFFFFF" /> | ||
| 115 | + <path | ||
| 116 | + android:fillColor="#00000000" | ||
| 117 | + android:pathData="M19,39L89,39" | ||
| 118 | + android:strokeWidth="0.8" | ||
| 119 | + android:strokeColor="#33FFFFFF" /> | ||
| 120 | + <path | ||
| 121 | + android:fillColor="#00000000" | ||
| 122 | + android:pathData="M19,49L89,49" | ||
| 123 | + android:strokeWidth="0.8" | ||
| 124 | + android:strokeColor="#33FFFFFF" /> | ||
| 125 | + <path | ||
| 126 | + android:fillColor="#00000000" | ||
| 127 | + android:pathData="M19,59L89,59" | ||
| 128 | + android:strokeWidth="0.8" | ||
| 129 | + android:strokeColor="#33FFFFFF" /> | ||
| 130 | + <path | ||
| 131 | + android:fillColor="#00000000" | ||
| 132 | + android:pathData="M19,69L89,69" | ||
| 133 | + android:strokeWidth="0.8" | ||
| 134 | + android:strokeColor="#33FFFFFF" /> | ||
| 135 | + <path | ||
| 136 | + android:fillColor="#00000000" | ||
| 137 | + android:pathData="M19,79L89,79" | ||
| 138 | + android:strokeWidth="0.8" | ||
| 139 | + android:strokeColor="#33FFFFFF" /> | ||
| 140 | + <path | ||
| 141 | + android:fillColor="#00000000" | ||
| 142 | + android:pathData="M29,19L29,89" | ||
| 143 | + android:strokeWidth="0.8" | ||
| 144 | + android:strokeColor="#33FFFFFF" /> | ||
| 145 | + <path | ||
| 146 | + android:fillColor="#00000000" | ||
| 147 | + android:pathData="M39,19L39,89" | ||
| 148 | + android:strokeWidth="0.8" | ||
| 149 | + android:strokeColor="#33FFFFFF" /> | ||
| 150 | + <path | ||
| 151 | + android:fillColor="#00000000" | ||
| 152 | + android:pathData="M49,19L49,89" | ||
| 153 | + android:strokeWidth="0.8" | ||
| 154 | + android:strokeColor="#33FFFFFF" /> | ||
| 155 | + <path | ||
| 156 | + android:fillColor="#00000000" | ||
| 157 | + android:pathData="M59,19L59,89" | ||
| 158 | + android:strokeWidth="0.8" | ||
| 159 | + android:strokeColor="#33FFFFFF" /> | ||
| 160 | + <path | ||
| 161 | + android:fillColor="#00000000" | ||
| 162 | + android:pathData="M69,19L69,89" | ||
| 163 | + android:strokeWidth="0.8" | ||
| 164 | + android:strokeColor="#33FFFFFF" /> | ||
| 165 | + <path | ||
| 166 | + android:fillColor="#00000000" | ||
| 167 | + android:pathData="M79,19L79,89" | ||
| 168 | + android:strokeWidth="0.8" | ||
| 169 | + android:strokeColor="#33FFFFFF" /> | ||
| 170 | +</vector> |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | + <background android:drawable="@drawable/ic_launcher_background" /> | ||
| 4 | + <foreground android:drawable="@drawable/ic_launcher_foreground" /> | ||
| 5 | + <monochrome android:drawable="@drawable/ic_launcher_foreground" /> | ||
| 6 | +</adaptive-icon> |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| 3 | + <background android:drawable="@drawable/ic_launcher_background" /> | ||
| 4 | + <foreground android:drawable="@drawable/ic_launcher_foreground" /> | ||
| 5 | + <monochrome android:drawable="@drawable/ic_launcher_foreground" /> | ||
| 6 | +</adaptive-icon> |
不能预览此文件类型
不能预览此文件类型
不能预览此文件类型
不能预览此文件类型
不能预览此文件类型
不能预览此文件类型
不能预览此文件类型
不能预览此文件类型
不能预览此文件类型
不能预览此文件类型
| 1 | +<?xml version="1.0" encoding="utf-8"?> | ||
| 2 | +<resources> | ||
| 3 | + <color name="purple_200">#FFBB86FC</color> | ||
| 4 | + <color name="purple_500">#FF6200EE</color> | ||
| 5 | + <color name="purple_700">#FF3700B3</color> | ||
| 6 | + <color name="teal_200">#FF03DAC5</color> | ||
| 7 | + <color name="teal_700">#FF018786</color> | ||
| 8 | + <color name="black">#FF000000</color> | ||
| 9 | + <color name="white">#FFFFFFFF</color> | ||
| 10 | +</resources> |
| 1 | +<?xml version="1.0" encoding="utf-8"?><!-- | ||
| 2 | + Sample backup rules file; uncomment and customize as necessary. | ||
| 3 | + See https://developer.android.com/guide/topics/data/autobackup | ||
| 4 | + for details. | ||
| 5 | + Note: This file is ignored for devices older that API 31 | ||
| 6 | + See https://developer.android.com/about/versions/12/backup-restore | ||
| 7 | +--> | ||
| 8 | +<full-backup-content> | ||
| 9 | + <!-- | ||
| 10 | + <include domain="sharedpref" path="."/> | ||
| 11 | + <exclude domain="sharedpref" path="device.xml"/> | ||
| 12 | +--> | ||
| 13 | +</full-backup-content> |
| 1 | +<?xml version="1.0" encoding="utf-8"?><!-- | ||
| 2 | + Sample data extraction rules file; uncomment and customize as necessary. | ||
| 3 | + See https://developer.android.com/about/versions/12/backup-restore#xml-changes | ||
| 4 | + for details. | ||
| 5 | +--> | ||
| 6 | +<data-extraction-rules> | ||
| 7 | + <cloud-backup> | ||
| 8 | + <!-- TODO: Use <include> and <exclude> to control what is backed up. | ||
| 9 | + <include .../> | ||
| 10 | + <exclude .../> | ||
| 11 | + --> | ||
| 12 | + </cloud-backup> | ||
| 13 | + <!-- | ||
| 14 | + <device-transfer> | ||
| 15 | + <include .../> | ||
| 16 | + <exclude .../> | ||
| 17 | + </device-transfer> | ||
| 18 | + --> | ||
| 19 | +</data-extraction-rules> |
android/SherpaOnnxTtsEngine/app/src/test/java/com/k2fsa/sherpa/onnx/tts/engine/ExampleUnitTest.kt
0 → 100644
| 1 | +package com.k2fsa.sherpa.onnx.tts.engine | ||
| 2 | + | ||
| 3 | +import org.junit.Test | ||
| 4 | + | ||
| 5 | +import org.junit.Assert.* | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * Example local unit test, which will execute on the development machine (host). | ||
| 9 | + * | ||
| 10 | + * See [testing documentation](http://d.android.com/tools/testing). | ||
| 11 | + */ | ||
| 12 | +class ExampleUnitTest { | ||
| 13 | + @Test | ||
| 14 | + fun addition_isCorrect() { | ||
| 15 | + assertEquals(4, 2 + 2) | ||
| 16 | + } | ||
| 17 | +} |
android/SherpaOnnxTtsEngine/build.gradle.kts
0 → 100644
| 1 | +# Project-wide Gradle settings. | ||
| 2 | +# IDE (e.g. Android Studio) users: | ||
| 3 | +# Gradle settings configured through the IDE *will override* | ||
| 4 | +# any settings specified in this file. | ||
| 5 | +# For more details on how to configure your build environment visit | ||
| 6 | +# http://www.gradle.org/docs/current/userguide/build_environment.html | ||
| 7 | +# Specifies the JVM arguments used for the daemon process. | ||
| 8 | +# The setting is particularly useful for tweaking memory settings. | ||
| 9 | +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 | ||
| 10 | +# When configured, Gradle will run in incubating parallel mode. | ||
| 11 | +# This option should only be used with decoupled projects. More details, visit | ||
| 12 | +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | ||
| 13 | +# org.gradle.parallel=true | ||
| 14 | +# AndroidX package structure to make it clearer which packages are bundled with the | ||
| 15 | +# Android operating system, and which are packaged with your app's APK | ||
| 16 | +# https://developer.android.com/topic/libraries/support-library/androidx-rn | ||
| 17 | +android.useAndroidX=true | ||
| 18 | +# Kotlin code style for this project: "official" or "obsolete": | ||
| 19 | +kotlin.code.style=official | ||
| 20 | +# Enables namespacing of each library's R class so that its R class includes only the | ||
| 21 | +# resources declared in the library itself and none from the library's dependencies, | ||
| 22 | +# thereby reducing the size of the R class for that library | ||
| 23 | +android.nonTransitiveRClass=true |
不能预览此文件类型
android/SherpaOnnxTtsEngine/gradlew
0 → 100755
| 1 | +#!/usr/bin/env sh | ||
| 2 | + | ||
| 3 | +# | ||
| 4 | +# Copyright 2015 the original author or authors. | ||
| 5 | +# | ||
| 6 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 7 | +# you may not use this file except in compliance with the License. | ||
| 8 | +# You may obtain a copy of the License at | ||
| 9 | +# | ||
| 10 | +# https://www.apache.org/licenses/LICENSE-2.0 | ||
| 11 | +# | ||
| 12 | +# Unless required by applicable law or agreed to in writing, software | ||
| 13 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
| 14 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 15 | +# See the License for the specific language governing permissions and | ||
| 16 | +# limitations under the License. | ||
| 17 | +# | ||
| 18 | + | ||
| 19 | +############################################################################## | ||
| 20 | +## | ||
| 21 | +## Gradle start up script for UN*X | ||
| 22 | +## | ||
| 23 | +############################################################################## | ||
| 24 | + | ||
| 25 | +# Attempt to set APP_HOME | ||
| 26 | +# Resolve links: $0 may be a link | ||
| 27 | +PRG="$0" | ||
| 28 | +# Need this for relative symlinks. | ||
| 29 | +while [ -h "$PRG" ] ; do | ||
| 30 | + ls=`ls -ld "$PRG"` | ||
| 31 | + link=`expr "$ls" : '.*-> \(.*\)$'` | ||
| 32 | + if expr "$link" : '/.*' > /dev/null; then | ||
| 33 | + PRG="$link" | ||
| 34 | + else | ||
| 35 | + PRG=`dirname "$PRG"`"/$link" | ||
| 36 | + fi | ||
| 37 | +done | ||
| 38 | +SAVED="`pwd`" | ||
| 39 | +cd "`dirname \"$PRG\"`/" >/dev/null | ||
| 40 | +APP_HOME="`pwd -P`" | ||
| 41 | +cd "$SAVED" >/dev/null | ||
| 42 | + | ||
| 43 | +APP_NAME="Gradle" | ||
| 44 | +APP_BASE_NAME=`basename "$0"` | ||
| 45 | + | ||
| 46 | +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| 47 | +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||
| 48 | + | ||
| 49 | +# Use the maximum available, or set MAX_FD != -1 to use that value. | ||
| 50 | +MAX_FD="maximum" | ||
| 51 | + | ||
| 52 | +warn () { | ||
| 53 | + echo "$*" | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +die () { | ||
| 57 | + echo | ||
| 58 | + echo "$*" | ||
| 59 | + echo | ||
| 60 | + exit 1 | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +# OS specific support (must be 'true' or 'false'). | ||
| 64 | +cygwin=false | ||
| 65 | +msys=false | ||
| 66 | +darwin=false | ||
| 67 | +nonstop=false | ||
| 68 | +case "`uname`" in | ||
| 69 | + CYGWIN* ) | ||
| 70 | + cygwin=true | ||
| 71 | + ;; | ||
| 72 | + Darwin* ) | ||
| 73 | + darwin=true | ||
| 74 | + ;; | ||
| 75 | + MINGW* ) | ||
| 76 | + msys=true | ||
| 77 | + ;; | ||
| 78 | + NONSTOP* ) | ||
| 79 | + nonstop=true | ||
| 80 | + ;; | ||
| 81 | +esac | ||
| 82 | + | ||
| 83 | +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +# Determine the Java command to use to start the JVM. | ||
| 87 | +if [ -n "$JAVA_HOME" ] ; then | ||
| 88 | + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||
| 89 | + # IBM's JDK on AIX uses strange locations for the executables | ||
| 90 | + JAVACMD="$JAVA_HOME/jre/sh/java" | ||
| 91 | + else | ||
| 92 | + JAVACMD="$JAVA_HOME/bin/java" | ||
| 93 | + fi | ||
| 94 | + if [ ! -x "$JAVACMD" ] ; then | ||
| 95 | + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||
| 96 | + | ||
| 97 | +Please set the JAVA_HOME variable in your environment to match the | ||
| 98 | +location of your Java installation." | ||
| 99 | + fi | ||
| 100 | +else | ||
| 101 | + JAVACMD="java" | ||
| 102 | + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||
| 103 | + | ||
| 104 | +Please set the JAVA_HOME variable in your environment to match the | ||
| 105 | +location of your Java installation." | ||
| 106 | +fi | ||
| 107 | + | ||
| 108 | +# Increase the maximum file descriptors if we can. | ||
| 109 | +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then | ||
| 110 | + MAX_FD_LIMIT=`ulimit -H -n` | ||
| 111 | + if [ $? -eq 0 ] ; then | ||
| 112 | + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | ||
| 113 | + MAX_FD="$MAX_FD_LIMIT" | ||
| 114 | + fi | ||
| 115 | + ulimit -n $MAX_FD | ||
| 116 | + if [ $? -ne 0 ] ; then | ||
| 117 | + warn "Could not set maximum file descriptor limit: $MAX_FD" | ||
| 118 | + fi | ||
| 119 | + else | ||
| 120 | + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | ||
| 121 | + fi | ||
| 122 | +fi | ||
| 123 | + | ||
| 124 | +# For Darwin, add options to specify how the application appears in the dock | ||
| 125 | +if $darwin; then | ||
| 126 | + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | ||
| 127 | +fi | ||
| 128 | + | ||
| 129 | +# For Cygwin or MSYS, switch paths to Windows format before running java | ||
| 130 | +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then | ||
| 131 | + APP_HOME=`cygpath --path --mixed "$APP_HOME"` | ||
| 132 | + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | ||
| 133 | + | ||
| 134 | + JAVACMD=`cygpath --unix "$JAVACMD"` | ||
| 135 | + | ||
| 136 | + # We build the pattern for arguments to be converted via cygpath | ||
| 137 | + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | ||
| 138 | + SEP="" | ||
| 139 | + for dir in $ROOTDIRSRAW ; do | ||
| 140 | + ROOTDIRS="$ROOTDIRS$SEP$dir" | ||
| 141 | + SEP="|" | ||
| 142 | + done | ||
| 143 | + OURCYGPATTERN="(^($ROOTDIRS))" | ||
| 144 | + # Add a user-defined pattern to the cygpath arguments | ||
| 145 | + if [ "$GRADLE_CYGPATTERN" != "" ] ; then | ||
| 146 | + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | ||
| 147 | + fi | ||
| 148 | + # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||
| 149 | + i=0 | ||
| 150 | + for arg in "$@" ; do | ||
| 151 | + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | ||
| 152 | + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | ||
| 153 | + | ||
| 154 | + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | ||
| 155 | + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | ||
| 156 | + else | ||
| 157 | + eval `echo args$i`="\"$arg\"" | ||
| 158 | + fi | ||
| 159 | + i=`expr $i + 1` | ||
| 160 | + done | ||
| 161 | + case $i in | ||
| 162 | + 0) set -- ;; | ||
| 163 | + 1) set -- "$args0" ;; | ||
| 164 | + 2) set -- "$args0" "$args1" ;; | ||
| 165 | + 3) set -- "$args0" "$args1" "$args2" ;; | ||
| 166 | + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; | ||
| 167 | + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | ||
| 168 | + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | ||
| 169 | + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | ||
| 170 | + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | ||
| 171 | + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | ||
| 172 | + esac | ||
| 173 | +fi | ||
| 174 | + | ||
| 175 | +# Escape application args | ||
| 176 | +save () { | ||
| 177 | + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | ||
| 178 | + echo " " | ||
| 179 | +} | ||
| 180 | +APP_ARGS=`save "$@"` | ||
| 181 | + | ||
| 182 | +# Collect all arguments for the java command, following the shell quoting and substitution rules | ||
| 183 | +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | ||
| 184 | + | ||
| 185 | +exec "$JAVACMD" "$@" |
android/SherpaOnnxTtsEngine/gradlew.bat
0 → 100644
| 1 | +@rem | ||
| 2 | +@rem Copyright 2015 the original author or authors. | ||
| 3 | +@rem | ||
| 4 | +@rem Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | +@rem you may not use this file except in compliance with the License. | ||
| 6 | +@rem You may obtain a copy of the License at | ||
| 7 | +@rem | ||
| 8 | +@rem https://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | +@rem | ||
| 10 | +@rem Unless required by applicable law or agreed to in writing, software | ||
| 11 | +@rem distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | +@rem See the License for the specific language governing permissions and | ||
| 14 | +@rem limitations under the License. | ||
| 15 | +@rem | ||
| 16 | + | ||
| 17 | +@if "%DEBUG%" == "" @echo off | ||
| 18 | +@rem ########################################################################## | ||
| 19 | +@rem | ||
| 20 | +@rem Gradle startup script for Windows | ||
| 21 | +@rem | ||
| 22 | +@rem ########################################################################## | ||
| 23 | + | ||
| 24 | +@rem Set local scope for the variables with windows NT shell | ||
| 25 | +if "%OS%"=="Windows_NT" setlocal | ||
| 26 | + | ||
| 27 | +set DIRNAME=%~dp0 | ||
| 28 | +if "%DIRNAME%" == "" set DIRNAME=. | ||
| 29 | +set APP_BASE_NAME=%~n0 | ||
| 30 | +set APP_HOME=%DIRNAME% | ||
| 31 | + | ||
| 32 | +@rem Resolve any "." and ".." in APP_HOME to make it shorter. | ||
| 33 | +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi | ||
| 34 | + | ||
| 35 | +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| 36 | +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" | ||
| 37 | + | ||
| 38 | +@rem Find java.exe | ||
| 39 | +if defined JAVA_HOME goto findJavaFromJavaHome | ||
| 40 | + | ||
| 41 | +set JAVA_EXE=java.exe | ||
| 42 | +%JAVA_EXE% -version >NUL 2>&1 | ||
| 43 | +if "%ERRORLEVEL%" == "0" goto execute | ||
| 44 | + | ||
| 45 | +echo. | ||
| 46 | +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||
| 47 | +echo. | ||
| 48 | +echo Please set the JAVA_HOME variable in your environment to match the | ||
| 49 | +echo location of your Java installation. | ||
| 50 | + | ||
| 51 | +goto fail | ||
| 52 | + | ||
| 53 | +:findJavaFromJavaHome | ||
| 54 | +set JAVA_HOME=%JAVA_HOME:"=% | ||
| 55 | +set JAVA_EXE=%JAVA_HOME%/bin/java.exe | ||
| 56 | + | ||
| 57 | +if exist "%JAVA_EXE%" goto execute | ||
| 58 | + | ||
| 59 | +echo. | ||
| 60 | +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | ||
| 61 | +echo. | ||
| 62 | +echo Please set the JAVA_HOME variable in your environment to match the | ||
| 63 | +echo location of your Java installation. | ||
| 64 | + | ||
| 65 | +goto fail | ||
| 66 | + | ||
| 67 | +:execute | ||
| 68 | +@rem Setup the command line | ||
| 69 | + | ||
| 70 | +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||
| 71 | + | ||
| 72 | + | ||
| 73 | +@rem Execute Gradle | ||
| 74 | +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* | ||
| 75 | + | ||
| 76 | +:end | ||
| 77 | +@rem End local scope for the variables with windows NT shell | ||
| 78 | +if "%ERRORLEVEL%"=="0" goto mainEnd | ||
| 79 | + | ||
| 80 | +:fail | ||
| 81 | +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||
| 82 | +rem the _cmd.exe /c_ return code! | ||
| 83 | +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | ||
| 84 | +exit /b 1 | ||
| 85 | + | ||
| 86 | +:mainEnd | ||
| 87 | +if "%OS%"=="Windows_NT" endlocal | ||
| 88 | + | ||
| 89 | +:omega |
| 1 | +pluginManagement { | ||
| 2 | + repositories { | ||
| 3 | + google() | ||
| 4 | + mavenCentral() | ||
| 5 | + gradlePluginPortal() | ||
| 6 | + } | ||
| 7 | +} | ||
| 8 | +dependencyResolutionManagement { | ||
| 9 | + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) | ||
| 10 | + repositories { | ||
| 11 | + google() | ||
| 12 | + mavenCentral() | ||
| 13 | + } | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +rootProject.name = "SherpaOnnxTtsEngine" | ||
| 17 | +include(":app") |
| @@ -34,6 +34,10 @@ class OfflineTtsImpl { | @@ -34,6 +34,10 @@ class OfflineTtsImpl { | ||
| 34 | 34 | ||
| 35 | // Return the sample rate of the generated audio | 35 | // Return the sample rate of the generated audio |
| 36 | virtual int32_t SampleRate() const = 0; | 36 | virtual int32_t SampleRate() const = 0; |
| 37 | + | ||
| 38 | + // Number of supported speakers. | ||
| 39 | + // If it supports only a single speaker, then it return 0 or 1. | ||
| 40 | + virtual int32_t NumSpeakers() const = 0; | ||
| 37 | }; | 41 | }; |
| 38 | 42 | ||
| 39 | } // namespace sherpa_onnx | 43 | } // namespace sherpa_onnx |
| @@ -74,6 +74,10 @@ class OfflineTtsVitsImpl : public OfflineTtsImpl { | @@ -74,6 +74,10 @@ class OfflineTtsVitsImpl : public OfflineTtsImpl { | ||
| 74 | return model_->GetMetaData().sample_rate; | 74 | return model_->GetMetaData().sample_rate; |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | + int32_t NumSpeakers() const override { | ||
| 78 | + return model_->GetMetaData().num_speakers; | ||
| 79 | + } | ||
| 80 | + | ||
| 77 | GeneratedAudio Generate( | 81 | GeneratedAudio Generate( |
| 78 | const std::string &_text, int64_t sid = 0, float speed = 1.0, | 82 | const std::string &_text, int64_t sid = 0, float speed = 1.0, |
| 79 | GeneratedAudioCallback callback = nullptr) const override { | 83 | GeneratedAudioCallback callback = nullptr) const override { |
| @@ -73,4 +73,6 @@ GeneratedAudio OfflineTts::Generate( | @@ -73,4 +73,6 @@ GeneratedAudio OfflineTts::Generate( | ||
| 73 | 73 | ||
| 74 | int32_t OfflineTts::SampleRate() const { return impl_->SampleRate(); } | 74 | int32_t OfflineTts::SampleRate() const { return impl_->SampleRate(); } |
| 75 | 75 | ||
| 76 | +int32_t OfflineTts::NumSpeakers() const { return impl_->NumSpeakers(); } | ||
| 77 | + | ||
| 76 | } // namespace sherpa_onnx | 78 | } // namespace sherpa_onnx |
| @@ -86,6 +86,10 @@ class OfflineTts { | @@ -86,6 +86,10 @@ class OfflineTts { | ||
| 86 | // Return the sample rate of the generated audio | 86 | // Return the sample rate of the generated audio |
| 87 | int32_t SampleRate() const; | 87 | int32_t SampleRate() const; |
| 88 | 88 | ||
| 89 | + // Number of supported speakers. | ||
| 90 | + // If it supports only a single speaker, then it return 0 or 1. | ||
| 91 | + int32_t NumSpeakers() const; | ||
| 92 | + | ||
| 89 | private: | 93 | private: |
| 90 | std::unique_ptr<OfflineTtsImpl> impl_; | 94 | std::unique_ptr<OfflineTtsImpl> impl_; |
| 91 | }; | 95 | }; |
| @@ -524,6 +524,8 @@ class SherpaOnnxOfflineTts { | @@ -524,6 +524,8 @@ class SherpaOnnxOfflineTts { | ||
| 524 | 524 | ||
| 525 | int32_t SampleRate() const { return tts_.SampleRate(); } | 525 | int32_t SampleRate() const { return tts_.SampleRate(); } |
| 526 | 526 | ||
| 527 | + int32_t NumSpeakers() const { return tts_.NumSpeakers(); } | ||
| 528 | + | ||
| 527 | private: | 529 | private: |
| 528 | OfflineTts tts_; | 530 | OfflineTts tts_; |
| 529 | }; | 531 | }; |
| @@ -652,6 +654,13 @@ JNIEXPORT jint JNICALL Java_com_k2fsa_sherpa_onnx_OfflineTts_getSampleRate( | @@ -652,6 +654,13 @@ JNIEXPORT jint JNICALL Java_com_k2fsa_sherpa_onnx_OfflineTts_getSampleRate( | ||
| 652 | ->SampleRate(); | 654 | ->SampleRate(); |
| 653 | } | 655 | } |
| 654 | 656 | ||
| 657 | +SHERPA_ONNX_EXTERN_C | ||
| 658 | +JNIEXPORT jint JNICALL Java_com_k2fsa_sherpa_onnx_OfflineTts_getNumSpeakers( | ||
| 659 | + JNIEnv *env, jobject /*obj*/, jlong ptr) { | ||
| 660 | + return reinterpret_cast<sherpa_onnx::SherpaOnnxOfflineTts *>(ptr) | ||
| 661 | + ->NumSpeakers(); | ||
| 662 | +} | ||
| 663 | + | ||
| 655 | // see | 664 | // see |
| 656 | // https://stackoverflow.com/questions/29043872/android-jni-return-multiple-variables | 665 | // https://stackoverflow.com/questions/29043872/android-jni-return-multiple-variables |
| 657 | static jobject NewInteger(JNIEnv *env, int32_t value) { | 666 | static jobject NewInteger(JNIEnv *env, int32_t value) { |
| @@ -51,6 +51,7 @@ void PybindOfflineTts(py::module *m) { | @@ -51,6 +51,7 @@ void PybindOfflineTts(py::module *m) { | ||
| 51 | .def(py::init<const OfflineTtsConfig &>(), py::arg("config"), | 51 | .def(py::init<const OfflineTtsConfig &>(), py::arg("config"), |
| 52 | py::call_guard<py::gil_scoped_release>()) | 52 | py::call_guard<py::gil_scoped_release>()) |
| 53 | .def_property_readonly("sample_rate", &PyClass::SampleRate) | 53 | .def_property_readonly("sample_rate", &PyClass::SampleRate) |
| 54 | + .def_property_readonly("num_speakers", &PyClass::NumSpeakers) | ||
| 54 | .def( | 55 | .def( |
| 55 | "generate", | 56 | "generate", |
| 56 | [](const PyClass &self, const std::string &text, int64_t sid, | 57 | [](const PyClass &self, const std::string &text, int64_t sid, |
-
请 注册 或 登录 后发表评论