Fangjun Kuang
Committed by GitHub

Add Android APK for Silero VAD (#335)

正在显示 68 个修改的文件 包含 1561 行增加44 行删除
@@ -38,6 +38,7 @@ jobs: @@ -38,6 +38,7 @@ jobs:
38 shell: bash 38 shell: bash
39 run: | 39 run: |
40 export ANDROID_NDK=$ANDROID_NDK_LATEST_HOME 40 export ANDROID_NDK=$ANDROID_NDK_LATEST_HOME
  41 + ./build-apk-vad.sh
41 ./build-apk-two-pass.sh 42 ./build-apk-two-pass.sh
42 ./build-apk.sh 43 ./build-apk.sh
43 44
1 cmake_minimum_required(VERSION 3.13 FATAL_ERROR) 1 cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
2 project(sherpa-onnx) 2 project(sherpa-onnx)
3 3
4 -set(SHERPA_ONNX_VERSION "1.7.18") 4 +set(SHERPA_ONNX_VERSION "1.7.19")
5 5
6 # Disable warning about 6 # Disable warning about
7 # 7 #
  1 +# Introduction
  2 +
  3 +Please refer to
  4 +https://k2-fsa.github.io/sherpa/onnx/android/index.html
  5 +for usage.
  1 +// Copyright (c) 2023 Xiaomi Corporation
1 package com.k2fsa.sherpa.onnx 2 package com.k2fsa.sherpa.onnx
2 3
3 import android.content.res.AssetManager 4 import android.content.res.AssetManager
  1 +// Copyright (c) 2023 Xiaomi Corporation
1 package com.k2fsa.sherpa.onnx 2 package com.k2fsa.sherpa.onnx
2 3
3 import android.content.res.AssetManager 4 import android.content.res.AssetManager
  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 +# Default ignored files
  2 +/shelf/
  3 +/workspace.xml
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="CompilerConfiguration">
  4 + <bytecodeTargetLevel target="11" />
  5 + </component>
  6 +</project>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="GradleMigrationSettings" migrationVersion="1" />
  4 + <component name="GradleSettings">
  5 + <option name="linkedExternalProjectsSettings">
  6 + <GradleProjectSettings>
  7 + <option name="testRunner" value="GRADLE" />
  8 + <option name="distributionType" value="DEFAULT_WRAPPED" />
  9 + <option name="externalProjectPath" value="$PROJECT_DIR$" />
  10 + <option name="modules">
  11 + <set>
  12 + <option value="$PROJECT_DIR$" />
  13 + <option value="$PROJECT_DIR$/app" />
  14 + </set>
  15 + </option>
  16 + </GradleProjectSettings>
  17 + </option>
  18 + </component>
  19 +</project>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="ExternalStorageConfigurationManager" enabled="true" />
  4 + <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
  5 + <output url="file://$PROJECT_DIR$/build/classes" />
  6 + </component>
  7 + <component name="ProjectType">
  8 + <option name="id" value="Android" />
  9 + </component>
  10 +</project>
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project version="4">
  3 + <component name="VcsDirectoryMappings">
  4 + <mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
  5 + </component>
  6 +</project>
  1 +plugins {
  2 + id 'com.android.application'
  3 + id 'org.jetbrains.kotlin.android'
  4 +}
  5 +
  6 +android {
  7 + namespace 'com.k2fsa.sherpa.onnx'
  8 + compileSdk 33
  9 +
  10 + defaultConfig {
  11 + applicationId "com.k2fsa.sherpa.onnx"
  12 + minSdk 21
  13 + targetSdk 33
  14 + versionCode 1
  15 + versionName "1.0"
  16 +
  17 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  18 + }
  19 +
  20 + buildTypes {
  21 + release {
  22 + minifyEnabled false
  23 + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  24 + }
  25 + }
  26 + compileOptions {
  27 + sourceCompatibility JavaVersion.VERSION_1_8
  28 + targetCompatibility JavaVersion.VERSION_1_8
  29 + }
  30 + kotlinOptions {
  31 + jvmTarget = '1.8'
  32 + }
  33 +}
  34 +
  35 +dependencies {
  36 +
  37 + implementation 'androidx.core:core-ktx:1.7.0'
  38 + implementation 'androidx.appcompat:appcompat:1.6.1'
  39 + implementation 'com.google.android.material:material:1.9.0'
  40 + implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
  41 + testImplementation 'junit:junit:4.13.2'
  42 + androidTestImplementation 'androidx.test.ext:junit:1.1.5'
  43 + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
  44 +}
  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
  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", 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.SherpaOnnxVad"
  16 + tools:targetApi="31">
  17 + <activity
  18 + android:name=".MainActivity"
  19 + android:exported="true">
  20 + <intent-filter>
  21 + <action android:name="android.intent.action.MAIN" />
  22 +
  23 + <category android:name="android.intent.category.LAUNCHER" />
  24 + </intent-filter>
  25 +
  26 + <meta-data
  27 + android:name="android.app.lib_name"
  28 + android:value="" />
  29 + </activity>
  30 + </application>
  31 +
  32 +</manifest>
  1 +package com.k2fsa.sherpa.onnx
  2 +
  3 +import android.Manifest
  4 +import android.content.pm.PackageManager
  5 +import android.media.AudioFormat
  6 +import android.media.AudioRecord
  7 +import android.media.MediaRecorder
  8 +import android.os.Bundle
  9 +import android.util.Log
  10 +import android.view.View
  11 +import android.widget.Button
  12 +import androidx.appcompat.app.AppCompatActivity
  13 +import androidx.core.app.ActivityCompat
  14 +import kotlin.concurrent.thread
  15 +
  16 +
  17 +private const val TAG = "sherpa-onnx"
  18 +private const val REQUEST_RECORD_AUDIO_PERMISSION = 200
  19 +
  20 +class MainActivity : AppCompatActivity() {
  21 +
  22 + private lateinit var recordButton: Button
  23 + private lateinit var circle: View
  24 +
  25 + private lateinit var vad: Vad
  26 +
  27 + private var audioRecord: AudioRecord? = null
  28 + private var recordingThread: Thread? = null
  29 + private val audioSource = MediaRecorder.AudioSource.MIC
  30 + private val sampleRateInHz = 16000
  31 + private val channelConfig = AudioFormat.CHANNEL_IN_MONO
  32 +
  33 + // Note: We don't use AudioFormat.ENCODING_PCM_FLOAT
  34 + // since the AudioRecord.read(float[]) needs API level >= 23
  35 + // but we are targeting API level >= 21
  36 + private val audioFormat = AudioFormat.ENCODING_PCM_16BIT
  37 +
  38 + private val permissions: Array<String> = arrayOf(Manifest.permission.RECORD_AUDIO)
  39 +
  40 + @Volatile
  41 + private var isRecording: Boolean = false
  42 +
  43 + override fun onRequestPermissionsResult(
  44 + requestCode: Int, permissions: Array<String>, grantResults: IntArray
  45 + ) {
  46 + super.onRequestPermissionsResult(requestCode, permissions, grantResults)
  47 + val permissionToRecordAccepted = if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
  48 + grantResults[0] == PackageManager.PERMISSION_GRANTED
  49 + } else {
  50 + false
  51 + }
  52 +
  53 + if (!permissionToRecordAccepted) {
  54 + Log.e(TAG, "Audio record is disallowed")
  55 + finish()
  56 + }
  57 +
  58 + Log.i(TAG, "Audio record is permitted")
  59 + }
  60 +
  61 + override fun onCreate(savedInstanceState: Bundle?) {
  62 + super.onCreate(savedInstanceState)
  63 + setContentView(R.layout.activity_main)
  64 +
  65 + ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION)
  66 +
  67 + Log.i(TAG, "Start to initialize model")
  68 + initVadModel()
  69 + Log.i(TAG, "Finished initializing model")
  70 +
  71 + circle= findViewById(R.id.powerCircle)
  72 +
  73 + recordButton = findViewById(R.id.record_button)
  74 + recordButton.setOnClickListener { onclick() }
  75 + }
  76 +
  77 + private fun onclick() {
  78 + if (!isRecording) {
  79 + val ret = initMicrophone()
  80 + if (!ret) {
  81 + Log.e(TAG, "Failed to initialize microphone")
  82 + return
  83 + }
  84 + Log.i(TAG, "state: ${audioRecord?.state}")
  85 + audioRecord!!.startRecording()
  86 + recordButton.setText(R.string.stop)
  87 + isRecording = true
  88 +
  89 + vad.reset()
  90 + recordingThread = thread(true) {
  91 + processSamples()
  92 + }
  93 + Log.i(TAG, "Started recording")
  94 + onVad(false)
  95 +
  96 + } else {
  97 + isRecording = false
  98 +
  99 + audioRecord!!.stop()
  100 + audioRecord!!.release()
  101 + audioRecord = null
  102 +
  103 + recordButton.setText(R.string.start)
  104 + onVad(false)
  105 + Log.i(TAG, "Stopped recording")
  106 + }
  107 + }
  108 +
  109 + private fun onVad(isSpeech: Boolean) {
  110 + if(isSpeech) {
  111 + circle.background = resources.getDrawable(R.drawable.red_circle)
  112 + } else {
  113 + circle.background = resources.getDrawable(R.drawable.black_circle)
  114 + }
  115 + }
  116 +
  117 + private fun initVadModel() {
  118 + val type = 0
  119 + println("Select VAD model type ${type}")
  120 + val config = getVadModelConfig(type)
  121 +
  122 + vad = Vad(
  123 + assetManager = application.assets,
  124 + config = config!!,
  125 + )
  126 + }
  127 +
  128 + private fun initMicrophone(): Boolean {
  129 + if (ActivityCompat.checkSelfPermission(
  130 + this, Manifest.permission.RECORD_AUDIO
  131 + ) != PackageManager.PERMISSION_GRANTED
  132 + ) {
  133 + ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION)
  134 + return false
  135 + }
  136 +
  137 + val numBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
  138 + Log.i(
  139 + TAG, "buffer size in milliseconds: ${numBytes * 1000.0f / sampleRateInHz}"
  140 + )
  141 +
  142 + audioRecord = AudioRecord(
  143 + audioSource,
  144 + sampleRateInHz,
  145 + channelConfig,
  146 + audioFormat,
  147 + numBytes * 2 // a sample has two bytes as we are using 16-bit PCM
  148 + )
  149 + return true
  150 + }
  151 +
  152 + private fun processSamples() {
  153 + Log.i(TAG, "processing samples")
  154 +
  155 + val bufferSize = 512 // in samples
  156 + val buffer = ShortArray(bufferSize)
  157 +
  158 + while (isRecording) {
  159 + val ret = audioRecord?.read(buffer, 0, buffer.size)
  160 + if (ret != null && ret > 0) {
  161 + val samples = FloatArray(ret) { buffer[it] / 32768.0f }
  162 +
  163 + vad.acceptWaveform(samples)
  164 + while(!vad.empty()) {vad.pop();}
  165 +
  166 + val isSpeechDetected = vad.isSpeechDetected()
  167 +
  168 + runOnUiThread {
  169 + onVad(isSpeechDetected)
  170 + }
  171 + }
  172 + }
  173 + }
  174 +}
  1 +// Copyright (c) 2023 Xiaomi Corporation
  2 +package com.k2fsa.sherpa.onnx
  3 +
  4 +import android.content.res.AssetManager
  5 +
  6 +data class SileroVadModelConfig(
  7 + var model: String,
  8 + var threshold: Float = 0.5F,
  9 + var minSilenceDuration: Float = 0.25F,
  10 + var minSpeechDuration: Float = 0.25F,
  11 + var windowSize: Int = 512,
  12 +)
  13 +
  14 +data class VadModelConfig(
  15 + var sileroVadModelConfig: SileroVadModelConfig,
  16 + var sampleRate: Int = 16000,
  17 + var numThreads: Int = 1,
  18 + var provider: String = "cpu",
  19 + var debug: Boolean = false,
  20 +)
  21 +
  22 +class Vad(
  23 + assetManager: AssetManager? = null,
  24 + var config: VadModelConfig,
  25 +) {
  26 + private val ptr: Long
  27 +
  28 + init {
  29 + if (assetManager != null) {
  30 + ptr = new(assetManager, config)
  31 + } else {
  32 + ptr = newFromFile(config)
  33 + }
  34 + }
  35 +
  36 + protected fun finalize() {
  37 + delete(ptr)
  38 + }
  39 +
  40 + fun acceptWaveform(samples: FloatArray) = acceptWaveform(ptr, samples)
  41 +
  42 + fun empty(): Boolean = empty(ptr)
  43 + fun pop() = pop(ptr)
  44 +
  45 + // return an array containing
  46 + // [start: Int, samples: FloatArray]
  47 + fun front() = front(ptr)
  48 +
  49 + fun isSpeechDetected(): Boolean = isSpeechDetected(ptr)
  50 +
  51 + fun reset() = reset(ptr)
  52 +
  53 + private external fun delete(ptr: Long)
  54 +
  55 + private external fun new(
  56 + assetManager: AssetManager,
  57 + config: VadModelConfig,
  58 + ): Long
  59 +
  60 + private external fun newFromFile(
  61 + config: VadModelConfig,
  62 + ): Long
  63 +
  64 + private external fun acceptWaveform(ptr: Long, samples: FloatArray)
  65 + private external fun empty(ptr: Long): Boolean
  66 + private external fun pop(ptr: Long)
  67 + private external fun front(ptr: Long): Array<Any>
  68 + private external fun isSpeechDetected(ptr: Long): Boolean
  69 + private external fun reset(ptr: Long)
  70 +
  71 + companion object {
  72 + init {
  73 + System.loadLibrary("sherpa-onnx-jni")
  74 + }
  75 + }
  76 +}
  77 +
  78 +// Please visit
  79 +// https://github.com/snakers4/silero-vad/blob/master/files/silero_vad.onnx
  80 +// to download silero_vad.onnx
  81 +// and put it inside the assets/
  82 +// directory
  83 +fun getVadModelConfig(type: Int): VadModelConfig? {
  84 + when (type) {
  85 + 0 -> {
  86 + return VadModelConfig(
  87 + sileroVadModelConfig = SileroVadModelConfig(
  88 + model = "silero_vad.onnx",
  89 + threshold = 0.5F,
  90 + minSilenceDuration = 0.25F,
  91 + minSpeechDuration = 0.25F,
  92 + windowSize = 512,
  93 + ),
  94 + sampleRate = 16000,
  95 + numThreads = 1,
  96 + provider = "cpu",
  97 + )
  98 + }
  99 + }
  100 + return null;
  101 +}
  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 +<selector xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <item>
  4 + <shape android:shape="oval">
  5 +
  6 + <solid android:color="#FF000000"/>
  7 +
  8 + <size
  9 + android:width="300dp"
  10 + android:height="300dp"/>
  11 + </shape>
  12 + </item>
  13 +</selector>
  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 +<selector xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <item>
  4 + <shape android:shape="oval">
  5 +
  6 + <solid android:color="#FFFF0000"/>
  7 +
  8 + <size
  9 + android:width="300dp"
  10 + android:height="300dp"/>
  11 + </shape>
  12 + </item>
  13 +</selector>
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + xmlns:app="http://schemas.android.com/apk/res-auto"
  4 + xmlns:tools="http://schemas.android.com/tools"
  5 + android:layout_width="match_parent"
  6 + android:layout_height="match_parent"
  7 + tools:context=".MainActivity">
  8 + <LinearLayout
  9 + android:layout_width="match_parent"
  10 + android:layout_height="match_parent"
  11 + android:gravity="bottom"
  12 + android:orientation="vertical"
  13 + >
  14 +
  15 + <Space
  16 + android:layout_width="match_parent"
  17 + android:layout_height="10dp" />
  18 +
  19 + <LinearLayout
  20 + android:id="@+id/powerCircle"
  21 + android:layout_width="wrap_content"
  22 + android:layout_height="wrap_content"
  23 + android:layout_gravity="center_horizontal"
  24 + android:background="@drawable/black_circle"
  25 + android:orientation="vertical" />
  26 +
  27 + <Space
  28 + android:layout_width="match_parent"
  29 + android:layout_height="200dp" />
  30 +
  31 + <Button
  32 + android:id="@+id/record_button"
  33 + android:layout_width="match_parent"
  34 + android:layout_height="wrap_content"
  35 + android:text="@string/start" />
  36 +
  37 +
  38 +
  39 + </LinearLayout>
  40 +
  41 +
  42 +
  43 +</androidx.constraintlayout.widget.ConstraintLayout>
  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 +</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 +</adaptive-icon>
  1 +<resources xmlns:tools="http://schemas.android.com/tools">
  2 + <!-- Base application theme. -->
  3 + <style name="Theme.SherpaOnnxVad" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
  4 + <!-- Primary brand color. -->
  5 + <item name="colorPrimary">@color/purple_200</item>
  6 + <item name="colorPrimaryVariant">@color/purple_700</item>
  7 + <item name="colorOnPrimary">@color/black</item>
  8 + <!-- Secondary brand color. -->
  9 + <item name="colorSecondary">@color/teal_200</item>
  10 + <item name="colorSecondaryVariant">@color/teal_200</item>
  11 + <item name="colorOnSecondary">@color/black</item>
  12 + <!-- Status bar color. -->
  13 + <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
  14 + <!-- Customize your theme here. -->
  15 + </style>
  16 +</resources>
  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">Next-gen Kaldi: SileroVAD</string>
  3 +
  4 + <string name="hint">Click the Start button to play Silero VAD with Next-gen Kaldi.</string>
  5 + <string name="start">Start</string>
  6 + <string name="stop">Stop</string>
  7 +</resources>
  1 +<resources xmlns:tools="http://schemas.android.com/tools">
  2 + <!-- Base application theme. -->
  3 + <style name="Theme.SherpaOnnxVad" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
  4 + <!-- Primary brand color. -->
  5 + <item name="colorPrimary">@color/purple_500</item>
  6 + <item name="colorPrimaryVariant">@color/purple_700</item>
  7 + <item name="colorOnPrimary">@color/white</item>
  8 + <!-- Secondary brand color. -->
  9 + <item name="colorSecondary">@color/teal_200</item>
  10 + <item name="colorSecondaryVariant">@color/teal_700</item>
  11 + <item name="colorOnSecondary">@color/black</item>
  12 + <!-- Status bar color. -->
  13 + <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
  14 + <!-- Customize your theme here. -->
  15 + </style>
  16 +</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
  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 + id 'com.android.application' version '7.3.1' apply false
  4 + id 'com.android.library' version '7.3.1' apply false
  5 + id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
  6 +}
  1 +# Project-wide Gradle settings.
  2 +# IDE (e.g. Android Studio) users:
  3 +# Gradle settings configured through the IDE *will override*
  4 +# any settings specified in this file.
  5 +# For more details on how to configure your build environment visit
  6 +# http://www.gradle.org/docs/current/userguide/build_environment.html
  7 +# Specifies the JVM arguments used for the daemon process.
  8 +# The setting is particularly useful for tweaking memory settings.
  9 +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
  10 +# When configured, Gradle will run in incubating parallel mode.
  11 +# This option should only be used with decoupled projects. More details, visit
  12 +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
  13 +# org.gradle.parallel=true
  14 +# AndroidX package structure to make it clearer which packages are bundled with the
  15 +# Android operating system, and which are packaged with your app's APK
  16 +# https://developer.android.com/topic/libraries/support-library/androidx-rn
  17 +android.useAndroidX=true
  18 +# Kotlin code style for this project: "official" or "obsolete":
  19 +kotlin.code.style=official
  20 +# Enables namespacing of each library's R class so that its R class includes only the
  21 +# resources declared in the library itself and none from the library's dependencies,
  22 +# thereby reducing the size of the R class for that library
  23 +android.nonTransitiveRClass=true
  1 +#Sat Sep 23 10:24:21 CST 2023
  2 +distributionBase=GRADLE_USER_HOME
  3 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
  4 +distributionPath=wrapper/dists
  5 +zipStorePath=wrapper/dists
  6 +zipStoreBase=GRADLE_USER_HOME
  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 + gradlePluginPortal()
  4 + google()
  5 + mavenCentral()
  6 + }
  7 +}
  8 +dependencyResolutionManagement {
  9 + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  10 + repositories {
  11 + google()
  12 + mavenCentral()
  13 + }
  14 +}
  15 +rootProject.name = "SherpaOnnxVad"
  16 +include ':app'
