-
Notifications
You must be signed in to change notification settings - Fork 8
Sample Android App for JNI library #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This is awesome, i am going to try it out now. Thank you so much 🚀 |
.apk works like a charm. Just ran the tiny model on a S24 Ultra SM8650. Great work Thank you! @Acs176 |
Great news @v-prgmr! Could you share some latency data when running the transcribe on that device? |
I updated the PR because I noticed a bug. The lib directory to access the .so files was being hardcoded in the c++ side. The android app sets this directory on runtime when loading the libraries (it puts some hash in the path), so these .so files were not being loaded correctly into the app and you couldn't use QNN unless you had previously run the adb-push.sh script, which puts the files in the folder that I allowed the WhisperKitRunner to receive the libs path through the NativeWhisperKit from the Android app on runtime. This ensures that the libraries for QNN load correctly. I also added a section in the screen of the app to display tflite logs from logcat. That way you get some more feedback in case you run from apk. Try it out again @v-prgmr, you should get much faster responses if QNN was not loading correctly. |
@Acs176 just pulled the latest commit from your fork and rebuilt the app and ran it on SM8650. Here are the screenshots for jfk.wav and english_test2.wav |
Review is in progress! Will post feedback shortly |
Great work! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove the .wav files? We have a couple representative test files in $SOURCE/test/ already, perhaps a script could move them to app assets as part of the app build process?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed a script to avoid duplicating test files and .so's
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto, and so forth for other .wav files
|
||
// QNN libraries | ||
"QnnSystem", // Core system library for QNN | ||
"QnnGpu", // GPU support for QNN |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to handle the Qnn Skel & Stubs in the gradle file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't found a way to avoid loading them manually with System.loadLibrary()
) | ||
|
||
fun loadAllLibraries() { | ||
libraries.forEach { lib -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am surprised we can load QnnHtpV75Skel on SoCs that do not correspond to it (or, non-QCOM SoCs).
We should load minimal dependencies (see base list in CMakeLists) and then have soft linking / SoC check before linking to Qnn libs, what do you think?
resultState = mutableStateOf("") | ||
statusState = mutableStateOf("LOADING MODEL (This may take a few minutes when using QNN)") | ||
|
||
waveFile = File(sdcardDataFolder!!.absolutePath + "/" + microphoneInputFileName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we remove the initial assets, perhaps we could initialize this via a safe file loader- if the file exists, load it; else, create a writeable file with .wav extension ?
if we don't remove them, would be preferable to have the existing assets included in the app build rather than duplicated in the repo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
went with the solution to check if the file is already existing
import java.nio.ByteOrder; | ||
import java.nio.charset.StandardCharsets; | ||
|
||
public class WaveUtil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
javax.sound.sampled has .wav utilities that may be useful and is in the standard jdk, can avoid manually writing the .wav header magic-
https://stackoverflow.com/questions/3297749/java-reading-manipulating-and-writing-wav-files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this library is sadly not supported in android projects
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
understood, thank you for clarifying!
Can we use more standard interfaces? https://developer.android.com/reference/android/media/.
Some of these should allow us to use standard interfaces
import java.util.LinkedList; | ||
import java.util.UUID; | ||
|
||
class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this for? It seems there is some chromebook specific considerations as well?
import java.util.Iterator; | ||
import java.util.List; | ||
|
||
public class HIDDeviceManager { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need these HID / USB considerations if the app is instead installed to the device after being built on host?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a source or reference for this file?
return false; | ||
} | ||
|
||
private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems unrelated ? ditto for a few other functions
private static final int SDL_MAJOR_VERSION = 3; | ||
private static final int SDL_MINOR_VERSION = 2; | ||
private static final int SDL_MICRO_VERSION = 1; | ||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we remove the commented code?
package org.libsdl.app; | ||
|
||
import android.app.Activity; | ||
import android.app.AlertDialog; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need SDL initialized or interfacing in the native layer?
There is initialization & handling of this in the internal cpp library. I would think we can use standard audio functionality at the app layer for audio capture & file read/write, lightweight calls in to whisperkit native layer from the app and the developer doesn't need to know about SDL / audio resampling - they pass the file, they receive the transcription
Is that understanding correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one maybe we can solve. I've been trying hard to avoid having to embed this SDL app into our app, but if I don't do it, I am met with this error:
2025-03-11 09:50:50.851 14525-14525 m.whispertflite com.whispertflite A java_vm_ext.cc:616] JNI DETECTED ERROR IN APPLICATION: JNI FindClass called with pending exception java.lang.ClassNotFoundException: Didn't find class "org.libsdl.app.SDLActivity" on path: DexPathList[[dex file "/data/data/com.whispertflite/code_cache/.overlay/base.apk/classes5.dex", zip file "/data/app/~~1muUDF0X1sTtD6EmTn-Juw==/com.whispertflite-BC8hIf5K5XQDH5WQKpRPBA==/base.apk"],nativeLibraryDirectories=[/data/app/~~1muUDF0X1sTtD6EmTn-Juw==/com.whispertflite-BC8hIf5K5XQDH5WQKpRPBA==/lib/arm64, /data/app/~~1muUDF0X1sTtD6EmTn-Juw==/com.whispertflite-BC8hIf5K5XQDH5WQKpRPBA==/base.apk!/lib/arm64-v8a, /system/lib64, /product_h/lib64, /system_ext/lib64]]
If I try to load the SDL3 .so, I also get this error unless the app is embedded like I did. I'm unsure on what else to try in order to load all .so's successfully without having to include that app in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any input regarding this? @bpkeene
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to add these .so files to the github repo - they're actually pulled in as part of the Docker environment creation and placed in .source/ , can you reference them as part of the app build process to put in the .apk? (ditto elsewhere)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looked over things, can we make changes with respect to java-SDL (remove?) and not distribute the qnn libs? They are already pulled in as part of the docker environment creation, we should be able to reference them appropriately as part of the gradle build system
@bpkeene thanks for the very thorough review! I will get back to you and start fixing the issues asap :) |
Hey, I am trying to run your sample app. I have cloned your branch These warnings have And it fails with Here's a log from logcat when I tried to run in AndroidStudio to Galaxy S25. https://pastebin.com/E13JqP1A I am currently using M1 Macbook Air, macOS 15.4.1
I have run these following commands. Plus ran your |
Description
This PR includes a sample android application under the
android-app
directory. It includes all the necessary .so's to run WhisperKit with any delegate. It's meant to have just very basic functionality (record, play audio, transcribe audio).The only step needed to run the app is copying the
openai_whisper-tiny/
folder into theassets
folder of the android project. Currently I only support this model, can be updated in the future.Features
Audio Transcription
There's a dropdown to select the audio file and a Transcribe button to do just that.
Recording
Press the record button once to start a microphone recording, press again to end it. The file will be stored with the name MicInput.wav and can be selected from the dropdown to play it or transcribe it.
Known issues