whisper.rn 0.3.0-rc.4 → 0.3.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -11
- package/android/build.gradle +9 -0
- package/android/src/main/java/com/rnwhisper/Downloader.java +83 -0
- package/android/src/main/java/com/rnwhisper/RNWhisperPackage.java +33 -13
- package/android/src/main/java/com/rnwhisper/WhisperContext.java +9 -9
- package/android/src/main/jni/whisper/Whisper.mk +1 -1
- package/android/src/main/jni/whisper/jni.cpp +102 -0
- package/android/src/newarch/java/com/rnwhisper/RNWhisperModule.java +286 -0
- package/android/src/{main → oldarch}/java/com/rnwhisper/RNWhisperModule.java +59 -5
- package/ios/RNWhisper.mm +54 -8
- package/ios/RNWhisperDownloader.h +8 -0
- package/ios/RNWhisperDownloader.m +39 -0
- package/lib/commonjs/NativeRNWhisper.js +10 -0
- package/lib/commonjs/NativeRNWhisper.js.map +1 -0
- package/lib/commonjs/index.js +81 -23
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeRNWhisper.js +3 -0
- package/lib/module/NativeRNWhisper.js.map +1 -0
- package/lib/module/index.js +73 -16
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/NativeRNWhisper.d.ts +65 -0
- package/lib/typescript/NativeRNWhisper.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +17 -43
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/NativeRNWhisper.ts +79 -0
- package/src/index.ts +164 -122
package/README.md
CHANGED
|
@@ -8,9 +8,12 @@ React Native binding of [whisper.cpp](https://github.com/ggerganov/whisper.cpp).
|
|
|
8
8
|
|
|
9
9
|
[whisper.cpp](https://github.com/ggerganov/whisper.cpp): High-performance inference of [OpenAI's Whisper](https://github.com/openai/whisper) automatic speech recognition (ASR) model
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Screenshots
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
| <img src="https://github.com/mybigday/whisper.rn/assets/3001525/2fea7b2d-c911-44fb-9afc-8efc7b594446" width="300" /> | <img src="https://github.com/mybigday/whisper.rn/assets/3001525/a5005a6c-44f7-4db9-95e8-0fd951a2e147" width="300" /> |
|
|
14
|
+
| :------------------------------------------: | :------------------------------------------: |
|
|
15
|
+
| iOS: Tested on iPhone 13 Pro Max | Android: Tested on Pixel 6 |
|
|
16
|
+
| (tiny.en, Core ML enabled) | (tiny.en, armv8.2-a+fp16) |
|
|
14
17
|
|
|
15
18
|
## Installation
|
|
16
19
|
|
|
@@ -47,8 +50,7 @@ Add the following line to ```android/app/src/main/AndroidManifest.xml```
|
|
|
47
50
|
import { initWhisper } from 'whisper.rn'
|
|
48
51
|
|
|
49
52
|
const whisperContext = await initWhisper({
|
|
50
|
-
filePath: 'file://.../ggml-
|
|
51
|
-
isBundleAsset: false, // Set to true if you want to load the model from bundle resources, the filePath will be like `ggml-base.en.bin`
|
|
53
|
+
filePath: 'file://.../ggml-tiny.en.bin',
|
|
52
54
|
})
|
|
53
55
|
|
|
54
56
|
const sampleFilePath = 'file://.../sample.wav'
|
|
@@ -81,35 +83,92 @@ In Android, you may need to request the microphone permission by [`PermissionAnd
|
|
|
81
83
|
|
|
82
84
|
Please visit the [Documentation](docs/) for more details.
|
|
83
85
|
|
|
86
|
+
## Usage with assets
|
|
87
|
+
|
|
88
|
+
You can also use the model file / audio file from assets:
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
import { initWhisper } from 'whisper.rn'
|
|
92
|
+
|
|
93
|
+
const whisperContext = await initWhisper({
|
|
94
|
+
filePath: require('../assets/ggml-tiny.en.bin'),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const { stop, promise } =
|
|
98
|
+
whisperContext.transcribe(require('../assets/sample.wav'), options)
|
|
99
|
+
|
|
100
|
+
// ...
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This requires editing the `metro.config.js` to support assets:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
// ...
|
|
107
|
+
const defaultAssetExts = require('metro-config/src/defaults/defaults').assetExts
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
// ...
|
|
111
|
+
resolver: {
|
|
112
|
+
// ...
|
|
113
|
+
assetExts: [
|
|
114
|
+
...defaultAssetExts,
|
|
115
|
+
'bin', // whisper.rn: ggml model binary
|
|
116
|
+
'mil', // whisper.rn: CoreML model asset
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Please note that it will significantly increase the size of the app in release mode.
|
|
123
|
+
|
|
84
124
|
## Core ML support
|
|
85
125
|
|
|
86
126
|
__*Platform: iOS 15.0+, tvOS 15.0+*__
|
|
87
127
|
|
|
88
128
|
To use Core ML on iOS, you will need to have the Core ML model files.
|
|
89
129
|
|
|
90
|
-
The `.mlmodelc` model files is load depend on the ggml model file path. For example, if your ggml model path is `ggml-
|
|
130
|
+
The `.mlmodelc` model files is load depend on the ggml model file path. For example, if your ggml model path is `ggml-tiny.en.bin`, the Core ML model path will be `ggml-tiny.en-encoder.mlmodelc`. Please note that the ggml model is still needed as decoder or encoder fallback.
|
|
91
131
|
|
|
92
132
|
Currently there is no official way to get the Core ML models by URL, you will need to convert Core ML models by yourself. Please see [Core ML Support](https://github.com/ggerganov/whisper.cpp#core-ml-support) of whisper.cpp for more details.
|
|
93
133
|
|
|
94
|
-
During the `.mlmodelc` is a directory, you will need to download 5 files:
|
|
134
|
+
During the `.mlmodelc` is a directory, you will need to download 5 files (3 required):
|
|
95
135
|
|
|
96
136
|
```json5
|
|
97
137
|
[
|
|
98
138
|
'model.mil',
|
|
99
|
-
'metadata.json',
|
|
100
139
|
'coremldata.bin',
|
|
101
140
|
'weights/weight.bin',
|
|
102
|
-
|
|
141
|
+
// Not required:
|
|
142
|
+
// 'metadata.json', 'analytics/coremldata.bin',
|
|
103
143
|
]
|
|
104
144
|
```
|
|
105
145
|
|
|
106
|
-
Or just
|
|
146
|
+
Or just use `require` to bundle that in your app, like the example app does, but this would increase the app size significantly.
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
const whisperContext = await initWhisper({
|
|
150
|
+
filePath: require('../assets/ggml-tiny.en.bin')
|
|
151
|
+
coreMLModelAsset:
|
|
152
|
+
Platform.OS === 'ios'
|
|
153
|
+
? {
|
|
154
|
+
filename: 'ggml-tiny.en-encoder.mlmodelc',
|
|
155
|
+
assets: [
|
|
156
|
+
require('../assets/ggml-tiny.en-encoder.mlmodelc/weights/weight.bin'),
|
|
157
|
+
require('../assets/ggml-tiny.en-encoder.mlmodelc/model.mil'),
|
|
158
|
+
require('../assets/ggml-tiny.en-encoder.mlmodelc/coremldata.bin'),
|
|
159
|
+
],
|
|
160
|
+
}
|
|
161
|
+
: undefined,
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
In real world, we recommended to split the asset imports into another platform specific file (e.g. `context-opts.ios.js`) to avoid these unused files in the bundle for Android.
|
|
107
166
|
|
|
108
167
|
## Run with example
|
|
109
168
|
|
|
110
|
-
The example app
|
|
169
|
+
The example app provide a simple UI for testing the functions.
|
|
111
170
|
|
|
112
|
-
|
|
171
|
+
Used Whisper model: `tiny.en` in https://huggingface.co/datasets/ggerganov/whisper.cpp
|
|
113
172
|
Sample file: `jfk.wav` in https://github.com/ggerganov/whisper.cpp/tree/master/samples
|
|
114
173
|
|
|
115
174
|
For test better performance on transcribe, you can run the app in Release mode.
|
|
@@ -130,6 +189,10 @@ jest.mock('whisper.rn', () => require('whisper.rn/jest/mock'))
|
|
|
130
189
|
|
|
131
190
|
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
132
191
|
|
|
192
|
+
## Troubleshooting
|
|
193
|
+
|
|
194
|
+
See the [troubleshooting](TROUBLESHOOTING.md) if you encounter any problem while using `whisper.rn`.
|
|
195
|
+
|
|
133
196
|
## License
|
|
134
197
|
|
|
135
198
|
MIT
|
package/android/build.gradle
CHANGED
|
@@ -59,6 +59,15 @@ android {
|
|
|
59
59
|
targetCompatibility JavaVersion.VERSION_1_8
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
sourceSets {
|
|
63
|
+
main {
|
|
64
|
+
if (isNewArchitectureEnabled()) {
|
|
65
|
+
java.srcDirs += ['src/newarch']
|
|
66
|
+
} else {
|
|
67
|
+
java.srcDirs += ['src/oldarch']
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
repositories {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
package com.rnwhisper;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
|
|
5
|
+
import java.io.BufferedInputStream;
|
|
6
|
+
import java.io.FileOutputStream;
|
|
7
|
+
import java.io.File;
|
|
8
|
+
import java.io.InputStream;
|
|
9
|
+
import java.io.OutputStream;
|
|
10
|
+
import java.net.URL;
|
|
11
|
+
import java.net.URLConnection;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* NOTE: This is simple downloader,
|
|
15
|
+
* the main purpose is supported load assets on RN Debug mode,
|
|
16
|
+
* so it's a very crude implementation.
|
|
17
|
+
*
|
|
18
|
+
* If you want to use file download in production to load model / audio files,
|
|
19
|
+
* I would recommend using react-native-fs or expo-file-system to manage the files.
|
|
20
|
+
*/
|
|
21
|
+
public class Downloader {
|
|
22
|
+
private static Context context;
|
|
23
|
+
|
|
24
|
+
public Downloader(Context context) {
|
|
25
|
+
this.context = context;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private String getDir() {
|
|
29
|
+
String dir = context.getCacheDir().getAbsolutePath() + "/rnwhisper_debug_assets/";
|
|
30
|
+
File file = new File(dir);
|
|
31
|
+
if (!file.exists()) {
|
|
32
|
+
file.mkdirs();
|
|
33
|
+
}
|
|
34
|
+
return dir;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private boolean fileExists(String filename) {
|
|
38
|
+
File file = new File(getDir() + filename);
|
|
39
|
+
return file.exists();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public String downloadFile(String urlPath) throws Exception {
|
|
43
|
+
String filename = urlPath.substring(urlPath.lastIndexOf('/') + 1);
|
|
44
|
+
if (filename.contains("?")) {
|
|
45
|
+
filename = filename.substring(0, filename.indexOf("?"));
|
|
46
|
+
}
|
|
47
|
+
String filepath = getDir() + filename;
|
|
48
|
+
if (fileExists(filename)) {
|
|
49
|
+
return filepath;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
URL url = new URL(urlPath);
|
|
53
|
+
URLConnection connection = url.openConnection();
|
|
54
|
+
connection.connect();
|
|
55
|
+
InputStream input = new BufferedInputStream(url.openStream());
|
|
56
|
+
OutputStream output = new FileOutputStream(filepath);
|
|
57
|
+
byte data[] = new byte[1024];
|
|
58
|
+
int count;
|
|
59
|
+
while ((count = input.read(data)) != -1) {
|
|
60
|
+
output.write(data, 0, count);
|
|
61
|
+
}
|
|
62
|
+
output.flush();
|
|
63
|
+
output.close();
|
|
64
|
+
input.close();
|
|
65
|
+
} catch (Exception e) {
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
return filepath;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private void deleteFile(File fileOrDir) {
|
|
72
|
+
if (fileOrDir.isDirectory()) {
|
|
73
|
+
for (File child : fileOrDir.listFiles()) {
|
|
74
|
+
deleteFile(child);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
fileOrDir.delete();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public void clearCache() {
|
|
81
|
+
deleteFile(new File(getDir()));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -1,28 +1,48 @@
|
|
|
1
1
|
package com.rnwhisper;
|
|
2
2
|
|
|
3
3
|
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
4
5
|
|
|
5
|
-
import com.facebook.react.ReactPackage;
|
|
6
6
|
import com.facebook.react.bridge.NativeModule;
|
|
7
7
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
8
|
-
import com.facebook.react.
|
|
8
|
+
import com.facebook.react.module.model.ReactModuleInfo;
|
|
9
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
10
|
+
import com.facebook.react.TurboReactPackage;
|
|
9
11
|
|
|
10
|
-
import java.util.ArrayList;
|
|
11
|
-
import java.util.Collections;
|
|
12
12
|
import java.util.List;
|
|
13
|
+
import java.util.HashMap;
|
|
14
|
+
import java.util.Map;
|
|
13
15
|
|
|
14
|
-
public class RNWhisperPackage
|
|
15
|
-
|
|
16
|
+
public class RNWhisperPackage extends TurboReactPackage {
|
|
17
|
+
|
|
18
|
+
@Nullable
|
|
16
19
|
@Override
|
|
17
|
-
public
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
|
|
21
|
+
if (name.equals(RNWhisperModule.NAME)) {
|
|
22
|
+
return new com.rnwhisper.RNWhisperModule(reactContext);
|
|
23
|
+
} else {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
@NonNull
|
|
24
28
|
@Override
|
|
25
|
-
public
|
|
26
|
-
return
|
|
29
|
+
public ReactModuleInfoProvider getReactModuleInfoProvider() {
|
|
30
|
+
return () -> {
|
|
31
|
+
final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
|
|
32
|
+
boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
|
33
|
+
moduleInfos.put(
|
|
34
|
+
RNWhisperModule.NAME,
|
|
35
|
+
new ReactModuleInfo(
|
|
36
|
+
RNWhisperModule.NAME,
|
|
37
|
+
RNWhisperModule.NAME,
|
|
38
|
+
false, // canOverrideExistingModule
|
|
39
|
+
false, // needsEagerInit
|
|
40
|
+
true, // hasConstants
|
|
41
|
+
false, // isCxxModule
|
|
42
|
+
isTurboModule // isTurboModule
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
return moduleInfos;
|
|
46
|
+
};
|
|
27
47
|
}
|
|
28
48
|
}
|
|
@@ -26,6 +26,7 @@ import java.io.File;
|
|
|
26
26
|
import java.io.FileInputStream;
|
|
27
27
|
import java.io.IOException;
|
|
28
28
|
import java.io.InputStream;
|
|
29
|
+
import java.io.PushbackInputStream;
|
|
29
30
|
import java.nio.ByteBuffer;
|
|
30
31
|
import java.nio.ByteOrder;
|
|
31
32
|
import java.nio.ShortBuffer;
|
|
@@ -281,10 +282,10 @@ public class WhisperContext {
|
|
|
281
282
|
eventEmitter.emit(eventName, event);
|
|
282
283
|
}
|
|
283
284
|
|
|
284
|
-
public WritableMap
|
|
285
|
+
public WritableMap transcribeInputStream(int jobId, InputStream inputStream, ReadableMap options) throws IOException, Exception {
|
|
285
286
|
this.jobId = jobId;
|
|
286
287
|
isTranscribing = true;
|
|
287
|
-
float[] audioData = decodeWaveFile(
|
|
288
|
+
float[] audioData = decodeWaveFile(inputStream);
|
|
288
289
|
int code = full(jobId, options, audioData, audioData.length);
|
|
289
290
|
isTranscribing = false;
|
|
290
291
|
this.jobId = -1;
|
|
@@ -383,14 +384,12 @@ public class WhisperContext {
|
|
|
383
384
|
freeContext(context);
|
|
384
385
|
}
|
|
385
386
|
|
|
386
|
-
public static float[] decodeWaveFile(
|
|
387
|
+
public static float[] decodeWaveFile(InputStream inputStream) throws IOException {
|
|
387
388
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
baos.write(buffer, 0, bytesRead);
|
|
393
|
-
}
|
|
389
|
+
byte[] buffer = new byte[1024];
|
|
390
|
+
int bytesRead;
|
|
391
|
+
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
392
|
+
baos.write(buffer, 0, bytesRead);
|
|
394
393
|
}
|
|
395
394
|
ByteBuffer byteBuffer = ByteBuffer.wrap(baos.toByteArray());
|
|
396
395
|
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
@@ -472,6 +471,7 @@ public class WhisperContext {
|
|
|
472
471
|
|
|
473
472
|
protected static native long initContext(String modelPath);
|
|
474
473
|
protected static native long initContextWithAsset(AssetManager assetManager, String modelPath);
|
|
474
|
+
protected static native long initContextWithInputStream(PushbackInputStream inputStream);
|
|
475
475
|
protected static native int fullTranscribe(
|
|
476
476
|
int job_id,
|
|
477
477
|
long context,
|
|
@@ -3,7 +3,7 @@ LOCAL_LDLIBS := -landroid -llog
|
|
|
3
3
|
|
|
4
4
|
# Make the final output library smaller by only keeping the symbols referenced from the app.
|
|
5
5
|
ifneq ($(APP_OPTIM),debug)
|
|
6
|
-
LOCAL_CFLAGS += -O3
|
|
6
|
+
LOCAL_CFLAGS += -O3 -DNDEBUG
|
|
7
7
|
LOCAL_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
|
|
8
8
|
LOCAL_CFLAGS += -ffunction-sections -fdata-sections
|
|
9
9
|
LOCAL_LDFLAGS += -Wl,--gc-sections
|
|
@@ -20,6 +20,97 @@ static inline int min(int a, int b) {
|
|
|
20
20
|
return (a < b) ? a : b;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// Load model from input stream (used for drawable / raw resources)
|
|
24
|
+
struct input_stream_context {
|
|
25
|
+
JNIEnv *env;
|
|
26
|
+
jobject input_stream;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
static size_t input_stream_read(void *ctx, void *output, size_t read_size) {
|
|
30
|
+
input_stream_context *context = (input_stream_context *)ctx;
|
|
31
|
+
JNIEnv *env = context->env;
|
|
32
|
+
jobject input_stream = context->input_stream;
|
|
33
|
+
jclass input_stream_class = env->GetObjectClass(input_stream);
|
|
34
|
+
|
|
35
|
+
jbyteArray buffer = env->NewByteArray(read_size);
|
|
36
|
+
jint bytes_read = env->CallIntMethod(
|
|
37
|
+
input_stream,
|
|
38
|
+
env->GetMethodID(input_stream_class, "read", "([B)I"),
|
|
39
|
+
buffer
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (bytes_read > 0) {
|
|
43
|
+
env->GetByteArrayRegion(buffer, 0, bytes_read, (jbyte *) output);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
env->DeleteLocalRef(buffer);
|
|
47
|
+
|
|
48
|
+
return bytes_read;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static bool input_stream_is_eof(void *ctx) {
|
|
52
|
+
input_stream_context *context = (input_stream_context *)ctx;
|
|
53
|
+
JNIEnv *env = context->env;
|
|
54
|
+
jobject input_stream = context->input_stream;
|
|
55
|
+
|
|
56
|
+
jclass input_stream_class = env->GetObjectClass(input_stream);
|
|
57
|
+
|
|
58
|
+
jbyteArray buffer = env->NewByteArray(1);
|
|
59
|
+
jint bytes_read = env->CallIntMethod(
|
|
60
|
+
input_stream,
|
|
61
|
+
env->GetMethodID(input_stream_class, "read", "([B)I"),
|
|
62
|
+
buffer
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
bool is_eof = (bytes_read == -1);
|
|
66
|
+
if (!is_eof) {
|
|
67
|
+
// If we successfully read a byte, "unread" it by pushing it back into the stream.
|
|
68
|
+
env->CallVoidMethod(
|
|
69
|
+
input_stream,
|
|
70
|
+
env->GetMethodID(input_stream_class, "unread", "([BII)V"),
|
|
71
|
+
buffer,
|
|
72
|
+
0,
|
|
73
|
+
1
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
env->DeleteLocalRef(buffer);
|
|
78
|
+
|
|
79
|
+
return is_eof;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static void input_stream_close(void *ctx) {
|
|
83
|
+
input_stream_context *context = (input_stream_context *)ctx;
|
|
84
|
+
JNIEnv *env = context->env;
|
|
85
|
+
jobject input_stream = context->input_stream;
|
|
86
|
+
jclass input_stream_class = env->GetObjectClass(input_stream);
|
|
87
|
+
|
|
88
|
+
env->CallVoidMethod(
|
|
89
|
+
input_stream,
|
|
90
|
+
env->GetMethodID(input_stream_class, "close", "()V")
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
env->DeleteGlobalRef(input_stream);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static struct whisper_context *whisper_init_from_input_stream(
|
|
97
|
+
JNIEnv *env,
|
|
98
|
+
jobject input_stream // PushbackInputStream
|
|
99
|
+
) {
|
|
100
|
+
input_stream_context *context = new input_stream_context;
|
|
101
|
+
context->env = env;
|
|
102
|
+
context->input_stream = env->NewGlobalRef(input_stream);
|
|
103
|
+
|
|
104
|
+
whisper_model_loader loader = {
|
|
105
|
+
.context = context,
|
|
106
|
+
.read = &input_stream_read,
|
|
107
|
+
.eof = &input_stream_is_eof,
|
|
108
|
+
.close = &input_stream_close
|
|
109
|
+
};
|
|
110
|
+
return whisper_init(&loader);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Load model from asset
|
|
23
114
|
static size_t asset_read(void *ctx, void *output, size_t read_size) {
|
|
24
115
|
return AAsset_read((AAsset *) ctx, output, read_size);
|
|
25
116
|
}
|
|
@@ -81,6 +172,17 @@ Java_com_rnwhisper_WhisperContext_initContextWithAsset(
|
|
|
81
172
|
return reinterpret_cast<jlong>(context);
|
|
82
173
|
}
|
|
83
174
|
|
|
175
|
+
JNIEXPORT jlong JNICALL
|
|
176
|
+
Java_com_rnwhisper_WhisperContext_initContextWithInputStream(
|
|
177
|
+
JNIEnv *env,
|
|
178
|
+
jobject thiz,
|
|
179
|
+
jobject input_stream
|
|
180
|
+
) {
|
|
181
|
+
UNUSED(thiz);
|
|
182
|
+
struct whisper_context *context = nullptr;
|
|
183
|
+
context = whisper_init_from_input_stream(env, input_stream);
|
|
184
|
+
return reinterpret_cast<jlong>(context);
|
|
185
|
+
}
|
|
84
186
|
|
|
85
187
|
JNIEXPORT jint JNICALL
|
|
86
188
|
Java_com_rnwhisper_WhisperContext_fullTranscribe(
|