app/src/main/cpp/whisper_jni.c (5550 bytes)
1 // The narrowest possible bridge to whisper.cpp: load a model, transcribe one 2 // buffer of 16 kHz mono float PCM, read the segments back. 3 // 4 // One transcription runs at a time (the service guarantees it), which is what 5 // lets progress and cancellation be plain process-wide atomics rather than 6 // callbacks into the JVM from whisper's worker threads. 7 8 #include <jni.h> 9 #include <stdatomic.h> 10 #include <stdbool.h> 11 #include <string.h> 12 #include <android/log.h> 13 14 #include "whisper.h" 15 16 #define TAG "subread-whisper" 17 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) 18 #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) 19 20 static atomic_int g_progress = 0; 21 static atomic_bool g_abort = false; 22 23 static void on_progress(struct whisper_context *ctx, struct whisper_state *state, 24 int progress, void *user_data) { 25 (void) ctx; (void) state; (void) user_data; 26 atomic_store(&g_progress, progress); 27 } 28 29 static bool should_abort(void *user_data) { 30 (void) user_data; 31 return atomic_load(&g_abort); 32 } 33 34 #define JNI_FN(name) Java_space_subread_app_whisper_WhisperLib_##name 35 36 JNIEXPORT jlong JNICALL 37 JNI_FN(initContext)(JNIEnv *env, jobject thiz, jstring model_path) { 38 (void) thiz; 39 const char *path = (*env)->GetStringUTFChars(env, model_path, NULL); 40 struct whisper_context_params cparams = whisper_context_default_params(); 41 cparams.use_gpu = false; 42 struct whisper_context *ctx = whisper_init_from_file_with_params(path, cparams); 43 if (ctx == NULL) LOGW("could not load model %s", path); 44 (*env)->ReleaseStringUTFChars(env, model_path, path); 45 return (jlong) ctx; 46 } 47 48 JNIEXPORT void JNICALL 49 JNI_FN(freeContext)(JNIEnv *env, jobject thiz, jlong ptr) { 50 (void) env; (void) thiz; 51 if (ptr != 0) whisper_free((struct whisper_context *) ptr); 52 } 53 54 // Returns 0 on success, 1000 if cancelled, anything else is whisper's own error. 55 JNIEXPORT jint JNICALL 56 JNI_FN(transcribe)(JNIEnv *env, jobject thiz, jlong ptr, jfloatArray samples, 57 jint n_threads, jstring language) { 58 (void) thiz; 59 struct whisper_context *ctx = (struct whisper_context *) ptr; 60 if (ctx == NULL) return -2; 61 62 const jsize n = (*env)->GetArrayLength(env, samples); 63 jfloat *pcm = (*env)->GetFloatArrayElements(env, samples, NULL); 64 const char *lang = (*env)->GetStringUTFChars(env, language, NULL); 65 66 struct whisper_full_params p = whisper_full_default_params(WHISPER_SAMPLING_GREEDY); 67 p.print_realtime = false; 68 p.print_progress = false; 69 p.print_timestamps = false; 70 p.print_special = false; 71 p.translate = false; 72 p.language = lang; // "auto" detects; the caller pins it after the first chunk 73 p.n_threads = n_threads; 74 // Each chunk stands alone. Carrying text over as a prompt is how one 75 // hallucinated line turns into a page of the same line. 76 p.no_context = true; 77 p.single_segment = false; 78 p.suppress_blank = true; 79 p.suppress_nst = true; 80 p.progress_callback = on_progress; 81 p.abort_callback = should_abort; 82 83 atomic_store(&g_progress, 0); 84 atomic_store(&g_abort, false); 85 86 int rc = whisper_full(ctx, p, pcm, n); 87 88 (*env)->ReleaseStringUTFChars(env, language, lang); 89 (*env)->ReleaseFloatArrayElements(env, samples, pcm, JNI_ABORT); 90 91 if (atomic_load(&g_abort)) return 1000; 92 if (rc != 0) LOGW("whisper_full failed: %d", rc); 93 return rc; 94 } 95 96 JNIEXPORT void JNICALL 97 JNI_FN(cancel)(JNIEnv *env, jobject thiz) { 98 (void) env; (void) thiz; 99 atomic_store(&g_abort, true); 100 } 101 102 JNIEXPORT jint JNICALL 103 JNI_FN(progress)(JNIEnv *env, jobject thiz) { 104 (void) env; (void) thiz; 105 return atomic_load(&g_progress); 106 } 107 108 JNIEXPORT jint JNICALL 109 JNI_FN(segmentCount)(JNIEnv *env, jobject thiz, jlong ptr) { 110 (void) env; (void) thiz; 111 return whisper_full_n_segments((struct whisper_context *) ptr); 112 } 113 114 // Text as raw UTF-8 bytes. NewStringUTF wants *modified* UTF-8 and mangles or 115 // aborts on anything outside the BMP, and the tiny model will happily emit a 116 // truncated multi-byte sequence at a segment edge. Let Kotlin decode leniently. 117 JNIEXPORT jbyteArray JNICALL 118 JNI_FN(segmentText)(JNIEnv *env, jobject thiz, jlong ptr, jint index) { 119 (void) thiz; 120 const char *text = whisper_full_get_segment_text((struct whisper_context *) ptr, index); 121 if (text == NULL) text = ""; 122 const jsize len = (jsize) strlen(text); 123 jbyteArray out = (*env)->NewByteArray(env, len); 124 if (out != NULL) (*env)->SetByteArrayRegion(env, out, 0, len, (const jbyte *) text); 125 return out; 126 } 127 128 // Both in centiseconds, as whisper reports them. 129 JNIEXPORT jlong JNICALL 130 JNI_FN(segmentStart)(JNIEnv *env, jobject thiz, jlong ptr, jint index) { 131 (void) env; (void) thiz; 132 return whisper_full_get_segment_t0((struct whisper_context *) ptr, index); 133 } 134 135 JNIEXPORT jlong JNICALL 136 JNI_FN(segmentEnd)(JNIEnv *env, jobject thiz, jlong ptr, jint index) { 137 (void) env; (void) thiz; 138 return whisper_full_get_segment_t1((struct whisper_context *) ptr, index); 139 } 140 141 JNIEXPORT jstring JNICALL 142 JNI_FN(detectedLanguage)(JNIEnv *env, jobject thiz, jlong ptr) { 143 (void) thiz; 144 const int id = whisper_full_lang_id((struct whisper_context *) ptr); 145 const char *code = id >= 0 ? whisper_lang_str(id) : NULL; 146 return (*env)->NewStringUTF(env, code != NULL ? code : ""); 147 } 148 149 JNIEXPORT jstring JNICALL 150 JNI_FN(systemInfo)(JNIEnv *env, jobject thiz) { 151 (void) thiz; 152 return (*env)->NewStringUTF(env, whisper_print_system_info()); 153 }