@@ -45,7 +45,9 @@ sleep 1 @@ -45,7 +45,9 @@ sleep 1
45 onnxruntime_version=v1.16.0 45 onnxruntime_version=v1.16.0
46 46
47 if [ ! -f ./android-onnxruntime-libs/$onnxruntime_version/jni/arm64-v8a/libonnxruntime.so ]; then 47 if [ ! -f ./android-onnxruntime-libs/$onnxruntime_version/jni/arm64-v8a/libonnxruntime.so ]; then
48 - GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/csukuangfj/android-onnxruntime-libs 48 + if [ ! -d android-onnxruntime-libs ]; then
  49 + GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/csukuangfj/android-onnxruntime-libs
  50 + fi
49 pushd android-onnxruntime-libs 51 pushd android-onnxruntime-libs
50 git lfs pull --include "$onnxruntime_version/jni/arm64-v8a/libonnxruntime.so" 52 git lfs pull --include "$onnxruntime_version/jni/arm64-v8a/libonnxruntime.so"
51 ln -s $onnxruntime_version/jni . 53 ln -s $onnxruntime_version/jni .
@@ -46,7 +46,9 @@ sleep 1 @@ -46,7 +46,9 @@ sleep 1
46 onnxruntime_version=v1.16.0 46 onnxruntime_version=v1.16.0
47 47
48 if [ ! -f ./android-onnxruntime-libs/$onnxruntime_version/jni/armeabi-v7a/libonnxruntime.so ]; then 48 if [ ! -f ./android-onnxruntime-libs/$onnxruntime_version/jni/armeabi-v7a/libonnxruntime.so ]; then
49 - GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/csukuangfj/android-onnxruntime-libs 49 + if [ ! -d android-onnxruntime-libs ]; then
  50 + GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/csukuangfj/android-onnxruntime-libs
  51 + fi
