]> git.djapps.eu Git - pkg/ggml/sources/whisper.cpp/commitdiff
android : decouple example into a library and app module (#1445)
authorTobrun <redacted>
Tue, 7 Nov 2023 12:27:33 +0000 (13:27 +0100)
committerGitHub <redacted>
Tue, 7 Nov 2023 12:27:33 +0000 (14:27 +0200)
14 files changed:
examples/whisper.android/app/build.gradle
examples/whisper.android/app/src/main/java/com/whispercppdemo/ui/main/MainScreenViewModel.kt
examples/whisper.android/app/src/main/java/com/whispercppdemo/whisper/LibWhisper.kt [deleted file]
examples/whisper.android/app/src/main/java/com/whispercppdemo/whisper/WhisperCpuConfig.kt [deleted file]
examples/whisper.android/app/src/main/jni/whisper/CMakeLists.txt [deleted file]
examples/whisper.android/app/src/main/jni/whisper/jni.c [deleted file]
examples/whisper.android/lib/.gitignore [new file with mode: 0644]
examples/whisper.android/lib/build.gradle [new file with mode: 0644]
examples/whisper.android/lib/src/main/AndroidManifest.xml [new file with mode: 0644]
examples/whisper.android/lib/src/main/java/com/whispercpp/whisper/LibWhisper.kt [new file with mode: 0644]
examples/whisper.android/lib/src/main/java/com/whispercpp/whisper/WhisperCpuConfig.kt [new file with mode: 0644]
examples/whisper.android/lib/src/main/jni/whisper/CMakeLists.txt [new file with mode: 0644]
examples/whisper.android/lib/src/main/jni/whisper/jni.c [new file with mode: 0644]
examples/whisper.android/settings.gradle

index 7134d9f138d54a53d0aaea86f3ad7176b6cb9922..9f407998cdb395f8929c2ed6892e7e8345a70fd7 100644 (file)
@@ -18,9 +18,7 @@ android {
         vectorDrawables {
             useSupportLibrary true
         }
-        ndk {
-            abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
-        }
+
     }
 
     buildTypes {
@@ -43,20 +41,10 @@ android {
     composeOptions {
         kotlinCompilerExtensionVersion '1.5.0'
     }
-    ndkVersion "25.2.9519653"
-    externalNativeBuild {
-        cmake {
-            path = file("src/main/jni/whisper/CMakeLists.txt")
-        }
-    }
-    packagingOptions {
-        resources {
-            excludes += '/META-INF/{AL2.0,LGPL2.1}'
-        }
-    }
 }
 
 dependencies {
+    implementation project(':lib')
     implementation 'androidx.activity:activity-compose:1.7.2'
     implementation 'androidx.compose.material:material-icons-core:1.5.0'
     implementation 'androidx.compose.material3:material3:1.1.1'
index bd4779326738561ba19edcc26871d302235e9aa1..d614ce3338e3a01627d64d8fe61659da9c3b001b 100644 (file)
@@ -15,7 +15,7 @@ import androidx.lifecycle.viewmodel.initializer
 import androidx.lifecycle.viewmodel.viewModelFactory
 import com.whispercppdemo.media.decodeWaveFile
 import com.whispercppdemo.recorder.Recorder
-import com.whispercppdemo.whisper.WhisperContext
+import com.whispercpp.whisper.WhisperContext
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
@@ -35,7 +35,7 @@ class MainScreenViewModel(private val application: Application) : ViewModel() {
     private val modelsPath = File(application.filesDir, "models")
     private val samplesPath = File(application.filesDir, "samples")
     private var recorder: Recorder = Recorder()
-    private var whisperContext: WhisperContext? = null
+    private var whisperContext: com.whispercpp.whisper.WhisperContext? = null
     private var mediaPlayer: MediaPlayer? = null
     private var recordedFile: File? = null
 
@@ -47,7 +47,7 @@ class MainScreenViewModel(private val application: Application) : ViewModel() {
     }
 
     private suspend fun printSystemInfo() {
-        printMessage(String.format("System Info: %s\n", WhisperContext.getSystemInfo()))
+        printMessage(String.format("System Info: %s\n", com.whispercpp.whisper.WhisperContext.getSystemInfo()))
     }
 
     private suspend fun loadData() {
@@ -78,7 +78,7 @@ class MainScreenViewModel(private val application: Application) : ViewModel() {
         printMessage("Loading model...\n")
         val models = application.assets.list("models/")
         if (models != null) {
-            whisperContext = WhisperContext.createContextFromAsset(application.assets, "models/" + models[0])
+            whisperContext = com.whispercpp.whisper.WhisperContext.createContextFromAsset(application.assets, "models/" + models[0])
             printMessage("Loaded model ${models[0]}.\n")
         }
 
diff --git a/examples/whisper.android/app/src/main/java/com/whispercppdemo/whisper/LibWhisper.kt b/examples/whisper.android/app/src/main/java/com/whispercppdemo/whisper/LibWhisper.kt
deleted file mode 100644 (file)
index b0d6703..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-package com.whispercppdemo.whisper
-
-import android.content.res.AssetManager
-import android.os.Build
-import android.util.Log
-import kotlinx.coroutines.*
-import java.io.File
-import java.io.InputStream
-import java.util.concurrent.Executors
-
-private const val LOG_TAG = "LibWhisper"
-
-class WhisperContext private constructor(private var ptr: Long) {
-    // Meet Whisper C++ constraint: Don't access from more than one thread at a time.
-    private val scope: CoroutineScope = CoroutineScope(
-        Executors.newSingleThreadExecutor().asCoroutineDispatcher()
-    )
-
-    suspend fun transcribeData(data: FloatArray): String = withContext(scope.coroutineContext) {
-        require(ptr != 0L)
-        val numThreads = WhisperCpuConfig.preferredThreadCount
-        Log.d(LOG_TAG, "Selecting $numThreads threads")
-        WhisperLib.fullTranscribe(ptr, numThreads, data)
-        val textCount = WhisperLib.getTextSegmentCount(ptr)
-        return@withContext buildString {
-            for (i in 0 until textCount) {
-                append(WhisperLib.getTextSegment(ptr, i))
-            }
-        }
-    }
-
-    suspend fun benchMemory(nthreads: Int): String = withContext(scope.coroutineContext) {
-        return@withContext WhisperLib.benchMemcpy(nthreads)
-    }
-
-    suspend fun benchGgmlMulMat(nthreads: Int): String = withContext(scope.coroutineContext) {
-        return@withContext WhisperLib.benchGgmlMulMat(nthreads)
-    }
-
-    suspend fun release() = withContext(scope.coroutineContext) {
-        if (ptr != 0L) {
-            WhisperLib.freeContext(ptr)
-            ptr = 0
-        }
-    }
-
-    protected fun finalize() {
-        runBlocking {
-            release()
-        }
-    }
-
-    companion object {
-        fun createContextFromFile(filePath: String): WhisperContext {
-            val ptr = WhisperLib.initContext(filePath)
-            if (ptr == 0L) {
-                throw java.lang.RuntimeException("Couldn't create context with path $filePath")
-            }
-            return WhisperContext(ptr)
-        }
-
-        fun createContextFromInputStream(stream: InputStream): WhisperContext {
-            val ptr = WhisperLib.initContextFromInputStream(stream)
-
-            if (ptr == 0L) {
-                throw java.lang.RuntimeException("Couldn't create context from input stream")
-            }
-            return WhisperContext(ptr)
-        }
-
-        fun createContextFromAsset(assetManager: AssetManager, assetPath: String): WhisperContext {
-            val ptr = WhisperLib.initContextFromAsset(assetManager, assetPath)
-
-            if (ptr == 0L) {
-                throw java.lang.RuntimeException("Couldn't create context from asset $assetPath")
-            }
-            return WhisperContext(ptr)
-        }
-
-        fun getSystemInfo(): String {
-            return WhisperLib.getSystemInfo()
-        }
-    }
-}
-
-private class WhisperLib {
-    companion object {
-        init {
-            Log.d(LOG_TAG, "Primary ABI: ${Build.SUPPORTED_ABIS[0]}")
-            var loadVfpv4 = false
-            var loadV8fp16 = false
-            if (isArmEabiV7a()) {
-                // armeabi-v7a needs runtime detection support
-                val cpuInfo = cpuInfo()
-                cpuInfo?.let {
-                    Log.d(LOG_TAG, "CPU info: $cpuInfo")
-                    if (cpuInfo.contains("vfpv4")) {
-                        Log.d(LOG_TAG, "CPU supports vfpv4")
-                        loadVfpv4 = true
-                    }
-                }
-            } else if (isArmEabiV8a()) {
-                // ARMv8.2a needs runtime detection support
-                val cpuInfo = cpuInfo()
-                cpuInfo?.let {
-                    Log.d(LOG_TAG, "CPU info: $cpuInfo")
-                    if (cpuInfo.contains("fphp")) {
-                        Log.d(LOG_TAG, "CPU supports fp16 arithmetic")
-                        loadV8fp16 = true
-                    }
-                }
-            }
-
-            if (loadVfpv4) {
-                Log.d(LOG_TAG, "Loading libwhisper_vfpv4.so")
-                System.loadLibrary("whisper_vfpv4")
-            } else if (loadV8fp16) {
-                Log.d(LOG_TAG, "Loading libwhisper_v8fp16_va.so")
-                System.loadLibrary("whisper_v8fp16_va")
-            } else {
-                Log.d(LOG_TAG, "Loading libwhisper.so")
-                System.loadLibrary("whisper")
-            }
-        }
-
-        // JNI methods
-        external fun initContextFromInputStream(inputStream: InputStream): Long
-        external fun initContextFromAsset(assetManager: AssetManager, assetPath: String): Long
-        external fun initContext(modelPath: String): Long
-        external fun freeContext(contextPtr: Long)
-        external fun fullTranscribe(contextPtr: Long, numThreads: Int, audioData: FloatArray)
-        external fun getTextSegmentCount(contextPtr: Long): Int
-        external fun getTextSegment(contextPtr: Long, index: Int): String
-        external fun getSystemInfo(): String
-        external fun benchMemcpy(nthread: Int): String
-        external fun benchGgmlMulMat(nthread: Int): String
-    }
-}
-
-private fun isArmEabiV7a(): Boolean {
-    return Build.SUPPORTED_ABIS[0].equals("armeabi-v7a")
-}
-
-private fun isArmEabiV8a(): Boolean {
-    return Build.SUPPORTED_ABIS[0].equals("arm64-v8a")
-}
-
-private fun cpuInfo(): String? {
-    return try {
-        File("/proc/cpuinfo").inputStream().bufferedReader().use {
-            it.readText()
-        }
-    } catch (e: Exception) {
-        Log.w(LOG_TAG, "Couldn't read /proc/cpuinfo", e)
-        null
-    }
-}
\ No newline at end of file
diff --git a/examples/whisper.android/app/src/main/java/com/whispercppdemo/whisper/WhisperCpuConfig.kt b/examples/whisper.android/app/src/main/java/com/whispercppdemo/whisper/WhisperCpuConfig.kt
deleted file mode 100644 (file)
index 5fa9a4e..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.whispercppdemo.whisper
-
-import android.util.Log
-import java.io.BufferedReader
-import java.io.FileReader
-
-object WhisperCpuConfig {
-    val preferredThreadCount: Int
-        // Always use at least 2 threads:
-        get() = CpuInfo.getHighPerfCpuCount().coerceAtLeast(2)
-}
-
-private class CpuInfo(private val lines: List<String>) {
-    private fun getHighPerfCpuCount(): Int = try {
-        getHighPerfCpuCountByFrequencies()
-    } catch (e: Exception) {
-        Log.d(LOG_TAG, "Couldn't read CPU frequencies", e)
-        getHighPerfCpuCountByVariant()
-    }
-
-    private fun getHighPerfCpuCountByFrequencies(): Int =
-        getCpuValues(property = "processor") { getMaxCpuFrequency(it.toInt()) }
-            .also { Log.d(LOG_TAG, "Binned cpu frequencies (frequency, count): ${it.binnedValues()}") }
-            .countDroppingMin()
-
-    private fun getHighPerfCpuCountByVariant(): Int =
-        getCpuValues(property = "CPU variant") { it.substringAfter("0x").toInt(radix = 16) }
-            .also { Log.d(LOG_TAG, "Binned cpu variants (variant, count): ${it.binnedValues()}") }
-            .countKeepingMin()
-
-    private fun List<Int>.binnedValues() = groupingBy { it }.eachCount()
-
-    private fun getCpuValues(property: String, mapper: (String) -> Int) = lines
-        .asSequence()
-        .filter { it.startsWith(property) }
-        .map { mapper(it.substringAfter(':').trim()) }
-        .sorted()
-        .toList()
-
-
-    private fun List<Int>.countDroppingMin(): Int {
-        val min = min()
-        return count { it > min }
-    }
-
-    private fun List<Int>.countKeepingMin(): Int {
-        val min = min()
-        return count { it == min }
-    }
-
-    companion object {
-        private const val LOG_TAG = "WhisperCpuConfig"
-
-        fun getHighPerfCpuCount(): Int = try {
-            readCpuInfo().getHighPerfCpuCount()
-        } catch (e: Exception) {
-            Log.d(LOG_TAG, "Couldn't read CPU info", e)
-            // Our best guess -- just return the # of CPUs minus 4.
-            (Runtime.getRuntime().availableProcessors() - 4).coerceAtLeast(0)
-        }
-
-        private fun readCpuInfo() = CpuInfo(
-            BufferedReader(FileReader("/proc/cpuinfo"))
-                .useLines { it.toList() }
-        )
-
-        private fun getMaxCpuFrequency(cpuIndex: Int): Int {
-            val path = "/sys/devices/system/cpu/cpu${cpuIndex}/cpufreq/cpuinfo_max_freq"
-            val maxFreq = BufferedReader(FileReader(path)).use { it.readLine() }
-            return maxFreq.toInt()
-        }
-    }
-}
\ No newline at end of file
diff --git a/examples/whisper.android/app/src/main/jni/whisper/CMakeLists.txt b/examples/whisper.android/app/src/main/jni/whisper/CMakeLists.txt
deleted file mode 100644 (file)
index 390fd19..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-cmake_minimum_required(VERSION 3.10)
-
-project(whisper.cpp)
-
-set(CMAKE_CXX_STANDARD 11)
-set(WHISPER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../../../../../../)
-
-set(
-        SOURCE_FILES
-        ${WHISPER_LIB_DIR}/ggml.c
-        ${WHISPER_LIB_DIR}/ggml-alloc.c
-        ${WHISPER_LIB_DIR}/ggml-backend.c
-        ${WHISPER_LIB_DIR}/ggml-quants.c
-        ${WHISPER_LIB_DIR}/whisper.cpp
-        ${CMAKE_SOURCE_DIR}/jni.c
-)
-
-find_library(LOG_LIB log)
-
-function(build_library target_name)
-    add_library(
-        ${target_name}
-        SHARED
-        ${SOURCE_FILES}
-    )
-
-    target_link_libraries(${target_name} ${LOG_LIB} android)
-
-    if (${target_name} STREQUAL "whisper_v8fp16_va")
-        target_compile_options(${target_name} PRIVATE -march=armv8.2-a+fp16)
-    elseif (${target_name} STREQUAL "whisper_vfpv4")
-        target_compile_options(${target_name} PRIVATE -mfpu=neon-vfpv4)
-    endif ()
-
-    if (NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug")
-
-        target_compile_options(${target_name} PRIVATE -O3)
-        target_compile_options(${target_name} PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden)
-        target_compile_options(${target_name} PRIVATE -ffunction-sections -fdata-sections)
-
-        target_link_options(${target_name} PRIVATE -Wl,--gc-sections)
-        target_link_options(${target_name} PRIVATE -Wl,--exclude-libs,ALL)
-        target_link_options(${target_name} PRIVATE -flto)
-
-    endif ()
-endfunction()
-
-build_library("whisper") # Default target
-
-if (${ANDROID_ABI} STREQUAL "arm64-v8a")
-    build_library("whisper_v8fp16_va")
-elseif (${ANDROID_ABI} STREQUAL "armeabi-v7a")
-    build_library("whisper_vfpv4")
-endif ()
-
-include_directories(${WHISPER_LIB_DIR})
diff --git a/examples/whisper.android/app/src/main/jni/whisper/jni.c b/examples/whisper.android/app/src/main/jni/whisper/jni.c
deleted file mode 100644 (file)
index a8b3ded..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-#include <jni.h>
-#include <android/asset_manager.h>
-#include <android/asset_manager_jni.h>
-#include <android/log.h>
-#include <stdlib.h>
-#include <sys/sysinfo.h>
-#include <string.h>
-#include "whisper.h"
-#include "ggml.h"
-
-#define UNUSED(x) (void)(x)
-#define TAG "JNI"
-
-#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,     TAG, __VA_ARGS__)
-#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,     TAG, __VA_ARGS__)
-
-static inline int min(int a, int b) {
-    return (a < b) ? a : b;
-}
-
-static inline int max(int a, int b) {
-    return (a > b) ? a : b;
-}
-
-struct input_stream_context {
-    size_t offset;
-    JNIEnv * env;
-    jobject thiz;
-    jobject input_stream;
-
-    jmethodID mid_available;
-    jmethodID mid_read;
-};
-
-size_t inputStreamRead(void * ctx, void * output, size_t read_size) {
-    struct input_stream_context* is = (struct input_stream_context*)ctx;
-
-    jint avail_size = (*is->env)->CallIntMethod(is->env, is->input_stream, is->mid_available);
-    jint size_to_copy = read_size < avail_size ? (jint)read_size : avail_size;
-
-    jbyteArray byte_array = (*is->env)->NewByteArray(is->env, size_to_copy);
-
-    jint n_read = (*is->env)->CallIntMethod(is->env, is->input_stream, is->mid_read, byte_array, 0, size_to_copy);
-
-    if (size_to_copy != read_size || size_to_copy != n_read) {
-        LOGI("Insufficient Read: Req=%zu, ToCopy=%d, Available=%d", read_size, size_to_copy, n_read);
-    }
-
-    jbyte* byte_array_elements = (*is->env)->GetByteArrayElements(is->env, byte_array, NULL);
-    memcpy(output, byte_array_elements, size_to_copy);
-    (*is->env)->ReleaseByteArrayElements(is->env, byte_array, byte_array_elements, JNI_ABORT);
-
-    (*is->env)->DeleteLocalRef(is->env, byte_array);
-
-    is->offset += size_to_copy;
-
-    return size_to_copy;
-}
-bool inputStreamEof(void * ctx) {
-    struct input_stream_context* is = (struct input_stream_context*)ctx;
-
-    jint result = (*is->env)->CallIntMethod(is->env, is->input_stream, is->mid_available);
-    return result <= 0;
-}
-void inputStreamClose(void * ctx) {
-
-}
-
-JNIEXPORT jlong JNICALL
-Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_initContextFromInputStream(
-        JNIEnv *env, jobject thiz, jobject input_stream) {
-    UNUSED(thiz);
-
-    struct whisper_context *context = NULL;
-    struct whisper_model_loader loader = {};
-    struct input_stream_context inp_ctx = {};
-
-    inp_ctx.offset = 0;
-    inp_ctx.env = env;
-    inp_ctx.thiz = thiz;
-    inp_ctx.input_stream = input_stream;
-
-    jclass cls = (*env)->GetObjectClass(env, input_stream);
-    inp_ctx.mid_available = (*env)->GetMethodID(env, cls, "available", "()I");
-    inp_ctx.mid_read = (*env)->GetMethodID(env, cls, "read", "([BII)I");
-
-    loader.context = &inp_ctx;
-    loader.read = inputStreamRead;
-    loader.eof = inputStreamEof;
-    loader.close = inputStreamClose;
-
-    loader.eof(loader.context);
-
-    context = whisper_init(&loader);
-    return (jlong) context;
-}
-
-static size_t asset_read(void *ctx, void *output, size_t read_size) {
-    return AAsset_read((AAsset *) ctx, output, read_size);
-}
-
-static bool asset_is_eof(void *ctx) {
-    return AAsset_getRemainingLength64((AAsset *) ctx) <= 0;
-}
-
-static void asset_close(void *ctx) {
-    AAsset_close((AAsset *) ctx);
-}
-
-static struct whisper_context *whisper_init_from_asset(
-        JNIEnv *env,
-        jobject assetManager,
-        const char *asset_path
-) {
-    LOGI("Loading model from asset '%s'\n", asset_path);
-    AAssetManager *asset_manager = AAssetManager_fromJava(env, assetManager);
-    AAsset *asset = AAssetManager_open(asset_manager, asset_path, AASSET_MODE_STREAMING);
-    if (!asset) {
-        LOGW("Failed to open '%s'\n", asset_path);
-        return NULL;
-    }
-
-    whisper_model_loader loader = {
-            .context = asset,
-            .read = &asset_read,
-            .eof = &asset_is_eof,
-            .close = &asset_close
-    };
-
-    return whisper_init_with_params(&loader, whisper_context_default_params());
-}
-
-JNIEXPORT jlong JNICALL
-Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_initContextFromAsset(
-        JNIEnv *env, jobject thiz, jobject assetManager, jstring asset_path_str) {
-    UNUSED(thiz);
-    struct whisper_context *context = NULL;
-    const char *asset_path_chars = (*env)->GetStringUTFChars(env, asset_path_str, NULL);
-    context = whisper_init_from_asset(env, assetManager, asset_path_chars);
-    (*env)->ReleaseStringUTFChars(env, asset_path_str, asset_path_chars);
-    return (jlong) context;
-}
-
-JNIEXPORT jlong JNICALL
-Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_initContext(
-        JNIEnv *env, jobject thiz, jstring model_path_str) {
-    UNUSED(thiz);
-    struct whisper_context *context = NULL;
-    const char *model_path_chars = (*env)->GetStringUTFChars(env, model_path_str, NULL);
-    context = whisper_init_from_file_with_params(model_path_chars, whisper_context_default_params());
-    (*env)->ReleaseStringUTFChars(env, model_path_str, model_path_chars);
-    return (jlong) context;
-}
-
-JNIEXPORT void JNICALL
-Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_freeContext(
-        JNIEnv *env, jobject thiz, jlong context_ptr) {
-    UNUSED(env);
-    UNUSED(thiz);
-    struct whisper_context *context = (struct whisper_context *) context_ptr;
-    whisper_free(context);
-}
-
-JNIEXPORT void JNICALL
-Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_fullTranscribe(
-        JNIEnv *env, jobject thiz, jlong context_ptr, jint num_threads, jfloatArray audio_data) {
-    UNUSED(thiz);
-    struct whisper_context *context = (struct whisper_context *) context_ptr;
-    jfloat *audio_data_arr = (*env)->GetFloatArrayElements(env, audio_data, NULL);
-    const jsize audio_data_length = (*env)->GetArrayLength(env, audio_data);
-
-    // The below adapted from the Objective-C iOS sample
-    struct whisper_full_params params = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
-    params.print_realtime = true;
-    params.print_progress = false;
-    params.print_timestamps = true;
-    params.print_special = false;
-    params.translate = false;
-    params.language = "en";
-    params.n_threads = num_threads;
-    params.offset_ms = 0;
-    params.no_context = true;
-    params.single_segment = false;
-
-    whisper_reset_timings(context);
-
-    LOGI("About to run whisper_full");
-    if (whisper_full(context, params, audio_data_arr, audio_data_length) != 0) {
-        LOGI("Failed to run the model");
-    } else {
-        whisper_print_timings(context);
-    }
-    (*env)->ReleaseFloatArrayElements(env, audio_data, audio_data_arr, JNI_ABORT);
-}
-
-JNIEXPORT jint JNICALL
-Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_getTextSegmentCount(
-        JNIEnv *env, jobject thiz, jlong context_ptr) {
-    UNUSED(env);
-    UNUSED(thiz);
-    struct whisper_context *context = (struct whisper_context *) context_ptr;
-    return whisper_full_n_segments(context);
-}
-
-JNIEXPORT jstring JNICALL
-Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_getTextSegment(
-        JNIEnv *env, jobject thiz, jlong context_ptr, jint index) {
-    UNUSED(thiz);
-    struct whisper_context *context = (struct whisper_context *) context_ptr;
-    const char *text = whisper_full_get_segment_text(context, index);
-    jstring string = (*env)->NewStringUTF(env, text);
-    return string;
-}
-
-JNIEXPORT jstring JNICALL
-Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_getSystemInfo(
-        JNIEnv *env, jobject thiz
-) {
-    UNUSED(thiz);
-    const char *sysinfo = whisper_print_system_info();
-    jstring string = (*env)->NewStringUTF(env, sysinfo);
-    return string;
-}
-
-JNIEXPORT jstring JNICALL
-Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_benchMemcpy(JNIEnv *env, jobject thiz,
-                                                                      jint n_threads) {
-    UNUSED(thiz);
-    const char *bench_ggml_memcpy = whisper_bench_memcpy_str(n_threads);
-    jstring string = (*env)->NewStringUTF(env, bench_ggml_memcpy);
-}
-
-JNIEXPORT jstring JNICALL
-Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_benchGgmlMulMat(JNIEnv *env, jobject thiz,
-                                                                          jint n_threads) {
-    UNUSED(thiz);
-    const char *bench_ggml_mul_mat = whisper_bench_ggml_mul_mat_str(n_threads);
-    jstring string = (*env)->NewStringUTF(env, bench_ggml_mul_mat);
-}
diff --git a/examples/whisper.android/lib/.gitignore b/examples/whisper.android/lib/.gitignore
new file mode 100644 (file)
index 0000000..42afabf
--- /dev/null
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/examples/whisper.android/lib/build.gradle b/examples/whisper.android/lib/build.gradle
new file mode 100644 (file)
index 0000000..c32a689
--- /dev/null
@@ -0,0 +1,51 @@
+plugins {
+    id 'com.android.library'
+    id 'org.jetbrains.kotlin.android'
+}
+
+android {
+    namespace 'com.whispercpp'
+    compileSdk 34
+
+    defaultConfig {
+        minSdk 26
+        targetSdk 34
+        versionCode 1
+        versionName "1.0"
+
+        ndk {
+            abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    ndkVersion "25.2.9519653"
+    externalNativeBuild {
+        cmake {
+            path = file("src/main/jni/whisper/CMakeLists.txt")
+        }
+    }
+    packagingOptions {
+        resources {
+            excludes += '/META-INF/{AL2.0,LGPL2.1}'
+        }
+    }
+}
+
+dependencies {
+    implementation 'androidx.core:core-ktx:1.9.0'
+    implementation 'androidx.appcompat:appcompat:1.6.1'
+    implementation 'com.google.android.material:material:1.8.0'
+}
\ No newline at end of file
diff --git a/examples/whisper.android/lib/src/main/AndroidManifest.xml b/examples/whisper.android/lib/src/main/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..a5918e6
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+</manifest>
\ No newline at end of file
diff --git a/examples/whisper.android/lib/src/main/java/com/whispercpp/whisper/LibWhisper.kt b/examples/whisper.android/lib/src/main/java/com/whispercpp/whisper/LibWhisper.kt
new file mode 100644 (file)
index 0000000..513202f
--- /dev/null
@@ -0,0 +1,157 @@
+package com.whispercpp.whisper
+
+import android.content.res.AssetManager
+import android.os.Build
+import android.util.Log
+import kotlinx.coroutines.*
+import java.io.File
+import java.io.InputStream
+import java.util.concurrent.Executors
+
+private const val LOG_TAG = "LibWhisper"
+
+class WhisperContext private constructor(private var ptr: Long) {
+    // Meet Whisper C++ constraint: Don't access from more than one thread at a time.
+    private val scope: CoroutineScope = CoroutineScope(
+        Executors.newSingleThreadExecutor().asCoroutineDispatcher()
+    )
+
+    suspend fun transcribeData(data: FloatArray): String = withContext(scope.coroutineContext) {
+        require(ptr != 0L)
+        val numThreads = WhisperCpuConfig.preferredThreadCount
+        Log.d(LOG_TAG, "Selecting $numThreads threads")
+        WhisperLib.fullTranscribe(ptr, numThreads, data)
+        val textCount = WhisperLib.getTextSegmentCount(ptr)
+        return@withContext buildString {
+            for (i in 0 until textCount) {
+                append(WhisperLib.getTextSegment(ptr, i))
+            }
+        }
+    }
+
+    suspend fun benchMemory(nthreads: Int): String = withContext(scope.coroutineContext) {
+        return@withContext WhisperLib.benchMemcpy(nthreads)
+    }
+
+    suspend fun benchGgmlMulMat(nthreads: Int): String = withContext(scope.coroutineContext) {
+        return@withContext WhisperLib.benchGgmlMulMat(nthreads)
+    }
+
+    suspend fun release() = withContext(scope.coroutineContext) {
+        if (ptr != 0L) {
+            WhisperLib.freeContext(ptr)
+            ptr = 0
+        }
+    }
+
+    protected fun finalize() {
+        runBlocking {
+            release()
+        }
+    }
+
+    companion object {
+        fun createContextFromFile(filePath: String): WhisperContext {
+            val ptr = WhisperLib.initContext(filePath)
+            if (ptr == 0L) {
+                throw java.lang.RuntimeException("Couldn't create context with path $filePath")
+            }
+            return WhisperContext(ptr)
+        }
+
+        fun createContextFromInputStream(stream: InputStream): WhisperContext {
+            val ptr = WhisperLib.initContextFromInputStream(stream)
+
+            if (ptr == 0L) {
+                throw java.lang.RuntimeException("Couldn't create context from input stream")
+            }
+            return WhisperContext(ptr)
+        }
+
+        fun createContextFromAsset(assetManager: AssetManager, assetPath: String): WhisperContext {
+            val ptr = WhisperLib.initContextFromAsset(assetManager, assetPath)
+
+            if (ptr == 0L) {
+                throw java.lang.RuntimeException("Couldn't create context from asset $assetPath")
+            }
+            return WhisperContext(ptr)
+        }
+
+        fun getSystemInfo(): String {
+            return WhisperLib.getSystemInfo()
+        }
+    }
+}
+
+private class WhisperLib {
+    companion object {
+        init {
+            Log.d(LOG_TAG, "Primary ABI: ${Build.SUPPORTED_ABIS[0]}")
+            var loadVfpv4 = false
+            var loadV8fp16 = false
+            if (isArmEabiV7a()) {
+                // armeabi-v7a needs runtime detection support
+                val cpuInfo = cpuInfo()
+                cpuInfo?.let {
+                    Log.d(LOG_TAG, "CPU info: $cpuInfo")
+                    if (cpuInfo.contains("vfpv4")) {
+                        Log.d(LOG_TAG, "CPU supports vfpv4")
+                        loadVfpv4 = true
+                    }
+                }
+            } else if (isArmEabiV8a()) {
+                // ARMv8.2a needs runtime detection support
+                val cpuInfo = cpuInfo()
+                cpuInfo?.let {
+                    Log.d(LOG_TAG, "CPU info: $cpuInfo")
+                    if (cpuInfo.contains("fphp")) {
+                        Log.d(LOG_TAG, "CPU supports fp16 arithmetic")
+                        loadV8fp16 = true
+                    }
+                }
+            }
+
+            if (loadVfpv4) {
+                Log.d(LOG_TAG, "Loading libwhisper_vfpv4.so")
+                System.loadLibrary("whisper_vfpv4")
+            } else if (loadV8fp16) {
+                Log.d(LOG_TAG, "Loading libwhisper_v8fp16_va.so")
+                System.loadLibrary("whisper_v8fp16_va")
+            } else {
+                Log.d(LOG_TAG, "Loading libwhisper.so")
+                System.loadLibrary("whisper")
+            }
+        }
+
+        // JNI methods
+        external fun initContextFromInputStream(inputStream: InputStream): Long
+        external fun initContextFromAsset(assetManager: AssetManager, assetPath: String): Long
+        external fun initContext(modelPath: String): Long
+        external fun freeContext(contextPtr: Long)
+        external fun fullTranscribe(contextPtr: Long, numThreads: Int, audioData: FloatArray)
+        external fun getTextSegmentCount(contextPtr: Long): Int
+        external fun getTextSegment(contextPtr: Long, index: Int): String
+        external fun getSystemInfo(): String
+        external fun benchMemcpy(nthread: Int): String
+        external fun benchGgmlMulMat(nthread: Int): String
+    }
+}
+
+private fun isArmEabiV7a(): Boolean {
+    return Build.SUPPORTED_ABIS[0].equals("armeabi-v7a")
+}
+
+private fun isArmEabiV8a(): Boolean {
+    return Build.SUPPORTED_ABIS[0].equals("arm64-v8a")
+}
+
+private fun cpuInfo(): String? {
+    return try {
+        File("/proc/cpuinfo").inputStream().bufferedReader().use {
+            it.readText()
+        }
+    } catch (e: Exception) {
+        Log.w(LOG_TAG, "Couldn't read /proc/cpuinfo", e)
+        null
+    }
+}
\ No newline at end of file
diff --git a/examples/whisper.android/lib/src/main/java/com/whispercpp/whisper/WhisperCpuConfig.kt b/examples/whisper.android/lib/src/main/java/com/whispercpp/whisper/WhisperCpuConfig.kt
new file mode 100644 (file)
index 0000000..edfb415
--- /dev/null
@@ -0,0 +1,73 @@
+package com.whispercpp.whisper
+
+import android.util.Log
+import java.io.BufferedReader
+import java.io.FileReader
+
+object WhisperCpuConfig {
+    val preferredThreadCount: Int
+        // Always use at least 2 threads:
+        get() = CpuInfo.getHighPerfCpuCount().coerceAtLeast(2)
+}
+
+private class CpuInfo(private val lines: List<String>) {
+    private fun getHighPerfCpuCount(): Int = try {
+        getHighPerfCpuCountByFrequencies()
+    } catch (e: Exception) {
+        Log.d(LOG_TAG, "Couldn't read CPU frequencies", e)
+        getHighPerfCpuCountByVariant()
+    }
+
+    private fun getHighPerfCpuCountByFrequencies(): Int =
+        getCpuValues(property = "processor") { getMaxCpuFrequency(it.toInt()) }
+            .also { Log.d(LOG_TAG, "Binned cpu frequencies (frequency, count): ${it.binnedValues()}") }
+            .countDroppingMin()
+
+    private fun getHighPerfCpuCountByVariant(): Int =
+        getCpuValues(property = "CPU variant") { it.substringAfter("0x").toInt(radix = 16) }
+            .also { Log.d(LOG_TAG, "Binned cpu variants (variant, count): ${it.binnedValues()}") }
+            .countKeepingMin()
+
+    private fun List<Int>.binnedValues() = groupingBy { it }.eachCount()
+
+    private fun getCpuValues(property: String, mapper: (String) -> Int) = lines
+        .asSequence()
+        .filter { it.startsWith(property) }
+        .map { mapper(it.substringAfter(':').trim()) }
+        .sorted()
+        .toList()
+
+
+    private fun List<Int>.countDroppingMin(): Int {
+        val min = min()
+        return count { it > min }
+    }
+
+    private fun List<Int>.countKeepingMin(): Int {
+        val min = min()
+        return count { it == min }
+    }
+
+    companion object {
+        private const val LOG_TAG = "WhisperCpuConfig"
+
+        fun getHighPerfCpuCount(): Int = try {
+            readCpuInfo().getHighPerfCpuCount()
+        } catch (e: Exception) {
+            Log.d(LOG_TAG, "Couldn't read CPU info", e)
+            // Our best guess -- just return the # of CPUs minus 4.
+            (Runtime.getRuntime().availableProcessors() - 4).coerceAtLeast(0)
+        }
+
+        private fun readCpuInfo() = CpuInfo(
+            BufferedReader(FileReader("/proc/cpuinfo"))
+                .useLines { it.toList() }
+        )
+
+        private fun getMaxCpuFrequency(cpuIndex: Int): Int {
+            val path = "/sys/devices/system/cpu/cpu${cpuIndex}/cpufreq/cpuinfo_max_freq"
+            val maxFreq = BufferedReader(FileReader(path)).use { it.readLine() }
+            return maxFreq.toInt()
+        }
+    }
+}
\ No newline at end of file
diff --git a/examples/whisper.android/lib/src/main/jni/whisper/CMakeLists.txt b/examples/whisper.android/lib/src/main/jni/whisper/CMakeLists.txt
new file mode 100644 (file)
index 0000000..390fd19
--- /dev/null
@@ -0,0 +1,56 @@
+cmake_minimum_required(VERSION 3.10)
+
+project(whisper.cpp)
+
+set(CMAKE_CXX_STANDARD 11)
+set(WHISPER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../../../../../../)
+
+set(
+        SOURCE_FILES
+        ${WHISPER_LIB_DIR}/ggml.c
+        ${WHISPER_LIB_DIR}/ggml-alloc.c
+        ${WHISPER_LIB_DIR}/ggml-backend.c
+        ${WHISPER_LIB_DIR}/ggml-quants.c
+        ${WHISPER_LIB_DIR}/whisper.cpp
+        ${CMAKE_SOURCE_DIR}/jni.c
+)
+
+find_library(LOG_LIB log)
+
+function(build_library target_name)
+    add_library(
+        ${target_name}
+        SHARED
+        ${SOURCE_FILES}
+    )
+
+    target_link_libraries(${target_name} ${LOG_LIB} android)
+
+    if (${target_name} STREQUAL "whisper_v8fp16_va")
+        target_compile_options(${target_name} PRIVATE -march=armv8.2-a+fp16)
+    elseif (${target_name} STREQUAL "whisper_vfpv4")
+        target_compile_options(${target_name} PRIVATE -mfpu=neon-vfpv4)
+    endif ()
+
+    if (NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug")
+
+        target_compile_options(${target_name} PRIVATE -O3)
+        target_compile_options(${target_name} PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden)
+        target_compile_options(${target_name} PRIVATE -ffunction-sections -fdata-sections)
+
+        target_link_options(${target_name} PRIVATE -Wl,--gc-sections)
+        target_link_options(${target_name} PRIVATE -Wl,--exclude-libs,ALL)
+        target_link_options(${target_name} PRIVATE -flto)
+
+    endif ()
+endfunction()
+
+build_library("whisper") # Default target
+
+if (${ANDROID_ABI} STREQUAL "arm64-v8a")
+    build_library("whisper_v8fp16_va")
+elseif (${ANDROID_ABI} STREQUAL "armeabi-v7a")
+    build_library("whisper_vfpv4")
+endif ()
+
+include_directories(${WHISPER_LIB_DIR})
diff --git a/examples/whisper.android/lib/src/main/jni/whisper/jni.c b/examples/whisper.android/lib/src/main/jni/whisper/jni.c
new file mode 100644 (file)
index 0000000..08825ed
--- /dev/null
@@ -0,0 +1,239 @@
+#include <jni.h>
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#include <android/log.h>
+#include <stdlib.h>
+#include <sys/sysinfo.h>
+#include <string.h>
+#include "whisper.h"
+#include "ggml.h"
+
+#define UNUSED(x) (void)(x)
+#define TAG "JNI"
+
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,     TAG, __VA_ARGS__)
+#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,     TAG, __VA_ARGS__)
+
+static inline int min(int a, int b) {
+    return (a < b) ? a : b;
+}
+
+static inline int max(int a, int b) {
+    return (a > b) ? a : b;
+}
+
+struct input_stream_context {
+    size_t offset;
+    JNIEnv * env;
+    jobject thiz;
+    jobject input_stream;
+
+    jmethodID mid_available;
+    jmethodID mid_read;
+};
+
+size_t inputStreamRead(void * ctx, void * output, size_t read_size) {
+    struct input_stream_context* is = (struct input_stream_context*)ctx;
+
+    jint avail_size = (*is->env)->CallIntMethod(is->env, is->input_stream, is->mid_available);
+    jint size_to_copy = read_size < avail_size ? (jint)read_size : avail_size;
+
+    jbyteArray byte_array = (*is->env)->NewByteArray(is->env, size_to_copy);
+
+    jint n_read = (*is->env)->CallIntMethod(is->env, is->input_stream, is->mid_read, byte_array, 0, size_to_copy);
+
+    if (size_to_copy != read_size || size_to_copy != n_read) {
+        LOGI("Insufficient Read: Req=%zu, ToCopy=%d, Available=%d", read_size, size_to_copy, n_read);
+    }
+
+    jbyte* byte_array_elements = (*is->env)->GetByteArrayElements(is->env, byte_array, NULL);
+    memcpy(output, byte_array_elements, size_to_copy);
+    (*is->env)->ReleaseByteArrayElements(is->env, byte_array, byte_array_elements, JNI_ABORT);
+
+    (*is->env)->DeleteLocalRef(is->env, byte_array);
+
+    is->offset += size_to_copy;
+
+    return size_to_copy;
+}
+bool inputStreamEof(void * ctx) {
+    struct input_stream_context* is = (struct input_stream_context*)ctx;
+
+    jint result = (*is->env)->CallIntMethod(is->env, is->input_stream, is->mid_available);
+    return result <= 0;
+}
+void inputStreamClose(void * ctx) {
+
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_whispercppdemo_whisper_WhisperLib_00024Companion_initContextFromInputStream(
+        JNIEnv *env, jobject thiz, jobject input_stream) {
+    UNUSED(thiz);
+
+    struct whisper_context *context = NULL;
+    struct whisper_model_loader loader = {};
+    struct input_stream_context inp_ctx = {};
+
+    inp_ctx.offset = 0;
+    inp_ctx.env = env;
+    inp_ctx.thiz = thiz;
+    inp_ctx.input_stream = input_stream;
+
+    jclass cls = (*env)->GetObjectClass(env, input_stream);
+    inp_ctx.mid_available = (*env)->GetMethodID(env, cls, "available", "()I");
+    inp_ctx.mid_read = (*env)->GetMethodID(env, cls, "read", "([BII)I");
+
+    loader.context = &inp_ctx;
+    loader.read = inputStreamRead;
+    loader.eof = inputStreamEof;
+    loader.close = inputStreamClose;
+
+    loader.eof(loader.context);
+
+    context = whisper_init(&loader);
+    return (jlong) context;
+}
+
+static size_t asset_read(void *ctx, void *output, size_t read_size) {
+    return AAsset_read((AAsset *) ctx, output, read_size);
+}
+
+static bool asset_is_eof(void *ctx) {
+    return AAsset_getRemainingLength64((AAsset *) ctx) <= 0;
+}
+
+static void asset_close(void *ctx) {
+    AAsset_close((AAsset *) ctx);
+}
+
+static struct whisper_context *whisper_init_from_asset(
+        JNIEnv *env,
+        jobject assetManager,
+        const char *asset_path
+) {
+    LOGI("Loading model from asset '%s'\n", asset_path);
+    AAssetManager *asset_manager = AAssetManager_fromJava(env, assetManager);
+    AAsset *asset = AAssetManager_open(asset_manager, asset_path, AASSET_MODE_STREAMING);
+    if (!asset) {
+        LOGW("Failed to open '%s'\n", asset_path);
+        return NULL;
+    }
+
+    whisper_model_loader loader = {
+            .context = asset,
+            .read = &asset_read,
+            .eof = &asset_is_eof,
+            .close = &asset_close
+    };
+
+    return whisper_init_with_params(&loader, whisper_context_default_params());
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_whispercpp_whisper_WhisperLib_00024Companion_initContextFromAsset(
+        JNIEnv *env, jobject thiz, jobject assetManager, jstring asset_path_str) {
+    UNUSED(thiz);
+    struct whisper_context *context = NULL;
+    const char *asset_path_chars = (*env)->GetStringUTFChars(env, asset_path_str, NULL);
+    context = whisper_init_from_asset(env, assetManager, asset_path_chars);
+    (*env)->ReleaseStringUTFChars(env, asset_path_str, asset_path_chars);
+    return (jlong) context;
+}
+
+JNIEXPORT jlong JNICALL
+Java_com_whispercpp_whisper_WhisperLib_00024Companion_initContext(
+        JNIEnv *env, jobject thiz, jstring model_path_str) {
+    UNUSED(thiz);
+    struct whisper_context *context = NULL;
+    const char *model_path_chars = (*env)->GetStringUTFChars(env, model_path_str, NULL);
+    context = whisper_init_from_file_with_params(model_path_chars, whisper_context_default_params());
+    (*env)->ReleaseStringUTFChars(env, model_path_str, model_path_chars);
+    return (jlong) context;
+}
+
+JNIEXPORT void JNICALL
+Java_com_whispercpp_whisper_WhisperLib_00024Companion_freeContext(
+        JNIEnv *env, jobject thiz, jlong context_ptr) {
+    UNUSED(env);
+    UNUSED(thiz);
+    struct whisper_context *context = (struct whisper_context *) context_ptr;
+    whisper_free(context);
+}
+
+JNIEXPORT void JNICALL
+Java_com_whispercpp_whisper_WhisperLib_00024Companion_fullTranscribe(
+        JNIEnv *env, jobject thiz, jlong context_ptr, jint num_threads, jfloatArray audio_data) {
+    UNUSED(thiz);
+    struct whisper_context *context = (struct whisper_context *) context_ptr;
+    jfloat *audio_data_arr = (*env)->GetFloatArrayElements(env, audio_data, NULL);
+    const jsize audio_data_length = (*env)->GetArrayLength(env, audio_data);
+
+    // The below adapted from the Objective-C iOS sample
+    struct whisper_full_params params = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
+    params.print_realtime = true;
+    params.print_progress = false;
+    params.print_timestamps = true;
+    params.print_special = false;
+    params.translate = false;
+    params.language = "en";
+    params.n_threads = num_threads;
+    params.offset_ms = 0;
+    params.no_context = true;
+    params.single_segment = false;
+
+    whisper_reset_timings(context);
+
+    LOGI("About to run whisper_full");
+    if (whisper_full(context, params, audio_data_arr, audio_data_length) != 0) {
+        LOGI("Failed to run the model");
+    } else {
+        whisper_print_timings(context);
+    }
+    (*env)->ReleaseFloatArrayElements(env, audio_data, audio_data_arr, JNI_ABORT);
+}
+
+JNIEXPORT jint JNICALL
+Java_com_whispercpp_whisper_WhisperLib_00024Companion_getTextSegmentCount(
+        JNIEnv *env, jobject thiz, jlong context_ptr) {
+    UNUSED(env);
+    UNUSED(thiz);
+    struct whisper_context *context = (struct whisper_context *) context_ptr;
+    return whisper_full_n_segments(context);
+}
+
+JNIEXPORT jstring JNICALL
+Java_com_whispercpp_whisper_WhisperLib_00024Companion_getTextSegment(
+        JNIEnv *env, jobject thiz, jlong context_ptr, jint index) {
+    UNUSED(thiz);
+    struct whisper_context *context = (struct whisper_context *) context_ptr;
+    const char *text = whisper_full_get_segment_text(context, index);
+    jstring string = (*env)->NewStringUTF(env, text);
+    return string;
+}
+
+JNIEXPORT jstring JNICALL
+Java_com_whispercpp_whisper_WhisperLib_00024Companion_getSystemInfo(
+        JNIEnv *env, jobject thiz
+) {
+    UNUSED(thiz);
+    const char *sysinfo = whisper_print_system_info();
+    jstring string = (*env)->NewStringUTF(env, sysinfo);
+    return string;
+}
+
+JNIEXPORT jstring JNICALL
+Java_com_whispercpp_whisper_WhisperLib_00024Companion_benchMemcpy(JNIEnv *env, jobject thiz,
+                                                                      jint n_threads) {
+    UNUSED(thiz);
+    const char *bench_ggml_memcpy = whisper_bench_memcpy_str(n_threads);
+    jstring string = (*env)->NewStringUTF(env, bench_ggml_memcpy);
+}
+
+JNIEXPORT jstring JNICALL
+Java_com_whispercpp_whisper_WhisperLib_00024Companion_benchGgmlMulMat(JNIEnv *env, jobject thiz,
+                                                                          jint n_threads) {
+    UNUSED(thiz);
+    const char *bench_ggml_mul_mat = whisper_bench_ggml_mul_mat_str(n_threads);
+    jstring string = (*env)->NewStringUTF(env, bench_ggml_mul_mat);
+}
index 3deecee6a412ff6b96844d0a2a616810cdd2cb24..dbd8508e8e1e457810223273e212bf4b41c8d072 100644 (file)
@@ -14,3 +14,4 @@ dependencyResolutionManagement {
 }
 rootProject.name = "WhisperCppDemo"
 include ':app'
+include ':lib'