Skip to content

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

Acs176
Copy link
Contributor

@Acs176 Acs176 commented Feb 19, 2025

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 the assets folder of the android project. Currently I only support this model, can be updated in the future.

Features

Screenshot 2025-02-19 at 17 30 37

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

  • Model caching doesn't work correctly with QNN delegate and it takes several minutes to load the model when starting the app (if using that delegate).
  • Issues running in Android Studio emulator. I've experienced some strange crashes when running on the emulator which I suspect are related to the QNN .so files not loading successfully

@v-prgmr
Copy link
Contributor

v-prgmr commented Feb 19, 2025

This is awesome, i am going to try it out now. Thank you so much 🚀

@v-prgmr
Copy link
Contributor

v-prgmr commented Feb 19, 2025

.apk works like a charm. Just ran the tiny model on a S24 Ultra SM8650. Great work Thank you! @Acs176

@Acs176
Copy link
Contributor Author

Acs176 commented Feb 20, 2025

Great news @v-prgmr! Could you share some latency data when running the transcribe on that device?
So far I've only been able to run it on an SM7450 (added the soc manually to the allowed list) and get around 1400ms transcribing the jfk.wav file.

@Acs176
Copy link
Contributor Author

Acs176 commented Feb 20, 2025

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 TranscribeTask.cpp indicated.

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.

@v-prgmr
Copy link
Contributor

v-prgmr commented Feb 20, 2025

@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

Screenshot_20250220_155324.jpg

Screenshot_20250220_155343.jpg

@bpkeene
Copy link
Contributor

bpkeene commented Feb 26, 2025

Review is in progress! Will post feedback shortly

@yuguolong
Copy link

Great work!

Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Contributor Author

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 ->
Copy link
Contributor

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)
Copy link
Contributor

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

Copy link
Contributor Author

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 {
Copy link
Contributor

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

Copy link
Contributor Author

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

Copy link
Contributor

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 {
Copy link
Contributor

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 {
Copy link
Contributor

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?

Copy link
Contributor

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) {
Copy link
Contributor

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;
/*
Copy link
Contributor

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

Copy link
Contributor

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)

Copy link
Contributor

@bpkeene bpkeene left a 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

@Acs176
Copy link
Contributor Author

Acs176 commented Mar 6, 2025

@bpkeene thanks for the very thorough review! I will get back to you and start fixing the issues asap :)

@nyhryan
Copy link

nyhryan commented Apr 30, 2025

Hey, I am trying to run your sample app. I have cloned your branch android-app and tried to run with Galaxy S25. However the launch will fail. I can see some of .so files were failed to be loaded. (libQnnDspV66Skel.so, libQnnHtpV68Skel.so, libQnnHtpV69Skel.so...)

These warnings have libQnnDspV66Skel.so" is 32-bit instead of 64-bit messages.

And it fails with Failed to register methods of org/libsdl/app/SDLAudioManager...

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

make setup (confirmed everything is installed)
make download-models
make env
make build (inside make env docker environment)
(plus I ran make build qni,  make build qnn)

I have run these following commands. Plus ran your scripts/build_android_app.sh, copied models/openai_whisper-tiny folder into the assets folder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants