Fangjun Kuang
Committed by GitHub

Add Android demo for real-time ASR with non-streaming ASR models. (#2214)

正在显示 60 个修改的文件 包含 1651 行增加8 行删除
@@ -18,3 +18,4 @@ for usage. @@ -18,3 +18,4 @@ for usage.
18 |[SherpaOnnxWebSocket](./SherpaOnnxWebSocket)| |It shows how to write a websocket client for the [Python streaming websocket server](https://github.com/k2-fsa/sherpa-onnx/blob/master/python-api-examples/streaming_server.py).| 18 |[SherpaOnnxWebSocket](./SherpaOnnxWebSocket)| |It shows how to write a websocket client for the [Python streaming websocket server](https://github.com/k2-fsa/sherpa-onnx/blob/master/python-api-examples/streaming_server.py).|
19 |[SherpaOnnxAudioTagging](./SherpaOnnxAudioTagging)|[URL](https://k2-fsa.github.io/sherpa/onnx/audio-tagging/apk.html)| It shows how to use audio tagging.| 19 |[SherpaOnnxAudioTagging](./SherpaOnnxAudioTagging)|[URL](https://k2-fsa.github.io/sherpa/onnx/audio-tagging/apk.html)| It shows how to use audio tagging.|
20 |[SherpaOnnxAudioTaggingWearOS](./SherpaOnnxAudioTagging)|[URL](https://k2-fsa.github.io/sherpa/onnx/audio-tagging/apk-wearos.html)| It shows how to use audio tagging on WearOS.| 20 |[SherpaOnnxAudioTaggingWearOS](./SherpaOnnxAudioTagging)|[URL](https://k2-fsa.github.io/sherpa/onnx/audio-tagging/apk-wearos.html)| It shows how to use audio tagging on WearOS.|
  21 +|[SherpaOnnxSimulateStreamingAsr](./SherpaOnnxSimulateStreamingAsr)|| It shows how to use a non-streaming ASR model for streaming speech recognition.|
  1 +*.iml
  2 +.gradle
  3 +/local.properties
  4 +/.idea/caches
  5 +/.idea/libraries
  6 +/.idea/modules.xml
  7 +/.idea/workspace.xml
  8 +/.idea/navEditor.xml
  9 +/.idea/assetWizardSettings.xml
  10 +.DS_Store
  11 +/build
  12 +/captures
  13 +.externalNativeBuild
  14 +.cxx
  15 +local.properties
  1 +plugins {
  2 + alias(libs.plugins.android.application)
  3 + alias(libs.plugins.jetbrains.kotlin.android)
  4 +}
  5 +
  6 +android {
  7 + namespace = "com.k2fsa.sherpa.onnx.simulate.streaming.asr"
  8 + compileSdk = 34
  9 +
  10 + defaultConfig {
  11 + applicationId = "com.k2fsa.sherpa.onnx.simulate.streaming.asr"
  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 + implementation(libs.androidx.core.ktx)
  54 + implementation(libs.androidx.lifecycle.runtime.ktx)
  55 + implementation(libs.androidx.activity.compose)
  56 + implementation(platform(libs.androidx.compose.bom))
  57 + implementation(libs.androidx.ui)
  58 + implementation(libs.androidx.ui.graphics)
  59 + implementation(libs.androidx.ui.tooling.preview)
  60 + implementation(libs.androidx.material3)
  61 + implementation(libs.androidx.navigation.compose)
  62 + testImplementation(libs.junit)
  63 + androidTestImplementation(libs.androidx.junit)
  64 + androidTestImplementation(libs.androidx.espresso.core)
  65 + androidTestImplementation(platform(libs.androidx.compose.bom))
  66 + androidTestImplementation(libs.androidx.ui.test.junit4)
  67 + debugImplementation(libs.androidx.ui.tooling)
  68 + debugImplementation(libs.androidx.ui.test.manifest)
  69 +}
  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.simulate.streaming.asr
  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.simulate.streaming.asr", 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 + <uses-permission android:name="android.permission.RECORD_AUDIO" />
  6 +
  7 + <application
  8 + android:allowBackup="true"
  9 + android:dataExtractionRules="@xml/data_extraction_rules"
  10 + android:fullBackupContent="@xml/backup_rules"
  11 + android:icon="@mipmap/ic_launcher"
  12 + android:label="@string/app_name"
  13 + android:roundIcon="@mipmap/ic_launcher_round"
  14 + android:supportsRtl="true"
  15 + android:theme="@style/Theme.SimulateStreamingAsr"
  16 + tools:targetApi="31">
  17 + <activity
  18 + android:name=".MainActivity"
  19 + android:exported="true"
  20 + android:label="@string/app_name"
  21 + android:theme="@style/Theme.SimulateStreamingAsr">
  22 + <intent-filter>
  23 + <action android:name="android.intent.action.MAIN" />
  24 +
  25 + <category android:name="android.intent.category.LAUNCHER" />
  26 + </intent-filter>
  27 + </activity>
  28 + </application>
  29 +
  30 +</manifest>
  1 +../../../../../../../../../../sherpa-onnx/kotlin-api/FeatureConfig.kt
  1 +../../../../../../../../../../sherpa-onnx/kotlin-api/HomophoneReplacerConfig.kt
  1 +../../../../../../../../../../sherpa-onnx/kotlin-api/OfflineRecognizer.kt
  1 +../../../../../../../../../../sherpa-onnx/kotlin-api/OfflineStream.kt
  1 +../../../../../../../../../../sherpa-onnx/kotlin-api/Vad.kt
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr
  2 +
  3 +import androidx.compose.ui.graphics.vector.ImageVector
  4 +
  5 +data class BarItem(
  6 + val title: String,
  7 +
  8 + // see https://www.composables.com/icons
  9 + // and
  10 + // https://developer.android.com/reference/kotlin/androidx/compose/material/icons/filled/package-summary
  11 + val image: ImageVector,
  12 + val route: String,
  13 +)
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr
  2 +
  3 +import android.Manifest
  4 +import android.content.pm.PackageManager
  5 +import android.os.Bundle
  6 +import android.util.Log
  7 +import android.widget.Toast
  8 +import androidx.activity.ComponentActivity
  9 +import androidx.activity.compose.setContent
  10 +import androidx.activity.enableEdgeToEdge
  11 +import androidx.compose.foundation.layout.Column
  12 +import androidx.compose.foundation.layout.fillMaxSize
  13 +import androidx.compose.foundation.layout.padding
  14 +import androidx.compose.material3.CenterAlignedTopAppBar
  15 +import androidx.compose.material3.ExperimentalMaterial3Api
  16 +import androidx.compose.material3.Icon
  17 +import androidx.compose.material3.MaterialTheme
  18 +import androidx.compose.material3.NavigationBar
  19 +import androidx.compose.material3.NavigationBarItem
  20 +import androidx.compose.material3.Scaffold
  21 +import androidx.compose.material3.Surface
  22 +import androidx.compose.material3.Text
  23 +import androidx.compose.material3.TopAppBarDefaults
  24 +import androidx.compose.runtime.Composable
  25 +import androidx.compose.runtime.getValue
  26 +import androidx.compose.ui.Modifier
  27 +import androidx.compose.ui.text.font.FontWeight
  28 +import androidx.core.app.ActivityCompat
  29 +import androidx.navigation.NavGraph.Companion.findStartDestination
  30 +import androidx.navigation.NavHostController
  31 +import androidx.navigation.compose.NavHost
  32 +import androidx.navigation.compose.composable
  33 +import androidx.navigation.compose.currentBackStackEntryAsState
  34 +import androidx.navigation.compose.rememberNavController
  35 +import com.k2fsa.sherpa.onnx.simulate.streaming.asr.screens.HelpScreen
  36 +import com.k2fsa.sherpa.onnx.simulate.streaming.asr.screens.HomeScreen
  37 +import com.k2fsa.sherpa.onnx.simulate.streaming.asr.ui.theme.SimulateStreamingAsrTheme
  38 +
  39 +const val TAG = "sherpa-onnx-sim-asr"
  40 +private const val REQUEST_RECORD_AUDIO_PERMISSION = 200
  41 +
  42 +@Suppress("DEPRECATION")
  43 +class MainActivity : ComponentActivity() {
  44 + private val permissions: Array<String> = arrayOf(Manifest.permission.RECORD_AUDIO)
  45 +
  46 + override fun onCreate(savedInstanceState: Bundle?) {
  47 + super.onCreate(savedInstanceState)
  48 + enableEdgeToEdge()
  49 + setContent {
  50 + SimulateStreamingAsrTheme {
  51 + Surface(
  52 + modifier = Modifier.fillMaxSize(),
  53 + color = MaterialTheme.colorScheme.background
  54 + ) {
  55 + MainScreen()
  56 + }
  57 + }
  58 + }
  59 + ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION)
  60 +
  61 + SimulateStreamingAsr.initOfflineRecognizer(this.assets, this.application)
  62 + SimulateStreamingAsr.initVad(this.assets)
  63 + }
  64 +
  65 + @Deprecated("Deprecated in Java")
  66 + override fun onRequestPermissionsResult(
  67 + requestCode: Int,
  68 + permissions: Array<out String>,
  69 + grantResults: IntArray
  70 + ) {
  71 + super.onRequestPermissionsResult(requestCode, permissions, grantResults)
  72 + val permissionToRecordAccepted = if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
  73 + grantResults[0] == PackageManager.PERMISSION_GRANTED
  74 + } else {
  75 + false
  76 + }
  77 +
  78 + if (!permissionToRecordAccepted) {
  79 + Log.e(TAG, "Audio record is disallowed")
  80 + Toast.makeText(
  81 + this,
  82 + "This App needs to access the microphone",
  83 + Toast.LENGTH_SHORT
  84 + )
  85 + .show()
  86 + finish()
  87 + }
  88 +
  89 + Log.i(TAG, "Audio record is permitted")
  90 + }
  91 +}
  92 +
  93 +@OptIn(ExperimentalMaterial3Api::class)
  94 +@Composable
  95 +fun MainScreen(modifier: Modifier = Modifier) {
  96 + val navController = rememberNavController()
  97 +
  98 + Scaffold(
  99 + topBar = {
  100 + CenterAlignedTopAppBar(
  101 + colors = TopAppBarDefaults.topAppBarColors(
  102 + containerColor = MaterialTheme.colorScheme.primaryContainer,
  103 + titleContentColor = MaterialTheme.colorScheme.primary,
  104 + ),
  105 + title = {
  106 + Text(
  107 + "Next-gen Kaldi: Simulate real-time speech recognition",
  108 + fontWeight = FontWeight.Bold,
  109 + )
  110 + },
  111 + )
  112 + },
  113 + content = { padding ->
  114 + Column(Modifier.padding(padding)) {
  115 + NavigationHost(navController = navController)
  116 +
  117 + }
  118 + },
  119 + bottomBar = {
  120 + BottomNavigationBar(navController = navController)
  121 + }
  122 + )
  123 +}
  124 +
  125 +@Composable
  126 +fun NavigationHost(navController: NavHostController) {
  127 + NavHost(navController = navController, startDestination = NavRoutes.Home.route) {
  128 + composable(NavRoutes.Home.route) {
  129 + HomeScreen()
  130 + }
  131 +
  132 + composable(NavRoutes.Help.route) {
  133 + HelpScreen()
  134 + }
  135 + }
  136 +}
  137 +
  138 +@Composable
  139 +fun BottomNavigationBar(navController: NavHostController) {
  140 + NavigationBar {
  141 + val backStackEntry by navController.currentBackStackEntryAsState()
  142 + val currentRoute = backStackEntry?.destination?.route
  143 +
  144 + NavBarItems.BarItems.forEach { navItem ->
  145 + NavigationBarItem(selected = currentRoute == navItem.route,
  146 + onClick = {
  147 + navController.navigate(navItem.route) {
  148 + popUpTo(navController.graph.findStartDestination().id) {
  149 + saveState = true
  150 + }
  151 + launchSingleTop = true
  152 + restoreState = true
  153 + }
  154 + },
  155 + icon = {
  156 + Icon(imageVector = navItem.image, contentDescription = navItem.title)
  157 + }, label = {
  158 + Text(text = navItem.title)
  159 + })
  160 + }
  161 + }
  162 +}
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr
  2 +
  3 +import androidx.compose.material.icons.Icons
  4 +import androidx.compose.material.icons.filled.Home
  5 +import androidx.compose.material.icons.filled.Info
  6 +
  7 +object NavBarItems {
  8 + val BarItems = listOf(
  9 + BarItem(
  10 + title = "Home",
  11 + image = Icons.Filled.Home,
  12 + route = "home",
  13 + ),
  14 + BarItem(
  15 + title = "Help",
  16 + image = Icons.Filled.Info,
  17 + route = "help",
  18 + ),
  19 + )
  20 +}
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr
  2 +
  3 +sealed class NavRoutes(val route: String) {
  4 + object Home : NavRoutes("home")
  5 + object Help : NavRoutes("help")
  6 +}
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr
  2 +
  3 +import android.app.Application
  4 +import android.content.res.AssetManager
  5 +import android.util.Log
  6 +import com.k2fsa.sherpa.onnx.HomophoneReplacerConfig
  7 +import com.k2fsa.sherpa.onnx.OfflineRecognizer
  8 +import com.k2fsa.sherpa.onnx.OfflineRecognizerConfig
  9 +import com.k2fsa.sherpa.onnx.Vad
  10 +import com.k2fsa.sherpa.onnx.getOfflineModelConfig
  11 +import com.k2fsa.sherpa.onnx.getVadModelConfig
  12 +import java.io.File
  13 +import java.io.FileOutputStream
  14 +import java.io.IOException
  15 +
  16 +object SimulateStreamingAsr {
  17 + private var _recognizer: OfflineRecognizer? = null
  18 + val recognizer: OfflineRecognizer
  19 + get() {
  20 + return _recognizer!!
  21 + }
  22 +
  23 + private var _vad: Vad? = null
  24 + val vad: Vad
  25 + get() {
  26 + return _vad!!
  27 + }
  28 +
  29 + fun initOfflineRecognizer(assetManager: AssetManager? = null, application: Application) {
  30 + synchronized(this) {
  31 + if (_recognizer != null) {
  32 + return
  33 + }
  34 + Log.i(TAG, "Initializing sherpa-onnx offline recognizer")
  35 + // Please change getOfflineModelConfig() to add new models
  36 + // See https://k2-fsa.github.io/sherpa/onnx/pretrained_models/index.html
  37 + // for a list of available models
  38 + val asrModelType = 15
  39 + val asrRuleFsts: String?
  40 + asrRuleFsts = null
  41 + Log.i(TAG, "Select model type $asrModelType for ASR")
  42 +
  43 + val useHr = false
  44 + val hr = HomophoneReplacerConfig(
  45 + // Used only when useHr is true
  46 + // Please download the following 3 files from
  47 + // https://github.com/k2-fsa/sherpa-onnx/releases/tag/hr-files
  48 + //
  49 + // dict and lexicon.txt can be shared by different apps
  50 + //
  51 + // replace.fst is specific for an app
  52 + dictDir = "dict",
  53 + lexicon = "lexicon.txt",
  54 + ruleFsts = "replace.fst",
  55 + )
  56 +
  57 + val config = OfflineRecognizerConfig(
  58 + modelConfig = getOfflineModelConfig(type = asrModelType)!!,
  59 + )
  60 +
  61 + if (config.modelConfig.numThreads == 1) {
  62 + config.modelConfig.numThreads = 2
  63 + }
  64 +
  65 + if (asrRuleFsts != null) {
  66 + config.ruleFsts = asrRuleFsts
  67 + }
  68 +
  69 + if (useHr) {
  70 + if (hr.dictDir.isNotEmpty() && hr.dictDir.first() != '/') {
  71 + // We need to copy it from the assets directory to some path
  72 + val newDir = copyDataDir(hr.dictDir, application)
  73 + hr.dictDir = "$newDir/${hr.dictDir}"
  74 + }
  75 + config.hr = hr
  76 + }
  77 +
  78 + _recognizer = OfflineRecognizer(
  79 + assetManager = assetManager,
  80 + config = config,
  81 + )
  82 +
  83 + Log.i(TAG, "sherpa-onnx offline recognizer initialized")
  84 + }
  85 + }
  86 +
  87 + fun initVad(assetManager: AssetManager? = null) {
  88 + if (_vad != null) {
  89 + return
  90 + }
  91 + val type = 0
  92 + Log.i(TAG, "Select VAD model type $type")
  93 + val config = getVadModelConfig(type)
  94 +
  95 + _vad = Vad(
  96 + assetManager = assetManager,
  97 + config = config!!,
  98 + )
  99 + Log.i(TAG, "sherpa-onnx vad initialized")
  100 + }
  101 +
  102 + private fun copyDataDir(dataDir: String, application: Application): String {
  103 + Log.i(TAG, "data dir is $dataDir")
  104 + copyAssets(dataDir, application)
  105 +
  106 + val newDataDir = application.getExternalFilesDir(null)!!.absolutePath
  107 + Log.i(TAG, "newDataDir: $newDataDir")
  108 + return newDataDir
  109 + }
  110 +
  111 + private fun copyAssets(path: String, application: Application) {
  112 + val assets: Array<String>?
  113 + try {
  114 + assets = application.assets.list(path)
  115 + if (assets!!.isEmpty()) {
  116 + copyFile(path, application)
  117 + } else {
  118 + val fullPath = "${application.getExternalFilesDir(null)}/$path"
  119 + val dir = File(fullPath)
  120 + dir.mkdirs()
  121 + for (asset in assets.iterator()) {
  122 + val p: String = if (path == "") "" else "$path/"
  123 + copyAssets(p + asset, application)
  124 + }
  125 + }
  126 + } catch (ex: IOException) {
  127 + Log.e(TAG, "Failed to copy $path. $ex")
  128 + }
  129 + }
  130 +
  131 + private fun copyFile(filename: String, application: Application) {
  132 + try {
  133 + val istream = application.assets.open(filename)
  134 + val newFilename = application.getExternalFilesDir(null).toString() + "/" + filename
  135 + val ostream = FileOutputStream(newFilename)
  136 + // Log.i(TAG, "Copying $filename to $newFilename")
  137 + val buffer = ByteArray(1024)
  138 + var read = 0
  139 + while (read != -1) {
  140 + ostream.write(buffer, 0, read)
  141 + read = istream.read(buffer)
  142 + }
  143 + istream.close()
  144 + ostream.flush()
  145 + ostream.close()
  146 + } catch (ex: Exception) {
  147 + Log.e(TAG, "Failed to copy $filename, $ex")
  148 + }
  149 + }
  150 +}
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr.screens
  2 +
  3 +import androidx.compose.runtime.Composable
  4 +import androidx.compose.foundation.layout.Box
  5 +import androidx.compose.foundation.layout.Column
  6 +import androidx.compose.foundation.layout.Spacer
  7 +import androidx.compose.foundation.layout.fillMaxSize
  8 +import androidx.compose.foundation.layout.height
  9 +import androidx.compose.foundation.layout.padding
  10 +import androidx.compose.material3.Text
  11 +import androidx.compose.ui.Modifier
  12 +import androidx.compose.ui.unit.dp
  13 +import androidx.compose.ui.unit.sp
  14 +
  15 +@Composable
  16 +fun HelpScreen() {
  17 + Box(modifier = Modifier.fillMaxSize()) {
  18 + Column(
  19 + modifier = Modifier.padding(8.dp)
  20 + ) {
  21 + Text(
  22 + "This app uses a non-streaming ASR model together with silero-vad " +
  23 + "for streaming/real-time speech recognition. ",
  24 + fontSize=10.sp
  25 + )
  26 + Spacer(modifier = Modifier.height(10.dp))
  27 + Text("Please see http://github.com/k2-fsa/sherpa-onnx ")
  28 +
  29 + Spacer(modifier = Modifier.height(10.dp))
  30 + Text("Everything is open-sourced!", fontSize = 20.sp)
  31 + }
  32 + }
  33 +}
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr.screens
  2 +
  3 +import android.Manifest
  4 +import android.annotation.SuppressLint
  5 +import android.app.Activity
  6 +import android.content.pm.PackageManager
  7 +import android.media.AudioFormat
  8 +import android.media.AudioRecord
  9 +import android.media.MediaRecorder
  10 +import android.util.Log
  11 +import android.widget.Toast
  12 +import androidx.compose.foundation.layout.Arrangement
  13 +import androidx.compose.foundation.layout.Box
  14 +import androidx.compose.foundation.layout.Column
  15 +import androidx.compose.foundation.layout.PaddingValues
  16 +import androidx.compose.foundation.layout.Row
  17 +import androidx.compose.foundation.layout.Spacer
  18 +import androidx.compose.foundation.layout.fillMaxHeight
  19 +import androidx.compose.foundation.layout.fillMaxSize
  20 +import androidx.compose.foundation.layout.fillMaxWidth
  21 +import androidx.compose.foundation.layout.width
  22 +import androidx.compose.foundation.lazy.LazyColumn
  23 +import androidx.compose.foundation.lazy.itemsIndexed
  24 +import androidx.compose.foundation.lazy.rememberLazyListState
  25 +import androidx.compose.material3.Button
  26 +import androidx.compose.material3.Text
  27 +import androidx.compose.runtime.Composable
  28 +import androidx.compose.runtime.getValue
  29 +import androidx.compose.runtime.mutableStateListOf
  30 +import androidx.compose.runtime.mutableStateOf
  31 +import androidx.compose.runtime.remember
  32 +import androidx.compose.runtime.rememberCoroutineScope
  33 +import androidx.compose.runtime.setValue
  34 +import androidx.compose.ui.Alignment
  35 +import androidx.compose.ui.Modifier
  36 +import androidx.compose.ui.platform.LocalClipboardManager
  37 +import androidx.compose.ui.platform.LocalContext
  38 +import androidx.compose.ui.res.stringResource
  39 +import androidx.compose.ui.text.AnnotatedString
  40 +import androidx.compose.ui.unit.dp
  41 +import androidx.core.app.ActivityCompat
  42 +import com.k2fsa.sherpa.onnx.simulate.streaming.asr.R
  43 +import com.k2fsa.sherpa.onnx.simulate.streaming.asr.SimulateStreamingAsr
  44 +import com.k2fsa.sherpa.onnx.simulate.streaming.asr.TAG
  45 +import kotlinx.coroutines.CoroutineScope
  46 +import kotlinx.coroutines.Dispatchers
  47 +import kotlinx.coroutines.channels.Channel
  48 +import kotlinx.coroutines.launch
  49 +
  50 +private var audioRecord: AudioRecord? = null
  51 +
  52 +private const val sampleRateInHz = 16000
  53 +private var samplesChannel = Channel<FloatArray>(capacity = Channel.UNLIMITED)
  54 +
  55 +@Composable
  56 +fun HomeScreen() {
  57 + val context = LocalContext.current
  58 + val clipboardManager = LocalClipboardManager.current
  59 +
  60 + val activity = LocalContext.current as Activity
  61 + var isStarted by remember { mutableStateOf(false) }
  62 + val resultList: MutableList<String> = remember { mutableStateListOf() }
  63 + val lazyColumnListState = rememberLazyListState()
  64 + val coroutineScope = rememberCoroutineScope()
  65 +
  66 + val onRecordingButtonClick: () -> Unit = {
  67 + isStarted = !isStarted
  68 + if (isStarted) {
  69 + if (ActivityCompat.checkSelfPermission(
  70 + activity,
  71 + Manifest.permission.RECORD_AUDIO
  72 + ) != PackageManager.PERMISSION_GRANTED
  73 + ) {
  74 + Log.i(TAG, "Recording is not allowed")
  75 + } else {
  76 + // recording is allowed
  77 + val audioSource = MediaRecorder.AudioSource.MIC
  78 + val channelConfig = AudioFormat.CHANNEL_IN_MONO
  79 + val audioFormat = AudioFormat.ENCODING_PCM_16BIT
  80 + val numBytes =
  81 + AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
  82 + audioRecord = AudioRecord(
  83 + audioSource,
  84 + sampleRateInHz,
  85 + AudioFormat.CHANNEL_IN_MONO,
  86 + AudioFormat.ENCODING_PCM_16BIT,
  87 + numBytes * 2 // a sample has two bytes as we are using 16-bit PCM
  88 + )
  89 +
  90 + SimulateStreamingAsr.vad.reset()
  91 +
  92 + CoroutineScope(Dispatchers.IO).launch {
  93 + Log.i(TAG, "processing samples")
  94 + val interval = 0.1 // i.e., 100 ms
  95 + val bufferSize = (interval * sampleRateInHz).toInt() // in samples
  96 + val buffer = ShortArray(bufferSize)
  97 +
  98 + audioRecord?.let { it ->
  99 + it.startRecording()
  100 +
  101 + while (isStarted) {
  102 + val ret = audioRecord?.read(buffer, 0, buffer.size)
  103 + ret?.let { n ->
  104 + val samples = FloatArray(n) { buffer[it] / 32768.0f }
  105 + samplesChannel.send(samples)
  106 + }
  107 + }
  108 + val samples = FloatArray(0)
  109 + samplesChannel.send(samples)
  110 + }
  111 + }
  112 +
  113 + CoroutineScope(Dispatchers.Default).launch {
  114 + var buffer = arrayListOf<Float>()
  115 + var offset = 0
  116 + val windowSize = 512
  117 + var isSpeechStarted = false
  118 + var startTime = System.currentTimeMillis()
  119 + var lastText = ""
  120 + var added = false
  121 +
  122 +
  123 + while (isStarted) {
  124 + for (s in samplesChannel) {
  125 + if (s.isEmpty()) {
  126 + break
  127 + }
  128 +
  129 + buffer.addAll(s.toList())
  130 + while (offset + windowSize < buffer.size) {
  131 + SimulateStreamingAsr.vad.acceptWaveform(
  132 + buffer.subList(
  133 + offset,
  134 + offset + windowSize
  135 + ).toFloatArray()
  136 + )
  137 + offset += windowSize
  138 + if (!isSpeechStarted && SimulateStreamingAsr.vad.isSpeechDetected()) {
  139 + isSpeechStarted = true
  140 + startTime = System.currentTimeMillis()
  141 + }
  142 + }
  143 +
  144 + val elapsed = System.currentTimeMillis() - startTime
  145 + if (isSpeechStarted && elapsed > 200) {
  146 + // Run ASR every 0.2 seconds == 200 milliseconds
  147 + // You can change it to some other value
  148 + val stream = SimulateStreamingAsr.recognizer.createStream()
  149 + stream.acceptWaveform(
  150 + buffer.subList(0, offset).toFloatArray(),
  151 + sampleRateInHz
  152 + )
  153 + SimulateStreamingAsr.recognizer.decode(stream)
  154 + val result = SimulateStreamingAsr.recognizer.getResult(stream)
  155 + stream.release()
  156 +
  157 + lastText = result.text
  158 +
  159 + if (lastText.isNotBlank()) {
  160 + if (!added || resultList.isEmpty()) {
  161 + resultList.add(lastText)
  162 + added = true
  163 + } else {
  164 + resultList[resultList.size - 1] = lastText
  165 + }
  166 +
  167 + coroutineScope.launch {
  168 + lazyColumnListState.animateScrollToItem(resultList.size - 1)
  169 + }
  170 + }
  171 +
  172 + startTime = System.currentTimeMillis()
  173 + }
  174 +
  175 +
  176 + while (!SimulateStreamingAsr.vad.empty()) {
  177 + val stream = SimulateStreamingAsr.recognizer.createStream()
  178 + stream.acceptWaveform(
  179 + SimulateStreamingAsr.vad.front().samples,
  180 + sampleRateInHz
  181 + )
  182 + SimulateStreamingAsr.recognizer.decode(stream)
  183 + val result = SimulateStreamingAsr.recognizer.getResult(stream)
  184 + stream.release()
  185 +
  186 + isSpeechStarted = false
  187 + SimulateStreamingAsr.vad.pop()
  188 +
  189 + buffer = arrayListOf()
  190 + offset = 0
  191 + if (lastText.isNotBlank()) {
  192 + if (added && resultList.isNotEmpty()) {
  193 + resultList[resultList.size - 1] = result.text
  194 + } else {
  195 + resultList.add(result.text)
  196 + }
  197 +
  198 + coroutineScope.launch {
  199 + lazyColumnListState.animateScrollToItem(resultList.size - 1)
  200 + }
  201 + added = false
  202 + }
  203 + }
  204 + }
  205 + }
  206 + }
  207 + }
  208 + } else {
  209 + audioRecord?.stop()
  210 + audioRecord?.release()
  211 + audioRecord = null
  212 + }
  213 + }
  214 + Box(
  215 + modifier = Modifier.fillMaxSize(),
  216 + contentAlignment = Alignment.TopCenter,
  217 + ) {
  218 + Column(modifier = Modifier) {
  219 + HomeButtonRow(
  220 + isStarted = isStarted,
  221 + onRecordingButtonClick = onRecordingButtonClick,
  222 + onCopyButtonClick = {
  223 + if (resultList.isNotEmpty()) {
  224 + val s = resultList.mapIndexed { i, s -> "${i + 1}: $s" }
  225 + .joinToString(separator = "\n")
  226 + clipboardManager.setText(AnnotatedString(s))
  227 +
  228 + Toast.makeText(
  229 + context,
  230 + "Copied to clipboard",
  231 + Toast.LENGTH_SHORT
  232 + )
  233 + .show()
  234 + } else {
  235 + Toast.makeText(
  236 + context,
  237 + "Nothing to copy",
  238 + Toast.LENGTH_SHORT
  239 + )
  240 + .show()
  241 +
  242 + }
  243 + },
  244 + onClearButtonClick = {
  245 + resultList.clear()
  246 + }
  247 + )
  248 +
  249 + if (resultList.size > 0) {
  250 + LazyColumn(
  251 + modifier = Modifier
  252 + .fillMaxWidth()
  253 + .fillMaxHeight(),
  254 + contentPadding = PaddingValues(16.dp),
  255 + state = lazyColumnListState
  256 + ) {
  257 + itemsIndexed(resultList) { index, line ->
  258 + Text(text = "${index+1}: $line")
  259 + }
  260 + }
  261 + }
  262 +
  263 + }
  264 + }
  265 +}
  266 +
  267 +@SuppressLint("UnrememberedMutableState")
  268 +@Composable
  269 +private fun HomeButtonRow(
  270 + modifier: Modifier = Modifier,
  271 + isStarted: Boolean,
  272 + onRecordingButtonClick: () -> Unit,
  273 + onCopyButtonClick: () -> Unit,
  274 + onClearButtonClick: () -> Unit,
  275 +) {
  276 + Row(
  277 + modifier = modifier.fillMaxWidth(),
  278 + horizontalArrangement = Arrangement.Center,
  279 + ) {
  280 + Button(
  281 + onClick = onRecordingButtonClick
  282 + ) {
  283 + Text(text = stringResource(if (isStarted) R.string.stop else R.string.start))
  284 + }
  285 +
  286 + Spacer(modifier = Modifier.width(24.dp))
  287 +
  288 + Button(onClick = onCopyButtonClick) {
  289 + Text(text = stringResource(id = R.string.copy))
  290 + }
  291 +
  292 + Spacer(modifier = Modifier.width(24.dp))
  293 +
  294 + Button(onClick = onClearButtonClick) {
  295 + Text(text = stringResource(id = R.string.clear))
  296 + }
  297 + }
  298 +}
  299 +
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr.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)
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr.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.ui.platform.LocalContext
  13 +
  14 +private val DarkColorScheme = darkColorScheme(
  15 + primary = Purple80,
  16 + secondary = PurpleGrey80,
  17 + tertiary = Pink80
  18 +)
  19 +
  20 +private val LightColorScheme = lightColorScheme(
  21 + primary = Purple40,
  22 + secondary = PurpleGrey40,
  23 + tertiary = Pink40
  24 +
  25 + /* Other default colors to override
  26 + background = Color(0xFFFFFBFE),
  27 + surface = Color(0xFFFFFBFE),
  28 + onPrimary = Color.White,
  29 + onSecondary = Color.White,
  30 + onTertiary = Color.White,
  31 + onBackground = Color(0xFF1C1B1F),
  32 + onSurface = Color(0xFF1C1B1F),
  33 + */
  34 +)
  35 +
  36 +@Composable
  37 +fun SimulateStreamingAsrTheme(
  38 + darkTheme: Boolean = isSystemInDarkTheme(),
  39 + // Dynamic color is available on Android 12+
  40 + dynamicColor: Boolean = true,
  41 + content: @Composable () -> Unit
  42 +) {
  43 + val colorScheme = when {
  44 + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
  45 + val context = LocalContext.current
  46 + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
  47 + }
  48 +
  49 + darkTheme -> DarkColorScheme
  50 + else -> LightColorScheme
  51 + }
  52 +
  53 + MaterialTheme(
  54 + colorScheme = colorScheme,
  55 + typography = Typography,
  56 + content = content
  57 + )
  58 +}
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr.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 +<resources>
  2 + <string name="app_name">SimulateStreamingAsr</string>
  3 + <string name="start">Start</string>
  4 + <string name="stop">Stop</string>
  5 + <string name="copy">Copy</string>
  6 + <string name="clear">Clear</string>
  7 +</resources>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<resources>
  3 +
  4 + <style name="Theme.SimulateStreamingAsr" parent="android:Theme.Material.Light.NoActionBar" />
  5 +</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>
  1 +package com.k2fsa.sherpa.onnx.simulate.streaming.asr
  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 +}
  1 +// Top-level build file where you can add configuration options common to all sub-projects/modules.
  2 +plugins {
  3 + alias(libs.plugins.android.application) apply false
  4 + alias(libs.plugins.jetbrains.kotlin.android) apply false
  5 +}
  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. For more details, visit
  12 +# https://developer.android.com/r/tools/gradle-multi-project-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
  1 +[versions]
  2 +agp = "8.4.0"
  3 +kotlin = "1.9.0"
  4 +coreKtx = "1.10.0"
  5 +junit = "4.13.2"
  6 +junitVersion = "1.1.5"
  7 +espressoCore = "3.5.1"
  8 +lifecycleRuntimeKtx = "2.6.1"
  9 +activityCompose = "1.8.0"
  10 +composeBom = "2023.08.00"
  11 +navigationCompose = "2.8.2"
  12 +
  13 +[libraries]
  14 +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
  15 +junit = { group = "junit", name = "junit", version.ref = "junit" }
  16 +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
  17 +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
  18 +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
  19 +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
  20 +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
  21 +androidx-ui = { group = "androidx.compose.ui", name = "ui" }
  22 +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
  23 +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
  24 +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
  25 +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
  26 +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
  27 +androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
  28 +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
  29 +
  30 +
  31 +[plugins]
  32 +android-application = { id = "com.android.application", version.ref = "agp" }
  33 +jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
  34 +
  1 +#Wed May 14 11:10:06 CST 2025
  2 +distributionBase=GRADLE_USER_HOME
  3 +distributionPath=wrapper/dists
  4 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
  5 +zipStoreBase=GRADLE_USER_HOME
  6 +zipStorePath=wrapper/dists
  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" "$@"
  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 + content {
  5 + includeGroupByRegex("com\\.android.*")
  6 + includeGroupByRegex("com\\.google.*")
  7 + includeGroupByRegex("androidx.*")
  8 + }
  9 + }
  10 + mavenCentral()
  11 + gradlePluginPortal()
  12 + }
  13 +}
  14 +dependencyResolutionManagement {
  15 + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  16 + repositories {
  17 + google()
  18 + mavenCentral()
  19 + }
  20 +}
  21 +
  22 +rootProject.name = "SimulateStreamingAsr"
  23 +include(":app")
