vectorDrawables {
useSupportLibrary true
}
- ndk {
- abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
- }
+
}
buildTypes {
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'
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
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
}
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() {
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")
}
+++ /dev/null
-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
+++ /dev/null
-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
+++ /dev/null
-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})
+++ /dev/null
-#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);
-}
--- /dev/null
+/build
\ No newline at end of file
--- /dev/null
+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
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+</manifest>
\ No newline at end of file
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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})
--- /dev/null
+#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);
+}
}
rootProject.name = "WhisperCppDemo"
include ':app'
+include ':lib'