50 pushd android-onnxruntime-libs 52 pushd android-onnxruntime-libs
51 git lfs pull --include "$onnxruntime_version/jni/armeabi-v7a/libonnxruntime.so" 53 git lfs pull --include "$onnxruntime_version/jni/armeabi-v7a/libonnxruntime.so"
52 ln -s $onnxruntime_version/jni . 54 ln -s $onnxruntime_version/jni .
@@ -46,7 +46,9 @@ sleep 1 @@ -46,7 +46,9 @@ sleep 1
46 onnxruntime_version=v1.16.0 46 onnxruntime_version=v1.16.0
47 47
48 if [ ! -f ./android-onnxruntime-libs/$onnxruntime_version/jni/x86_64/libonnxruntime.so ]; then 48 if [ ! -f ./android-onnxruntime-libs/$onnxruntime_version/jni/x86_64/libonnxruntime.so ]; then
49 - GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/csukuangfj/android-onnxruntime-libs 49 + if [ ! -d android-onnxruntime-libs ]; then
  50 + GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/csukuangfj/android-onnxruntime-libs
  51 + fi
50 pushd android-onnxruntime-libs 52 pushd android-onnxruntime-libs
51 git lfs pull --include "$onnxruntime_version/jni/x86_64/libonnxruntime.so" 53 git lfs pull --include "$onnxruntime_version/jni/x86_64/libonnxruntime.so"
52 ln -s $onnxruntime_version/jni . 54 ln -s $onnxruntime_version/jni .
@@ -46,7 +46,9 @@ sleep 1 @@ -46,7 +46,9 @@ sleep 1
46 onnxruntime_version=v1.16.0 46 onnxruntime_version=v1.16.0
47 47
48 if [ ! -f ./android-onnxruntime-libs/$onnxruntime_version/jni/x86/libonnxruntime.so ]; then 48 if [ ! -f ./android-onnxruntime-libs/$onnxruntime_version/jni/x86/libonnxruntime.so ]; then
49 - GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/csukuangfj/android-onnxruntime-libs 49 + if [ ! -d android-onnxruntime-libs ]; then
  50 + GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/csukuangfj/android-onnxruntime-libs
  51 + fi
