Pārlūkot izejas kodu

Support building android app (#37)

* add jniLibs

* Support Vulkan

* update kaldi-native-fbank to v1.8
Fangjun Kuang 2 gadi atpakaļ
vecāks
revīzija
d5dd41a7e2
65 mainītis faili ar 1520 papildinājumiem un 65 dzēšanām
  1. 16 0
      android/SherpaNcnn/.gitignore
  2. 3 0
      android/SherpaNcnn/.idea/.gitignore
  3. 6 0
      android/SherpaNcnn/.idea/compiler.xml
  4. 17 0
      android/SherpaNcnn/.idea/deploymentTargetDropDown.xml
  5. 19 0
      android/SherpaNcnn/.idea/gradle.xml
  6. 10 0
      android/SherpaNcnn/.idea/misc.xml
  7. 6 0
      android/SherpaNcnn/.idea/vcs.xml
  8. 1 0
      android/SherpaNcnn/app/.gitignore
  9. 44 0
      android/SherpaNcnn/app/build.gradle
  10. 21 0
      android/SherpaNcnn/app/proguard-rules.pro
  11. 24 0
      android/SherpaNcnn/app/src/androidTest/java/com/k2fsa/sherpa/ncnn/ExampleInstrumentedTest.kt
  12. 32 0
      android/SherpaNcnn/app/src/main/AndroidManifest.xml
  13. 2 0
      android/SherpaNcnn/app/src/main/assets/.gitignore
  14. 159 0
      android/SherpaNcnn/app/src/main/java/com/k2fsa/sherpa/ncnn/MainActivity.kt
  15. 147 0
      android/SherpaNcnn/app/src/main/java/com/k2fsa/sherpa/ncnn/SherpaNcnn.kt
  16. 17 0
      android/SherpaNcnn/app/src/main/java/com/k2fsa/sherpa/ncnn/WaveReader.kt
  17. 1 0
      android/SherpaNcnn/app/src/main/jniLibs/.gitignore
  18. 0 0
      android/SherpaNcnn/app/src/main/jniLibs/arm64-v8a/.gitkeep
  19. 0 0
      android/SherpaNcnn/app/src/main/jniLibs/armeabi-v7a/.gitkeep
  20. 3 0
      android/SherpaNcnn/app/src/main/jniLibs/readme.md
  21. 0 0
      android/SherpaNcnn/app/src/main/jniLibs/x86_64/.gitkeep
  22. 30 0
      android/SherpaNcnn/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  23. 170 0
      android/SherpaNcnn/app/src/main/res/drawable/ic_launcher_background.xml
  24. 38 0
      android/SherpaNcnn/app/src/main/res/layout/activity_main.xml
  25. 5 0
      android/SherpaNcnn/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  26. 5 0
      android/SherpaNcnn/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  27. BIN
      android/SherpaNcnn/app/src/main/res/mipmap-hdpi/ic_launcher.webp
  28. BIN
      android/SherpaNcnn/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  29. BIN
      android/SherpaNcnn/app/src/main/res/mipmap-mdpi/ic_launcher.webp
  30. BIN
      android/SherpaNcnn/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  31. BIN
      android/SherpaNcnn/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  32. BIN
      android/SherpaNcnn/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  33. BIN
      android/SherpaNcnn/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  34. BIN
      android/SherpaNcnn/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  35. BIN
      android/SherpaNcnn/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  36. BIN
      android/SherpaNcnn/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  37. 16 0
      android/SherpaNcnn/app/src/main/res/values-night/themes.xml
  38. 10 0
      android/SherpaNcnn/app/src/main/res/values/colors.xml
  39. 17 0
      android/SherpaNcnn/app/src/main/res/values/strings.xml
  40. 16 0
      android/SherpaNcnn/app/src/main/res/values/themes.xml
  41. 13 0
      android/SherpaNcnn/app/src/main/res/xml/backup_rules.xml
  42. 19 0
      android/SherpaNcnn/app/src/main/res/xml/data_extraction_rules.xml
  43. 17 0
      android/SherpaNcnn/app/src/test/java/com/k2fsa/sherpa/ncnn/ExampleUnitTest.kt
  44. 6 0
      android/SherpaNcnn/build.gradle
  45. 23 0
      android/SherpaNcnn/gradle.properties
  46. BIN
      android/SherpaNcnn/gradle/wrapper/gradle-wrapper.jar
  47. 6 0
      android/SherpaNcnn/gradle/wrapper/gradle-wrapper.properties
  48. 185 0
      android/SherpaNcnn/gradlew
  49. 89 0
      android/SherpaNcnn/gradlew.bat
  50. 16 0
      android/SherpaNcnn/settings.gradle
  51. 123 0
      build-android-arm64-v8a-with-vulkan.sh
  52. 14 2
      build-android-arm64-v8a.sh
  53. 13 2
      build-android-armv7-eabi.sh
  54. 13 0
      build-android-x86-64.sh
  55. 3 3
      cmake/kaldi-native-fbank.cmake
  56. 21 0
      install-vulkan-macos.md
  57. 18 1
      sherpa-ncnn/csrc/conv-emformer-model.cc
  58. 7 2
      sherpa-ncnn/csrc/features.cc
  59. 3 0
      sherpa-ncnn/csrc/features.h
  60. 18 0
      sherpa-ncnn/csrc/lstm-model.cc
  61. 2 0
      sherpa-ncnn/csrc/model.cc
  62. 3 0
      sherpa-ncnn/csrc/model.h
  63. 2 2
      sherpa-ncnn/csrc/sherpa-ncnn-microphone.cc
  64. 2 3
      sherpa-ncnn/csrc/sherpa-ncnn.cc
  65. 69 50
      sherpa-ncnn/jni/jni.cc

+ 16 - 0
android/SherpaNcnn/.gitignore

@@ -0,0 +1,16 @@
+release/
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties

+ 3 - 0
android/SherpaNcnn/.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 6 - 0
android/SherpaNcnn/.idea/compiler.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="11" />
+  </component>
+</project>

+ 17 - 0
android/SherpaNcnn/.idea/deploymentTargetDropDown.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="deploymentTargetDropDown">
+    <targetSelectedWithDropDown>
+      <Target>
+        <type value="QUICK_BOOT_TARGET" />
+        <deviceKey>
+          <Key>
+            <type value="VIRTUAL_DEVICE_PATH" />
+            <value value="$USER_HOME$/.android/avd/Pixel_4_API_31.avd" />
+          </Key>
+        </deviceKey>
+      </Target>
+    </targetSelectedWithDropDown>
+    <timeTargetWasSelectedWithDropDown value="2022-12-16T07:38:50.268336Z" />
+  </component>
+</project>

+ 19 - 0
android/SherpaNcnn/.idea/gradle.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 10 - 0
android/SherpaNcnn/.idea/misc.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>

+ 6 - 0
android/SherpaNcnn/.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
+  </component>
+</project>

+ 1 - 0
android/SherpaNcnn/app/.gitignore

@@ -0,0 +1 @@
+/build

+ 44 - 0
android/SherpaNcnn/app/build.gradle

@@ -0,0 +1,44 @@
+plugins {
+    id 'com.android.application'
+    id 'org.jetbrains.kotlin.android'
+}
+
+android {
+    namespace 'com.k2fsa.sherpa.ncnn'
+    compileSdk 32
+
+    defaultConfig {
+        applicationId "com.k2fsa.sherpa.ncnn"
+        minSdk 21
+        targetSdk 32
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.core:core-ktx:1.7.0'
+    implementation 'androidx.appcompat:appcompat:1.5.1'
+    implementation 'com.google.android.material:material:1.7.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+}

+ 21 - 0
android/SherpaNcnn/app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 24 - 0
android/SherpaNcnn/app/src/androidTest/java/com/k2fsa/sherpa/ncnn/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.k2fsa.sherpa.ncnn
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.k2fsa.sherpa.ncnn", appContext.packageName)
+    }
+}

+ 32 - 0
android/SherpaNcnn/app/src/main/AndroidManifest.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.SherpaNcnn"
+        tools:targetApi="31">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.app.lib_name"
+                android:value="" />
+        </activity>
+    </application>
+
+</manifest>

+ 2 - 0
android/SherpaNcnn/app/src/main/assets/.gitignore

@@ -0,0 +1,2 @@
+sherpa-ncnn-conv-emformer-transducer-2022-12-06
+sherpa-ncnn-conv-emformer-transducer-2022-12-08

+ 159 - 0
android/SherpaNcnn/app/src/main/java/com/k2fsa/sherpa/ncnn/MainActivity.kt

@@ -0,0 +1,159 @@
+package com.k2fsa.sherpa.ncnn
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.media.AudioFormat
+import android.media.AudioRecord
+import android.media.MediaRecorder
+import android.os.Bundle
+import android.text.method.ScrollingMovementMethod
+import android.util.Log
+import android.widget.Button
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
+import kotlin.concurrent.thread
+
+private const val TAG = "sherpa-ncnn"
+private const val REQUEST_RECORD_AUDIO_PERMISSION = 200
+
+class MainActivity : AppCompatActivity() {
+    private val permissions: Array<String> = arrayOf(Manifest.permission.RECORD_AUDIO)
+
+    // If there is a GPU and useGPU is true, we will use GPU
+    // If there is no GPU and useGPU is true, we won't use GPU
+    private val useGPU: Boolean = true
+
+    private lateinit var model: SherpaNcnn
+    private var audioRecord: AudioRecord? = null
+    private lateinit var recordButton: Button
+    private lateinit var textView: TextView
+    private var recordingThread: Thread? = null
+
+    private val audioSource = MediaRecorder.AudioSource.MIC
+    private val sampleRateInHz = 16000
+    private val channelConfig = AudioFormat.CHANNEL_IN_MONO
+
+    // Note: We don't use AudioFormat.ENCODING_PCM_FLOAT
+    // since the AudioRecord.read(float[]) needs API level >= 23
+    // but we are targeting API level >= 21
+    private val audioFormat = AudioFormat.ENCODING_PCM_16BIT
+
+    @Volatile
+    private var isRecording: Boolean = false
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String>,
+        grantResults: IntArray
+    ) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+        val permissionToRecordAccepted = if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
+            grantResults[0] == PackageManager.PERMISSION_GRANTED
+        } else {
+            false
+        }
+
+        if (!permissionToRecordAccepted) {
+            Log.e(TAG, "Audio record is disallowed")
+            finish()
+        }
+
+        Log.i(TAG, "Audio record is permitted")
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+
+        ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION)
+
+        Log.i(TAG, "Start to initialize model")
+        initModel()
+        Log.i(TAG, "Finished initializing model")
+
+        recordButton = findViewById(R.id.record_button)
+        recordButton.setOnClickListener { onclick() }
+
+        textView = findViewById(R.id.my_text)
+        textView.movementMethod = ScrollingMovementMethod()
+    }
+
+    private fun onclick() {
+        if (!isRecording) {
+            val ret = initMicrophone()
+            if (!ret) {
+                Log.e(TAG, "Failed to initialize microphone")
+                return
+            }
+            Log.i(TAG, "state: ${audioRecord?.state}")
+            audioRecord!!.startRecording()
+            recordButton.setText(R.string.stop)
+            isRecording = true
+            model.reset()
+            recordingThread = thread(true) {
+                processSamples()
+            }
+            Log.i(TAG, "Started recording")
+        } else {
+            isRecording = false
+            audioRecord!!.stop()
+            audioRecord!!.release()
+            audioRecord = null
+            recordButton.setText(R.string.start)
+            textView.text = model.text
+            Log.i(TAG, "Stopped recording")
+        }
+    }
+
+    private fun processSamples() {
+        Log.i(TAG, "processing samples")
+
+        val interval = 0.02 // i.e., 20 ms
+        val bufferSize = (interval * sampleRateInHz).toInt() // in samples
+        val buffer = ShortArray(bufferSize)
+
+        while (isRecording) {
+            val ret = audioRecord?.read(buffer, 0, buffer.size)
+            if (ret != null && ret > 0) {
+                val samples = FloatArray(ret) { buffer[it] / 32768.0f }
+                model.decodeSamples(samples)
+                runOnUiThread { textView.text = model.text }
+            }
+        }
+    }
+
+    private fun initMicrophone(): Boolean {
+        if (ActivityCompat.checkSelfPermission(
+                this,
+                Manifest.permission.RECORD_AUDIO
+            ) != PackageManager.PERMISSION_GRANTED
+        ) {
+            ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION)
+            return false
+        }
+
+        val numBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
+        Log.i(
+            TAG,
+            "buffer size in milliseconds: ${numBytes * 1000.0f / sampleRateInHz}"
+        )
+
+        audioRecord = AudioRecord(
+            audioSource,
+            sampleRateInHz,
+            channelConfig,
+            audioFormat,
+            numBytes * 2 // a sample has two bytes as we are using 16-bit PCM
+        )
+        return true
+    }
+
+    private fun initModel() {
+        model = SherpaNcnn(
+            assetManager = application.assets,
+            modelConfig = getModelConfig(type = 1, useGPU = useGPU)!!,
+            fbankConfig = getFbankConfig(),
+        )
+    }
+}

