diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c1dafca --- /dev/null +++ b/.gitignore @@ -0,0 +1,155 @@ +### Eclipse template +*.pydevproject +.metadata +.gradle* +classes/ +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +rebel.xml + +# Eclipse Core +.project + +generatedsources + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm + +*.iml +.flattened-pom.xml +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +db + +### Java template +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +#*.jar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Leiningen template +classes/ +target/ +logs/ +checkouts/ +.lein-deps-sum +.lein-repl-history +.lein-plugins/ +.lein-failures +.nrepl-port + +querydsl/ + +.DS_Store + +*.exe +*.out + +*.log +node_modules/ +dist/ +dist.zip +package-lock.json +*.lock +local.properties +.cxx +.externalNativeBuild +/captures +/build +__pycache__/ +*.pyc + + +cmake-build-debug/ +cmake-build-debug-mingw/ +venv/ +.vs/ +Debug/ +*.zip +*.bin diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..f5d7aea --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdkVersion 30 + buildToolsVersion '30.0.3' + + defaultConfig { + applicationId "com.litongjava.whisper.cpp.android.java.demo" + minSdkVersion 21 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + //litongjava + implementation 'com.litongjava:android-view-inject:1.0' + implementation 'com.litongjava:litongjava-android-utils:1.0.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/litongjava/whispercppandroidjavademo/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/litongjava/whispercppandroidjavademo/ExampleInstrumentedTest.java new file mode 100644 index 0000000..1c42769 --- /dev/null +++ b/app/src/androidTest/java/com/litongjava/whispercppandroidjavademo/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.litongjava.whispercppandroidjavademo; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.litongjava.whispercppandroidjavademo", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..78ae5ec --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/logback.xml b/app/src/main/assets/logback.xml new file mode 100644 index 0000000..1869998 --- /dev/null +++ b/app/src/main/assets/logback.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + ${LOG_HOME}/project-name-%d{yyyy-MM-dd}.log + + 180 + + + + 10MB + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/MainActivity.java b/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/MainActivity.java new file mode 100644 index 0000000..be2b673 --- /dev/null +++ b/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/MainActivity.java @@ -0,0 +1,118 @@ +package com.litongjava.whisper.cpp.android.java.demo; + +import android.content.Context; +import android.content.res.AssetManager; +import android.os.Build; +import android.os.Bundle; +import android.widget.TextView; + +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AppCompatActivity; + +import com.litongjava.android.view.inject.annotation.FindViewById; +import com.litongjava.android.view.inject.annotation.FindViewByIdLayout; +import com.litongjava.android.view.inject.utils.ViewInjectUtils; +import com.litongjava.whisper.cpp.android.java.demo.media.WaveEncoder; +import com.litongjava.whisper.cpp.android.java.demo.utils.AssetUtils; +import com.whispercppdemo.whisper.WhisperContext; +import com.whispercppdemo.whisper.WhisperCpuConfig; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; + +@FindViewByIdLayout(R.layout.activity_main) +public class MainActivity extends AppCompatActivity { + private Logger log = LoggerFactory.getLogger(this.getClass()); + private WhisperContext whisperContext; + + @FindViewById(R.id.textHelloWorld) + private TextView textHelloWorld; + + @RequiresApi(api = Build.VERSION_CODES.O) + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + ///setContentView(R.layout.activity_main); + ViewInjectUtils.injectActivity(this, this); + int preferredThreadCount = WhisperCpuConfig.getPreferredThreadCount(); + log.info("preferredThreadCount:{}", preferredThreadCount); + + Context baseContext = getBaseContext(); + File filesDir = baseContext.getFilesDir(); + log.info("filesDir:{}", filesDir); + AssetManager assets = baseContext.getAssets(); + try { + String[] models = assets.list("models"); + log.info("models:{}", Arrays.toString(models)); + } catch (IOException e) { + e.printStackTrace(); + } + + try { + String[] samples = assets.list("samples"); + log.info("samples:{}", Arrays.toString(samples)); + } catch (IOException e) { + e.printStackTrace(); + } + String modelFilePath = "models/ggml-tiny.bin"; + File modelFile = AssetUtils.copyFileIfNotExists(baseContext, filesDir, modelFilePath); + modelFilePath = modelFile.getAbsolutePath(); + // 加载模型 + loadModel(modelFilePath); + + + String sampleFilePath = "samples/jfk.wav"; + File sampleFile = AssetUtils.copyFileIfNotExists(baseContext, filesDir, sampleFilePath); + // 识别样本 + transcribeSample(sampleFile); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private void loadModel(String modelPath) { + log.info("load model from :{}", modelPath); + whisperContext = WhisperContext.createContextFromFile(modelPath); + } + + private void transcribeSample(File sampleFile) { + log.info("transcribe file from :{}", sampleFile.getAbsolutePath()); + float[] audioData = new float[0]; // 读取音频样本 + try { + audioData = WaveEncoder.decodeWaveFile(sampleFile); + } catch (IOException e) { + e.printStackTrace(); + } + + String transcription = null; // 转录音频数据 + try { + transcription = whisperContext.transcribeData(audioData); + } catch (ExecutionException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + log.info("Transcription: {}", transcription); // 打印转录结果 + textHelloWorld.setText(transcription); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + @Override + protected void onDestroy() { + super.onDestroy(); + if (whisperContext != null) { + try { + whisperContext.release(); + } catch (ExecutionException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + whisperContext = null; + } + } +} diff --git a/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/media/WaveEncoder.java b/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/media/WaveEncoder.java new file mode 100644 index 0000000..6c934ad --- /dev/null +++ b/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/media/WaveEncoder.java @@ -0,0 +1,105 @@ +package com.litongjava.whisper.cpp.android.java.demo.media; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; + +public class WaveEncoder { + + public static float[] decodeWaveFile(File file) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (FileInputStream fis = new FileInputStream(file)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = fis.read(buffer)) != -1) { + baos.write(buffer, 0, bytesRead); + } + } + ByteBuffer byteBuffer = ByteBuffer.wrap(baos.toByteArray()); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + int channel = byteBuffer.getShort(22); + byteBuffer.position(44); + + ShortBuffer shortBuffer = byteBuffer.asShortBuffer(); + short[] shortArray = new short[shortBuffer.limit()]; + shortBuffer.get(shortArray); + + float[] output = new float[shortArray.length / channel]; + + for (int index = 0; index < output.length; index++) { + if (channel == 1) { + output[index] = Math.max(-1f, Math.min(1f, shortArray[index] / 32767.0f)); + } else { + output[index] = Math.max(-1f, Math.min(1f, (shortArray[2 * index] + shortArray[2 * index + 1]) / 32767.0f / 2.0f)); + } + } + return output; + } + + public static void encodeWaveFile(File file, short[] data) throws IOException { + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(headerBytes(data.length * 2)); + + ByteBuffer buffer = ByteBuffer.allocate(data.length * 2); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.asShortBuffer().put(data); + + byte[] bytes = new byte[buffer.limit()]; + buffer.get(bytes); + + fos.write(bytes); + } + } + + private static byte[] headerBytes(int totalLength) { + if (totalLength < 44) + throw new IllegalArgumentException("Total length must be at least 44 bytes"); + + ByteBuffer buffer = ByteBuffer.allocate(44); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + buffer.put((byte) 'R'); + buffer.put((byte) 'I'); + buffer.put((byte) 'F'); + buffer.put((byte) 'F'); + + buffer.putInt(totalLength - 8); + + buffer.put((byte) 'W'); + buffer.put((byte) 'A'); + buffer.put((byte) 'V'); + buffer.put((byte) 'E'); + + buffer.put((byte) 'f'); + buffer.put((byte) 'm'); + buffer.put((byte) 't'); + buffer.put((byte) ' '); + + buffer.putInt(16); + buffer.putShort((short) 1); + buffer.putShort((short) 1); + buffer.putInt(16000); + buffer.putInt(32000); + buffer.putShort((short) 2); + buffer.putShort((short) 16); + + buffer.put((byte) 'd'); + buffer.put((byte) 'a'); + buffer.put((byte) 't'); + buffer.put((byte) 'a'); + + buffer.putInt(totalLength - 44); + buffer.position(0); + + byte[] bytes = new byte[buffer.limit()]; + buffer.get(bytes); + + return bytes; + } +} diff --git a/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/recoder/Recorder.java b/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/recoder/Recorder.java new file mode 100644 index 0000000..3b27c8c --- /dev/null +++ b/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/recoder/Recorder.java @@ -0,0 +1,113 @@ +package com.litongjava.whisper.cpp.android.java.demo.recoder; + +import android.annotation.SuppressLint; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder; + +import com.litongjava.whisper.cpp.android.java.demo.media.WaveEncoder; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +public class Recorder { + + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private AudioRecordThread recorder; + + public void startRecording(File outputFile, OnErrorListener onErrorListener) { + executorService.submit(() -> { + recorder = new AudioRecordThread(outputFile, onErrorListener); + recorder.start(); + }); + } + + public void stopRecording() { + executorService.submit(() -> { + if (recorder != null) { + recorder.stopRecording(); + try { + recorder.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + recorder = null; + } + }); + } + + private static class AudioRecordThread extends Thread { + private final File outputFile; + private final OnErrorListener onErrorListener; + private final AtomicBoolean quit = new AtomicBoolean(false); + + AudioRecordThread(File outputFile, OnErrorListener onErrorListener) { + super("AudioRecorder"); + this.outputFile = outputFile; + this.onErrorListener = onErrorListener; + } + + @SuppressLint("MissingPermission") + @Override + public void run() { + try { + int bufferSize = AudioRecord.getMinBufferSize( + 16000, + AudioFormat.CHANNEL_IN_MONO, + AudioFormat.ENCODING_PCM_16BIT + ) * 4; + short[] buffer = new short[bufferSize / 2]; + + AudioRecord audioRecord = new AudioRecord( + MediaRecorder.AudioSource.MIC, + 16000, + AudioFormat.CHANNEL_IN_MONO, + AudioFormat.ENCODING_PCM_16BIT, + bufferSize + ); + + try { + audioRecord.startRecording(); + + List allData = new ArrayList<>(); + + while (!quit.get()) { + int read = audioRecord.read(buffer, 0, buffer.length); + if (read > 0) { + for (int i = 0; i < read; i++) { + allData.add(buffer[i]); + } + } else { + throw new RuntimeException("audioRecord.read returned " + read); + } + } + + audioRecord.stop(); + + short[] dataArr = new short[allData.size()]; + for (int i = 0; i < allData.size(); i++) { + dataArr[i] = allData.get(i); + } + + WaveEncoder.encodeWaveFile(outputFile, dataArr); + } finally { + audioRecord.release(); + } + } catch (Exception e) { + onErrorListener.onError(e); + } + } + + void stopRecording() { + quit.set(true); + } + } + + public interface OnErrorListener { + void onError(Exception e); + } +} diff --git a/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/utils/AssetUtils.java b/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/utils/AssetUtils.java new file mode 100644 index 0000000..e42f3fa --- /dev/null +++ b/app/src/main/java/com/litongjava/whisper/cpp/android/java/demo/utils/AssetUtils.java @@ -0,0 +1,91 @@ +package com.litongjava.whisper.cpp.android.java.demo.utils; + +import android.content.Context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class AssetUtils { + private static Logger log = LoggerFactory.getLogger(AssetUtils.class); + + public static File copyFileIfNotExists(Context context, File distDir, String filename) { + File dstFile = new File(distDir, filename); + if (dstFile.exists()) { + return dstFile; + } else { + File parentFile = dstFile.getParentFile(); + log.info("parentFile:{}", parentFile); + if (!parentFile.exists()) { + parentFile.mkdirs(); + } + AssetUtils.copyFileFromAssets(context, filename, dstFile); + } + return dstFile; + } + + public static void copyDirectoryFromAssets(Context appCtx, String srcDir, String dstDir) { + if (srcDir.isEmpty() || dstDir.isEmpty()) { + return; + } + try { + if (!new File(dstDir).exists()) { + new File(dstDir).mkdirs(); + } + for (String fileName : appCtx.getAssets().list(srcDir)) { + String srcSubPath = srcDir + File.separator + fileName; + String dstSubPath = dstDir + File.separator + fileName; + if (new File(srcSubPath).isDirectory()) { + copyDirectoryFromAssets(appCtx, srcSubPath, dstSubPath); + } else { + copyFileFromAssets(appCtx, srcSubPath, dstSubPath); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void copyFileFromAssets(Context appCtx, String srcPath, String dstPath) { + File dstFile = new File(dstPath); + copyFileFromAssets(appCtx, srcPath, dstFile); + } + + public static void copyFileFromAssets(Context appCtx, String srcPath, File dstFile) { + if (srcPath.isEmpty()) { + return; + } + InputStream is = null; + OutputStream os = null; + try { + is = new BufferedInputStream(appCtx.getAssets().open(srcPath)); + + os = new BufferedOutputStream(new FileOutputStream(dstFile)); + byte[] buffer = new byte[1024]; + int length = 0; + while ((length = is.read(buffer)) != -1) { + os.write(buffer, 0, length); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + os.close(); + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/whispercppdemo/whisper/CpuInfo.java b/app/src/main/java/com/whispercppdemo/whisper/CpuInfo.java new file mode 100644 index 0000000..b6ed7a4 --- /dev/null +++ b/app/src/main/java/com/whispercppdemo/whisper/CpuInfo.java @@ -0,0 +1,121 @@ +package com.whispercppdemo.whisper; + +import android.os.Build; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CpuInfo { + private static final String LOG_TAG = "WhisperCpuConfig"; + + private List lines; + + public CpuInfo(List lines) { + this.lines = lines; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + public int getHighPerfCpuCount0() { + try { + return getHighPerfCpuCountByFrequencies(); + } catch (Exception e) { + Log.d(LOG_TAG, "Couldn't read CPU frequencies", e); + return getHighPerfCpuCountByVariant(); + } + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private int getHighPerfCpuCountByFrequencies() { + List frequencies = getCpuValues("processor", line -> { + try { + return getMaxCpuFrequency(Integer.parseInt(line.trim())); + } catch (IOException e) { + e.printStackTrace(); + } + return 0; + } + ); + Log.d(LOG_TAG, "Binned cpu frequencies (frequency, count): " + binnedValues(frequencies)); + return countDroppingMin(frequencies); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private int getHighPerfCpuCountByVariant() { + List variants = getCpuValues("CPU variant", line -> Integer.parseInt(line.trim().substring(line.indexOf("0x") + 2), 16)); + Log.d(LOG_TAG, "Binned cpu variants (variant, count): " + binnedValues(variants)); + return countKeepingMin(variants); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private Map binnedValues(List values) { + Map countMap = new HashMap<>(); + for (int value : values) { + countMap.put(value, countMap.getOrDefault(value, 0) + 1); + } + return countMap; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private List getCpuValues(String property, Mapper mapper) { + List values = new ArrayList<>(); + for (String line : lines) { + if (line.startsWith(property)) { + values.add(mapper.map(line.substring(line.indexOf(':') + 1))); + } + } + values.sort(Integer::compareTo); + return values; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private int countDroppingMin(List values) { + int min = values.stream().mapToInt(i -> i).min().orElse(Integer.MAX_VALUE); + return (int) values.stream().filter(value -> value > min).count(); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private int countKeepingMin(List values) { + int min = values.stream().mapToInt(i -> i).min().orElse(Integer.MAX_VALUE); + return (int) values.stream().filter(value -> value.equals(min)).count(); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + public static int getHighPerfCpuCount() { + try { + return readCpuInfo().getHighPerfCpuCount0(); + } catch (Exception e) { + Log.d(LOG_TAG, "Couldn't read CPU info", e); + return Math.max(Runtime.getRuntime().availableProcessors() - 4, 0); + } + } + + private static CpuInfo readCpuInfo() throws IOException { + try (BufferedReader reader = new BufferedReader(new FileReader("/proc/cpuinfo"))) { + List lines = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + return new CpuInfo(lines); + } + } + + private static int getMaxCpuFrequency(int cpuIndex) throws IOException { + String path = "/sys/devices/system/cpu/cpu" + cpuIndex + "/cpufreq/cpuinfo_max_freq"; + try (BufferedReader reader = new BufferedReader(new FileReader(path))) { + return Integer.parseInt(reader.readLine()); + } + } + + private interface Mapper { + int map(String line); + } +} diff --git a/app/src/main/java/com/whispercppdemo/whisper/Utils.java b/app/src/main/java/com/whispercppdemo/whisper/Utils.java new file mode 100644 index 0000000..f825cb1 --- /dev/null +++ b/app/src/main/java/com/whispercppdemo/whisper/Utils.java @@ -0,0 +1,34 @@ +package com.whispercppdemo.whisper; + +import android.os.Build; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import java.io.File; +import java.nio.file.Path; + +public class Utils { + private static final String LOG_TAG = "LibWhisper"; + + + public static boolean isArmEabiV7a() { + return Build.SUPPORTED_ABIS[0].equals("armeabi-v7a"); + } + + public static boolean isArmEabiV8a() { + return Build.SUPPORTED_ABIS[0].equals("arm64-v8a"); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + public static String cpuInfo() { + try { + Path path = new File("/proc/cpuinfo").toPath(); + return new String(java.nio.file.Files.readAllBytes(path)); + } catch (Exception e) { + Log.w(LOG_TAG, "Couldn't read /proc/cpuinfo", e); + return null; + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/whispercppdemo/whisper/WhisperContext.java b/app/src/main/java/com/whispercppdemo/whisper/WhisperContext.java new file mode 100644 index 0000000..1f415b5 --- /dev/null +++ b/app/src/main/java/com/whispercppdemo/whisper/WhisperContext.java @@ -0,0 +1,98 @@ +package com.whispercppdemo.whisper; + +import android.content.res.AssetManager; +import android.os.Build; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import java.io.InputStream; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class WhisperContext { + + private static final String LOG_TAG = "LibWhisper"; + private long ptr; + private final ExecutorService executorService; + + private WhisperContext(long ptr) { + this.ptr = ptr; + this.executorService = Executors.newSingleThreadExecutor(); + } + + public String transcribeData(float[] data) throws ExecutionException, InterruptedException { + return executorService.submit(new Callable() { + @RequiresApi(api = Build.VERSION_CODES.O) + @Override + public String call() throws Exception { + if (ptr == 0L) { + throw new IllegalStateException(); + } + int numThreads = WhisperCpuConfig.getPreferredThreadCount(); + Log.d(LOG_TAG, "Selecting " + numThreads + " threads"); + WhisperLib.fullTranscribe(ptr, numThreads, data); + int textCount = WhisperLib.getTextSegmentCount(ptr); + StringBuilder result = new StringBuilder(); + for (int i = 0; i < textCount; i++) { + result.append(WhisperLib.getTextSegment(ptr, i)); + } + return result.toString(); + } + }).get(); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + public String benchMemory(int nthreads) throws ExecutionException, InterruptedException { + return executorService.submit(() -> WhisperLib.benchMemcpy(nthreads)).get(); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + public String benchGgmlMulMat(int nthreads) throws ExecutionException, InterruptedException { + return executorService.submit(() -> WhisperLib.benchGgmlMulMat(nthreads)).get(); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + public void release() throws ExecutionException, InterruptedException { + executorService.submit(() -> { + if (ptr != 0L) { + WhisperLib.freeContext(ptr); + ptr = 0; + } + }).get(); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + public static WhisperContext createContextFromFile(String filePath) { + long ptr = WhisperLib.initContext(filePath); + if (ptr == 0L) { + throw new RuntimeException("Couldn't create context with path " + filePath); + } + return new WhisperContext(ptr); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + public static WhisperContext createContextFromInputStream(InputStream stream) { + long ptr = WhisperLib.initContextFromInputStream(stream); + if (ptr == 0L) { + throw new RuntimeException("Couldn't create context from input stream"); + } + return new WhisperContext(ptr); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + public static WhisperContext createContextFromAsset(AssetManager assetManager, String assetPath) { + long ptr = WhisperLib.initContextFromAsset(assetManager, assetPath); + if (ptr == 0L) { + throw new RuntimeException("Couldn't create context from asset " + assetPath); + } + return new WhisperContext(ptr); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + public static String getSystemInfo() { + return WhisperLib.getSystemInfo(); + } +} diff --git a/app/src/main/java/com/whispercppdemo/whisper/WhisperCpuConfig.java b/app/src/main/java/com/whispercppdemo/whisper/WhisperCpuConfig.java new file mode 100644 index 0000000..a1e69e3 --- /dev/null +++ b/app/src/main/java/com/whispercppdemo/whisper/WhisperCpuConfig.java @@ -0,0 +1,12 @@ +package com.whispercppdemo.whisper; + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +public class WhisperCpuConfig { + @RequiresApi(api = Build.VERSION_CODES.N) + public static int getPreferredThreadCount() { + return Math.max(CpuInfo.getHighPerfCpuCount(), 2); + } +} diff --git a/app/src/main/java/com/whispercppdemo/whisper/WhisperLib.java b/app/src/main/java/com/whispercppdemo/whisper/WhisperLib.java new file mode 100644 index 0000000..7a38177 --- /dev/null +++ b/app/src/main/java/com/whispercppdemo/whisper/WhisperLib.java @@ -0,0 +1,83 @@ +package com.whispercppdemo.whisper; + +import android.content.res.AssetManager; +import android.os.Build; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; + +@RequiresApi(api = Build.VERSION_CODES.O) +public class WhisperLib { + private static final String LOG_TAG = "LibWhisper"; + private static Logger log = LoggerFactory.getLogger(WhisperLib.class); + + static { + + Log.d(LOG_TAG, "Primary ABI: " + Build.SUPPORTED_ABIS[0]); + log.info("Primary ABI: " + Build.SUPPORTED_ABIS[0]); + boolean loadVfpv4 = false; + boolean loadV8fp16 = false; + if (Utils.isArmEabiV7a()) { + String cpuInfo = Utils.cpuInfo(); + if (cpuInfo != null) { + Log.d(LOG_TAG, "CPU info: " + cpuInfo); + log.info("CPU info: " + cpuInfo); + if (cpuInfo.contains("vfpv4")) { + Log.d(LOG_TAG, "CPU supports vfpv4"); + log.info("CPU supports vfpv4"); + loadVfpv4 = true; + } + } + } else if (Utils.isArmEabiV8a()) { + String cpuInfo = Utils.cpuInfo(); + if (cpuInfo != null) { + Log.d(LOG_TAG, "CPU info: " + cpuInfo); + log.info("CPU info: " + cpuInfo); + if (cpuInfo.contains("fphp")) { + Log.d(LOG_TAG, "CPU supports fp16 arithmetic"); + log.info("CPU supports fp16 arithmetic"); + loadV8fp16 = true; + } + } + } + + if (loadVfpv4) { + Log.d(LOG_TAG, "Loading libwhisper_vfpv4.so"); + log.info("Loading libwhisper_vfpv4.so"); + System.loadLibrary("whisper_vfpv4"); + } else if (loadV8fp16) { + Log.d(LOG_TAG, "Loading libwhisper_v8fp16_va.so"); + log.info("Loading libwhisper_v8fp16_va.so"); + System.loadLibrary("whisper_v8fp16_va"); + } else { + Log.d(LOG_TAG, "Loading libwhisper.so"); + log.info("Loading libwhisper.so"); + System.loadLibrary("whisper"); + } + } + + public static native long initContextFromInputStream(InputStream inputStream); + + public static native long initContextFromAsset(AssetManager assetManager, String assetPath); + + public static native long initContext(String modelPath); + + public static native void freeContext(long contextPtr); + + public static native void fullTranscribe(long contextPtr, int numThreads, float[] audioData); + + public static native int getTextSegmentCount(long contextPtr); + + public static native String getTextSegment(long contextPtr, int index); + + public static native String getSystemInfo(); + + public static native String benchMemcpy(int nthread); + + public static native String benchGgmlMulMat(int nthread); +} \ No newline at end of file diff --git a/app/src/main/jniLibs/arm64-v8a/libwhisper.so b/app/src/main/jniLibs/arm64-v8a/libwhisper.so new file mode 100644 index 0000000..d0da8d4 Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libwhisper.so differ diff --git a/app/src/main/jniLibs/arm64-v8a/libwhisper_v8fp16_va.so b/app/src/main/jniLibs/arm64-v8a/libwhisper_v8fp16_va.so new file mode 100644 index 0000000..2a43994 Binary files /dev/null and b/app/src/main/jniLibs/arm64-v8a/libwhisper_v8fp16_va.so differ diff --git a/app/src/main/jniLibs/armeabi-v7a/libwhisper.so b/app/src/main/jniLibs/armeabi-v7a/libwhisper.so new file mode 100644 index 0000000..97c5724 Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libwhisper.so differ diff --git a/app/src/main/jniLibs/armeabi-v7a/libwhisper_vfpv4.so b/app/src/main/jniLibs/armeabi-v7a/libwhisper_vfpv4.so new file mode 100644 index 0000000..d44bbfe Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libwhisper_vfpv4.so differ diff --git a/app/src/main/jniLibs/x86/libwhisper.so b/app/src/main/jniLibs/x86/libwhisper.so new file mode 100644 index 0000000..1a52935 Binary files /dev/null and b/app/src/main/jniLibs/x86/libwhisper.so differ diff --git a/app/src/main/jniLibs/x86_64/libwhisper.so b/app/src/main/jniLibs/x86_64/libwhisper.so new file mode 100644 index 0000000..73c5015 Binary files /dev/null and b/app/src/main/jniLibs/x86_64/libwhisper.so differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..5c3bfcd --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..140f829 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..c6d97ef --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..03eed25 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..03eed25 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..6b46238 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..09837df --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..2c20992 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + whisper.cpp.android.java.demo + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..47d9a33 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/test/java/com/litongjava/whispercppandroidjavademo/ExampleUnitTest.java b/app/src/test/java/com/litongjava/whispercppandroidjavademo/ExampleUnitTest.java new file mode 100644 index 0000000..d580cb8 --- /dev/null +++ b/app/src/test/java/com/litongjava/whispercppandroidjavademo/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.litongjava.whispercppandroidjavademo; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..681d4a2 --- /dev/null +++ b/build.gradle @@ -0,0 +1,29 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://jitpack.io' } + mavenCentral() + google() + } + dependencies { + classpath "com.android.tools.build:gradle:4.1.3" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://jitpack.io' } + mavenCentral() + jcenter() + google() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..52f5917 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8319d0c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#Sat Jan 29 13:32:53 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +#distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..7a8099d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name = "whisper.cpp.android.java.demo" \ No newline at end of file