50 pushd android-onnxruntime-libs 52 pushd android-onnxruntime-libs
51 git lfs pull --include "$onnxruntime_version/jni/x86/libonnxruntime.so" 53 git lfs pull --include "$onnxruntime_version/jni/x86/libonnxruntime.so"
52 ln -s $onnxruntime_version/jni . 54 ln -s $onnxruntime_version/jni .
  1 +#!/usr/bin/env bash
  2 +
  3 +# Please set the environment variable ANDROID_NDK
  4 +# before running this script
  5 +
  6 +# Inside the $ANDROID_NDK directory, you can find a binary ndk-build
  7 +# and some other files like the file "build/cmake/android.toolchain.cmake"
  8 +
  9 +set -e
  10 +
  11 +log() {
  12 + # This function is from espnet
  13 + local fname=${BASH_SOURCE[1]##*/}
  14 + echo -e "$(date '+%Y-%m-%d %H:%M:%S') (${fname}:${BASH_LINENO[0]}:${FUNCNAME[1]}) $*"
  15 +}
  16 +
  17 +SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
  18 +
  19 +log "Building APK for sherpa-onnx v${SHERPA_ONNX_VERSION}"
  20 +
  21 +log "====================arm64-v8a================="
  22 +./build-android-arm64-v8a.sh
  23 +log "====================armv7-eabi================"
  24 +./build-android-armv7-eabi.sh
  25 +log "====================x86-64===================="
  26 +./build-android-x86-64.sh
  27 +log "====================x86===================="
  28 +./build-android-x86.sh
  29 +
  30 +
  31 +mkdir -p apks
  32 +
  33 +log "https://github.com/snakers4/silero-vad/raw/master/files/silero_vad.onnx"
  34 +
  35 +# Download the model
  36 +pushd ./android/SherpaOnnxVad/app/src/main/assets/
  37 +wget https://github.com/snakers4/silero-vad/raw/master/files/silero_vad.onnx
  38 +popd
  39 +
  40 +for arch in arm64-v8a armeabi-v7a x86_64 x86; do
  41 + log "------------------------------------------------------------"
  42 + log "build apk for $arch"
  43 + log "------------------------------------------------------------"
  44 + src_arch=$arch
  45 + if [ $arch == "armeabi-v7a" ]; then
  46 + src_arch=armv7-eabi
  47 + elif [ $arch == "x86_64" ]; then
  48 + src_arch=x86-64
  49 + fi
  50 +
  51 + ls -lh ./build-android-$src_arch/install/lib/*.so
  52 +
  53 + cp -v ./build-android-$src_arch/install/lib/*.so ./android/SherpaOnnxVad/app/src/main/jniLibs/$arch/
  54 +
  55 + pushd ./android/SherpaOnnxVad
  56 + ./gradlew build
  57 + popd
  58 +
  59 + mv android/SherpaOnnxVad/app/build/outputs/apk/debug/app-debug.apk ./apks/sherpa-onnx-${SHERPA_ONNX_VERSION}-$arch-silero-vad.apk
  60 + ls -lh apks
  61 + rm -v ./android/SherpaOnnxVad/app/src/main/jniLibs/$arch/*.so
  62 +done
  63 +
  64 +rm -rf ./android/SherpaOnnxVad/app/src/main/assets/*.onnx
  65 +
  66 +ls -lh apks/
  1 +../android/SherpaOnnxVad/app/src/main/java/com/k2fsa/sherpa/onnx/Vad.kt
@@ -32,7 +32,7 @@ std::vector<std::vector<std::string>> SplitToBatches( @@ -32,7 +32,7 @@ std::vector<std::vector<std::string>> SplitToBatches(
32 process_num += batch_size; 32 process_num += batch_size;
33 } 33 }
34 if (itr != input.cend()) { 34 if (itr != input.cend()) {
35 - outputs.emplace_back(itr, input.cend()); 35 + outputs.emplace_back(itr, input.cend());
36 } 36 }
37 return outputs; 37 return outputs;
38 } 38 }
@@ -41,8 +41,8 @@ std::vector<std::string> LoadScpFile(const std::string &wav_scp_path) { @@ -41,8 +41,8 @@ std::vector<std::string> LoadScpFile(const std::string &wav_scp_path) {
41 std::vector<std::string> wav_paths; 41 std::vector<std::string> wav_paths;
42 std::ifstream in(wav_scp_path); 42 std::ifstream in(wav_scp_path);
43 if (!in.is_open()) { 43 if (!in.is_open()) {
44 - fprintf(stderr, "Failed to open file: %s.\n", wav_scp_path.c_str());  
45 - return wav_paths; 44 + fprintf(stderr, "Failed to open file: %s.\n", wav_scp_path.c_str());
  45 + return wav_paths;
46 } 46 }
47 std::string line, column1, column2; 47 std::string line, column1, column2;
48 while (std::getline(in, line)) { 48 while (std::getline(in, line)) {
@@ -55,8 +55,8 @@ std::vector<std::string> LoadScpFile(const std::string &wav_scp_path) { @@ -55,8 +55,8 @@ std::vector<std::string> LoadScpFile(const std::string &wav_scp_path) {
55 } 55 }
56 56
57 void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths, 57 void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths,
58 - sherpa_onnx::OfflineRecognizer* recognizer,  
59 - float* total_length, float* total_time) { 58 + sherpa_onnx::OfflineRecognizer *recognizer,
  59 + float *total_length, float *total_time) {
60 std::vector<std::unique_ptr<sherpa_onnx::OfflineStream>> ss; 60 std::vector<std::unique_ptr<sherpa_onnx::OfflineStream>> ss;
61 std::vector<sherpa_onnx::OfflineStream *> ss_pointers; 61 std::vector<sherpa_onnx::OfflineStream *> ss_pointers;
62 float duration = 0.0f; 62 float duration = 0.0f;
@@ -70,7 +70,7 @@ void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths, @@ -70,7 +70,7 @@ void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths,
70 sherpa_onnx::ReadWave(wav_filename, &sampling_rate, &is_ok); 70 sherpa_onnx::ReadWave(wav_filename, &sampling_rate, &is_ok);
71 if (!is_ok) { 71 if (!is_ok) {
72 fprintf(stderr, "Failed to read %s\n", wav_filename.c_str()); 72 fprintf(stderr, "Failed to read %s\n", wav_filename.c_str());
73 - continue; 73 + continue;
74 } 74 }
75 duration += samples.size() / static_cast<float>(sampling_rate); 75 duration += samples.size() / static_cast<float>(sampling_rate);
76 auto s = recognizer->CreateStream(); 76 auto s = recognizer->CreateStream();
@@ -97,7 +97,7 @@ void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths, @@ -97,7 +97,7 @@ void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths,
97 sherpa_onnx::ReadWave(wav_filename, &sampling_rate, &is_ok); 97 sherpa_onnx::ReadWave(wav_filename, &sampling_rate, &is_ok);
98 if (!is_ok) { 98 if (!is_ok) {
99 fprintf(stderr, "Failed to read %s\n", wav_filename.c_str()); 99 fprintf(stderr, "Failed to read %s\n", wav_filename.c_str());
100 - continue; 100 + continue;
101 } 101 }
102 duration += samples.size() / static_cast<float>(sampling_rate); 102 duration += samples.size() / static_cast<float>(sampling_rate);
103 auto s = recognizer->CreateStream(); 103 auto s = recognizer->CreateStream();
@@ -109,9 +109,9 @@ void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths, @@ -109,9 +109,9 @@ void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths,
109 recognizer->DecodeStreams(ss_pointers.data(), ss_pointers.size()); 109 recognizer->DecodeStreams(ss_pointers.data(), ss_pointers.size());
110 const auto end = std::chrono::steady_clock::now(); 110 const auto end = std::chrono::steady_clock::now();
111 float elapsed_seconds = 111 float elapsed_seconds =
112 - std::chrono::duration_cast<std::chrono::milliseconds>(end - begin)  
113 - .count() /  
114 - 1000.; 112 + std::chrono::duration_cast<std::chrono::milliseconds>(end - begin)
  113 + .count() /
  114 + 1000.;
115 elapsed_seconds_batch += elapsed_seconds; 115 elapsed_seconds_batch += elapsed_seconds;
116 int i = 0; 116 int i = 0;
117 for (const auto &wav_filename : wav_paths) { 117 for (const auto &wav_filename : wav_paths) {
@@ -122,7 +122,7 @@ void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths, @@ -122,7 +122,7 @@ void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths,
122 ss_pointers.clear(); 122 ss_pointers.clear();
123 ss.clear(); 123 ss.clear();
124 } 124 }
125 - fprintf(stderr, "thread %lu.\n", std::this_thread::get_id()); 125 +
126 { 126 {
127 std::lock_guard<std::mutex> guard(mtx); 127 std::lock_guard<std::mutex> guard(mtx);
128 *total_length += duration; 128 *total_length += duration;
@@ -132,7 +132,6 @@ void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths, @@ -132,7 +132,6 @@ void AsrInference(const std::vector<std::vector<std::string>> &chunk_wav_paths,
132 } 132 }
133 } 133 }
134 134
135 -  
136 int main(int32_t argc, char *argv[]) { 135 int main(int32_t argc, char *argv[]) {
137 const char *kUsageMessage = R"usage( 136 const char *kUsageMessage = R"usage(
138 Speech recognition using non-streaming models with sherpa-onnx. 137 Speech recognition using non-streaming models with sherpa-onnx.
@@ -223,17 +222,17 @@ https://k2-fsa.github.io/sherpa/onnx/pretrained_models/index.html @@ -223,17 +222,17 @@ https://k2-fsa.github.io/sherpa/onnx/pretrained_models/index.html
223 for a list of pre-trained models to download. 222 for a list of pre-trained models to download.
224 )usage"; 223 )usage";
225 std::string wav_scp = ""; // file path, kaldi style wav list. 224 std::string wav_scp = ""; // file path, kaldi style wav list.
226 - int32_t nj = 1; // thread number  
227 - int32_t batch_size = 1; // number of wav files processed at once. 225 + int32_t nj = 1; // thread number
  226 + int32_t batch_size = 1; // number of wav files processed at once.
228 sherpa_onnx::ParseOptions po(kUsageMessage); 227 sherpa_onnx::ParseOptions po(kUsageMessage);
229 sherpa_onnx::OfflineRecognizerConfig config; 228 sherpa_onnx::OfflineRecognizerConfig config;
230 config.Register(&po); 229 config.Register(&po);
231 po.Register("wav-scp", &wav_scp, 230 po.Register("wav-scp", &wav_scp,
232 "a file including wav-id and wav-path, kaldi style wav list." 231 "a file including wav-id and wav-path, kaldi style wav list."
233 - "default="". when it is not empty, wav files which positional " 232 + "default="
  233 + ". when it is not empty, wav files which positional "
234 "parameters provide are invalid."); 234 "parameters provide are invalid.");
235 - po.Register("nj", &nj,  
236 - "multi-thread num for decoding, default=1"); 235 + po.Register("nj", &nj, "multi-thread num for decoding, default=1");
237 po.Register("batch-size", &batch_size, 236 po.Register("batch-size", &batch_size,
238 "number of wav files processed at once during the decoding" 237 "number of wav files processed at once during the decoding"
239 "process. default=1"); 238 "process. default=1");
@@ -262,7 +261,8 @@ for a list of pre-trained models to download. @@ -262,7 +261,8 @@ for a list of pre-trained models to download.
262 1000.; 261 1000.;
263 fprintf(stderr, 262 fprintf(stderr,
264 "Started nj: %d, batch_size: %d, wav_path: %s. recognizer init time: " 263 "Started nj: %d, batch_size: %d, wav_path: %s. recognizer init time: "
265 - "%.6f\n", nj, batch_size, wav_scp.c_str(), elapsed_seconds); 264 + "%.6f\n",
  265 + nj, batch_size, wav_scp.c_str(), elapsed_seconds);
266 std::this_thread::sleep_for(std::chrono::seconds(10)); // sleep 10s 266 std::this_thread::sleep_for(std::chrono::seconds(10)); // sleep 10s
267 std::vector<std::string> wav_paths; 267 std::vector<std::string> wav_paths;
268 if (!wav_scp.empty()) { 268 if (!wav_scp.empty()) {
@@ -282,12 +282,12 @@ for a list of pre-trained models to download. @@ -282,12 +282,12 @@ for a list of pre-trained models to download.
282 float total_length = 0.0f; 282 float total_length = 0.0f;
283 float total_time = 0.0f; 283 float total_time = 0.0f;
284 for (int i = 0; i < nj; i++) { 284 for (int i = 0; i < nj; i++) {
285 - threads.emplace_back(std::thread(AsrInference, batch_wav_paths,  
286 - &recognizer, &total_length, &total_time)); 285 + threads.emplace_back(std::thread(AsrInference, batch_wav_paths, &recognizer,
  286 + &total_length, &total_time));
287 } 287 }
288 288
289 - for (auto& thread : threads) {  
290 - thread.join(); 289 + for (auto &thread : threads) {
  290 + thread.join();
291 } 291 }
292 292
293 fprintf(stderr, "num threads: %d\n", config.model_config.num_threads); 293 fprintf(stderr, "num threads: %d\n", config.model_config.num_threads);
@@ -297,8 +297,8 @@ for a list of pre-trained models to download. @@ -297,8 +297,8 @@ for a list of pre-trained models to download.
297 } 297 }
298 fprintf(stderr, "Elapsed seconds: %.3f s\n", total_time); 298 fprintf(stderr, "Elapsed seconds: %.3f s\n", total_time);
299 float rtf = total_time / total_length; 299 float rtf = total_time / total_length;
300 - fprintf(stderr, "Real time factor (RTF): %.6f / %.6f = %.4f\n",  
301 - total_time, total_length, rtf); 300 + fprintf(stderr, "Real time factor (RTF): %.6f / %.6f = %.4f\n", total_time,
  301 + total_length, rtf);
302 fprintf(stderr, "SPEEDUP: %.4f\n", 1.0 / rtf); 302 fprintf(stderr, "SPEEDUP: %.4f\n", 1.0 / rtf);
303 303
304 return 0; 304 return 0;
@@ -37,6 +37,29 @@ class SileroVadModel::Impl { @@ -37,6 +37,29 @@ class SileroVadModel::Impl {
37 min_speech_samples_ = sample_rate_ * config_.silero_vad.min_speech_duration; 37 min_speech_samples_ = sample_rate_ * config_.silero_vad.min_speech_duration;
38 } 38 }
39 39
  40 +#if __ANDROID_API__ >= 9
  41 + Impl(AAssetManager *mgr, const VadModelConfig &config)
  42 + : config_(config),
  43 + env_(ORT_LOGGING_LEVEL_ERROR),
  44 + sess_opts_(GetSessionOptions(config)),
  45 + allocator_{} {
  46 + auto buf = ReadFile(mgr, config.silero_vad.model);
  47 + Init(buf.data(), buf.size());
  48 +
  49 + sample_rate_ = config.sample_rate;
  50 + if (sample_rate_ != 16000) {
  51 + SHERPA_ONNX_LOGE("Expected sample rate 16000. Given: %d",
  52 + config.sample_rate);
  53 + exit(-1);
  54 + }
  55 +
  56 + min_silence_samples_ =
  57 + sample_rate_ * config_.silero_vad.min_silence_duration;
  58 +
  59 + min_speech_samples_ = sample_rate_ * config_.silero_vad.min_speech_duration;
  60 + }
  61 +#endif
  62 +
40 void Reset() { 63 void Reset() {
41 // 2 - number of LSTM layer 64 // 2 - number of LSTM layer
42 // 1 - batch size 65 // 1 - batch size
@@ -260,6 +283,11 @@ class SileroVadModel::Impl { @@ -260,6 +283,11 @@ class SileroVadModel::Impl {
260 SileroVadModel::SileroVadModel(const VadModelConfig &config) 283 SileroVadModel::SileroVadModel(const VadModelConfig &config)
261 : impl_(std::make_unique<Impl>(config)) {} 284 : impl_(std::make_unique<Impl>(config)) {}
262 285
  286 +#if __ANDROID_API__ >= 9
  287 +SileroVadModel::SileroVadModel(AAssetManager *mgr, const VadModelConfig &config)
  288 + : impl_(std::make_unique<Impl>(mgr, config)) {}
  289 +#endif
  290 +
263 SileroVadModel::~SileroVadModel() = default; 291 SileroVadModel::~SileroVadModel() = default;
264 292
265 void SileroVadModel::Reset() { return impl_->Reset(); } 293 void SileroVadModel::Reset() { return impl_->Reset(); }
@@ -6,6 +6,11 @@ @@ -6,6 +6,11 @@
6 6
7 #include <memory> 7 #include <memory>
8 8
  9 +#if __ANDROID_API__ >= 9
  10 +#include "android/asset_manager.h"
  11 +#include "android/asset_manager_jni.h"
  12 +#endif
  13 +
9 #include "sherpa-onnx/csrc/vad-model.h" 14 #include "sherpa-onnx/csrc/vad-model.h"
10 15
11 namespace sherpa_onnx { 16 namespace sherpa_onnx {
@@ -13,6 +18,11 @@ namespace sherpa_onnx { @@ -13,6 +18,11 @@ namespace sherpa_onnx {
13 class SileroVadModel : public VadModel { 18 class SileroVadModel : public VadModel {
14 public: 19 public:
15 explicit SileroVadModel(const VadModelConfig &config); 20 explicit SileroVadModel(const VadModelConfig &config);
  21 +
  22 +#if __ANDROID_API__ >= 9
  23 + SileroVadModel(AAssetManager *mgr, const VadModelConfig &config);
  24 +#endif
  25 +
16 ~SileroVadModel() override; 26 ~SileroVadModel() override;
17 27
18 // reset the internal model states 28 // reset the internal model states
@@ -13,4 +13,12 @@ std::unique_ptr<VadModel> VadModel::Create(const VadModelConfig &config) { @@ -13,4 +13,12 @@ std::unique_ptr<VadModel> VadModel::Create(const VadModelConfig &config) {
13 return std::make_unique<SileroVadModel>(config); 13 return std::make_unique<SileroVadModel>(config);
14 } 14 }
15 15
  16 +#if __ANDROID_API__ >= 9
  17 +std::unique_ptr<VadModel> VadModel::Create(AAssetManager *mgr,
  18 + const VadModelConfig &config) {
  19 + // TODO(fangjun): Support other VAD models.
  20 + return std::make_unique<SileroVadModel>(mgr, config);
  21 +}
  22 +#endif
  23 +
16 } // namespace sherpa_onnx 24 } // namespace sherpa_onnx
@@ -6,6 +6,11 @@ @@ -6,6 +6,11 @@
6 6
7 #include <memory> 7 #include <memory>
8 8
  9 +#if __ANDROID_API__ >= 9
  10 +#include "android/asset_manager.h"
  11 +#include "android/asset_manager_jni.h"
  12 +#endif
  13 +
9 #include "sherpa-onnx/csrc/vad-model-config.h" 14 #include "sherpa-onnx/csrc/vad-model-config.h"
10 15
11 namespace sherpa_onnx { 16 namespace sherpa_onnx {
@@ -16,6 +21,11 @@ class VadModel { @@ -16,6 +21,11 @@ class VadModel {
16 21
17 static std::unique_ptr<VadModel> Create(const VadModelConfig &config); 22 static std::unique_ptr<VadModel> Create(const VadModelConfig &config);
18 23
  24 +#if __ANDROID_API__ >= 9
  25 + static std::unique_ptr<VadModel> Create(AAssetManager *mgr,
  26 + const VadModelConfig &config);
  27 +#endif
  28 +
19 // reset the internal model states 29 // reset the internal model states
20 virtual void Reset() = 0; 30 virtual void Reset() = 0;
21 31
@@ -19,10 +19,32 @@ class VoiceActivityDetector::Impl { @@ -19,10 +19,32 @@ class VoiceActivityDetector::Impl {
19 config_(config), 19 config_(config),
20 buffer_(buffer_size_in_seconds * config.sample_rate) {} 20 buffer_(buffer_size_in_seconds * config.sample_rate) {}
21 21
  22 +#if __ANDROID_API__ >= 9
  23 + Impl(AAssetManager *mgr, const VadModelConfig &config,
  24 + float buffer_size_in_seconds = 60)
  25 + : model_(VadModel::Create(mgr, config)),
  26 + config_(config),
  27 + buffer_(buffer_size_in_seconds * config.sample_rate) {}
  28 +#endif
  29 +
22 void AcceptWaveform(const float *samples, int32_t n) { 30 void AcceptWaveform(const float *samples, int32_t n) {
23 - buffer_.Push(samples, n); 31 + int32_t window_size = model_->WindowSize();
  32 +
  33 + // note n is usally window_size and there is no need to use
  34 + // an extra buffer here
  35 + last_.insert(last_.end(), samples, samples + n);
  36 + int32_t k = static_cast<int32_t>(last_.size()) / window_size;
  37 + const float *p = last_.data();
  38 + bool is_speech = false;
  39 +
  40 + for (int32_t i = 0; i != k; ++i, p += window_size) {
  41 + buffer_.Push(p, window_size);
  42 + is_speech = model_->IsSpeech(p, window_size);
  43 + }
  44 +
  45 + last_ = std::vector<float>(
  46 + p, static_cast<const float *>(last_.data()) + last_.size());
24 47
25 - bool is_speech = model_->IsSpeech(samples, n);  
26 if (is_speech) { 48 if (is_speech) {
27 if (start_ == -1) { 49 if (start_ == -1) {
28 // beginning of speech 50 // beginning of speech
@@ -31,15 +53,15 @@ class VoiceActivityDetector::Impl { @@ -31,15 +53,15 @@ class VoiceActivityDetector::Impl {
31 } 53 }
32 } else { 54 } else {
33 // non-speech 55 // non-speech
34 - if (start_ != -1) { 56 + if (start_ != -1 && buffer_.Size()) {
35 // end of speech, save the speech segment 57 // end of speech, save the speech segment
36 int32_t end = buffer_.Tail() - model_->MinSilenceDurationSamples(); 58 int32_t end = buffer_.Tail() - model_->MinSilenceDurationSamples();
37 59
38 - std::vector<float> samples = buffer_.Get(start_, end - start_); 60 + std::vector<float> s = buffer_.Get(start_, end - start_);
39 SpeechSegment segment; 61 SpeechSegment segment;
40 62
41 segment.start = start_; 63 segment.start = start_;
42 - segment.samples = std::move(samples); 64 + segment.samples = std::move(s);
43 65
44 segments_.push(std::move(segment)); 66 segments_.push(std::move(segment));
45 67
@@ -73,6 +95,7 @@ class VoiceActivityDetector::Impl { @@ -73,6 +95,7 @@ class VoiceActivityDetector::Impl {
73 std::unique_ptr<VadModel> model_; 95 std::unique_ptr<VadModel> model_;
74 VadModelConfig config_; 96 VadModelConfig config_;
75 CircularBuffer buffer_; 97 CircularBuffer buffer_;
  98 + std::vector<float> last_;
76 99
77 int32_t start_ = -1; 100 int32_t start_ = -1;
78 }; 101 };
@@ -81,6 +104,13 @@ VoiceActivityDetector::VoiceActivityDetector( @@ -81,6 +104,13 @@ VoiceActivityDetector::VoiceActivityDetector(
81 const VadModelConfig &config, float buffer_size_in_seconds /*= 60*/) 104 const VadModelConfig &config, float buffer_size_in_seconds /*= 60*/)
82 : impl_(std::make_unique<Impl>(config, buffer_size_in_seconds)) {} 105 : impl_(std::make_unique<Impl>(config, buffer_size_in_seconds)) {}
83 106
  107 +#if __ANDROID_API__ >= 9
  108 +VoiceActivityDetector::VoiceActivityDetector(
  109 + AAssetManager *mgr, const VadModelConfig &config,
  110 + float buffer_size_in_seconds /*= 60*/)
  111 + : impl_(std::make_unique<Impl>(mgr, config, buffer_size_in_seconds)) {}
  112 +#endif
  113 +