+ 147 - 0
android/SherpaNcnn/app/src/main/java/com/k2fsa/sherpa/ncnn/SherpaNcnn.kt

@@ -0,0 +1,147 @@
+package com.k2fsa.sherpa.ncnn
+
+import android.content.res.AssetManager
+
+data class FrameExtractionOptions(
+    var sampFreq: Float = 16000.0f,
+    var frameShiftMs: Float = 10.0f,
+    var frameLengthMs: Float = 25.0f,
+    var dither: Float = 0.0f,
+    var preemphCoeff: Float = 0.97f,
+    var removeDcOffset: Boolean = true,
+    var windowType: String = "povey",
+    var roundToPowerOfTwo: Boolean = true,
+    var blackmanCoeff: Float = 0.42f,
+    var snipEdges: Boolean = true,
+    var maxFeatureVectors: Int = -1
+)
+
+data class MelBanksOptions(
+    var numBins: Int = 25,
+    var lowFreq: Float = 20.0f,
+    var highFreq: Float = 0.0f,
+    var vtlnLow: Float = 100.0f,
+    var vtlnHigh: Float = -500.0f,
+    var debugMel: Boolean = false,
+    var htkMode: Boolean = false,
+)
+
+data class FbankOptions(
+    var frameOpts: FrameExtractionOptions = FrameExtractionOptions(),
+    var melOpts: MelBanksOptions = MelBanksOptions(),
+    var useEnergy: Boolean = false,
+    var energyFloor: Float = 0.0f,
+    var rawEnergy: Boolean = true,
+    var htkCompat: Boolean = false,
+    var useLogFbank: Boolean = true,
+    var usePower: Boolean = true,
+)
+
+data class ModelConfig(
+    var encoderParam: String,
+    var encoderBin: String,
+    var decoderParam: String,
+    var decoderBin: String,
+    var joinerParam: String,
+    var joinerBin: String,
+    var tokens: String,
+    var numThreads: Int = 4,
+    var useGPU: Boolean = true, // If there is a GPU and useGPU true, we will use GPU
+)
+
+class SherpaNcnn(
+    assetManager: AssetManager,
+    modelConfig: ModelConfig,
+    var fbankConfig: FbankOptions,
+) {
+    private val ptr: Long
+
+    init {
+        ptr = new(assetManager, modelConfig, fbankConfig)
+    }
+
+    protected fun finalize() {
+        delete(ptr)
+    }
+
+    fun decodeSamples(samples: FloatArray) =
+        decodeSamples(ptr, samples, sampleRate = fbankConfig.frameOpts.sampFreq)
+
+    fun inputFinished() = inputFinished(ptr)
+    fun reset() = reset(ptr)
+
+    val text: String
+        get() = getText(ptr)
+
+    private external fun new(
+        assetManager: AssetManager,
+        modelConfig: ModelConfig,
+        fbankConfig: FbankOptions
+    ): Long
+
+    private external fun delete(ptr: Long)
+    private external fun decodeSamples(ptr: Long, samples: FloatArray, sampleRate: Float)
+    private external fun inputFinished(ptr: Long)
+    private external fun getText(ptr: Long): String
+    private external fun reset(ptr: Long)
+
+    companion object {
+        init {
+            System.loadLibrary("sherpa-ncnn-jni")
+        }
+    }
+}
+
+fun getFbankConfig(): FbankOptions {
+    val fbankConfig = FbankOptions()
+    fbankConfig.frameOpts.dither = 0.0f
+    fbankConfig.melOpts.numBins = 80
+
+    return fbankConfig
+}
+
+/*
+@param type
+0 - https://huggingface.co/csukuangfj/sherpa-ncnn-conv-emformer-transducer-2022-12-04
+    This model supports only Chinese
+
+1 - https://huggingface.co/csukuangfj/sherpa-ncnn-conv-emformer-transducer-2022-12-06
+    This model supports both English and Chinese
+
+2 - https://huggingface.co/csukuangfj/sherpa-ncnn-conv-emformer-transducer-2022-12-08
+    This is a small model with about 18 M parameters. It supports only Chinese
+ */
+fun getModelConfig(type: Int, useGPU: Boolean): ModelConfig? {
+    when (type) {
+        1 -> {
+            val modelDir = "sherpa-ncnn-conv-emformer-transducer-2022-12-06"
+            return ModelConfig(
+                encoderParam = "$modelDir/encoder_jit_trace-pnnx.ncnn.param",
+                encoderBin = "$modelDir/encoder_jit_trace-pnnx.ncnn.bin",
+                decoderParam = "$modelDir/decoder_jit_trace-pnnx.ncnn.param",
+                decoderBin = "$modelDir/decoder_jit_trace-pnnx.ncnn.bin",
+                joinerParam = "$modelDir/joiner_jit_trace-pnnx.ncnn.param",
+                joinerBin = "$modelDir/joiner_jit_trace-pnnx.ncnn.bin",
+                tokens = "$modelDir/tokens.txt",
+                numThreads = 4,
+                useGPU = useGPU,
+            )
+
+        }
+        2 -> {
+            val modelDir = "sherpa-ncnn-conv-emformer-transducer-2022-12-08/v2"
+            return ModelConfig(
+                encoderParam = "$modelDir/encoder_jit_trace-pnnx-epoch-15-avg-3.ncnn.param",
+                encoderBin = "$modelDir/encoder_jit_trace-pnnx-epoch-15-avg-3.ncnn.bin",
+                decoderParam = "$modelDir/decoder_jit_trace-pnnx-epoch-15-avg-3.ncnn.param",
+                decoderBin = "$modelDir/decoder_jit_trace-pnnx-epoch-15-avg-3.ncnn.bin",
+                joinerParam = "$modelDir/joiner_jit_trace-pnnx-epoch-15-avg-3.ncnn.param",
+                joinerBin = "$modelDir/joiner_jit_trace-pnnx-epoch-15-avg-3.ncnn.bin",
+                tokens = "$modelDir/tokens.txt",
+                numThreads = 4,
+                useGPU = useGPU,
+            )
+        }
+    }
+    return null
+}

