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) WhisperLib.fullTranscribe(ptr, 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, 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 } }