diff --git a/.github/scripts/AssetManager.kt b/.github/scripts/AssetManager.kt deleted file mode 100644 index 309a9b7c..00000000 --- a/.github/scripts/AssetManager.kt +++ /dev/null @@ -1,4 +0,0 @@ -package android.content.res - -// a dummy class for testing only -class AssetManager diff --git a/.github/scripts/SherpaOnnx.kt b/.github/scripts/SherpaOnnx.kt deleted file mode 120000 index 99dd30cd..00000000 --- a/.github/scripts/SherpaOnnx.kt +++ /dev/null @@ -1 +0,0 @@ -../../android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/SherpaOnnx.kt \ No newline at end of file diff --git a/.github/scripts/WaveReader.kt b/.github/scripts/WaveReader.kt deleted file mode 120000 index 378aa32b..00000000 --- a/.github/scripts/WaveReader.kt +++ /dev/null @@ -1 +0,0 @@ -../../android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/WaveReader.kt \ No newline at end of file diff --git a/.github/scripts/test-jni.sh b/.github/scripts/test-jni.sh deleted file mode 100755 index 0f13e3ee..00000000 --- a/.github/scripts/test-jni.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash - -set -e - -mkdir -p build -cd build - -cmake \ - -DSHERPA_ONNX_ENABLE_PYTHON=OFF \ - -DSHERPA_ONNX_ENABLE_TESTS=OFF \ - -DSHERPA_ONNX_ENABLE_CHECK=OFF \ - -DBUILD_SHARED_LIBS=ON \ - -DSHERPA_ONNX_ENABLE_PORTAUDIO=OFF \ - -DSHERPA_ONNX_ENABLE_JNI=ON \ - .. - -make -j4 -ls -lh lib - -cd .. - -export LD_LIBRARY_PATH=$PWD/build/lib:$LD_LIBRARY_PATH - -cd .github/scripts/ - -git lfs install -git clone https://huggingface.co/csukuangfj/sherpa-onnx-streaming-zipformer-en-2023-02-21 - -kotlinc-jvm -include-runtime -d main.jar Main.kt WaveReader.kt SherpaOnnx.kt AssetManager.kt - -ls -lh main.jar - -java -Djava.library.path=../../build/lib -jar main.jar diff --git a/.github/workflows/jni.yaml b/.github/workflows/jni.yaml index 36b76e54..1dbccb4f 100644 --- a/.github/workflows/jni.yaml +++ b/.github/workflows/jni.yaml @@ -8,9 +8,9 @@ on: - '.github/workflows/jni.yaml' - 'CMakeLists.txt' - 'cmake/**' + - 'kotlin-api-examples/**' - 'sherpa-onnx/csrc/*' - 'sherpa-onnx/jni/*' - - '.github/scripts/test-jni.sh' pull_request: branches: - master @@ -18,9 +18,9 @@ on: - '.github/workflows/jni.yaml' - 'CMakeLists.txt' - 'cmake/**' + - 'kotlin-api-examples/**' - 'sherpa-onnx/csrc/*' - 'sherpa-onnx/jni/*' - - '.github/scripts/test-jni.sh' concurrency: group: jni-${{ github.ref }} @@ -56,4 +56,5 @@ jobs: - name: Run JNI test shell: bash run: | - .github/scripts/test-jni.sh + cd ./kotlin-api-examples + ./run.sh diff --git a/.gitignore b/.gitignore index 212f04ea..f28802d8 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ sherpa-onnx-zipformer-en-2023-04-01 run-offline-decode-files.sh sherpa-onnx-nemo-ctc-en-citrinet-512 run-offline-decode-files-nemo-ctc.sh +*.jar diff --git a/CMakeLists.txt b/CMakeLists.txt index e825af00..6e2e4293 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,11 @@ if(DEFINED ANDROID_ABI) set(SHERPA_ONNX_ENABLE_JNI ON CACHE BOOL "" FORCE) endif() +if(SHERPA_ONNX_ENABLE_JNI AND NOT BUILD_SHARED_LIBS) + message(STATUS "Set BUILD_SHARED_LIBS to ON since SHERPA_ONNX_ENABLE_JNI is ON") + set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE) +endif() + message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") message(STATUS "BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}") diff --git a/android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/SherpaOnnx.kt b/android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/SherpaOnnx.kt index 890fc9fa..47a806ee 100644 --- a/android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/SherpaOnnx.kt +++ b/android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/SherpaOnnx.kt @@ -38,19 +38,23 @@ data class OnlineRecognizerConfig( ) class SherpaOnnx( - assetManager: AssetManager, var config: OnlineRecognizerConfig + assetManager: AssetManager? = null, + var config: OnlineRecognizerConfig, ) { private val ptr: Long init { - ptr = new(assetManager, config) + if (assetManager != null) { + ptr = new(assetManager, config) + } else { + ptr = newFromFile(config) + } } protected fun finalize() { delete(ptr) } - fun acceptWaveform(samples: FloatArray, sampleRate: Int) = acceptWaveform(ptr, samples, sampleRate) @@ -70,6 +74,10 @@ class SherpaOnnx( config: OnlineRecognizerConfig, ): Long + private external fun newFromFile( + config: OnlineRecognizerConfig, + ): Long + private external fun acceptWaveform(ptr: Long, samples: FloatArray, sampleRate: Int) private external fun inputFinished(ptr: Long) private external fun getText(ptr: Long): String @@ -86,7 +94,7 @@ class SherpaOnnx( } fun getFeatureConfig(sampleRate: Int, featureDim: Int): FeatureConfig { - return FeatureConfig(sampleRate=sampleRate, featureDim=featureDim) + return FeatureConfig(sampleRate = sampleRate, featureDim = featureDim) } /* diff --git a/android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/WaveReader.kt b/android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/WaveReader.kt index 82cf7cac..3060450d 100644 --- a/android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/WaveReader.kt +++ b/android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/WaveReader.kt @@ -4,10 +4,21 @@ 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 + // Read a mono wave file asset + // The returned array has two entries: + // - the first entry contains an 1-D float array + // - the second entry is the sample rate + external fun readWaveFromAsset( + assetManager: AssetManager, + filename: String, + ): Array + + // Read a mono wave file from disk + // The returned array has two entries: + // - the first entry contains an 1-D float array + // - the second entry is the sample rate + external fun readWaveFromFile( + filename: String, ): Array init { diff --git a/.github/scripts/Main.kt b/kotlin-api-examples/Main.kt similarity index 81% rename from .github/scripts/Main.kt rename to kotlin-api-examples/Main.kt index 49d8379e..abf1cf2f 100644 --- a/.github/scripts/Main.kt +++ b/kotlin-api-examples/Main.kt @@ -8,6 +8,9 @@ fun main() { featureDim = 80, ) + // please refer to + // https://k2-fsa.github.io/sherpa/onnx/pretrained_models/index.html + // to dowload pre-trained models var modelConfig = OnlineTransducerModelConfig( encoder = "./sherpa-onnx-streaming-zipformer-en-2023-02-21/encoder-epoch-99-avg-1.onnx", decoder = "./sherpa-onnx-streaming-zipformer-en-2023-02-21/decoder-epoch-99-avg-1.onnx", @@ -29,12 +32,10 @@ fun main() { ) var model = SherpaOnnx( - assetManager = AssetManager(), config = config, ) - var objArray = WaveReader.readWave( - assetManager = AssetManager(), + var objArray = WaveReader.readWaveFromFile( filename = "./sherpa-onnx-streaming-zipformer-en-2023-02-21/test_wavs/0.wav", ) var samples : FloatArray = objArray[0] as FloatArray @@ -45,8 +46,8 @@ fun main() { model.decode() } - var tail_paddings = FloatArray((sampleRate * 0.5).toInt()) // 0.5 seconds - model.acceptWaveform(tail_paddings, sampleRate=sampleRate) + var tailPaddings = FloatArray((sampleRate * 0.5).toInt()) // 0.5 seconds + model.acceptWaveform(tailPaddings, sampleRate=sampleRate) model.inputFinished() while (model.isReady()) { model.decode() diff --git a/kotlin-api-examples/SherpaOnnx.kt b/kotlin-api-examples/SherpaOnnx.kt new file mode 120000 index 00000000..6ebb58f6 --- /dev/null +++ b/kotlin-api-examples/SherpaOnnx.kt @@ -0,0 +1 @@ +../android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/SherpaOnnx.kt \ No newline at end of file diff --git a/kotlin-api-examples/WaveReader.kt b/kotlin-api-examples/WaveReader.kt new file mode 120000 index 00000000..cd487a6c --- /dev/null +++ b/kotlin-api-examples/WaveReader.kt @@ -0,0 +1 @@ +../android/SherpaOnnx/app/src/main/java/com/k2fsa/sherpa/onnx/WaveReader.kt \ No newline at end of file diff --git a/kotlin-api-examples/faked-asset-manager.kt b/kotlin-api-examples/faked-asset-manager.kt new file mode 100644 index 00000000..477c29bc --- /dev/null +++ b/kotlin-api-examples/faked-asset-manager.kt @@ -0,0 +1,3 @@ +package android.content.res + +class AssetManager {} diff --git a/kotlin-api-examples/run.sh b/kotlin-api-examples/run.sh new file mode 100755 index 00000000..6b8ba65c --- /dev/null +++ b/kotlin-api-examples/run.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# +# This scripts shows how to build JNI libs for sherpa-onnx +# Note: This scripts runs only on Linux and macOS, though sherpa-onnx +# supports building JNI libs for Windows. + +set -e + +cd .. +mkdir -p build +cd build + +cmake \ + -DSHERPA_ONNX_ENABLE_PYTHON=OFF \ + -DSHERPA_ONNX_ENABLE_TESTS=OFF \ + -DSHERPA_ONNX_ENABLE_CHECK=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DSHERPA_ONNX_ENABLE_PORTAUDIO=OFF \ + -DSHERPA_ONNX_ENABLE_JNI=ON \ + .. + +make -j4 +ls -lh lib + +export LD_LIBRARY_PATH=$PWD/build/lib:$LD_LIBRARY_PATH + +cd ../kotlin-api-examples + +if [ ! -f ./sherpa-onnx-streaming-zipformer-en-2023-02-21/tokens.txt ]; then + git lfs install + git clone https://huggingface.co/csukuangfj/sherpa-onnx-streaming-zipformer-en-2023-02-21 +fi + +kotlinc-jvm -include-runtime -d main.jar Main.kt WaveReader.kt SherpaOnnx.kt faked-asset-manager.kt + +ls -lh main.jar + +java -Djava.library.path=../build/lib -jar main.jar diff --git a/sherpa-onnx/jni/jni.cc b/sherpa-onnx/jni/jni.cc index bf0ccdca..62165462 100644 --- a/sherpa-onnx/jni/jni.cc +++ b/sherpa-onnx/jni/jni.cc @@ -31,18 +31,13 @@ namespace sherpa_onnx { class SherpaOnnx { public: - SherpaOnnx( #if __ANDROID_API__ >= 9 - AAssetManager *mgr, + SherpaOnnx(AAssetManager *mgr, const OnlineRecognizerConfig &config) + : recognizer_(mgr, config), stream_(recognizer_.CreateStream()) {} #endif - const sherpa_onnx::OnlineRecognizerConfig &config) - : recognizer_( -#if __ANDROID_API__ >= 9 - mgr, -#endif - config), - stream_(recognizer_.CreateStream()) { - } + + explicit SherpaOnnx(const OnlineRecognizerConfig &config) + : recognizer_(config), stream_(recognizer_.CreateStream()) {} void AcceptWaveform(int32_t sample_rate, const float *samples, int32_t n) { if (input_sample_rate_ == -1) { @@ -73,8 +68,8 @@ class SherpaOnnx { void Decode() const { recognizer_.DecodeStream(stream_.get()); } private: - sherpa_onnx::OnlineRecognizer recognizer_; - std::unique_ptr stream_; + OnlineRecognizer recognizer_; + std::unique_ptr stream_; int32_t input_sample_rate_ = -1; }; @@ -218,6 +213,16 @@ JNIEXPORT jlong JNICALL Java_com_k2fsa_sherpa_onnx_SherpaOnnx_new( return (jlong)model; } +SHERPA_ONNX_EXTERN_C +JNIEXPORT jlong JNICALL Java_com_k2fsa_sherpa_onnx_SherpaOnnx_newFromFile( + JNIEnv *env, jobject /*obj*/, jobject _config) { + auto config = sherpa_onnx::GetConfig(env, _config); + SHERPA_ONNX_LOGE("config:\n%s", config.ToString().c_str()); + auto model = new sherpa_onnx::SherpaOnnx(config); + + return (jlong)model; +} + SHERPA_ONNX_EXTERN_C JNIEXPORT void JNICALL Java_com_k2fsa_sherpa_onnx_SherpaOnnx_delete( JNIEnv *env, jobject /*obj*/, jlong ptr) { @@ -289,9 +294,47 @@ static jobject NewInteger(JNIEnv *env, int32_t value) { return env->NewObject(cls, constructor, value); } +static jobjectArray ReadWaveImpl(JNIEnv *env, std::istream &is, + const char *p_filename) { + bool is_ok = false; + int32_t sampling_rate = -1; + std::vector samples = + sherpa_onnx::ReadWave(is, &sampling_rate, &is_ok); + + if (!is_ok) { + SHERPA_ONNX_LOGE("Failed to read %s", p_filename); + exit(-1); + } + + jfloatArray samples_arr = env->NewFloatArray(samples.size()); + env->SetFloatArrayRegion(samples_arr, 0, samples.size(), samples.data()); + + jobjectArray obj_arr = (jobjectArray)env->NewObjectArray( + 2, env->FindClass("java/lang/Object"), nullptr); + + env->SetObjectArrayElement(obj_arr, 0, samples_arr); + env->SetObjectArrayElement(obj_arr, 1, NewInteger(env, sampling_rate)); + + return obj_arr; +} + SHERPA_ONNX_EXTERN_C JNIEXPORT jobjectArray JNICALL -Java_com_k2fsa_sherpa_onnx_WaveReader_00024Companion_readWave( +Java_com_k2fsa_sherpa_onnx_WaveReader_00024Companion_readWaveFromFile( + JNIEnv *env, jclass /*cls*/, jstring filename) { + const char *p_filename = env->GetStringUTFChars(filename, nullptr); + std::ifstream is(p_filename, std::ios::binary); + + auto obj_arr = ReadWaveImpl(env, is, p_filename); + + env->ReleaseStringUTFChars(filename, p_filename); + + return obj_arr; +} + +SHERPA_ONNX_EXTERN_C +JNIEXPORT jobjectArray JNICALL +Java_com_k2fsa_sherpa_onnx_WaveReader_00024Companion_readWaveFromAsset( JNIEnv *env, jclass /*cls*/, jobject asset_manager, jstring filename) { const char *p_filename = env->GetStringUTFChars(filename, nullptr); #if __ANDROID_API__ >= 9 @@ -308,27 +351,10 @@ Java_com_k2fsa_sherpa_onnx_WaveReader_00024Companion_readWave( std::ifstream is(p_filename, std::ios::binary); #endif - bool is_ok = false; - int32_t sampling_rate = -1; - std::vector samples = - sherpa_onnx::ReadWave(is, &sampling_rate, &is_ok); + auto obj_arr = ReadWaveImpl(env, is, p_filename); env->ReleaseStringUTFChars(filename, p_filename); - if (!is_ok) { - SHERPA_ONNX_LOGE("Failed to read %s", p_filename); - exit(-1); - } - - jfloatArray ans = env->NewFloatArray(samples.size()); - env->SetFloatArrayRegion(ans, 0, samples.size(), samples.data()); - - jobjectArray obj_arr = (jobjectArray)env->NewObjectArray( - 2, env->FindClass("java/lang/Object"), nullptr); - - env->SetObjectArrayElement(obj_arr, 0, ans); - env->SetObjectArrayElement(obj_arr, 1, NewInteger(env, sampling_rate)); - return obj_arr; } @@ -340,8 +366,9 @@ JNIEXPORT jobjectArray JNICALL Java_com_k2fsa_sherpa_onnx_OnlineRecognizer_readWave(JNIEnv *env, jclass /*cls*/, jstring filename) { - auto data = Java_com_k2fsa_sherpa_onnx_WaveReader_00024Companion_readWave( - env, nullptr, nullptr, filename); + auto data = + Java_com_k2fsa_sherpa_onnx_WaveReader_00024Companion_readWaveFromAsset( + env, nullptr, nullptr, filename); return data; }