+ 17 - 0
android/SherpaNcnn/app/src/main/java/com/k2fsa/sherpa/ncnn/WaveReader.kt

@@ -0,0 +1,17 @@
+package com.k2fsa.sherpa.ncnn
+
+import android.content.res.AssetManager
+
+class WaveReader {
+    companion object {
+        // Read a mono wave file.
+        // No resampling is made.
+        external fun readWave(
+            assetManager: AssetManager, filename: String, expected_sample_rate: Float = 16000.0f
+        ): FloatArray?
+
+        init {
+            System.loadLibrary("sherpa-ncnn-jni")
+        }
+    }
+}

+ 1 - 0
android/SherpaNcnn/app/src/main/jniLibs/.gitignore

@@ -0,0 +1 @@
+*.so

+ 0 - 0
android/SherpaNcnn/app/src/main/jniLibs/arm64-v8a/.gitkeep


+ 0 - 0
android/SherpaNcnn/app/src/main/jniLibs/armeabi-v7a/.gitkeep


+ 3 - 0
android/SherpaNcnn/app/src/main/jniLibs/readme.md

@@ -0,0 +1,3 @@
+# Introduction
+
+TODO(fangjun): Add doc for how to build `.so` files

+ 0 - 0
android/SherpaNcnn/app/src/main/jniLibs/x86_64/.gitkeep


+ 30 - 0
android/SherpaNcnn/app/src/main/res/drawable-v24/ic_launcher_foreground.xml

@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <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">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        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"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

+ 170 - 0
android/SherpaNcnn/app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 38 - 0
android/SherpaNcnn/app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/my_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="2.5"
+            android:padding="24dp"
+            android:scrollbars="vertical"
+            android:singleLine="false"
+            android:text="@string/hint"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <Button
+            android:id="@+id/record_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="0.5"
+            android:text="@string/start" />
+    </LinearLayout>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 5 - 0
android/SherpaNcnn/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 5 - 0
android/SherpaNcnn/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
android/SherpaNcnn/app/src/main/res/mipmap-hdpi/ic_launcher.webp


BIN
android/SherpaNcnn/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp


BIN
android/SherpaNcnn/app/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
android/SherpaNcnn/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
android/SherpaNcnn/app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
android/SherpaNcnn/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


BIN
android/SherpaNcnn/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp


BIN
android/SherpaNcnn/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


BIN
android/SherpaNcnn/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


BIN
android/SherpaNcnn/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


+ 16 - 0
android/SherpaNcnn/app/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.SherpaNcnn" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 10 - 0
android/SherpaNcnn/app/src/main/res/values/colors.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>

+ 17 - 0
android/SherpaNcnn/app/src/main/res/values/strings.xml

@@ -0,0 +1,17 @@
+<resources>
+    <string name="app_name">ASR with Next-gen Kaldi</string>
+    <string name="hint">Click the Start button to play speech-to-text with Next-gen Kaldi.
+        \n
+        \n\n\n
+        The source code and pre-trained models are publicly available.
+        Please see https://github.com/k2-fsa/sherpa-ncnn for details.
+
+        \n\n\n
+        Note: This transducer-based model supports Chinese as well as English.
+
+        \n\n\n
+        We use https://github.com/tencent/ncnn for neural network computation.
+    </string>
+    <string name="start">Start</string>
+    <string name="stop">Stop</string>
+</resources>

+ 16 - 0
android/SherpaNcnn/app/src/main/res/values/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.SherpaNcnn" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 13 - 0
android/SherpaNcnn/app/src/main/res/xml/backup_rules.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>

+ 19 - 0
android/SherpaNcnn/app/src/main/res/xml/data_extraction_rules.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>

+ 17 - 0
android/SherpaNcnn/app/src/test/java/com/k2fsa/sherpa/ncnn/ExampleUnitTest.kt

@@ -0,0 +1,17 @@
+package com.k2fsa.sherpa.ncnn
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}

+ 6 - 0
android/SherpaNcnn/build.gradle

@@ -0,0 +1,6 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+    id 'com.android.application' version '7.3.1' apply false
+    id 'com.android.library' version '7.3.1' apply false
+    id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
+}

+ 23 - 0
android/SherpaNcnn/gradle.properties

@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true

BIN
android/SherpaNcnn/gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
android/SherpaNcnn/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Thu Dec 15 13:53:03 CST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME

+ 185 - 0
android/SherpaNcnn/gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 89 - 0
android/SherpaNcnn/gradlew.bat

@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 16 - 0
android/SherpaNcnn/settings.gradle

