]> git.djapps.eu Git - pkg/ggml/sources/whisper.cpp/commitdiff
bindings : add java bindings (#931)
authorNicholas Albion <redacted>
Sat, 20 May 2023 15:25:02 +0000 (01:25 +1000)
committerGitHub <redacted>
Sat, 20 May 2023 15:25:02 +0000 (18:25 +0300)
* WIP - java bindings

* updated README

* failed attempt at JNI

* fullTranscribe() test passes

* tested on Ubuntu 20

* link to Java bindings

36 files changed:
.gitignore
README.md
bindings/java/.idea/uiDesigner.xml [new file with mode: 0644]
bindings/java/CMakeLists.txt [new file with mode: 0644]
bindings/java/README.md [new file with mode: 0644]
bindings/java/build.gradle [new file with mode: 0644]
bindings/java/gradle.properties [new file with mode: 0644]
bindings/java/gradle/wrapper/gradle-wrapper.jar [new file with mode: 0644]
bindings/java/gradle/wrapper/gradle-wrapper.properties [new file with mode: 0644]
bindings/java/gradlew [new file with mode: 0644]
bindings/java/gradlew.bat [new file with mode: 0644]
bindings/java/settings.gradle [new file with mode: 0644]
bindings/java/src/main/cpp/whisper_java.cpp [new file with mode: 0644]
bindings/java/src/main/cpp/whisper_java.h [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperContext.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperCpp.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperCppJnaLibrary.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperJavaJnaLibrary.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperEncoderBeginCallback.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperLogitsFilterCallback.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperNewSegmentCallback.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperProgressCallback.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/ggml/GgmlTensor.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/ggml/GgmlType.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/EModel.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperModel.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperModelLoader.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperState.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperTokenData.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperFilters.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperFullParams.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperHParams.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperJavaParams.java [new file with mode: 0644]
bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperSamplingStrategy.java [new file with mode: 0644]
bindings/java/src/test/java/io/github/ggerganov/whispercpp/WhisperCppTest.java [new file with mode: 0644]
bindings/java/src/test/java/io/github/ggerganov/whispercpp/WhisperJnaLibraryTest.java [new file with mode: 0644]

index 08aa2a85d65bdd77be9e1e57799b201978f8cc88..503a47358af9c43c94d7c32d67b63b2384751546 100644 (file)
@@ -1,43 +1,46 @@
-*.o
-*.a
-.cache/
-.coreml/
-.test/
-.vs/
-.vscode/
-.DS_Store
-
-build/
-build-em/
-build-debug/
-build-release/
-build-static/
-build-cublas/
-build-no-accel/
-build-sanitize-addr/
-build-sanitize-thread/
-
-/main
-/stream
-/command
-/talk
-/talk-llama
-/bench
-/quantize
-
-arm_neon.h
-sync.sh
-libwhisper.a
-libwhisper.so
-compile_commands.json
-
-examples/arm_neon.h
-examples/whisper.objc/whisper.objc.xcodeproj/xcshareddata
-examples/whisper.objc/whisper.objc.xcodeproj/xcuserdata/
-examples/whisper.objc/whisper.objc.xcodeproj/project.xcworkspace/xcuserdata
-
-extra/bench-gg.txt
-
-models/*.mlmodel
-models/*.mlmodelc
-models/*.mlpackage
+*.o\r
+*.a\r
+.cache/\r
+.coreml/\r
+.test/\r
+.vs/\r
+.vscode/\r
+.DS_Store\r
+\r
+build/\r
+build-em/\r
+build-debug/\r
+build-release/\r
+build-static/\r
+build-cublas/\r
+build-no-accel/\r
+build-sanitize-addr/\r
+build-sanitize-thread/\r
+\r
+/main\r
+/stream\r
+/command\r
+/talk\r
+/talk-llama\r
+/bench\r
+/quantize\r
+\r
+arm_neon.h\r
+sync.sh\r
+libwhisper.a\r
+libwhisper.so\r
+compile_commands.json\r
+\r
+examples/arm_neon.h\r
+examples/whisper.objc/whisper.objc.xcodeproj/xcshareddata\r
+examples/whisper.objc/whisper.objc.xcodeproj/xcuserdata/\r
+examples/whisper.objc/whisper.objc.xcodeproj/project.xcworkspace/xcuserdata\r
+\r
+extra/bench-gg.txt\r
+\r
+models/*.mlmodel\r
+models/*.mlmodelc\r
+models/*.mlpackage\r
+bindings/java/.gradle/\r
+bindings/java/.idea/\r
+.idea/\r
index 75d6f81629d53e71060d2f141b6edae50d70153b..7d76f900f00d143b3099775a0a69421f335abb0e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@ Supported platforms:
 - [x] Mac OS (Intel and Arm)
 - [x] [iOS](examples/whisper.objc)
 - [x] [Android](examples/whisper.android)
+- [x] [Java](bindings/java/README.md)
 - [x] Linux / [FreeBSD](https://github.com/ggerganov/whisper.cpp/issues/56#issuecomment-1350920264)
 - [x] [WebAssembly](examples/whisper.wasm)
 - [x] Windows ([MSVC](https://github.com/ggerganov/whisper.cpp/blob/master/.github/workflows/build.yml#L117-L144) and [MinGW](https://github.com/ggerganov/whisper.cpp/issues/168)]
diff --git a/bindings/java/.idea/uiDesigner.xml b/bindings/java/.idea/uiDesigner.xml
new file mode 100644 (file)
index 0000000..6d50cd4
--- /dev/null
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<project version="4">\r
+  <component name="Palette2">\r
+    <group name="Swing">\r
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">\r
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />\r
+      </item>\r
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">\r
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />\r
+      </item>\r
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">\r
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />\r
+      </item>\r
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">\r
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />\r
+      </item>\r
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">\r
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />\r
+        <initial-values>\r
+          <property name="text" value="Button" />\r
+        </initial-values>\r
+      </item>\r
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">\r
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />\r
+        <initial-values>\r
+          <property name="text" value="RadioButton" />\r
+        </initial-values>\r
+      </item>\r
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">\r
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />\r
+        <initial-values>\r
+          <property name="text" value="CheckBox" />\r
+        </initial-values>\r
+      </item>\r
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">\r
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />\r
+        <initial-values>\r
+          <property name="text" value="Label" />\r
+        </initial-values>\r
+      </item>\r
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">\r
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">\r
+          <preferred-size width="150" height="-1" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">\r
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">\r
+          <preferred-size width="150" height="-1" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">\r
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">\r
+          <preferred-size width="150" height="-1" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">\r
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">\r
+          <preferred-size width="150" height="50" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">\r
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">\r
+          <preferred-size width="150" height="50" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">\r
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">\r
+          <preferred-size width="150" height="50" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">\r
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />\r
+      </item>\r
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">\r
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">\r
+          <preferred-size width="150" height="50" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">\r
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">\r
+          <preferred-size width="150" height="50" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">\r
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">\r
+          <preferred-size width="150" height="50" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">\r
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">\r
+          <preferred-size width="200" height="200" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">\r
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">\r
+          <preferred-size width="200" height="200" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">\r
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />\r
+      </item>\r
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">\r
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />\r
+      </item>\r
+      <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">\r
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />\r
+      </item>\r
+      <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">\r
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />\r
+      </item>\r
+      <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">\r
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">\r
+          <preferred-size width="-1" height="20" />\r
+        </default-constraints>\r
+      </item>\r
+      <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">\r
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />\r
+      </item>\r
+      <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">\r
+        <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />\r
+      </item>\r
+    </group>\r
+  </component>\r
+</project>
\ No newline at end of file
diff --git a/bindings/java/CMakeLists.txt b/bindings/java/CMakeLists.txt
new file mode 100644 (file)
index 0000000..7e47bb3
--- /dev/null
@@ -0,0 +1,50 @@
+cmake_minimum_required(VERSION 3.10)\r
+\r
+project(whisper_java VERSION 1.4.2)\r
+\r
+# Set the target name and source file/s\r
+set(TARGET_NAME whisper_java)\r
+set(SOURCES src/main/cpp/whisper_java.cpp)\r
+\r
+# include <whisper.h>\r
+include_directories(../../)\r
+\r
+# Set the output directory for the DLL/shared library based on the platform as required by JNA\r
+if(WIN32)\r
+    set(OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated/resources/main/win32-x86-64)\r
+elseif(UNIX AND NOT APPLE)\r
+    set(OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated/resources/main/linux-x86-64)\r
+elseif(APPLE)\r
+    set(OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated/resources/main/macos-x86-64)\r
+endif()\r
+\r
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIR})\r
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${OUTPUT_DIR})\r
+\r
+# Create the whisper_java library\r
+add_library(${TARGET_NAME} SHARED ${SOURCES})\r
+\r
+# Link against ../../build/Release/whisper.dll (or so/dynlib)\r
+target_link_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/../../../build/${CMAKE_BUILD_TYPE})\r
+target_link_libraries(${TARGET_NAME} PRIVATE whisper)\r
+\r
+# Set the appropriate compiler flags for Windows, Linux, and macOS\r
+if(WIN32)\r
+    target_compile_options(${TARGET_NAME} PRIVATE /W4 /D_CRT_SECURE_NO_WARNINGS)\r
+elseif(UNIX AND NOT APPLE)\r
+    target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra)\r
+elseif(APPLE)\r
+    target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra)\r
+endif()\r
+\r
+target_compile_definitions(${TARGET_NAME} PRIVATE WHISPER_SHARED)\r
+# add_definitions(-DWHISPER_SHARED)\r
+\r
+# Force CMake to save the libs to build/generated/resources/main/${os}-${arch} as required by JNA\r
+foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES})\r
+    string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)\r
+    set_target_properties(${TARGET_NAME} PROPERTIES\r
+                          RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${OUTPUT_DIR}\r
+                          LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${OUTPUT_DIR}\r
+                          ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${OUTPUT_DIR})\r
+endforeach(OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES)\r
diff --git a/bindings/java/README.md b/bindings/java/README.md
new file mode 100644 (file)
index 0000000..429287c
--- /dev/null
@@ -0,0 +1,63 @@
+# Java JNI bindings for Whisper
+
+This package provides Java JNI bindings for whisper.cpp. They have been tested on:
+
+  * <strike>Darwin (OS X) 12.6 on x64_64</strike>
+  * Ubuntu on x86_64
+  * Windows on x86_64
+
+The "low level" bindings are in `WhisperCppJnaLibrary` and `WhisperJavaJnaLibrary` which caches `whisper_full_params` and `whisper_context` in `whisper_java.cpp`. 
+
+There are a lot of classes in the `callbacks`, `ggml`, `model` and `params` directories but most of them have not been tested. 
+
+The most simple usage is as follows:
+
+```java
+import io.github.ggerganov.whispercpp.WhisperCpp;
+
+public class Example {
+
+    public static void main(String[] args) {
+        String modelpath;
+        WhisperCpp whisper = new WhisperCpp();
+        // By default, models are loaded from ~/.cache/whisper/ and are usually named "ggml-${name}.bin"
+        // or you can provide the absolute path to the model file.
+        whisper.initContext("base.en"); 
+        
+        long context = whisper.initContext(modelpath);
+        try {
+            whisper.fullTranscribe(context, samples);
+            
+            int segmentCount = whisper.getTextSegmentCount(context);
+            for (int i = 0; i < segmentCount; i++) {
+                String text = whisper.getTextSegment(context, i);
+                System.out.println(segment.getText());
+            }
+        } finally {
+             whisper.freeContext(context);
+        }
+     }
+}
+```
+
+## Building & Testing
+
+In order to build, you need to have the JDK 8 or higher installed. Run the tests with:
+
+```bash
+git clone https://github.com/ggerganov/whisper.cpp.git
+cd whisper.cpp/bindings/java
+
+mkdir build
+pushd build
+cmake ..
+cmake --build .
+popd
+
+./gradlew build
+```
+
+## License
+
+The license for the Go bindings is the same as the license for the rest of the whisper.cpp project, which is the MIT License. See the `LICENSE` file for more details.
+
diff --git a/bindings/java/build.gradle b/bindings/java/build.gradle
new file mode 100644 (file)
index 0000000..4a9b02f
--- /dev/null
@@ -0,0 +1,104 @@
+plugins {\r
+    id 'java'\r
+    id 'java-library'\r
+    id 'maven-publish'\r
+}\r
+\r
+archivesBaseName = 'whispercpp'\r
+group = 'io.github.ggerganov'\r
+version = '1.4.0'\r
+\r
+sourceCompatibility = 1.8\r
+targetCompatibility = 1.8\r
+\r
+sourceSets {\r
+    main {\r
+        resources {\r
+            srcDirs = ['src/main/resources', 'build/generated/resources/main']\r
+        }\r
+    }\r
+    test {\r
+        runtimeClasspath += files('build/generated/resources/main')\r
+    }\r
+}\r
+\r
+tasks.register('copyLibwhisperSo', Copy) {\r
+    from '../../build'\r
+    include 'libwhisper.so'\r
+    into 'build/generated/resources/main/linux-x86-64'\r
+}\r
+\r
+tasks.register('copyWhisperDll', Copy) {\r
+    from '../../build/Release'\r
+    include 'whisper.dll'\r
+    into 'build/generated/resources/main/windows-x86-64'\r
+}\r
+\r
+tasks.build.dependsOn copyLibwhisperSo, copyWhisperDll\r
+\r
+test {\r
+    systemProperty 'jna.library.path', project.file('build/generated/resources/main').absolutePath\r
+}\r
+\r
+java {\r
+    withSourcesJar()\r
+    withJavadocJar()\r
+}\r
+\r
+jar {\r
+    exclude '**/whisper_java.exp', '**/whisper_java.lib'\r
+}\r
+\r
+javadoc {\r
+    options.addStringOption('Xdoclint:none', '-quiet')\r
+}\r
+\r
+tasks.withType(Test) {\r
+    useJUnitPlatform()\r
+}\r
+\r
+dependencies {\r
+    implementation "net.java.dev.jna:jna:5.13.0"\r
+    testImplementation "org.junit.jupiter:junit-jupiter:5.9.2"\r
+    testImplementation "org.assertj:assertj-core:3.24.2"\r
+}\r
+\r
+repositories {\r
+    mavenCentral()\r
+}\r
+\r
+publishing {\r
+    publications {\r
+        mavenJava(MavenPublication) {\r
+            artifactId = 'whispercpp'\r
+            from components.java\r
+            pom {\r
+                name = 'whispercpp'\r
+                description = "Java JNA bindings for OpenAI's Whisper model, implemented in C/C++"\r
+                url = 'https://github.com/ggerganov/whisper.cpp'\r
+                licenses {\r
+                    license {\r
+                        name = 'MIT licence'\r
+                        url = 'https://raw.githubusercontent.com/ggerganov/whisper.cpp/master/LICENSE'\r
+                    }\r
+                }\r
+                developers {\r
+                    developer {\r
+                        id = 'ggerganov'\r
+                        name = 'Georgi Gerganov'\r
+                        email = 'ggerganov@gmail.com'\r
+                    }\r
+                    developer {\r
+                        id = 'nalbion'\r
+                        name = 'Nicholas Albion'\r
+                        email = 'nalbion@yahoo.com'\r
+                    }\r
+                }\r
+                scm {\r
+                    connection = 'scm:git:git://github.com/ggerganov/whisper.cpp.git'\r
+                    url = 'https://github.com/ggerganov/whisper.cpp'\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
diff --git a/bindings/java/gradle.properties b/bindings/java/gradle.properties
new file mode 100644 (file)
index 0000000..3ea68c2
--- /dev/null
@@ -0,0 +1,6 @@
+org.gradle.jvmargs=-Xms256m -Xmx1024m\r
+system.include.dir=/usr/include\r
+#system.local.include.dir=../../include\r
+system.local.include.dir=./build/generated/sources/headers/java/main\r
+jni.include.dir=/usr/lib/jvm/java-8-openjdk-amd64/include/\r
+jni.lib.dir=/usr/lib/jvm/java-8-openjdk-amd64/lib/\r
diff --git a/bindings/java/gradle/wrapper/gradle-wrapper.jar b/bindings/java/gradle/wrapper/gradle-wrapper.jar
new file mode 100644 (file)
index 0000000..ccebba7
Binary files /dev/null and b/bindings/java/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/bindings/java/gradle/wrapper/gradle-wrapper.properties b/bindings/java/gradle/wrapper/gradle-wrapper.properties
new file mode 100644 (file)
index 0000000..0c85a1f
--- /dev/null
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
+networkTimeout=10000
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/bindings/java/gradlew b/bindings/java/gradlew
new file mode 100644 (file)
index 0000000..79a61d4
--- /dev/null
@@ -0,0 +1,244 @@
+#!/bin/sh
+
+#
+# Copyright Â© 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions Â«$var», Â«${var}», Â«${var:-default}», Â«${var+SET}»,
+#           Â«${var#prefix}», Â«${var%suffix}», and Â«$( cmd )»;
+#         * compound commands having a testable exit status, especially Â«case»;
+#         * various built-in commands including Â«command», Â«set», and Â«ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC3045
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC3045
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/bindings/java/gradlew.bat b/bindings/java/gradlew.bat
new file mode 100644 (file)
index 0000000..6689b85
--- /dev/null
@@ -0,0 +1,92 @@
+@rem\r
+@rem Copyright 2015 the original author or authors.\r
+@rem\r
+@rem Licensed under the Apache License, Version 2.0 (the "License");\r
+@rem you may not use this file except in compliance with the License.\r
+@rem You may obtain a copy of the License at\r
+@rem\r
+@rem      https://www.apache.org/licenses/LICENSE-2.0\r
+@rem\r
+@rem Unless required by applicable law or agreed to in writing, software\r
+@rem distributed under the License is distributed on an "AS IS" BASIS,\r
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+@rem See the License for the specific language governing permissions and\r
+@rem limitations under the License.\r
+@rem\r
+\r
+@if "%DEBUG%"=="" @echo off\r
+@rem ##########################################################################\r
+@rem\r
+@rem  Gradle startup script for Windows\r
+@rem\r
+@rem ##########################################################################\r
+\r
+@rem Set local scope for the variables with windows NT shell\r
+if "%OS%"=="Windows_NT" setlocal\r
+\r
+set DIRNAME=%~dp0\r
+if "%DIRNAME%"=="" set DIRNAME=.\r
+@rem This is normally unused\r
+set APP_BASE_NAME=%~n0\r
+set APP_HOME=%DIRNAME%\r
+\r
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.\r
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi\r
+\r
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"\r
+\r
+@rem Find java.exe\r
+if defined JAVA_HOME goto findJavaFromJavaHome\r
+\r
+set JAVA_EXE=java.exe\r
+%JAVA_EXE% -version >NUL 2>&1\r
+if %ERRORLEVEL% equ 0 goto execute\r
+\r
+echo.\r
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r
+echo.\r
+echo Please set the JAVA_HOME variable in your environment to match the\r
+echo location of your Java installation.\r
+\r
+goto fail\r
+\r
+:findJavaFromJavaHome\r
+set JAVA_HOME=%JAVA_HOME:"=%\r
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe\r
+\r
+if exist "%JAVA_EXE%" goto execute\r
+\r
+echo.\r
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r
+echo.\r
+echo Please set the JAVA_HOME variable in your environment to match the\r
+echo location of your Java installation.\r
+\r
+goto fail\r
+\r
+:execute\r
+@rem Setup the command line\r
+\r
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar\r
+\r
+\r
+@rem Execute Gradle\r
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*\r
+\r
+:end\r
+@rem End local scope for the variables with windows NT shell\r
+if %ERRORLEVEL% equ 0 goto mainEnd\r
+\r
+:fail\r
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r
+rem the _cmd.exe /c_ return code!\r
+set EXIT_CODE=%ERRORLEVEL%\r
+if %EXIT_CODE% equ 0 set EXIT_CODE=1\r
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%\r
+exit /b %EXIT_CODE%\r
+\r
+:mainEnd\r
+if "%OS%"=="Windows_NT" endlocal\r
+\r
+:omega\r
diff --git a/bindings/java/settings.gradle b/bindings/java/settings.gradle
new file mode 100644 (file)
index 0000000..dbc6f38
--- /dev/null
@@ -0,0 +1 @@
+rootProject.name = "whispercpp"\r
diff --git a/bindings/java/src/main/cpp/whisper_java.cpp b/bindings/java/src/main/cpp/whisper_java.cpp
new file mode 100644 (file)
index 0000000..9e06aa0
--- /dev/null
@@ -0,0 +1,33 @@
+#include <stdio.h>\r
+#include "whisper_java.h"\r
+\r
+struct whisper_full_params default_params;\r
+struct whisper_context * whisper_ctx = nullptr;\r
+\r
+struct void whisper_java_default_params(enum whisper_sampling_strategy strategy) {\r
+    default_params = whisper_full_default_params(strategy);\r
+\r
+//    struct whisper_java_params result = {};\r
+//    return result;\r
+    return;\r
+}\r
+\r
+void whisper_java_init_from_file(const char * path_model) {\r
+    whisper_ctx = whisper_init_from_file(path_model);\r
+    if (0 == default_params.n_threads) {\r
+        whisper_java_default_params(WHISPER_SAMPLING_GREEDY);\r
+    }\r
+}\r
+\r
+/** Delegates to whisper_full, but without having to pass `whisper_full_params` */\r
+int whisper_java_full(\r
+          struct whisper_context * ctx,\r
+//      struct whisper_java_params   params,\r
+                     const float * samples,\r
+                             int   n_samples) {\r
+    return whisper_full(ctx, default_params, samples, n_samples);\r
+}\r
+\r
+void whisper_java_free() {\r
+//    free(default_params);\r
+}\r
diff --git a/bindings/java/src/main/cpp/whisper_java.h b/bindings/java/src/main/cpp/whisper_java.h
new file mode 100644 (file)
index 0000000..d64866b
--- /dev/null
@@ -0,0 +1,24 @@
+#define WHISPER_BUILD\r
+#include <whisper.h>\r
+\r
+#ifdef __cplusplus\r
+extern "C" {\r
+#endif\r
+\r
+struct whisper_java_params {\r
+};\r
+\r
+WHISPER_API void whisper_java_default_params(enum whisper_sampling_strategy strategy);\r
+\r
+WHISPER_API void whisper_java_init_from_file(const char * path_model);\r
+\r
+WHISPER_API int whisper_java_full(\r
+          struct whisper_context * ctx,\r
+//      struct whisper_java_params   params,\r
+                     const float * samples,\r
+                             int   n_samples);\r
+\r
+\r
+#ifdef __cplusplus\r
+}\r
+#endif\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperContext.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperContext.java
new file mode 100644 (file)
index 0000000..22d4ce8
--- /dev/null
@@ -0,0 +1,39 @@
+package io.github.ggerganov.whispercpp;\r
+\r
+import com.sun.jna.Structure;\r
+import com.sun.jna.ptr.PointerByReference;\r
+import io.github.ggerganov.whispercpp.ggml.GgmlType;\r
+import io.github.ggerganov.whispercpp.WhisperModel;\r
+\r
+import java.util.List;\r
+\r
+public class WhisperContext extends Structure {\r
+    int t_load_us = 0;\r
+    int t_start_us = 0;\r
+\r
+    /** weight type (FP32 / FP16 / QX) */\r
+    GgmlType wtype = GgmlType.GGML_TYPE_F16;\r
+    /** intermediate type (FP32 or FP16) */\r
+    GgmlType itype = GgmlType.GGML_TYPE_F16;\r
+\r
+//    WhisperModel model;\r
+    public PointerByReference model;\r
+//    whisper_vocab vocab;\r
+//    whisper_state * state = nullptr;\r
+    public PointerByReference vocab;\r
+    public PointerByReference state;\r
+\r
+    /** populated by whisper_init_from_file() */\r
+    String path_model;\r
+\r
+//    public static class ByReference extends WhisperContext implements Structure.ByReference {\r
+//    }\r
+//\r
+//    public static class ByValue extends WhisperContext implements Structure.ByValue {\r
+//    }\r
+//\r
+//    @Override\r
+//    protected List<String> getFieldOrder() {\r
+//        return List.of("t_load_us", "t_start_us", "wtype", "itype", "model", "vocab", "state", "path_model");\r
+//    }\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperCpp.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperCpp.java
new file mode 100644 (file)
index 0000000..f014407
--- /dev/null
@@ -0,0 +1,124 @@
+package io.github.ggerganov.whispercpp;\r
+\r
+import com.sun.jna.Pointer;\r
+import io.github.ggerganov.whispercpp.params.WhisperJavaParams;\r
+import io.github.ggerganov.whispercpp.params.WhisperSamplingStrategy;\r
+\r
+import java.io.File;\r
+import java.io.FileNotFoundException;\r
+import java.io.IOException;\r
+\r
+/**\r
+ * Before calling most methods, you must call `initContext(modelPath)` to initialise the `ctx` Pointer.\r
+ */\r
+public class WhisperCpp implements AutoCloseable {\r
+    private WhisperCppJnaLibrary lib = WhisperCppJnaLibrary.instance;\r
+    private WhisperJavaJnaLibrary javaLib = WhisperJavaJnaLibrary.instance;\r
+    private Pointer ctx = null;\r
+\r
+    public File modelDir() {\r
+        String modelDirPath = System.getenv("XDG_CACHE_HOME");\r
+        if (modelDirPath == null) {\r
+            modelDirPath = System.getProperty("user.home") + "/.cache";\r
+        }\r
+\r
+        return new File(modelDirPath, "whisper");\r
+    }\r
+\r
+    /**\r
+     * @param modelPath - absolute path, or just the name (eg: "base", "base-en" or "base.en")\r
+     * @return a Pointer to the WhisperContext\r
+     */\r
+    void initContext(String modelPath) throws FileNotFoundException {\r
+        if (ctx != null) {\r
+            lib.whisper_free(ctx);\r
+        }\r
+\r
+        if (!modelPath.contains("/") && !modelPath.contains("\\")) {\r
+            if (!modelPath.endsWith(".bin")) {\r
+                modelPath = "ggml-" + modelPath.replace("-", ".") + ".bin";\r
+            }\r
+\r
+            modelPath = new File(modelDir(), modelPath).getAbsolutePath();\r
+        }\r
+\r
+        javaLib.whisper_java_init_from_file(modelPath);\r
+        ctx = lib.whisper_init_from_file(modelPath);\r
+\r
+        if (ctx == null) {\r
+            throw new FileNotFoundException(modelPath);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Initialises `whisper_full_params` internally in whisper_java.cpp so JNA doesn't have to map everything.\r
+     * `whisper_java_init_from_file()` calls `whisper_java_default_params(WHISPER_SAMPLING_GREEDY)` for convenience.\r
+     */\r
+    public void getDefaultJavaParams(WhisperSamplingStrategy strategy) {\r
+        javaLib.whisper_java_default_params(strategy.ordinal());\r
+//        return lib.whisper_full_default_params(strategy.value)\r
+    }\r
+\r
+// whisper_full_default_params was too hard to integrate with, so for now we use javaLib.whisper_java_default_params\r
+//    fun getDefaultParams(strategy: WhisperSamplingStrategy): WhisperFullParams {\r
+//        return lib.whisper_full_default_params(strategy.value)\r
+//    }\r
+\r
+    @Override\r
+    public void close() {\r
+        freeContext();\r
+        System.out.println("Whisper closed");\r
+    }\r
+\r
+    private void freeContext() {\r
+        if (ctx != null) {\r
+            lib.whisper_free(ctx);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text.\r
+     * Not thread safe for same context\r
+     * Uses the specified decoding strategy to obtain the text.\r
+     */\r
+    public String fullTranscribe(/*WhisperJavaParams whisperParams,*/ float[] audioData) throws IOException {\r
+        if (ctx == null) {\r
+            throw new IllegalStateException("Model not initialised");\r
+        }\r
+\r
+        if (javaLib.whisper_java_full(ctx, /*whisperParams,*/ audioData, audioData.length) != 0) {\r
+            throw new IOException("Failed to process audio");\r
+        }\r
+\r
+        int nSegments = lib.whisper_full_n_segments(ctx);\r
+\r
+        StringBuilder str = new StringBuilder();\r
+\r
+        for (int i = 0; i < nSegments; i++) {\r
+            String text = lib.whisper_full_get_segment_text(ctx, i);\r
+            System.out.println("Segment:" + text);\r
+            str.append(text);\r
+        }\r
+\r
+        return str.toString().trim();\r
+    }\r
+\r
+//    public int getTextSegmentCount(Pointer ctx) {\r
+//        return lib.whisper_full_n_segments(ctx);\r
+//    }\r
+//    public String getTextSegment(Pointer ctx, int index) {\r
+//        return lib.whisper_full_get_segment_text(ctx, index);\r
+//    }\r
+\r
+    public String getSystemInfo() {\r
+        return lib.whisper_print_system_info();\r
+    }\r
+\r
+    public int benchMemcpy(int nthread) {\r
+        return lib.whisper_bench_memcpy(nthread);\r
+    }\r
+\r
+    public int benchGgmlMulMat(int nthread) {\r
+        return lib.whisper_bench_ggml_mul_mat(nthread);\r
+    }\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperCppJnaLibrary.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperCppJnaLibrary.java
new file mode 100644 (file)
index 0000000..6602565
--- /dev/null
@@ -0,0 +1,365 @@
+package io.github.ggerganov.whispercpp;\r
+\r
+import com.sun.jna.Library;\r
+import com.sun.jna.Native;\r
+import com.sun.jna.Pointer;\r
+import io.github.ggerganov.whispercpp.model.WhisperModelLoader;\r
+import io.github.ggerganov.whispercpp.model.WhisperTokenData;\r
+import io.github.ggerganov.whispercpp.params.WhisperFullParams;\r
+\r
+public interface WhisperCppJnaLibrary extends Library {\r
+    WhisperCppJnaLibrary instance = Native.load("whisper", WhisperCppJnaLibrary.class);\r
+\r
+    String whisper_print_system_info();\r
+\r
+    /**\r
+     * Allocate (almost) all memory needed for the model by loading from a file.\r
+     *\r
+     * @param path_model Path to the model file\r
+     * @return Whisper context on success, null on failure\r
+     */\r
+    Pointer whisper_init_from_file(String path_model);\r
+\r
+    /**\r
+     * Allocate (almost) all memory needed for the model by loading from a buffer.\r
+     *\r
+     * @param buffer       Model buffer\r
+     * @param buffer_size  Size of the model buffer\r
+     * @return Whisper context on success, null on failure\r
+     */\r
+    Pointer whisper_init_from_buffer(Pointer buffer, int buffer_size);\r
+\r
+    /**\r
+     * Allocate (almost) all memory needed for the model using a model loader.\r
+     *\r
+     * @param loader Model loader\r
+     * @return Whisper context on success, null on failure\r
+     */\r
+    Pointer whisper_init(WhisperModelLoader loader);\r
+\r
+    /**\r
+     * Allocate (almost) all memory needed for the model by loading from a file without allocating the state.\r
+     *\r
+     * @param path_model Path to the model file\r
+     * @return Whisper context on success, null on failure\r
+     */\r
+    Pointer whisper_init_from_file_no_state(String path_model);\r
+\r
+    /**\r
+     * Allocate (almost) all memory needed for the model by loading from a buffer without allocating the state.\r
+     *\r
+     * @param buffer       Model buffer\r
+     * @param buffer_size  Size of the model buffer\r
+     * @return Whisper context on success, null on failure\r
+     */\r
+    Pointer whisper_init_from_buffer_no_state(Pointer buffer, int buffer_size);\r
+\r
+//    Pointer whisper_init_from_buffer_no_state(Pointer buffer, long buffer_size);\r
+\r
+    /**\r
+     * Allocate (almost) all memory needed for the model using a model loader without allocating the state.\r
+     *\r
+     * @param loader Model loader\r
+     * @return Whisper context on success, null on failure\r
+     */\r
+    Pointer whisper_init_no_state(WhisperModelLoader loader);\r
+\r
+    /**\r
+     * Allocate memory for the Whisper state.\r
+     *\r
+     * @param ctx Whisper context\r
+     * @return Whisper state on success, null on failure\r
+     */\r
+    Pointer whisper_init_state(Pointer ctx);\r
+\r
+    /**\r
+     * Free all allocated memory associated with the Whisper context.\r
+     *\r
+     * @param ctx Whisper context\r
+     */\r
+    void whisper_free(Pointer ctx);\r
+\r
+    /**\r
+     * Free all allocated memory associated with the Whisper state.\r
+     *\r
+     * @param state Whisper state\r
+     */\r
+    void whisper_free_state(Pointer state);\r
+\r
+\r
+    /**\r
+     * Convert RAW PCM audio to log mel spectrogram.\r
+     * The resulting spectrogram is stored inside the default state of the provided whisper context.\r
+     *\r
+     * @param ctx - Pointer to a WhisperContext\r
+     * @return 0 on success\r
+     */\r
+    int whisper_pcm_to_mel(Pointer ctx, final float[] samples, int n_samples, int n_threads);\r
+\r
+    /**\r
+     * @param ctx Pointer to a WhisperContext\r
+     * @param state Pointer to WhisperState\r
+     * @param n_samples\r
+     * @param n_threads\r
+     * @return 0 on success\r
+     */\r
+    int whisper_pcm_to_mel_with_state(Pointer ctx, Pointer state, final float[] samples, int n_samples, int n_threads);\r
+\r
+    /**\r
+     * This can be used to set a custom log mel spectrogram inside the default state of the provided whisper context.\r
+     * Use this instead of whisper_pcm_to_mel() if you want to provide your own log mel spectrogram.\r
+     * n_mel must be 80\r
+     * @return 0 on success\r
+     */\r
+    int whisper_set_mel(Pointer ctx, final float[] data, int n_len, int n_mel);\r
+    int whisper_set_mel_with_state(Pointer ctx, Pointer state, final float[] data, int n_len, int n_mel);\r
+\r
+    /**\r
+     * Run the Whisper encoder on the log mel spectrogram stored inside the default state in the provided whisper context.\r
+     * Make sure to call whisper_pcm_to_mel() or whisper_set_mel() first.\r
+     * Offset can be used to specify the offset of the first frame in the spectrogram.\r
+     * @return 0 on success\r
+     */\r
+    int whisper_encode(Pointer ctx, int offset, int n_threads);\r
+\r
+    int whisper_encode_with_state(Pointer ctx, Pointer state, int offset, int n_threads);\r
+\r
+    /**\r
+     * Run the Whisper decoder to obtain the logits and probabilities for the next token.\r
+     * Make sure to call whisper_encode() first.\r
+     * tokens + n_tokens is the provided context for the decoder.\r
+     * n_past is the number of tokens to use from previous decoder calls.\r
+     * Returns 0 on success\r
+     * TODO: add support for multiple decoders\r
+     */\r
+    int whisper_decode(Pointer ctx, Pointer tokens, int n_tokens, int n_past, int n_threads);\r
+\r
+    /**\r
+     * @param ctx\r
+     * @param state\r
+     * @param tokens Pointer to int tokens\r
+     * @param n_tokens\r
+     * @param n_past\r
+     * @param n_threads\r
+     * @return\r
+     */\r
+    int whisper_decode_with_state(Pointer ctx, Pointer state, Pointer tokens, int n_tokens, int n_past, int n_threads);\r
+\r
+    /**\r
+     * Convert the provided text into tokens.\r
+     * The tokens pointer must be large enough to hold the resulting tokens.\r
+     * Returns the number of tokens on success, no more than n_max_tokens\r
+     * Returns -1 on failure\r
+     * TODO: not sure if correct\r
+     */\r
+    int whisper_tokenize(Pointer ctx, String text, Pointer tokens, int n_max_tokens);\r
+\r
+    /** Largest language id (i.e. number of available languages - 1) */\r
+    int whisper_lang_max_id();\r
+\r
+    /**\r
+     * @return the id of the specified language, returns -1 if not found.\r
+     * Examples:\r
+     *   "de" -> 2\r
+     *   "german" -> 2\r
+     */\r
+    int whisper_lang_id(String lang);\r
+\r
+    /** @return the short string of the specified language id (e.g. 2 -> "de"), returns nullptr if not found */\r
+    String whisper_lang_str(int id);\r
+\r
+    /**\r
+     * Use mel data at offset_ms to try and auto-detect the spoken language.\r
+     * Make sure to call whisper_pcm_to_mel() or whisper_set_mel() first\r
+     * Returns the top language id or negative on failure\r
+     * If not null, fills the lang_probs array with the probabilities of all languages\r
+     * The array must be whisper_lang_max_id() + 1 in size\r
+     *\r
+     * ref: https://github.com/openai/whisper/blob/main/whisper/decoding.py#L18-L69\r
+     */\r
+    int whisper_lang_auto_detect(Pointer ctx, int offset_ms, int n_threads, float[] lang_probs);\r
+\r
+    int whisper_lang_auto_detect_with_state(Pointer ctx, Pointer state, int offset_ms, int n_threads, float[] lang_probs);\r
+\r
+    int whisper_n_len           (Pointer ctx); // mel length\r
+    int whisper_n_len_from_state(Pointer state); // mel length\r
+    int whisper_n_vocab         (Pointer ctx);\r
+    int whisper_n_text_ctx      (Pointer ctx);\r
+    int whisper_n_audio_ctx     (Pointer ctx);\r
+    int whisper_is_multilingual (Pointer ctx);\r
+\r
+    int whisper_model_n_vocab      (Pointer ctx);\r
+    int whisper_model_n_audio_ctx  (Pointer ctx);\r
+    int whisper_model_n_audio_state(Pointer ctx);\r
+    int whisper_model_n_audio_head (Pointer ctx);\r
+    int whisper_model_n_audio_layer(Pointer ctx);\r
+    int whisper_model_n_text_ctx   (Pointer ctx);\r
+    int whisper_model_n_text_state (Pointer ctx);\r
+    int whisper_model_n_text_head  (Pointer ctx);\r
+    int whisper_model_n_text_layer (Pointer ctx);\r
+    int whisper_model_n_mels       (Pointer ctx);\r
+    int whisper_model_ftype        (Pointer ctx);\r
+    int whisper_model_type         (Pointer ctx);\r
+\r
+    /**\r
+     * Token logits obtained from the last call to whisper_decode().\r
+     * The logits for the last token are stored in the last row\r
+     * Rows: n_tokens\r
+     * Cols: n_vocab\r
+     */\r
+    float[] whisper_get_logits           (Pointer ctx);\r
+    float[] whisper_get_logits_from_state(Pointer state);\r
+\r
+    // Token Id -> String. Uses the vocabulary in the provided context\r
+    String whisper_token_to_str(Pointer ctx, int token);\r
+    String whisper_model_type_readable(Pointer ctx);\r
+\r
+    // Special tokens\r
+    int whisper_token_eot (Pointer ctx);\r
+    int whisper_token_sot (Pointer ctx);\r
+    int whisper_token_prev(Pointer ctx);\r
+    int whisper_token_solm(Pointer ctx);\r
+    int whisper_token_not (Pointer ctx);\r
+    int whisper_token_beg (Pointer ctx);\r
+    int whisper_token_lang(Pointer ctx, int lang_id);\r
+\r
+    // Task tokens\r
+    int whisper_token_translate();\r
+    int whisper_token_transcribe();\r
+\r
+    // Performance information from the default state.\r
+    void whisper_print_timings(Pointer ctx);\r
+    void whisper_reset_timings(Pointer ctx);\r
+\r
+    /**\r
+     * @param strategy - WhisperSamplingStrategy.value\r
+     */\r
+    WhisperFullParams whisper_full_default_params(int strategy);\r
+\r
+    /**\r
+     * Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text\r
+     * Not thread safe for same context\r
+     * Uses the specified decoding strategy to obtain the text.\r
+     */\r
+    int whisper_full(Pointer ctx, WhisperFullParams params, final float[] samples, int n_samples);\r
+\r
+    int whisper_full_with_state(Pointer ctx, Pointer state, WhisperFullParams params, final float[] samples, int n_samples);\r
+\r
+    // Split the input audio in chunks and process each chunk separately using whisper_full_with_state()\r
+    // Result is stored in the default state of the context\r
+    // Not thread safe if executed in parallel on the same context.\r
+    // It seems this approach can offer some speedup in some cases.\r
+    // However, the transcription accuracy can be worse at the beginning and end of each chunk.\r
+    int whisper_full_parallel(Pointer ctx, WhisperFullParams params, final float[] samples, int n_samples, int n_processors);\r
+\r
+    /**\r
+     * Number of generated text segments.\r
+     * A segment can be a few words, a sentence, or even a paragraph.\r
+     * @param ctx Pointer to WhisperContext\r
+     */\r
+    int whisper_full_n_segments (Pointer ctx);\r
+\r
+    /**\r
+     * @param state Pointer to WhisperState\r
+     */\r
+    int whisper_full_n_segments_from_state(Pointer state);\r
+\r
+    /**\r
+     * Language id associated with the context's default state.\r
+     * @param ctx Pointer to WhisperContext\r
+     */\r
+    int whisper_full_lang_id(Pointer ctx);\r
+\r
+    /** Language id associated with the provided state */\r
+    int whisper_full_lang_id_from_state(Pointer state);\r
+\r
+    /**\r
+     * Convert RAW PCM audio to log mel spectrogram but applies a Phase Vocoder to speed up the audio x2.\r
+     * The resulting spectrogram is stored inside the default state of the provided whisper context.\r
+     * @return 0 on success\r
+     */\r
+    int whisper_pcm_to_mel_phase_vocoder(Pointer ctx, final float[] samples, int n_samples, int n_threads);\r
+\r
+    int whisper_pcm_to_mel_phase_vocoder_with_state(Pointer ctx, Pointer state, final float[] samples, int n_samples, int n_threads);\r
+\r
+    /** Get the start time of the specified segment. */\r
+    long whisper_full_get_segment_t0(Pointer ctx, int i_segment);\r
+\r
+    /** Get the start time of the specified segment from the state. */\r
+    long whisper_full_get_segment_t0_from_state(Pointer state, int i_segment);\r
+\r
+    /** Get the end time of the specified segment. */\r
+    long whisper_full_get_segment_t1(Pointer ctx, int i_segment);\r
+\r
+    /** Get the end time of the specified segment from the state. */\r
+    long whisper_full_get_segment_t1_from_state(Pointer state, int i_segment);\r
+\r
+    /** Get the text of the specified segment. */\r
+    String whisper_full_get_segment_text(Pointer ctx, int i_segment);\r
+\r
+    /** Get the text of the specified segment from the state. */\r
+    String whisper_full_get_segment_text_from_state(Pointer state, int i_segment);\r
+\r
+    /** Get the number of tokens in the specified segment. */\r
+    int whisper_full_n_tokens(Pointer ctx, int i_segment);\r
+\r
+    /** Get the number of tokens in the specified segment from the state. */\r
+    int whisper_full_n_tokens_from_state(Pointer state, int i_segment);\r
+\r
+    /** Get the token text of the specified token in the specified segment. */\r
+    String whisper_full_get_token_text(Pointer ctx, int i_segment, int i_token);\r
+\r
+\r
+    /** Get the token text of the specified token in the specified segment from the state. */\r
+    String whisper_full_get_token_text_from_state(Pointer ctx, Pointer state, int i_segment, int i_token);\r
+\r
+    /** Get the token ID of the specified token in the specified segment. */\r
+    int whisper_full_get_token_id(Pointer ctx, int i_segment, int i_token);\r
+\r
+    /** Get the token ID of the specified token in the specified segment from the state. */\r
+    int whisper_full_get_token_id_from_state(Pointer state, int i_segment, int i_token);\r
+\r
+    /** Get token data for the specified token in the specified segment. */\r
+    WhisperTokenData whisper_full_get_token_data(Pointer ctx, int i_segment, int i_token);\r
+\r
+    /** Get token data for the specified token in the specified segment from the state. */\r
+    WhisperTokenData whisper_full_get_token_data_from_state(Pointer state, int i_segment, int i_token);\r
+\r
+    /** Get the probability of the specified token in the specified segment. */\r
+    float whisper_full_get_token_p(Pointer ctx, int i_segment, int i_token);\r
+\r
+    /** Get the probability of the specified token in the specified segment from the state. */\r
+    float whisper_full_get_token_p_from_state(Pointer state, int i_segment, int i_token);\r
+\r
+    /**\r
+     * Benchmark function for memcpy.\r
+     *\r
+     * @param nThreads Number of threads to use for the benchmark.\r
+     * @return The result of the benchmark.\r
+     */\r
+    int whisper_bench_memcpy(int nThreads);\r
+\r
+    /**\r
+     * Benchmark function for memcpy as a string.\r
+     *\r
+     * @param nThreads Number of threads to use for the benchmark.\r
+     * @return The result of the benchmark as a string.\r
+     */\r
+    String whisper_bench_memcpy_str(int nThreads);\r
+\r
+    /**\r
+     * Benchmark function for ggml_mul_mat.\r
+     *\r
+     * @param nThreads Number of threads to use for the benchmark.\r
+     * @return The result of the benchmark.\r
+     */\r
+    int whisper_bench_ggml_mul_mat(int nThreads);\r
+\r
+    /**\r
+     * Benchmark function for ggml_mul_mat as a string.\r
+     *\r
+     * @param nThreads Number of threads to use for the benchmark.\r
+     * @return The result of the benchmark as a string.\r
+     */\r
+    String whisper_bench_ggml_mul_mat_str(int nThreads);\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperJavaJnaLibrary.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/WhisperJavaJnaLibrary.java
new file mode 100644 (file)
index 0000000..74f8459
--- /dev/null
@@ -0,0 +1,23 @@
+package io.github.ggerganov.whispercpp;\r
+\r
+import com.sun.jna.Library;\r
+import com.sun.jna.Native;\r
+import com.sun.jna.Pointer;\r
+import io.github.ggerganov.whispercpp.params.WhisperJavaParams;\r
+\r
+interface WhisperJavaJnaLibrary extends Library {\r
+    WhisperJavaJnaLibrary instance = Native.load("whisper_java", WhisperJavaJnaLibrary.class);\r
+\r
+    void whisper_java_default_params(int strategy);\r
+\r
+    void whisper_java_free();\r
+\r
+    void whisper_java_init_from_file(String modelPath);\r
+\r
+    /**\r
+     * Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text.\r
+     * Not thread safe for same context\r
+     * Uses the specified decoding strategy to obtain the text.\r
+     */\r
+    int whisper_java_full(Pointer ctx, /*WhisperJavaParams params, */float[] samples, int nSamples);\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperEncoderBeginCallback.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperEncoderBeginCallback.java
new file mode 100644 (file)
index 0000000..b5e9797
--- /dev/null
@@ -0,0 +1,24 @@
+package io.github.ggerganov.whispercpp.callbacks;\r
+\r
+import com.sun.jna.Callback;\r
+import com.sun.jna.Pointer;\r
+import io.github.ggerganov.whispercpp.WhisperContext;\r
+import io.github.ggerganov.whispercpp.model.WhisperState;\r
+\r
+/**\r
+ * Callback before the encoder starts.\r
+ * If not null, called before the encoder starts.\r
+ * If it returns false, the computation is aborted.\r
+ */\r
+public interface WhisperEncoderBeginCallback extends Callback {\r
+\r
+    /**\r
+     * Callback method before the encoder starts.\r
+     *\r
+     * @param ctx        The whisper context.\r
+     * @param state      The whisper state.\r
+     * @param user_data  User data.\r
+     * @return True if the computation should proceed, false otherwise.\r
+     */\r
+    boolean callback(WhisperContext ctx, WhisperState state, Pointer user_data);\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperLogitsFilterCallback.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperLogitsFilterCallback.java
new file mode 100644 (file)
index 0000000..5377b4e
--- /dev/null
@@ -0,0 +1,28 @@
+package io.github.ggerganov.whispercpp.callbacks;\r
+\r
+import com.sun.jna.Pointer;\r
+import io.github.ggerganov.whispercpp.WhisperContext;\r
+import io.github.ggerganov.whispercpp.model.WhisperState;\r
+import io.github.ggerganov.whispercpp.model.WhisperTokenData;\r
+\r
+import javax.security.auth.callback.Callback;\r
+\r
+/**\r
+ * Callback to filter logits.\r
+ * Can be used to modify the logits before sampling.\r
+ * If not null, called after applying temperature to logits.\r
+ */\r
+public interface WhisperLogitsFilterCallback extends Callback {\r
+\r
+    /**\r
+     * Callback method to filter logits.\r
+     *\r
+     * @param ctx        The whisper context.\r
+     * @param state      The whisper state.\r
+     * @param tokens     The array of whisper_token_data.\r
+     * @param n_tokens   The number of tokens.\r
+     * @param logits     The array of logits.\r
+     * @param user_data  User data.\r
+     */\r
+    void callback(WhisperContext ctx, WhisperState state, WhisperTokenData[] tokens, int n_tokens, float[] logits, Pointer user_data);\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperNewSegmentCallback.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperNewSegmentCallback.java
new file mode 100644 (file)
index 0000000..95ca346
--- /dev/null
@@ -0,0 +1,24 @@
+package io.github.ggerganov.whispercpp.callbacks;\r
+\r
+import com.sun.jna.Callback;\r
+import com.sun.jna.Pointer;\r
+import io.github.ggerganov.whispercpp.WhisperContext;\r
+import io.github.ggerganov.whispercpp.model.WhisperState;\r
+\r
+/**\r
+ * Callback for the text segment.\r
+ * Called on every newly generated text segment.\r
+ * Use the whisper_full_...() functions to obtain the text segments.\r
+ */\r
+public interface WhisperNewSegmentCallback extends Callback {\r
+\r
+    /**\r
+     * Callback method for the text segment.\r
+     *\r
+     * @param ctx        The whisper context.\r
+     * @param state      The whisper state.\r
+     * @param n_new      The number of newly generated text segments.\r
+     * @param user_data  User data.\r
+     */\r
+    void callback(WhisperContext ctx, WhisperState state, int n_new, Pointer user_data);\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperProgressCallback.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/callbacks/WhisperProgressCallback.java
new file mode 100644 (file)
index 0000000..8866215
--- /dev/null
@@ -0,0 +1,23 @@
+package io.github.ggerganov.whispercpp.callbacks;\r
+\r
+import com.sun.jna.Pointer;\r
+import io.github.ggerganov.whispercpp.WhisperContext;\r
+import io.github.ggerganov.whispercpp.model.WhisperState;\r
+\r
+import javax.security.auth.callback.Callback;\r
+\r
+/**\r
+ * Callback for progress updates.\r
+ */\r
+public interface WhisperProgressCallback extends Callback {\r
+\r
+    /**\r
+     * Callback method for progress updates.\r
+     *\r
+     * @param ctx        The whisper context.\r
+     * @param state      The whisper state.\r
+     * @param progress   The progress value.\r
+     * @param user_data  User data.\r
+     */\r
+    void callback(WhisperContext ctx, WhisperState state, int progress, Pointer user_data);\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/ggml/GgmlTensor.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/ggml/GgmlTensor.java
new file mode 100644 (file)
index 0000000..2569957
--- /dev/null
@@ -0,0 +1,4 @@
+package io.github.ggerganov.whispercpp.ggml;\r
+\r
+public class GgmlTensor {\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/ggml/GgmlType.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/ggml/GgmlType.java
new file mode 100644 (file)
index 0000000..363120e
--- /dev/null
@@ -0,0 +1,18 @@
+package io.github.ggerganov.whispercpp.ggml;\r
+\r
+public enum GgmlType {\r
+    GGML_TYPE_F32,\r
+    GGML_TYPE_F16,\r
+    GGML_TYPE_Q4_0,\r
+    GGML_TYPE_Q4_1,\r
+    REMOVED_GGML_TYPE_Q4_2,  // support has been removed\r
+    REMOVED_GGML_TYPE_Q4_3, // support has been removed\r
+    GGML_TYPE_Q5_0,\r
+    GGML_TYPE_Q5_1,\r
+    GGML_TYPE_Q8_0,\r
+    GGML_TYPE_Q8_1,\r
+    GGML_TYPE_I8,\r
+    GGML_TYPE_I16,\r
+    GGML_TYPE_I32,\r
+    GGML_TYPE_COUNT,\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/EModel.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/EModel.java
new file mode 100644 (file)
index 0000000..b2475b3
--- /dev/null
@@ -0,0 +1,10 @@
+package io.github.ggerganov.whispercpp.model;\r
+\r
+public enum EModel {\r
+    MODEL_UNKNOWN,\r
+    MODEL_TINY,\r
+    MODEL_BASE,\r
+    MODEL_SMALL,\r
+    MODEL_MEDIUM,\r
+    MODEL_LARGE,\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperModel.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperModel.java
new file mode 100644 (file)
index 0000000..497ef42
--- /dev/null
@@ -0,0 +1,49 @@
+package io.github.ggerganov.whispercpp;\r
+\r
+import io.github.ggerganov.whispercpp.ggml.GgmlTensor;\r
+import io.github.ggerganov.whispercpp.model.EModel;\r
+\r
+public class WhisperModel {\r
+//    EModel type = EModel.MODEL_UNKNOWN;\r
+//\r
+//    WhisperHParams hparams;\r
+//    WhisperFilters filters;\r
+//\r
+//    // encoder.positional_embedding\r
+//    GgmlTensor e_pe;\r
+//\r
+//    // encoder.conv1\r
+//    GgmlTensor e_conv_1_w;\r
+//    GgmlTensor e_conv_1_b;\r
+//\r
+//    // encoder.conv2\r
+//    GgmlTensor e_conv_2_w;\r
+//    GgmlTensor e_conv_2_b;\r
+//\r
+//    // encoder.ln_post\r
+//    GgmlTensor e_ln_w;\r
+//    GgmlTensor e_ln_b;\r
+//\r
+//    // decoder.positional_embedding\r
+//    GgmlTensor d_pe;\r
+//\r
+//    // decoder.token_embedding\r
+//    GgmlTensor d_te;\r
+//\r
+//    // decoder.ln\r
+//    GgmlTensor d_ln_w;\r
+//    GgmlTensor d_ln_b;\r
+//\r
+//    std::vector<whisper_layer_encoder> layers_encoder;\r
+//    std::vector<whisper_layer_decoder> layers_decoder;\r
+//\r
+//    // context\r
+//    struct ggml_context * ctx;\r
+//\r
+//    // the model memory buffer is read-only and can be shared between processors\r
+//    std::vector<uint8_t> * buf;\r
+//\r
+//    // tensors\r
+//    int n_loaded;\r
+//    Map<String, GgmlTensor> tensors;\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperModelLoader.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperModelLoader.java
new file mode 100644 (file)
index 0000000..82615d9
--- /dev/null
@@ -0,0 +1,62 @@
+package io.github.ggerganov.whispercpp.model;\r
+\r
+import com.sun.jna.Callback;\r
+import com.sun.jna.Pointer;\r
+import com.sun.jna.Structure;\r
+\r
+\r
+public class WhisperModelLoader extends Structure {\r
+    public Pointer context;\r
+    public ReadFunction read;\r
+    public EOFFunction eof;\r
+    public CloseFunction close;\r
+\r
+    public static class ReadFunction implements Callback {\r
+        public Pointer invoke(Pointer ctx, Pointer output, int readSize) {\r
+            // TODO\r
+            return ctx;\r
+        }\r
+    }\r
+\r
+    public static class EOFFunction implements Callback {\r
+        public boolean invoke(Pointer ctx) {\r
+            // TODO\r
+            return false;\r
+        }\r
+    }\r
+\r
+    public static class CloseFunction implements Callback {\r
+        public void invoke(Pointer ctx) {\r
+            // TODO\r
+        }\r
+    }\r
+\r
+//    public WhisperModelLoader(Pointer p) {\r
+//        super(p);\r
+//        read = new ReadFunction();\r
+//        eof = new EOFFunction();\r
+//        close = new CloseFunction();\r
+//        read.setCallback(this);\r
+//        eof.setCallback(this);\r
+//        close.setCallback(this);\r
+//        read.write();\r
+//        eof.write();\r
+//        close.write();\r
+//    }\r
+\r
+    public WhisperModelLoader() {\r
+        super();\r
+    }\r
+\r
+    public interface ReadCallback extends Callback {\r
+        Pointer invoke(Pointer ctx, Pointer output, int readSize);\r
+    }\r
+\r
+    public interface EOFCallback extends Callback {\r
+        boolean invoke(Pointer ctx);\r
+    }\r
+\r
+    public interface CloseCallback extends Callback {\r
+        void invoke(Pointer ctx);\r
+    }\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperState.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperState.java
new file mode 100644 (file)
index 0000000..af93772
--- /dev/null
@@ -0,0 +1,4 @@
+package io.github.ggerganov.whispercpp.model;\r
+\r
+public class WhisperState {\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperTokenData.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/model/WhisperTokenData.java
new file mode 100644 (file)
index 0000000..bfa83f9
--- /dev/null
@@ -0,0 +1,50 @@
+package io.github.ggerganov.whispercpp.model;\r
+\r
+import com.sun.jna.Structure;\r
+\r
+import java.util.Arrays;\r
+import java.util.List;\r
+\r
+/**\r
+ * Structure representing token data.\r
+ */\r
+public class WhisperTokenData extends Structure {\r
+\r
+    /** Token ID. */\r
+    public int id;\r
+\r
+    /** Forced timestamp token ID. */\r
+    public int tid;\r
+\r
+    /** Probability of the token. */\r
+    public float p;\r
+\r
+    /** Log probability of the token. */\r
+    public float plog;\r
+\r
+    /** Probability of the timestamp token. */\r
+    public float pt;\r
+\r
+    /** Sum of probabilities of all timestamp tokens. */\r
+    public float ptsum;\r
+\r
+    /**\r
+     * Start time of the token (token-level timestamp data).\r
+     * Do not use if you haven't computed token-level timestamps.\r
+     */\r
+    public long t0;\r
+\r
+    /**\r
+     * End time of the token (token-level timestamp data).\r
+     * Do not use if you haven't computed token-level timestamps.\r
+     */\r
+    public long t1;\r
+\r
+    /** Voice length of the token. */\r
+    public float vlen;\r
+\r
+    @Override\r
+    protected List<String> getFieldOrder() {\r
+        return Arrays.asList("id", "tid", "p", "plog", "pt", "ptsum", "t0", "t1", "vlen");\r
+    }\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperFilters.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperFilters.java
new file mode 100644 (file)
index 0000000..b035243
--- /dev/null
@@ -0,0 +1,10 @@
+package io.github.ggerganov.whispercpp.params;\r
+\r
+import java.util.List;\r
+\r
+public class WhisperFilters {\r
+    int n_mel;\r
+    int n_fft;\r
+\r
+    List<Float> data;\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperFullParams.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperFullParams.java
new file mode 100644 (file)
index 0000000..ea0bccf
--- /dev/null
@@ -0,0 +1,187 @@
+package io.github.ggerganov.whispercpp.params;\r
+\r
+import com.sun.jna.Callback;\r
+import com.sun.jna.Pointer;\r
+import com.sun.jna.Structure;\r
+import io.github.ggerganov.whispercpp.callbacks.WhisperEncoderBeginCallback;\r
+import io.github.ggerganov.whispercpp.callbacks.WhisperLogitsFilterCallback;\r
+import io.github.ggerganov.whispercpp.callbacks.WhisperNewSegmentCallback;\r
+import io.github.ggerganov.whispercpp.callbacks.WhisperProgressCallback;\r
+\r
+/**\r
+ * Parameters for the whisper_full() function.\r
+ * If you change the order or add new parameters, make sure to update the default values in whisper.cpp:\r
+ * whisper_full_default_params()\r
+ */\r
+public class WhisperFullParams extends Structure {\r
+\r
+    /** Sampling strategy for whisper_full() function. */\r
+    public int strategy;\r
+\r
+    /** Number of threads. */\r
+    public int n_threads;\r
+\r
+    /** Maximum tokens to use from past text as a prompt for the decoder. */\r
+    public int n_max_text_ctx;\r
+\r
+    /** Start offset in milliseconds. */\r
+    public int offset_ms;\r
+\r
+    /** Audio duration to process in milliseconds. */\r
+    public int duration_ms;\r
+\r
+    /** Translate flag. */\r
+    public boolean translate;\r
+\r
+    /** Flag to indicate whether to use past transcription (if any) as an initial prompt for the decoder. */\r
+    public boolean no_context;\r
+\r
+    /** Flag to force single segment output (useful for streaming). */\r
+    public boolean single_segment;\r
+\r
+    /** Flag to print special tokens (e.g., &lt;SOT>, &lt;EOT>, &lt;BEG>, etc.). */\r
+    public boolean print_special;\r
+\r
+    /** Flag to print progress information. */\r
+    public boolean print_progress;\r
+\r
+    /** Flag to print results from within whisper.cpp (avoid it, use callback instead). */\r
+    public boolean print_realtime;\r
+\r
+    /** Flag to print timestamps for each text segment when printing realtime. */\r
+    public boolean print_timestamps;\r
+\r
+    /** [EXPERIMENTAL] Flag to enable token-level timestamps. */\r
+    public boolean token_timestamps;\r
+\r
+    /** [EXPERIMENTAL] Timestamp token probability threshold (~0.01). */\r
+    public float thold_pt;\r
+\r
+    /** [EXPERIMENTAL] Timestamp token sum probability threshold (~0.01). */\r
+    public float thold_ptsum;\r
+\r
+    /** Maximum segment length in characters. */\r
+    public int max_len;\r
+\r
+    /** Flag to split on word rather than on token (when used with max_len). */\r
+    public boolean split_on_word;\r
+\r
+    /** Maximum tokens per segment (0 = no limit). */\r
+    public int max_tokens;\r
+\r
+    /** Flag to speed up the audio by 2x using Phase Vocoder. */\r
+    public boolean speed_up;\r
+\r
+    /** Overwrite the audio context size (0 = use default). */\r
+    public int audio_ctx;\r
+\r
+    /** Tokens to provide to the whisper decoder as an initial prompt.\r
+     * These are prepended to any existing text context from a previous call. */\r
+    public String initial_prompt;\r
+\r
+    /** Prompt tokens. */\r
+    public Pointer prompt_tokens;\r
+\r
+    /** Number of prompt tokens. */\r
+    public int prompt_n_tokens;\r
+\r
+    /** Language for auto-detection.\r
+     * For auto-detection, set to `null`, `""`, or "auto". */\r
+    public String language;\r
+\r
+    /** Flag to indicate whether to detect language automatically. */\r
+    public boolean detect_language;\r
+\r
+    /** Common decoding parameters. */\r
+\r
+    /** Flag to suppress blank tokens. */\r
+    public boolean suppress_blank;\r
+\r
+    /** Flag to suppress non-speech tokens. */\r
+    public boolean suppress_non_speech_tokens;\r
+\r
+    /** Initial decoding temperature. */\r
+    public float temperature;\r
+\r
+    /** Maximum initial timestamp. */\r
+    public float max_initial_ts;\r
+\r
+    /** Length penalty. */\r
+    public float length_penalty;\r
+\r
+    /** Fallback parameters. */\r
+\r
+    /** Temperature increment. */\r
+    public float temperature_inc;\r
+\r
+    /** Entropy threshold (similar to OpenAI's "compression_ratio_threshold"). */\r
+    public float entropy_thold;\r
+\r
+    /** Log probability threshold. */\r
+    public float logprob_thold;\r
+\r
+    /** No speech threshold. */\r
+    public float no_speech_thold;\r
+\r
+    class GreedyParams extends Structure {\r
+        /** https://github.com/openai/whisper/blob/f82bc59f5ea234d4b97fb2860842ed38519f7e65/whisper/transcribe.py#L264 */\r
+        public int best_of;\r
+    }\r
+\r
+    /** Greedy decoding parameters. */\r
+    public GreedyParams greedy;\r
+\r
+    class BeamSearchParams extends Structure {\r
+        /** ref: https://github.com/openai/whisper/blob/f82bc59f5ea234d4b97fb2860842ed38519f7e65/whisper/transcribe.py#L265 */\r
+        int beam_size;\r
+\r
+        /** ref: https://arxiv.org/pdf/2204.05424.pdf */\r
+        float patience;\r
+    }\r
+\r
+    /**\r
+     * Beam search decoding parameters.\r
+     */\r
+    public BeamSearchParams beam_search;\r
+\r
+    /**\r
+     * Callback for every newly generated text segment.\r
+     */\r
+    public WhisperNewSegmentCallback new_segment_callback;\r
+\r
+    /**\r
+     * User data for the new_segment_callback.\r
+     */\r
+    public Pointer new_segment_callback_user_data;\r
+\r
+    /**\r
+     * Callback on each progress update.\r
+     */\r
+    public WhisperProgressCallback progress_callback;\r
+\r
+    /**\r
+     * User data for the progress_callback.\r
+     */\r
+    public Pointer progress_callback_user_data;\r
+\r
+    /**\r
+     * Callback each time before the encoder starts.\r
+     */\r
+    public WhisperEncoderBeginCallback encoder_begin_callback;\r
+\r
+    /**\r
+     * User data for the encoder_begin_callback.\r
+     */\r
+    public Pointer encoder_begin_callback_user_data;\r
+\r
+    /**\r
+     * Callback by each decoder to filter obtained logits.\r
+     */\r
+    public WhisperLogitsFilterCallback logits_filter_callback;\r
+\r
+    /**\r
+     * User data for the logits_filter_callback.\r
+     */\r
+    public Pointer logits_filter_callback_user_data;\r
+}\r
+\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperHParams.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperHParams.java
new file mode 100644 (file)
index 0000000..99feae0
--- /dev/null
@@ -0,0 +1,15 @@
+package io.github.ggerganov.whispercpp.params;\r
+\r
+public class WhisperHParams {\r
+    int n_vocab       = 51864;\r
+    int n_audio_ctx   = 1500;\r
+    int n_audio_state = 384;\r
+    int n_audio_head  = 6;\r
+    int n_audio_layer = 4;\r
+    int n_text_ctx    = 448;\r
+    int n_text_state  = 384;\r
+    int n_text_head   = 6;\r
+    int n_text_layer  = 4;\r
+    int n_mels        = 80;\r
+    int ftype         = 1;\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperJavaParams.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperJavaParams.java
new file mode 100644 (file)
index 0000000..728485c
--- /dev/null
@@ -0,0 +1,7 @@
+package io.github.ggerganov.whispercpp.params;\r
+\r
+import com.sun.jna.Structure;\r
+\r
+public class WhisperJavaParams extends Structure {\r
+\r
+}\r
diff --git a/bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperSamplingStrategy.java b/bindings/java/src/main/java/io/github/ggerganov/whispercpp/params/WhisperSamplingStrategy.java
new file mode 100644 (file)
index 0000000..a32c793
--- /dev/null
@@ -0,0 +1,10 @@
+package io.github.ggerganov.whispercpp.params;\r
+\r
+/** Available sampling strategies */\r
+public enum WhisperSamplingStrategy {\r
+    /** similar to OpenAI's GreedyDecoder */\r
+    WHISPER_SAMPLING_GREEDY,\r
+\r
+    /** similar to OpenAI's BeamSearchDecoder */\r
+    WHISPER_SAMPLING_BEAM_SEARCH\r
+}\r
diff --git a/bindings/java/src/test/java/io/github/ggerganov/whispercpp/WhisperCppTest.java b/bindings/java/src/test/java/io/github/ggerganov/whispercpp/WhisperCppTest.java
new file mode 100644 (file)
index 0000000..98390aa
--- /dev/null
@@ -0,0 +1,75 @@
+package io.github.ggerganov.whispercpp;\r
+\r
+import static org.junit.jupiter.api.Assertions.*;\r
+\r
+import io.github.ggerganov.whispercpp.params.WhisperJavaParams;\r
+import io.github.ggerganov.whispercpp.params.WhisperSamplingStrategy;\r
+import org.junit.jupiter.api.BeforeAll;\r
+import org.junit.jupiter.api.Test;\r
+import javax.sound.sampled.AudioInputStream;\r
+import javax.sound.sampled.AudioSystem;\r
+import java.io.File;\r
+import java.io.FileNotFoundException;\r
+\r
+class WhisperCppTest {\r
+    private static WhisperCpp whisper = new WhisperCpp();\r
+    private static boolean modelInitialised = false;\r
+\r
+    @BeforeAll\r
+    static void init() throws FileNotFoundException {\r
+        // By default, models are loaded from ~/.cache/whisper/ and are usually named "ggml-${name}.bin"\r
+        // or you can provide the absolute path to the model file.\r
+        String modelName = "base.en";\r
+        try {\r
+            whisper.initContext(modelName);\r
+            whisper.getDefaultJavaParams(WhisperSamplingStrategy.WHISPER_SAMPLING_GREEDY);\r
+//            whisper.getDefaultJavaParams(WhisperSamplingStrategy.WHISPER_SAMPLING_BEAM_SEARCH);\r
+            modelInitialised = true;\r
+        } catch (FileNotFoundException ex) {\r
+            System.out.println("Model " + modelName + " not found");\r
+        }\r
+    }\r
+\r
+    @Test\r
+    void testGetDefaultJavaParams() {\r
+        // When\r
+        whisper.getDefaultJavaParams(WhisperSamplingStrategy.WHISPER_SAMPLING_BEAM_SEARCH);\r
+\r
+        // Then if it doesn't throw we've connected to whisper.cpp\r
+    }\r
+\r
+    @Test\r
+    void testFullTranscribe() throws Exception {\r
+        if (!modelInitialised) {\r
+            System.out.println("Model not initialised, skipping test");\r
+            return;\r
+        }\r
+\r
+        // Given\r
+        File file = new File(System.getProperty("user.dir"), "../../samples/jfk.wav");\r
+        AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file);\r
+\r
+        byte[] b = new byte[audioInputStream.available()];\r
+        float[] floats = new float[b.length / 2];\r
+\r
+        try {\r
+            audioInputStream.read(b);\r
+\r
+            for (int i = 0, j = 0; i < b.length; i += 2, j++) {\r
+                int intSample = (int) (b[i + 1]) << 8 | (int) (b[i]) & 0xFF;\r
+                floats[j] = intSample / 32767.0f;\r
+            }\r
+\r
+            // When\r
+            String result = whisper.fullTranscribe(/*params,*/ floats);\r
+\r
+            // Then\r
+            System.out.println(result);\r
+            assertEquals("And so my fellow Americans, ask not what your country can do for you, " +\r
+                    "ask what you can do for your country.",\r
+                    result);\r
+        } finally {\r
+            audioInputStream.close();\r
+        }\r
+    }\r
+}\r
diff --git a/bindings/java/src/test/java/io/github/ggerganov/whispercpp/WhisperJnaLibraryTest.java b/bindings/java/src/test/java/io/github/ggerganov/whispercpp/WhisperJnaLibraryTest.java
new file mode 100644 (file)
index 0000000..07a340c
--- /dev/null
@@ -0,0 +1,17 @@
+package io.github.ggerganov.whispercpp;\r
+\r
+import static org.junit.jupiter.api.Assertions.*;\r
+\r
+import org.junit.jupiter.api.Test;\r
+\r
+class WhisperJnaLibraryTest {\r
+\r
+    @Test\r
+    void testWhisperPrint_system_info() {\r
+        String systemInfo = WhisperCppJnaLibrary.instance.whisper_print_system_info();\r
+        // eg: "AVX = 1 | AVX2 = 1 | AVX512 = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0\r
+        //    | WASM_SIMD = 0 | BLAS = 0 | SSE3 = 1 | VSX = 0 | COREML = 0 | "\r
+        System.out.println("System info: " + systemInfo);\r
+        assertTrue(systemInfo.length() > 10);\r
+    }\r
+}\r