Recently Written · git

subread-android

SubRead for Android: times an audiobook against its ebook on the device

git clone https://github.com/equwal/subread-android

Log | Files | Refs


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 }