@@ -0,0 +1,16 @@
+pluginManagement {
+    repositories {
+        gradlePluginPortal()
+        google()
+        mavenCentral()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+rootProject.name = "SherpaNcnn"
+include ':app'

+ 123 - 0
build-android-arm64-v8a-with-vulkan.sh

@@ -0,0 +1,123 @@
+#!/usr/bin/env bash
+set -e
+
+# First, we assume you have installed vulkan by following
+# windows: https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html
+# linux: https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html
+# macOS: https://vulkan.lunarg.com/doc/sdk/latest/mac/getting_started.html
+#
+# You can read ./install-vulkan-macos.md for a note about installation on macOS.
+
+dir=build-android-arm64-v8a-with-vulkan
+
+mkdir -p $dir
+cd $dir
+
+# Note from https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-android
+# (optional) remove the hardcoded debug flag in Android NDK android-ndk
+# issue: https://github.com/android/ndk/issues/243
+#
+# open $ANDROID_NDK/build/cmake/android.toolchain.cmake for ndk < r23
+# or $ANDROID_NDK/build/cmake/android-legacy.toolchain.cmake for ndk >= r23
+#
+# delete "-g" line
+#
+# list(APPEND ANDROID_COMPILER_FLAGS
+#   -g
+#   -DANDROID
+
+
+if [ -z $ANDROID_NDK ]; then
+  ANDROID_NDK=/ceph-fj/fangjun/software/android-sdk/ndk/21.0.6113669
+  # or use
+  # ANDROID_NDK=/ceph-fj/fangjun/software/android-ndk
+  #
+  # Inside the $ANDROID_NDK directory, you can find a binary ndk-build
+  # and some other files like the file "build/cmake/android.toolchain.cmake"
+
+  if [ ! -d $ANDROID_NDK ]; then
+    # For macOS, I have installed Android Studio, select the menu
+    # Tools -> SDK manager -> Android SDK
+    # and set "Android SDK location" to /Users/fangjun/software/my-android
+    ANDROID_NDK=/Users/fangjun/software/my-android/ndk/22.1.7171670
+  fi
+fi
+
+if [ ! -d $ANDROID_NDK ]; then
+  echo Please set the environment variable ANDROID_NDK before you run this script
+  exit 1
+fi
+
+if [ -z $VULKAN_SDK ]; then
+  VULKAN_SDK=/Users/fangjun/software/vulkansdk/1.3.236.0/macOS
+fi
+
+if [ ! -d $VULKAN_SDK ]; then
+  echo "Please install Vulkan SDK first. Please see ./install-vulkan-macos.md"
+  exit 1
+fi
+
+echo "ANDROID_NDK: $ANDROID_NDK"
+echo "VULKAN_SDK: $VULKAN_SDK"
+sleep 1
+
+if [ ! -e my-glslang/build/install/lib/libglslang.so ]; then
+  if [ ! -d my-glslang ]; then
+    git clone https://github.com/KhronosGroup/glslang.git my-glslang
+  fi
+
+  pushd my-glslang
+  # Note: the master branch of ncnn is using the following commit
+  git checkout 88fd417b0bb7d91755961c70e846d274c182f2b0
+
+  mkdir -p build
+  cd build
+
+  if [ $(uname) == "Darwin" ]; then
+    os=darwin
+  elif [ $(uname) == "Linux" ]; then
+    os=linux
+  else
+    echo "Unsupported system: $(uname -a)"
+    exit 1
+  fi
+
+  cmake $SOURCE_DIR \
+    -DBUILD_SHARED_LIBS=ON \
+    -DCMAKE_INSTALL_PREFIX="$(pwd)/install" \
+    -DANDROID_ABI=arm64-v8a \
+    -DCMAKE_BUILD_TYPE=Release \
+    -DANDROID_PLATFORM=android-24 \
+    -DCMAKE_SYSTEM_NAME=Android \
+    -DANDROID_TOOLCHAIN=clang \
+    -DANDROID_ARM_MODE=arm \
+    -DCMAKE_MAKE_PROGRAM=$ANDROID_NDK/prebuilt/${os}-x86_64/bin/make \
+    -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
+    ..
+
+  make -j4
+  make install/strip
+  ls -lh install/lib/
+
+  echo "Finish building glslang"
+  sleep 1
+
+  popd
+fi
+
+cmake -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK/build/cmake/android.toolchain.cmake" \
+    -Dglslang_DIR=$PWD/my-glslang/build/install/lib/cmake/glslang \
+    -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=False \
+    -DCMAKE_BUILD_TYPE=Release \
+    -DBUILD_SHARED_LIBS=ON \
+    -DNCNN_SYSTEM_GLSLANG=ON \
+    -DSHERPA_NCNN_ENABLE_PORTAUDIO=OFF \
+    -DCMAKE_INSTALL_PREFIX=./install \
+    -DANDROID_ABI="arm64-v8a" \
+    -DNCNN_VULKAN=ON \
+    -DANDROID_PLATFORM=android-24 ..
+
+make VERBOSE=1 -j4
+make install/strip
+
+cp -v my-glslang/build/install/lib/lib*.so install/lib/

+ 14 - 2
build-android-arm64-v8a.sh

@@ -1,13 +1,25 @@
 #!/usr/bin/env bash
 set -e
 
-# see https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-android
-
 dir=build-android-arm64-v8a
 
 mkdir -p $dir
 cd $dir
 
+# Note from https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-android
+# (optional) remove the hardcoded debug flag in Android NDK android-ndk
+# issue: https://github.com/android/ndk/issues/243
+#
+# open $ANDROID_NDK/build/cmake/android.toolchain.cmake for ndk < r23
+# or $ANDROID_NDK/build/cmake/android-legacy.toolchain.cmake for ndk >= r23
+#
+# delete "-g" line
+#
+# list(APPEND ANDROID_COMPILER_FLAGS
+#   -g
+#   -DANDROID
+
+
 if [ -z $ANDROID_NDK ]; then
   ANDROID_NDK=/ceph-fj/fangjun/software/android-sdk/ndk/21.0.6113669
   # or use

+ 13 - 2
build-android-armv7-eabi.sh

@@ -1,13 +1,24 @@
 #!/usr/bin/env bash
 set -x
 
-# see https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-android
-
 dir=build-android-armv7-eabi
 
 mkdir -p $dir
 cd $dir
 
+# Note from https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-android
+# (optional) remove the hardcoded debug flag in Android NDK android-ndk
+# issue: https://github.com/android/ndk/issues/243
+#
+# open $ANDROID_NDK/build/cmake/android.toolchain.cmake for ndk < r23
+# or $ANDROID_NDK/build/cmake/android-legacy.toolchain.cmake for ndk >= r23
+#
+# delete "-g" line
+#
+# list(APPEND ANDROID_COMPILER_FLAGS
+#   -g
+#   -DANDROID
+
 if [ -z $ANDROID_NDK ]; then
   ANDROID_NDK=/ceph-fj/fangjun/software/android-sdk/ndk/21.0.6113669
   # or use

+ 13 - 0
build-android-x86-64.sh

@@ -8,6 +8,19 @@ dir=build-android-x86-64
 mkdir -p $dir
 cd $dir
 
+# Note from https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-android
+# (optional) remove the hardcoded debug flag in Android NDK android-ndk
+# issue: https://github.com/android/ndk/issues/243
+#
+# open $ANDROID_NDK/build/cmake/android.toolchain.cmake for ndk < r23
+# or $ANDROID_NDK/build/cmake/android-legacy.toolchain.cmake for ndk >= r23
+#
+# delete "-g" line
+#
+# list(APPEND ANDROID_COMPILER_FLAGS
+#   -g
+#   -DANDROID
+
 if [ -z $ANDROID_NDK ]; then
   ANDROID_NDK=/ceph-fj/fangjun/software/android-sdk/ndk/21.0.6113669
   # or use

+ 3 - 3
cmake/kaldi-native-fbank.cmake

@@ -3,9 +3,9 @@ function(download_kaldi_native_fbank)
 
   # If you don't have access to the internet, please download it to your
   # local drive and modify the following line according to your needs.
-  # set(kaldi_native_fbank_URL  "file:///ceph-fj/fangjun/open-source/sherpa-ncnn/v1.7.tar.gz")
-  set(kaldi_native_fbank_URL  "https://github.com/csukuangfj/kaldi-native-fbank/archive/refs/tags/v1.7.tar.gz")
-  set(kaldi_native_fbank_HASH "SHA256=7785eb1a95efd4ea46604d1a6681e89a2dd120b5214b9ae4c0d7813a735b33f0")
+  # set(kaldi_native_fbank_URL  "file:///ceph-fj/fangjun/open-source/sherpa-ncnn/v1.8.tar.gz")
+  set(kaldi_native_fbank_URL  "https://github.com/csukuangfj/kaldi-native-fbank/archive/refs/tags/v1.8.tar.gz")
+  set(kaldi_native_fbank_HASH "SHA256=1aefda7e76456e2c5005e05fa10cdc7f91c3ffc0a7fad000a90b50e7ed18087b")
 
   set(KALDI_NATIVE_FBANK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
   set(KALDI_NATIVE_FBANK_BUILD_PYTHON OFF CACHE BOOL "" FORCE)

+ 21 - 0
install-vulkan-macos.md

@@ -0,0 +1,21 @@
+# Introductino
+
+This note describes how to install Vulkan on macOS.
+
+Please refer to https://vulkan.lunarg.com/doc/sdk/latest/mac/getting_started.html
+for more details.
+
+## 1.3.236.0
+
+1. Download https://sdk.lunarg.com/sdk/download/1.3.236.0/mac/vulkansdk-macos-1.3.236.0.dmg
+2. Double click `vulkansdk-macos-1.3.236.0.dmg` to run it after downloading
+3. Doulbe click the icon `InstallVulkan`
+4. Click `Next`.
+5. Specify the directory where Vulkan SDK will be installed. Here, I use
+  `/Users/fangjun/software/VulkanSDK/1.3.236.0` . You can choose any location you like.
+6. Click `Next`.
+7. Click `Select All` and then click `Next`
+8. Select `I accept the license` and click `Next`
+9. Click `Install`.
+10. Enter your password
+11. After installation, click `Finish`.

+ 18 - 1
sherpa-ncnn/csrc/conv-emformer-model.cc

@@ -9,13 +9,30 @@
 #include <utility>
 #include <vector>
 
-#include "net.h"  // NOLINT
+#include "net.h"       // NOLINT
+#include "platform.h"  // NOLINT
 #include "sherpa-ncnn/csrc/meta-data.h"
 
 namespace sherpa_ncnn {
 
 ConvEmformerModel::ConvEmformerModel(const ModelConfig &config)
     : num_threads_(config.num_threads) {
+  bool has_gpu = false;
+#if NCNN_VULKAN
+  has_gpu = ncnn::get_gpu_count() > 0;
+#endif
+
+  if (has_gpu && config.use_vulkan_compute) {
+    encoder_.opt.use_vulkan_compute = true;
+    decoder_.opt.use_vulkan_compute = true;
+    joiner_.opt.use_vulkan_compute = true;
+    NCNN_LOGE("Use GPU");
+  } else {
+    NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
+              static_cast<int32_t>(has_gpu),
+              static_cast<int32_t>(config.use_vulkan_compute));
+  }
+
   InitEncoder(config.encoder_param, config.encoder_bin);
   InitDecoder(config.decoder_param, config.decoder_bin);
   InitJoiner(config.joiner_param, config.joiner_bin);

+ 7 - 2
sherpa-ncnn/csrc/features.cc

@@ -25,8 +25,9 @@
 
 namespace sherpa_ncnn {
 
-FeatureExtractor::FeatureExtractor(const knf::FbankOptions &opts) {
-  fbank_ = std::make_unique<knf::OnlineFbank>(opts);
+FeatureExtractor::FeatureExtractor(const knf::FbankOptions &opts)
+    : opts_(opts) {
+  fbank_ = std::make_unique<knf::OnlineFbank>(opts_);
 }
 
 void FeatureExtractor::AcceptWaveform(float sampling_rate,
@@ -69,4 +70,8 @@ ncnn::Mat FeatureExtractor::GetFrames(int32_t frame_index, int32_t n) const {
   return features;
 }
 
+void FeatureExtractor::Reset() {
+  fbank_ = std::make_unique<knf::OnlineFbank>(opts_);
+}
+
 }  // namespace sherpa_ncnn

+ 3 - 0
sherpa-ncnn/csrc/features.h

@@ -63,8 +63,11 @@ class FeatureExtractor {
    */
   ncnn::Mat GetFrames(int32_t frame_index, int32_t n) const;
 
+  void Reset();
+
  private:
   std::unique_ptr<knf::OnlineFbank> fbank_;
+  knf::FbankOptions opts_;
   mutable std::mutex mutex_;
 };
 

+ 18 - 0
sherpa-ncnn/csrc/lstm-model.cc

@@ -20,10 +20,28 @@
 #include <utility>
 #include <vector>
 
+#include "platform.h"  // NOLINT
+
 namespace sherpa_ncnn {
 
 LstmModel::LstmModel(const ModelConfig &config)
     : num_threads_(config.num_threads) {
+  bool has_gpu = false;
+#if NCNN_VULKAN
+  has_gpu = ncnn::get_gpu_count() > 0;
+#endif
+
+  if (has_gpu && config.use_vulkan_compute) {
+    encoder_.opt.use_vulkan_compute = true;
+    decoder_.opt.use_vulkan_compute = true;
+    joiner_.opt.use_vulkan_compute = true;
+    NCNN_LOGE("Use GPU");
+  } else {
+    NCNN_LOGE("Don't Use GPU. has_gpu: %d, config.use_vulkan_compute: %d",
+              static_cast<int32_t>(has_gpu),
+              static_cast<int32_t>(config.use_vulkan_compute));
+  }
+
   InitEncoder(config.encoder_param, config.encoder_bin);
   InitDecoder(config.decoder_param, config.decoder_bin);
   InitJoiner(config.joiner_param, config.joiner_bin);

+ 2 - 0
sherpa-ncnn/csrc/model.cc

@@ -36,6 +36,8 @@ std::string ModelConfig::ToString() const {
   os << "joiner_param: " << joiner_param << "\n";
   os << "joiner_bin: " << joiner_bin << "\n";
 
+  os << "tokens: " << tokens << "\n";
+
   os << "num_threads: " << num_threads << "\n";
 
   return os.str();

+ 3 - 0
sherpa-ncnn/csrc/model.h

@@ -35,7 +35,10 @@ struct ModelConfig {
   std::string decoder_bin;    // path to decoder.ncnn.bin
   std::string joiner_param;   // path to joiner.ncnn.param
   std::string joiner_bin;     // path to joiner.ncnn.bin
+  std::string tokens;         // path to tokens.txt
   int32_t num_threads;        // number of threads to run the model
+  bool use_vulkan_compute = false;
+
   std::string ToString() const;
 };
 

+ 2 - 2
sherpa-ncnn/csrc/sherpa-ncnn-microphone.cc

@@ -73,7 +73,7 @@ https://huggingface.co/csukuangfj/sherpa-ncnn-2022-09-05
 
   sherpa_ncnn::ModelConfig config;
 
-  std::string tokens = argv[1];
+  config.tokens = argv[1];
   config.encoder_param = argv[2];
   config.encoder_bin = argv[3];
   config.decoder_param = argv[4];
@@ -86,7 +86,7 @@ https://huggingface.co/csukuangfj/sherpa-ncnn-2022-09-05
     config.num_threads = atoi(argv[8]);
   }
 
-  sherpa_ncnn::SymbolTable sym(tokens);
+  sherpa_ncnn::SymbolTable sym(config.tokens);
   fprintf(stderr, "%s\n", config.ToString().c_str());
 
   auto model = sherpa_ncnn::Model::Create(config);

+ 2 - 3
sherpa-ncnn/csrc/sherpa-ncnn.cc

@@ -50,8 +50,7 @@ https://huggingface.co/csukuangfj/sherpa-ncnn-2022-09-05
   }
   sherpa_ncnn::ModelConfig config;
 
-  std::string tokens = argv[1];
-
+  config.tokens = argv[1];
   config.encoder_param = argv[2];
   config.encoder_bin = argv[3];
   config.decoder_param = argv[4];
@@ -68,7 +67,7 @@ https://huggingface.co/csukuangfj/sherpa-ncnn-2022-09-05
 
   float expected_sampling_rate = 16000;
 
-  sherpa_ncnn::SymbolTable sym(tokens);
+  sherpa_ncnn::SymbolTable sym(config.tokens);
 
   std::cout << config.ToString() << "\n";
 

+ 69 - 50
sherpa-ncnn/jni/jni.cc

@@ -40,31 +40,20 @@ namespace sherpa_ncnn {
 class SherpaNcnn {
  public:
   SherpaNcnn(AAssetManager *mgr, const ModelConfig &model_config,
-             const knf::FbankOptions &fbank_config, const std::string &tokens)
+             const knf::FbankOptions &fbank_config)
       : model_(Model::Create(mgr, model_config)),
-        feature_extractor_(fbank_config),
-        sym_(mgr, tokens) {
-    // Initialize decoder_output
-    int32_t context_size = model_->ContextSize();
-    int32_t blank_id = 0;
-
-    ncnn::Mat decoder_input(context_size);
-    for (int32_t i = 0; i != context_size; ++i) {
-      static_cast<int32_t *>(decoder_input)[i] = blank_id;
-    }
-
-    decoder_out_ = model_->RunDecoder(decoder_input);
-
-    hyp_.resize(context_size, 0);
+        feature_extractor_(std::make_unique<FeatureExtractor>(fbank_config)),
+        sym_(mgr, model_config.tokens) {
+    Reset();
   }
 
   void DecodeSamples(float sample_rate, const float *samples, int32_t n) {
-    feature_extractor_.AcceptWaveform(sample_rate, samples, n);
+    feature_extractor_->AcceptWaveform(sample_rate, samples, n);
     Decode();
   }
 
   void InputFinished() {
-    feature_extractor_.InputFinished();
+    feature_extractor_->InputFinished();
     Decode();
   }
 
@@ -79,15 +68,33 @@ class SherpaNcnn {
     return text;
   }
 
+  void Reset() {
+    feature_extractor_->Reset();
+    num_processed_ = 0;
+    states_.clear();
+
+    int32_t context_size = model_->ContextSize();
+    int32_t blank_id = 0;
+
+    ncnn::Mat decoder_input(context_size);
+    for (int32_t i = 0; i != context_size; ++i) {
+      static_cast<int32_t *>(decoder_input)[i] = blank_id;
+    }
+
+    decoder_out_ = model_->RunDecoder(decoder_input);
+
+    hyp_.resize(context_size, 0);
+  }
+
  private:
   void Decode() {
     int32_t segment = model_->Segment();
     int32_t offset = model_->Offset();
 
     ncnn::Mat encoder_out;
-    while (feature_extractor_.NumFramesReady() - num_processed_ >= segment) {
+    while (feature_extractor_->NumFramesReady() - num_processed_ >= segment) {
       ncnn::Mat features =
-          feature_extractor_.GetFrames(num_processed_, segment);
+          feature_extractor_->GetFrames(num_processed_, segment);
       num_processed_ += offset;
 
       std::tie(encoder_out, states_) = model_->RunEncoder(features, states_);
@@ -98,7 +105,7 @@ class SherpaNcnn {
 
  private:
   std::unique_ptr<Model> model_;
-  FeatureExtractor feature_extractor_;
+  std::unique_ptr<FeatureExtractor> feature_extractor_;
   sherpa_ncnn::SymbolTable sym_;
 
   std::vector<int32_t> hyp_;
@@ -150,9 +157,18 @@ static ModelConfig GetModelConfig(JNIEnv *env, jobject config) {
   model_config.joiner_bin = p;
   env->ReleaseStringUTFChars(s, p);
 
+  fid = env->GetFieldID(cls, "tokens", "Ljava/lang/String;");
+  s = (jstring)env->GetObjectField(config, fid);
+  p = env->GetStringUTFChars(s, nullptr);
+  model_config.tokens = p;
+  env->ReleaseStringUTFChars(s, p);
+
   fid = env->GetFieldID(cls, "numThreads", "I");
   model_config.num_threads = env->GetIntField(config, fid);
 
+  fid = env->GetFieldID(cls, "useGPU", "Z");
+  model_config.use_vulkan_compute = env->GetBooleanField(config, fid);
+
   return model_config;
 }
 
@@ -165,92 +181,92 @@ static knf::FbankOptions GetFbankOptions(JNIEnv *env, jobject opts) {
 
   knf::FbankOptions fbank_opts;
 
-  fid = env->GetFieldID(cls, "use_energy", "Z");
+  fid = env->GetFieldID(cls, "useEnergy", "Z");
   fbank_opts.use_energy = env->GetBooleanField(opts, fid);
 
-  fid = env->GetFieldID(cls, "energy_floor", "F");
+  fid = env->GetFieldID(cls, "energyFloor", "F");
   fbank_opts.energy_floor = env->GetFloatField(opts, fid);
 
-  fid = env->GetFieldID(cls, "raw_energy", "Z");
+  fid = env->GetFieldID(cls, "rawEnergy", "Z");
   fbank_opts.raw_energy = env->GetBooleanField(opts, fid);
 
-  fid = env->GetFieldID(cls, "htk_compat", "Z");
+  fid = env->GetFieldID(cls, "htkCompat", "Z");
   fbank_opts.htk_compat = env->GetBooleanField(opts, fid);
 
-  fid = env->GetFieldID(cls, "use_log_fbank", "Z");
+  fid = env->GetFieldID(cls, "useLogFbank", "Z");
   fbank_opts.use_log_fbank = env->GetBooleanField(opts, fid);
 
-  fid = env->GetFieldID(cls, "use_power", "Z");
+  fid = env->GetFieldID(cls, "usePower", "Z");
   fbank_opts.use_power = env->GetBooleanField(opts, fid);
 
-  fid = env->GetFieldID(cls, "frame_opts",
+  fid = env->GetFieldID(cls, "frameOpts",
                         "Lcom/k2fsa/sherpa/ncnn/FrameExtractionOptions;");
 
   jobject frame_opts = env->GetObjectField(opts, fid);
   jclass frame_opts_cls = env->GetObjectClass(frame_opts);
 
-  fid = env->GetFieldID(frame_opts_cls, "samp_freq", "F");
+  fid = env->GetFieldID(frame_opts_cls, "sampFreq", "F");
   fbank_opts.frame_opts.samp_freq = env->GetFloatField(frame_opts, fid);
 
-  fid = env->GetFieldID(frame_opts_cls, "frame_shift_ms", "F");
+  fid = env->GetFieldID(frame_opts_cls, "frameShiftMs", "F");
   fbank_opts.frame_opts.frame_shift_ms = env->GetFloatField(frame_opts, fid);
 
-  fid = env->GetFieldID(frame_opts_cls, "frame_length_ms", "F");
+  fid = env->GetFieldID(frame_opts_cls, "frameLengthMs", "F");
   fbank_opts.frame_opts.frame_length_ms = env->GetFloatField(frame_opts, fid);
 
   fid = env->GetFieldID(frame_opts_cls, "dither", "F");
   fbank_opts.frame_opts.dither = env->GetFloatField(frame_opts, fid);
 
-  fid = env->GetFieldID(frame_opts_cls, "preemph_coeff", "F");
+  fid = env->GetFieldID(frame_opts_cls, "preemphCoeff", "F");
   fbank_opts.frame_opts.preemph_coeff = env->GetFloatField(frame_opts, fid);
 
-  fid = env->GetFieldID(frame_opts_cls, "remove_dc_offset", "Z");
+  fid = env->GetFieldID(frame_opts_cls, "removeDcOffset", "Z");
   fbank_opts.frame_opts.remove_dc_offset =
       env->GetBooleanField(frame_opts, fid);
 
-  fid = env->GetFieldID(frame_opts_cls, "window_type", "Ljava/lang/String;");
+  fid = env->GetFieldID(frame_opts_cls, "windowType", "Ljava/lang/String;");
   jstring window_type = (jstring)env->GetObjectField(frame_opts, fid);
   const char *p_window_type = env->GetStringUTFChars(window_type, nullptr);
   fbank_opts.frame_opts.window_type = p_window_type;
   env->ReleaseStringUTFChars(window_type, p_window_type);
 
-  fid = env->GetFieldID(frame_opts_cls, "round_to_power_of_two", "Z");
+  fid = env->GetFieldID(frame_opts_cls, "roundToPowerOfTwo", "Z");
   fbank_opts.frame_opts.round_to_power_of_two =
       env->GetBooleanField(frame_opts, fid);
 
-  fid = env->GetFieldID(frame_opts_cls, "blackman_coeff", "F");
+  fid = env->GetFieldID(frame_opts_cls, "blackmanCoeff", "F");
   fbank_opts.frame_opts.blackman_coeff = env->GetFloatField(frame_opts, fid);
 
-  fid = env->GetFieldID(frame_opts_cls, "snip_edges", "Z");
+  fid = env->GetFieldID(frame_opts_cls, "snipEdges", "Z");
   fbank_opts.frame_opts.snip_edges = env->GetBooleanField(frame_opts, fid);
 
-  fid = env->GetFieldID(frame_opts_cls, "max_feature_vectors", "I");
+  fid = env->GetFieldID(frame_opts_cls, "maxFeatureVectors", "I");
   fbank_opts.frame_opts.max_feature_vectors = env->GetIntField(frame_opts, fid);
 
-  fid = env->GetFieldID(cls, "mel_opts",
+  fid = env->GetFieldID(cls, "melOpts",
                         "Lcom/k2fsa/sherpa/ncnn/MelBanksOptions;");
   jobject mel_opts = env->GetObjectField(opts, fid);
   jclass mel_opts_cls = env->GetObjectClass(mel_opts);
 
-  fid = env->GetFieldID(mel_opts_cls, "num_bins", "I");
+  fid = env->GetFieldID(mel_opts_cls, "numBins", "I");
   fbank_opts.mel_opts.num_bins = env->GetIntField(mel_opts, fid);
 
-  fid = env->GetFieldID(mel_opts_cls, "low_freq", "F");
+  fid = env->GetFieldID(mel_opts_cls, "lowFreq", "F");
   fbank_opts.mel_opts.low_freq = env->GetFloatField(mel_opts, fid);
 
-  fid = env->GetFieldID(mel_opts_cls, "high_freq", "F");
+  fid = env->GetFieldID(mel_opts_cls, "highFreq", "F");
   fbank_opts.mel_opts.high_freq = env->GetFloatField(mel_opts, fid);
 
-  fid = env->GetFieldID(mel_opts_cls, "vtln_low", "F");
+  fid = env->GetFieldID(mel_opts_cls, "vtlnLow", "F");
   fbank_opts.mel_opts.vtln_low = env->GetFloatField(mel_opts, fid);
 
-  fid = env->GetFieldID(mel_opts_cls, "vtln_high", "F");
+  fid = env->GetFieldID(mel_opts_cls, "vtlnHigh", "F");
   fbank_opts.mel_opts.vtln_high = env->GetFloatField(mel_opts, fid);
 
-  fid = env->GetFieldID(mel_opts_cls, "debug_mel", "Z");
+  fid = env->GetFieldID(mel_opts_cls, "debugMel", "Z");
   fbank_opts.mel_opts.debug_mel = env->GetBooleanField(mel_opts, fid);
 
-  fid = env->GetFieldID(mel_opts_cls, "htk_mode", "Z");
+  fid = env->GetFieldID(mel_opts_cls, "htkMode", "Z");
   fbank_opts.mel_opts.htk_mode = env->GetBooleanField(mel_opts, fid);
 
   return fbank_opts;
@@ -261,7 +277,7 @@ static knf::FbankOptions GetFbankOptions(JNIEnv *env, jobject opts) {
 SHERPA_EXTERN_C
 JNIEXPORT jlong JNICALL Java_com_k2fsa_sherpa_ncnn_SherpaNcnn_new(
     JNIEnv *env, jobject /*obj*/, jobject asset_manager, jobject _model_config,
-    jobject _fbank_config, jstring tokens) {
+    jobject _fbank_config) {
   AAssetManager *mgr = AAssetManager_fromJava(env, asset_manager);
   if (!mgr) {
     NCNN_LOGE("Failed to get asset manager: %p", mgr);
@@ -273,10 +289,7 @@ JNIEXPORT jlong JNICALL Java_com_k2fsa_sherpa_ncnn_SherpaNcnn_new(
   knf::FbankOptions fbank_opts =
       sherpa_ncnn::GetFbankOptions(env, _fbank_config);
 
-  const char *p_tokens = env->GetStringUTFChars(tokens, nullptr);
-  auto model =
-      new sherpa_ncnn::SherpaNcnn(mgr, model_config, fbank_opts, p_tokens);
-  env->ReleaseStringUTFChars(tokens, p_tokens);
+  auto model = new sherpa_ncnn::SherpaNcnn(mgr, model_config, fbank_opts);
 
   return (jlong)model;
 }
@@ -287,6 +300,12 @@ JNIEXPORT void JNICALL Java_com_k2fsa_sherpa_ncnn_SherpaNcnn_delete(
   delete reinterpret_cast<sherpa_ncnn::SherpaNcnn *>(ptr);
 }
 
+SHERPA_EXTERN_C
+JNIEXPORT void JNICALL Java_com_k2fsa_sherpa_ncnn_SherpaNcnn_reset(
+    JNIEnv *env, jobject /*obj*/, jlong ptr) {
+  reinterpret_cast<sherpa_ncnn::SherpaNcnn *>(ptr)->Reset();
+}
+
 SHERPA_EXTERN_C
 JNIEXPORT void JNICALL Java_com_k2fsa_sherpa_ncnn_SherpaNcnn_decodeSamples(
     JNIEnv *env, jobject /*obj*/, jlong ptr, jfloatArray samples,