@@ -31,6 +31,7 @@ @@ -31,6 +31,7 @@
31 #include "sherpa-onnx/csrc/speaker-embedding-extractor.h" 31 #include "sherpa-onnx/csrc/speaker-embedding-extractor.h"
32 #include "sherpa-onnx/csrc/speaker-embedding-manager.h" 32 #include "sherpa-onnx/csrc/speaker-embedding-manager.h"
33 #include "sherpa-onnx/csrc/spoken-language-identification.h" 33 #include "sherpa-onnx/csrc/spoken-language-identification.h"
  34 +#include "sherpa-onnx/csrc/text-utils.h"
34 #include "sherpa-onnx/csrc/voice-activity-detector.h" 35 #include "sherpa-onnx/csrc/voice-activity-detector.h"
35 #include "sherpa-onnx/csrc/wave-reader.h" 36 #include "sherpa-onnx/csrc/wave-reader.h"
36 #include "sherpa-onnx/csrc/wave-writer.h" 37 #include "sherpa-onnx/csrc/wave-writer.h"
@@ -158,11 +159,14 @@ static sherpa_onnx::OnlineRecognizerConfig GetOnlineRecognizerConfig( @@ -158,11 +159,14 @@ static sherpa_onnx::OnlineRecognizerConfig GetOnlineRecognizerConfig(
158 recognizer_config.hr.rule_fsts = SHERPA_ONNX_OR(config->hr.rule_fsts, ""); 159 recognizer_config.hr.rule_fsts = SHERPA_ONNX_OR(config->hr.rule_fsts, "");
159 160
160 if (config->model_config.debug) { 161 if (config->model_config.debug) {
  162 + auto str_vec = sherpa_onnx::SplitString(recognizer_config.ToString(), 128);
  163 + for (const auto &s : str_vec) {
161 #if __OHOS__ 164 #if __OHOS__
162 - SHERPA_ONNX_LOGE("%{public}s\n", recognizer_config.ToString().c_str()); 165 + SHERPA_ONNX_LOGE("%{public}s\n", s.c_str());
163 #else 166 #else
164 - SHERPA_ONNX_LOGE("%s\n", recognizer_config.ToString().c_str()); 167 + SHERPA_ONNX_LOGE("%s\n", s.c_str());
165 #endif 168 #endif
  169 + }
166 } 170 }
167 171
168 return recognizer_config; 172 return recognizer_config;
@@ -503,11 +507,14 @@ static sherpa_onnx::OfflineRecognizerConfig GetOfflineRecognizerConfig( @@ -503,11 +507,14 @@ static sherpa_onnx::OfflineRecognizerConfig GetOfflineRecognizerConfig(
503 recognizer_config.hr.rule_fsts = SHERPA_ONNX_OR(config->hr.rule_fsts, ""); 507 recognizer_config.hr.rule_fsts = SHERPA_ONNX_OR(config->hr.rule_fsts, "");
504 508
505 if (config->model_config.debug) { 509 if (config->model_config.debug) {
  510 + auto str_vec = sherpa_onnx::SplitString(recognizer_config.ToString(), 128);
  511 + for (const auto &s : str_vec) {
506 #if __OHOS__ 512 #if __OHOS__
507 - SHERPA_ONNX_LOGE("%{public}s\n", recognizer_config.ToString().c_str()); 513 + SHERPA_ONNX_LOGE("%{public}s\n", s.c_str());
508 #else 514 #else
509 - SHERPA_ONNX_LOGE("%s\n", recognizer_config.ToString().c_str()); 515 + SHERPA_ONNX_LOGE("%s\n", s.c_str());
510 #endif 516 #endif
  517 + }
511 } 518 }
512 519
513 return recognizer_config; 520 return recognizer_config;
@@ -707,4 +707,20 @@ bool EndsWith(const std::string &haystack, const std::string &needle) { @@ -707,4 +707,20 @@ bool EndsWith(const std::string &haystack, const std::string &needle) {
707 return std::equal(needle.rbegin(), needle.rend(), haystack.rbegin()); 707 return std::equal(needle.rbegin(), needle.rend(), haystack.rbegin());
708 } 708 }
709 709
  710 +std::vector<std::string> SplitString(const std::string &s, int32_t chunk_size) {
  711 + std::vector<std::string> ans;
  712 + if (chunk_size < 1 || chunk_size > s.size()) {
  713 + ans.push_back(s);
  714 + } else {
  715 + int32_t n = static_cast<int32_t>(s.size());
  716 + int32_t i = 0;
  717 + while (i < n) {
  718 + int32_t end = std::min(i + chunk_size, n);
  719 + ans.push_back(s.substr(i, end - i));
  720 + i = end;
  721 + }
  722 + }
  723 + return ans;
  724 +}
  725 +
710 } // namespace sherpa_onnx 726 } // namespace sherpa_onnx
@@ -147,6 +147,8 @@ std::string ToString(const std::wstring &s); @@ -147,6 +147,8 @@ std::string ToString(const std::wstring &s);
147 147
148 bool EndsWith(const std::string &haystack, const std::string &needle); 148 bool EndsWith(const std::string &haystack, const std::string &needle);
149 149
  150 +std::vector<std::string> SplitString(const std::string &s, int32_t chunk_size);
  151 +
150 } // namespace sherpa_onnx 152 } // namespace sherpa_onnx
151 153
152 #endif // SHERPA_ONNX_CSRC_TEXT_UTILS_H_ 154 #endif // SHERPA_ONNX_CSRC_TEXT_UTILS_H_
@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 #include "sherpa-onnx/csrc/offline-recognizer.h" 5 #include "sherpa-onnx/csrc/offline-recognizer.h"
6 6
7 #include "sherpa-onnx/csrc/macros.h" 7 #include "sherpa-onnx/csrc/macros.h"
  8 +#include "sherpa-onnx/csrc/text-utils.h"
8 #include "sherpa-onnx/jni/common.h" 9 #include "sherpa-onnx/jni/common.h"
9 10
10 namespace sherpa_onnx { 11 namespace sherpa_onnx {
@@ -327,7 +328,12 @@ Java_com_k2fsa_sherpa_onnx_OfflineRecognizer_newFromAsset(JNIEnv *env, @@ -327,7 +328,12 @@ Java_com_k2fsa_sherpa_onnx_OfflineRecognizer_newFromAsset(JNIEnv *env,
327 } 328 }
328 #endif 329 #endif
329 auto config = sherpa_onnx::GetOfflineConfig(env, _config); 330 auto config = sherpa_onnx::GetOfflineConfig(env, _config);
330 - SHERPA_ONNX_LOGE("config:\n%s", config.ToString().c_str()); 331 +
  332 + // logcat truncates long strings, so we split the string into chunks
  333 + auto str_vec = sherpa_onnx::SplitString(config.ToString(), 128);
  334 + for (const auto &s : str_vec) {
  335 + SHERPA_ONNX_LOGE("%s", s.c_str());
  336 + }
331 337
332 auto model = new sherpa_onnx::OfflineRecognizer( 338 auto model = new sherpa_onnx::OfflineRecognizer(
333 #if __ANDROID_API__ >= 9 339 #if __ANDROID_API__ >= 9
@@ -344,7 +350,11 @@ Java_com_k2fsa_sherpa_onnx_OfflineRecognizer_newFromFile(JNIEnv *env, @@ -344,7 +350,11 @@ Java_com_k2fsa_sherpa_onnx_OfflineRecognizer_newFromFile(JNIEnv *env,
344 jobject /*obj*/, 350 jobject /*obj*/,
345 jobject _config) { 351 jobject _config) {
346 auto config = sherpa_onnx::GetOfflineConfig(env, _config); 352 auto config = sherpa_onnx::GetOfflineConfig(env, _config);
347 - SHERPA_ONNX_LOGE("config:\n%s", config.ToString().c_str()); 353 +
  354 + auto str_vec = sherpa_onnx::SplitString(config.ToString(), 128);
  355 + for (const auto &s : str_vec) {
  356 + SHERPA_ONNX_LOGE("%s", s.c_str());
  357 + }
348 358
349 if (!config.Validate()) { 359 if (!config.Validate()) {
350 SHERPA_ONNX_LOGE("Errors found in config!"); 360 SHERPA_ONNX_LOGE("Errors found in config!");
@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 #include "sherpa-onnx/csrc/online-recognizer.h" 5 #include "sherpa-onnx/csrc/online-recognizer.h"
6 6
7 #include "sherpa-onnx/csrc/macros.h" 7 #include "sherpa-onnx/csrc/macros.h"
  8 +#include "sherpa-onnx/csrc/text-utils.h"
8 #include "sherpa-onnx/jni/common.h" 9 #include "sherpa-onnx/jni/common.h"
9 10
10 namespace sherpa_onnx { 11 namespace sherpa_onnx {
@@ -295,7 +296,10 @@ Java_com_k2fsa_sherpa_onnx_OnlineRecognizer_newFromAsset(JNIEnv *env, @@ -295,7 +296,10 @@ Java_com_k2fsa_sherpa_onnx_OnlineRecognizer_newFromAsset(JNIEnv *env,
295 } 296 }
296 #endif 297 #endif
297 auto config = sherpa_onnx::GetConfig(env, _config); 298 auto config = sherpa_onnx::GetConfig(env, _config);
298 - SHERPA_ONNX_LOGE("config:\n%s", config.ToString().c_str()); 299 + auto str_vec = sherpa_onnx::SplitString(config.ToString(), 128);
  300 + for (const auto &s : str_vec) {
  301 + SHERPA_ONNX_LOGE("%s", s.c_str());
  302 + }
299 303
300 auto recognizer = new sherpa_onnx::OnlineRecognizer( 304 auto recognizer = new sherpa_onnx::OnlineRecognizer(
301 #if __ANDROID_API__ >= 9 305 #if __ANDROID_API__ >= 9
@@ -310,7 +314,11 @@ SHERPA_ONNX_EXTERN_C @@ -310,7 +314,11 @@ SHERPA_ONNX_EXTERN_C
310 JNIEXPORT jlong JNICALL Java_com_k2fsa_sherpa_onnx_OnlineRecognizer_newFromFile( 314 JNIEXPORT jlong JNICALL Java_com_k2fsa_sherpa_onnx_OnlineRecognizer_newFromFile(
311 JNIEnv *env, jobject /*obj*/, jobject _config) { 315 JNIEnv *env, jobject /*obj*/, jobject _config) {
312 auto config = sherpa_onnx::GetConfig(env, _config); 316 auto config = sherpa_onnx::GetConfig(env, _config);
313 - SHERPA_ONNX_LOGE("config:\n%s", config.ToString().c_str()); 317 +
  318 + auto str_vec = sherpa_onnx::SplitString(config.ToString(), 128);
  319 + for (const auto &s : str_vec) {
  320 + SHERPA_ONNX_LOGE("%s", s.c_str());
  321 + }
314 322
315 if (!config.Validate()) { 323 if (!config.Validate()) {
316 SHERPA_ONNX_LOGE("Errors found in config!"); 324 SHERPA_ONNX_LOGE("Errors found in config!");