84 VoiceActivityDetector::~VoiceActivityDetector() = default; 114 VoiceActivityDetector::~VoiceActivityDetector() = default;
85 115
86 void VoiceActivityDetector::AcceptWaveform(const float *samples, int32_t n) { 116 void VoiceActivityDetector::AcceptWaveform(const float *samples, int32_t n) {
@@ -7,6 +7,11 @@ @@ -7,6 +7,11 @@
7 #include <memory> 7 #include <memory>
8 #include <vector> 8 #include <vector>
9 9
  10 +#if __ANDROID_API__ >= 9
  11 +#include "android/asset_manager.h"
  12 +#include "android/asset_manager_jni.h"
  13 +#endif
  14 +
10 #include "sherpa-onnx/csrc/vad-model-config.h" 15 #include "sherpa-onnx/csrc/vad-model-config.h"
11 16
12 namespace sherpa_onnx { 17 namespace sherpa_onnx {
@@ -20,6 +25,12 @@ class VoiceActivityDetector { @@ -20,6 +25,12 @@ class VoiceActivityDetector {
20 public: 25 public:
21 explicit VoiceActivityDetector(const VadModelConfig &config, 26 explicit VoiceActivityDetector(const VadModelConfig &config,
22 float buffer_size_in_seconds = 60); 27 float buffer_size_in_seconds = 60);
  28 +
  29 +#if __ANDROID_API__ >= 9
  30 + VoiceActivityDetector(AAssetManager *mgr, const VadModelConfig &config,
  31 + float buffer_size_in_seconds = 60);
  32 +#endif
  33 +
23 ~VoiceActivityDetector(); 34 ~VoiceActivityDetector();
24 35
25 void AcceptWaveform(const float *samples, int32_t n); 36 void AcceptWaveform(const float *samples, int32_t n);
@@ -23,6 +23,7 @@ @@ -23,6 +23,7 @@
23 #include "sherpa-onnx/csrc/offline-recognizer.h" 23 #include "sherpa-onnx/csrc/offline-recognizer.h"
24 #include "sherpa-onnx/csrc/online-recognizer.h" 24 #include "sherpa-onnx/csrc/online-recognizer.h"
25 #include "sherpa-onnx/csrc/onnx-utils.h" 25 #include "sherpa-onnx/csrc/onnx-utils.h"
  26 +#include "sherpa-onnx/csrc/voice-activity-detector.h"
26 #include "sherpa-onnx/csrc/wave-reader.h" 27 #include "sherpa-onnx/csrc/wave-reader.h"
27 28
28 #define SHERPA_ONNX_EXTERN_C extern "C" 29 #define SHERPA_ONNX_EXTERN_C extern "C"
@@ -106,6 +107,33 @@ class SherpaOnnxOffline { @@ -106,6 +107,33 @@ class SherpaOnnxOffline {
106 OfflineRecognizer recognizer_; 107 OfflineRecognizer recognizer_;
107 }; 108 };
108 109
  110 +class SherpaOnnxVad {
  111 + public:
  112 +#if __ANDROID_API__ >= 9
  113 + SherpaOnnxVad(AAssetManager *mgr, const VadModelConfig &config)
  114 + : vad_(mgr, config) {}
  115 +#endif
  116 +
  117 + explicit SherpaOnnxVad(const VadModelConfig &config) : vad_(config) {}
  118 +
  119 + void AcceptWaveform(const float *samples, int32_t n) {
  120 + vad_.AcceptWaveform(samples, n);
  121 + }
  122 +
  123 + bool Empty() const { return vad_.Empty(); }
  124 +
  125 + void Pop() { vad_.Pop(); }
  126 +
  127 + const SpeechSegment &Front() const { return vad_.Front(); }
  128 +
  129 + bool IsSpeechDetected() const { return vad_.IsSpeechDetected(); }
  130 +
  131 + void Reset() { vad_.Reset(); }
  132 +
  133 + private:
  134 + VoiceActivityDetector vad_;
  135 +};
  136 +
109 static OnlineRecognizerConfig GetConfig(JNIEnv *env, jobject config) { 137 static OnlineRecognizerConfig GetConfig(JNIEnv *env, jobject config) {
110 OnlineRecognizerConfig ans; 138 OnlineRecognizerConfig ans;
111 139
@@ -411,9 +439,166 @@ static OfflineRecognizerConfig GetOfflineConfig(JNIEnv *env, jobject config) { @@ -411,9 +439,166 @@ static OfflineRecognizerConfig GetOfflineConfig(JNIEnv *env, jobject config) {
411 return ans; 439 return ans;
412 } 440 }
413 441
  442 +static VadModelConfig GetVadModelConfig(JNIEnv *env, jobject config) {
  443 + VadModelConfig ans;
  444 +
  445 + jclass cls = env->GetObjectClass(config);
  446 + jfieldID fid;
  447 +
  448 + // silero_vad
  449 + fid = env->GetFieldID(cls, "sileroVadModelConfig",
  450 + "Lcom/k2fsa/sherpa/onnx/SileroVadModelConfig;");
  451 + jobject silero_vad_config = env->GetObjectField(config, fid);
  452 + jclass silero_vad_config_cls = env->GetObjectClass(silero_vad_config);
  453 +
  454 + fid = env->GetFieldID(silero_vad_config_cls, "model", "Ljava/lang/String;");
  455 + auto s = (jstring)env->GetObjectField(silero_vad_config, fid);
  456 + auto p = env->GetStringUTFChars(s, nullptr);
  457 + ans.silero_vad.model = p;
  458 + env->ReleaseStringUTFChars(s, p);
  459 +
  460 + fid = env->GetFieldID(silero_vad_config_cls, "threshold", "F");
  461 + ans.silero_vad.threshold = env->GetFloatField(silero_vad_config, fid);
  462 +
  463 + fid = env->GetFieldID(silero_vad_config_cls, "minSilenceDuration", "F");
  464 + ans.silero_vad.min_silence_duration =
  465 + env->GetFloatField(silero_vad_config, fid);
  466 +
  467 + fid = env->GetFieldID(silero_vad_config_cls, "minSpeechDuration", "F");
  468 + ans.silero_vad.min_speech_duration =
  469 + env->GetFloatField(silero_vad_config, fid);
  470 +
  471 + fid = env->GetFieldID(silero_vad_config_cls, "windowSize", "I");
  472 + ans.silero_vad.window_size = env->GetIntField(silero_vad_config, fid);
  473 +
  474 + fid = env->GetFieldID(cls, "sampleRate", "I");
  475 + ans.sample_rate = env->GetIntField(config, fid);
  476 +
  477 + fid = env->GetFieldID(cls, "numThreads", "I");
  478 + ans.num_threads = env->GetIntField(config, fid);
  479 +
  480 + fid = env->GetFieldID(cls, "provider", "Ljava/lang/String;");
  481 + s = (jstring)env->GetObjectField(config, fid);
  482 + p = env->GetStringUTFChars(s, nullptr);
  483 + ans.provider = p;
  484 + env->ReleaseStringUTFChars(s, p);
  485 +
  486 + fid = env->GetFieldID(cls, "debug", "Z");
  487 + ans.debug = env->GetBooleanField(config, fid);
  488 +
  489 + return ans;
  490 +}
  491 +
414 } // namespace sherpa_onnx 492 } // namespace sherpa_onnx
415 493
416 SHERPA_ONNX_EXTERN_C 494 SHERPA_ONNX_EXTERN_C
  495 +JNIEXPORT jlong JNICALL Java_com_k2fsa_sherpa_onnx_Vad_new(
  496 + JNIEnv *env, jobject /*obj*/, jobject asset_manager, jobject _config) {
  497 +#if __ANDROID_API__ >= 9
  498 + AAssetManager *mgr = AAssetManager_fromJava(env, asset_manager);
  499 + if (!mgr) {
  500 + SHERPA_ONNX_LOGE("Failed to get asset manager: %p", mgr);
  501 + }
  502 +#endif
  503 + auto config = sherpa_onnx::GetVadModelConfig(env, _config);
  504 + SHERPA_ONNX_LOGE("config:\n%s", config.ToString().c_str());
  505 + auto model = new sherpa_onnx::SherpaOnnxVad(
  506 +#if __ANDROID_API__ >= 9
  507 + mgr,
  508 +#endif
  509 + config);
  510 +
  511 + return (jlong)model;
  512 +}
  513 +
  514 +JNIEXPORT jlong JNICALL Java_com_k2fsa_sherpa_onnx_Vad_newFromFile(
  515 + JNIEnv *env, jobject /*obj*/, jobject _config) {
  516 + auto config = sherpa_onnx::GetVadModelConfig(env, _config);
  517 + SHERPA_ONNX_LOGE("config:\n%s", config.ToString().c_str());
  518 + auto model = new sherpa_onnx::SherpaOnnxVad(config);
  519 +
  520 + return (jlong)model;
  521 +}
  522 +
  523 +SHERPA_ONNX_EXTERN_C
  524 +JNIEXPORT void JNICALL Java_com_k2fsa_sherpa_onnx_Vad_delete(JNIEnv *env,
  525 + jobject /*obj*/,
  526 + jlong ptr) {
  527 + delete reinterpret_cast<sherpa_onnx::SherpaOnnxVad *>(ptr);
  528 +}
  529 +
  530 +SHERPA_ONNX_EXTERN_C
  531 +JNIEXPORT void JNICALL Java_com_k2fsa_sherpa_onnx_Vad_acceptWaveform(
  532 + JNIEnv *env, jobject /*obj*/, jlong ptr, jfloatArray samples) {
  533 + auto model = reinterpret_cast<sherpa_onnx::SherpaOnnxVad *>(ptr);
  534 +
  535 + jfloat *p = env->GetFloatArrayElements(samples, nullptr);
  536 + jsize n = env->GetArrayLength(samples);
  537 +
  538 + model->AcceptWaveform(p, n);
  539 +
  540 + env->ReleaseFloatArrayElements(samples, p, JNI_ABORT);
  541 +}
  542 +
  543 +SHERPA_ONNX_EXTERN_C
  544 +JNIEXPORT bool JNICALL Java_com_k2fsa_sherpa_onnx_Vad_empty(JNIEnv *env,
  545 + jobject /*obj*/,
  546 + jlong ptr) {
  547 + auto model = reinterpret_cast<sherpa_onnx::SherpaOnnxVad *>(ptr);
  548 + return model->Empty();
  549 +}
  550 +
  551 +SHERPA_ONNX_EXTERN_C
  552 +JNIEXPORT void JNICALL Java_com_k2fsa_sherpa_onnx_Vad_pop(JNIEnv *env,
  553 + jobject /*obj*/,
  554 + jlong ptr) {
  555 + auto model = reinterpret_cast<sherpa_onnx::SherpaOnnxVad *>(ptr);
  556 + model->Pop();
  557 +}
  558 +
  559 +// see
  560 +// https://stackoverflow.com/questions/29043872/android-jni-return-multiple-variables
  561 +static jobject NewInteger(JNIEnv *env, int32_t value) {
  562 + jclass cls = env->FindClass("java/lang/Integer");
  563 + jmethodID constructor = env->GetMethodID(cls, "<init>", "(I)V");
  564 + return env->NewObject(cls, constructor, value);
  565 +}
  566 +
  567 +SHERPA_ONNX_EXTERN_C
  568 +JNIEXPORT jobjectArray JNICALL
  569 +Java_com_k2fsa_sherpa_onnx_Vad_front(JNIEnv *env, jobject /*obj*/, jlong ptr) {
  570 + const auto &front =
  571 + reinterpret_cast<sherpa_onnx::SherpaOnnxVad *>(ptr)->Front();
  572 +
  573 + jfloatArray samples_arr = env->NewFloatArray(front.samples.size());
  574 + env->SetFloatArrayRegion(samples_arr, 0, front.samples.size(),
  575 + front.samples.data());
  576 +
  577 + jobjectArray obj_arr = (jobjectArray)env->NewObjectArray(
  578 + 2, env->FindClass("java/lang/Object"), nullptr);
  579 +
  580 + env->SetObjectArrayElement(obj_arr, 0, NewInteger(env, front.start));
  581 + env->SetObjectArrayElement(obj_arr, 1, samples_arr);
  582 +
  583 + return obj_arr;
  584 +}
  585 +
  586 +SHERPA_ONNX_EXTERN_C
  587 +JNIEXPORT bool JNICALL Java_com_k2fsa_sherpa_onnx_Vad_isSpeechDetected(
  588 + JNIEnv *env, jobject /*obj*/, jlong ptr) {
  589 + auto model = reinterpret_cast<sherpa_onnx::SherpaOnnxVad *>(ptr);
  590 + return model->IsSpeechDetected();
  591 +}
  592 +
  593 +SHERPA_ONNX_EXTERN_C
  594 +JNIEXPORT void JNICALL Java_com_k2fsa_sherpa_onnx_Vad_reset(JNIEnv *env,
  595 + jobject /*obj*/,
  596 + jlong ptr) {
  597 + auto model = reinterpret_cast<sherpa_onnx::SherpaOnnxVad *>(ptr);
  598 + model->Reset();
  599 +}
  600 +
  601 +SHERPA_ONNX_EXTERN_C
417 JNIEXPORT jlong JNICALL Java_com_k2fsa_sherpa_onnx_SherpaOnnx_new( 602 JNIEXPORT jlong JNICALL Java_com_k2fsa_sherpa_onnx_SherpaOnnx_new(
418 JNIEnv *env, jobject /*obj*/, jobject asset_manager, jobject _config) { 603 JNIEnv *env, jobject /*obj*/, jobject asset_manager, jobject _config) {
419 #if __ANDROID_API__ >= 9 604 #if __ANDROID_API__ >= 9
@@ -564,12 +749,12 @@ SHERPA_ONNX_EXTERN_C @@ -564,12 +749,12 @@ SHERPA_ONNX_EXTERN_C
564 JNIEXPORT jobjectArray JNICALL Java_com_k2fsa_sherpa_onnx_SherpaOnnx_getTokens( 749 JNIEXPORT jobjectArray JNICALL Java_com_k2fsa_sherpa_onnx_SherpaOnnx_getTokens(
565 JNIEnv *env, jobject /*obj*/, jlong ptr) { 750 JNIEnv *env, jobject /*obj*/, jlong ptr) {
566 auto tokens = reinterpret_cast<sherpa_onnx::SherpaOnnx *>(ptr)->GetTokens(); 751 auto tokens = reinterpret_cast<sherpa_onnx::SherpaOnnx *>(ptr)->GetTokens();
567 - int size = tokens.size(); 752 + int32_t size = tokens.size();
568 jclass stringClass = env->FindClass("java/lang/String"); 753 jclass stringClass = env->FindClass("java/lang/String");
569 754
570 // convert C++ list into jni string array 755 // convert C++ list into jni string array
571 jobjectArray result = env->NewObjectArray(size, stringClass, NULL); 756 jobjectArray result = env->NewObjectArray(size, stringClass, NULL);
572 - for (int i = 0; i < size; i++) { 757 + for (int32_t i = 0; i < size; i++) {
573 // Convert the C++ string to a C string 758 // Convert the C++ string to a C string
574 const char *cstr = tokens[i].c_str(); 759 const char *cstr = tokens[i].c_str();
575 760
@@ -583,14 +768,6 @@ JNIEXPORT jobjectArray JNICALL Java_com_k2fsa_sherpa_onnx_SherpaOnnx_getTokens( @@ -583,14 +768,6 @@ JNIEXPORT jobjectArray JNICALL Java_com_k2fsa_sherpa_onnx_SherpaOnnx_getTokens(
583 return result; 768 return result;
584 } 769 }
585 770
586 -// see  
587 -// https://stackoverflow.com/questions/29043872/android-jni-return-multiple-variables  
588 -static jobject NewInteger(JNIEnv *env, int32_t value) {  
589 - jclass cls = env->FindClass("java/lang/Integer");  
590 - jmethodID constructor = env->GetMethodID(cls, "<init>", "(I)V");  
591 - return env->NewObject(cls, constructor, value);  
592 -}  
593 -  
594 static jobjectArray ReadWaveImpl(JNIEnv *env, std::istream &is, 771 static jobjectArray ReadWaveImpl(JNIEnv *env, std::istream &is,
595 const char *p_filename) { 772 const char *p_filename) {
596 bool is_ok = false; 773 bool is_ok = false;