Recently Written · git

subrep-android

Subrep on Android: live captions of the sound of the phone, made on the phone by Whisper, shared by link.

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

Log | Files | Refs


app/src/main/cpp/whisper_jni.c (4448 bytes)

1 // The narrowest bridge to whisper.cpp: load a model, transcribe one piece of 16 kHz mono float
2 // PCM, read the text back. One transcription runs at a time (Engine has one worker).
3 
4 #include <jni.h>
5 #include <string.h>
6 #include <android/log.h>
7 
8 #include "whisper.h"
9 
10 #define TAG "subrep-whisper"
11 #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
12 
13 #define JNI_FN(name) Java_com_honjimaku_subrep_WhisperLib_##name
14 
15 JNIEXPORT jlong JNICALL
16 JNI_FN(initContext)(JNIEnv *env, jobject thiz, jstring model_path) {
17     (void) thiz;
18     const char *path = (*env)->GetStringUTFChars(env, model_path, NULL);
19     struct whisper_context_params cparams = whisper_context_default_params();
20     cparams.use_gpu = false;
21     struct whisper_context *ctx = whisper_init_from_file_with_params(path, cparams);
22     if (ctx == NULL) LOGW("could not load model %s", path);
23     (*env)->ReleaseStringUTFChars(env, model_path, path);
24     return (jlong) ctx;
25 }
26 
27 JNIEXPORT void JNICALL
28 JNI_FN(freeContext)(JNIEnv *env, jobject thiz, jlong ptr) {
29     (void) env; (void) thiz;
30     if (ptr != 0) whisper_free((struct whisper_context *) ptr);
31 }
32 
33 // Returns 0 on success, else whisper's own error code.
34 //
35 // audio_ctx: how much of the 30 s encoder window to compute, in units of 20 ms. A piece of a
36 // few seconds does not need the whole window, and the encoder is most of the time on a phone.
37 JNIEXPORT jint JNICALL
38 JNI_FN(transcribe)(JNIEnv *env, jobject thiz, jlong ptr, jfloatArray samples,
39                    jint n_threads, jstring language, jint audio_ctx) {
40     (void) thiz;
41     struct whisper_context *ctx = (struct whisper_context *) ptr;
42     if (ctx == NULL) return -2;
43 
44     const jsize n = (*env)->GetArrayLength(env, samples);
45     jfloat *pcm = (*env)->GetFloatArrayElements(env, samples, NULL);
46     const char *lang = (*env)->GetStringUTFChars(env, language, NULL);
47 
48     struct whisper_full_params p = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
49     p.print_realtime   = false;
50     p.print_progress   = false;
51     p.print_timestamps = false;
52     p.print_special    = false;
53     p.translate        = false;
54     p.language         = lang;          // "auto" detects the language of the piece
55     p.n_threads        = n_threads;
56     // Each piece stands alone. Text carried over as a prompt is how one hallucinated line
57     // becomes a page of the same line.
58     p.no_context       = true;
59     p.single_segment   = true;
60     p.suppress_blank   = true;
61     p.suppress_nst     = true;
62     p.audio_ctx        = audio_ctx;
63     // Greedy, no second try at a higher temperature: a live caption must not wait for it.
64     p.temperature_inc  = 0.0f;
65     // Speech is at most about 8 tokens a second. A cap stops the loop that Whisper falls into
66     // on singing or noise ("o-o-o-o-o...") after a moment instead of a page later.
67     p.max_tokens       = (int) (n / 16000.0 * 8) + 24;
68 
69     int rc = whisper_full(ctx, p, pcm, n);
70 
71     (*env)->ReleaseStringUTFChars(env, language, lang);
72     (*env)->ReleaseFloatArrayElements(env, samples, pcm, JNI_ABORT);
73 
74     if (rc != 0) LOGW("whisper_full failed: %d", rc);
75     return rc;
76 }
77 
78 JNIEXPORT jint JNICALL
79 JNI_FN(segmentCount)(JNIEnv *env, jobject thiz, jlong ptr) {
80     (void) env; (void) thiz;
81     return whisper_full_n_segments((struct whisper_context *) ptr);
82 }
83 
84 // Text as raw UTF-8 bytes. NewStringUTF wants *modified* UTF-8 and mangles or aborts on
85 // anything outside the BMP, and a small model can end a segment inside a multi-byte
86 // character. Kotlin decodes leniently.
87 JNIEXPORT jbyteArray JNICALL
88 JNI_FN(segmentText)(JNIEnv *env, jobject thiz, jlong ptr, jint index) {
89     (void) thiz;
90     const char *text = whisper_full_get_segment_text((struct whisper_context *) ptr, index);
91     if (text == NULL) text = "";
92     const jsize len = (jsize) strlen(text);
93     jbyteArray out = (*env)->NewByteArray(env, len);
94     if (out != NULL) (*env)->SetByteArrayRegion(env, out, 0, len, (const jbyte *) text);
95     return out;
96 }
97 
98 JNIEXPORT jstring JNICALL
99 JNI_FN(detectedLanguage)(JNIEnv *env, jobject thiz, jlong ptr) {
100     (void) thiz;
101     const int id = whisper_full_lang_id((struct whisper_context *) ptr);
102     const char *code = id >= 0 ? whisper_lang_str(id) : NULL;
103     return (*env)->NewStringUTF(env, code != NULL ? code : "");
104 }
105 
106 JNIEXPORT jstring JNICALL
107 JNI_FN(systemInfo)(JNIEnv *env, jobject thiz) {
108     (void) thiz;
109     return (*env)->NewStringUTF(env, whisper_print_system_info());
110 }