Run optimized Whisper speech transcription models from the Arm AI Portal on Arm-powered Android devices
Introduction
Prepare the Android command-line tools for building Whisper Journal
Connect an Arm-based Android device and run Whisper Journal
Import and run Arm AI Portal speech recognition models on Android
Understand the Whisper Journal Android application
(Optional) Use a model not currently supported by Whisper Journal
Next Steps
Run optimized Whisper speech transcription models from the Arm AI Portal on Arm-powered Android devices
Introduction
Prepare the Android command-line tools for building Whisper Journal
Connect an Arm-based Android device and run Whisper Journal
Import and run Arm AI Portal speech recognition models on Android
Understand the Whisper Journal Android application
(Optional) Use a model not currently supported by Whisper Journal
Next Steps
Choose an implementation route
Whisper Journal already supports the LiteRT and ExecuTorch models listed in the supported model table on Run and compare registered speech recognition models . You don’t need to change the application to use those models.
You can try one of two hands-on routes to use a model that Whisper Journal doesn’t currently support:
- Register the model directly when it matches a supplied adapter
- Use the coding-agent workflow to generate another adapter
Record the following information for the model and compare it with the two supplied adapters before changing code:
- Runtime and package files
- Callable methods
- Input and output tensors
- Audio sample rate, channels, framing, padding, and duration
- Feature extraction
- Tokenizer and vocabulary
- Prompt tokens, suppression rules, and stopping conditions
- Output decoding
| Model compatibility | Hands-on workflow |
|---|---|
| Matches a supplied adapter’s methods, tensors, audio preprocessing, tokenizer, decoder profile, and required assets | Register another compatible model |
| Differs in any required contract element | Generate another adapter using a coding agent |
Sharing a runtime doesn’t make speech model packages interchangeable. Two .pte programs can expose different methods, and two .tflite models can use different cache layouts, tokenizer files, or decoding rules.
Register another compatible model
Reuse a supplied adapter when another model has the same complete execution contract as one of the supported packages.
Download and package the model using its repository ID:
printf 'Arm model repository ID: '
read -r MODEL_ID
export MODEL_ID
export MODEL_PACKAGE="$(.hf-venv/bin/python download_model.py \
--repo-id "$MODEL_ID" \
--print-path)"
$MODEL_ID = Read-Host "Arm model repository ID"
$MODEL_PACKAGE = .\.hf-venv\Scripts\python.exe download_model.py `
--repo-id "$MODEL_ID" `
--print-path
If the repository contains more than one primary .tflite or .pte model file, rerun the downloader with --filename followed by the exact repository-relative model filename.
Add one descriptor to the list returned by CompatibleModelRegistry.models(). The following example registers a LiteRT package that uses the same complete decoder profile as Whisper Base:
static List<WhisperModelDescriptor> models() {
return List.of(
new WhisperModelDescriptor(
"my-whisper-base-litert",
"My Whisper model · LiteRT",
AdapterRegistry.LITERT_ID,
"Arm/my-whisper-base-litert",
"my-whisper-base-litert.zip",
80,
51865,
LiteRtWhisperAdapter.CONFIG_WHISPER_BASE
)
);
}
Import org.arm.learningpath.whisper.litert.LiteRtWhisperAdapter in CompatibleModelRegistry.java when you use this LiteRT configuration constant.
The package filename must match the ZIP created by the downloader. Select the supplied adapter and configuration that match the complete model package, then rebuild and install the APK. Importing the package automatically activates its registered adapter.
Don’t register a speech model based only on its file extension or family name. A different export can keep a similar repository name but change the following:
- Callable methods
- Tensors
- Tokenizer
- Prompt tokens
- Decoder behavior
Determine when the supplied adapter doesn’t fit
Add a decoder profile inside the supplied LiteRT adapter when the model:
- Uses the same
encodeanddecodesignatures - Uses the same tensor layout and feature input
- Uses the same tokenizer behavior and decoding algorithm
- Uses different documented dimensions or token IDs
Reference the new configuration ID from the model descriptor.
The profile must validate the following:
- Model signatures
- Tensor shapes and data types
- Mel-bin count
- Vocabulary size
- Cache layout
- Prompt tokens
- Suppression rules
- Stopping conditions
Don’t change profile values to make an incompatible package load.
Use a separate adapter when the runtime or callable methods change, but the model still accepts one complete mono recording and returns a transcript through TranscriptionResult. For example, a model with another Android runtime can reuse the supplied recorder and transcript UI while implementing its own model loading, preprocessing, inference, tokenization, and decoding.
A separate adapter isn’t enough when the workflow no longer fits those shared interfaces. Streaming needs persistent decoder state and audio scheduling. Language selection, translation, timestamps, or speaker diarization can need different controls and result types. Extend SpeechToTextAdapter.java, TranscriptionResult.java, AudioRecorder.java, and MainActivity.java before adding such a workflow.
Keep recording, model execution, and transcript presentation separate where possible. The supplied recording path produces 16 kHz mono audio, so another model can reuse it only when that’s the documented input contract.
Generate another adapter using a coding agent
Use this workflow when the model doesn’t match every part of a supplied adapter’s contract. A coding agent can inspect the model package and prepare a build-time adapter for its preprocessing, task, runtime, and model format.
Use an advanced coding agent that supports long-context, multi-file editing, and terminal tools. The agent needs to be able to trace Java interfaces and reason about audio preprocessing, tensor, tokenizer, and decoder contracts. It needs to be able to update Gradle configuration and interpret build errors. Lightweight autocomplete or chat-only tools are unlikely to complete this workflow reliably.
Treat generated code as a starting point that needs developer review and device validation.
Run the platform-specific project and Android SDK checks from the cloned project directory:
if [ ! -f "settings.gradle.kts" ] || [ ! -x "gradlew" ]; then
printf 'Run this command from the cloned project directory.\n' >&2
exit 1
fi
if [ -z "${ANDROID_HOME:-}" ] || \
[ ! -d "$ANDROID_HOME/platforms/android-35" ] || \
[ ! -d "$ANDROID_HOME/build-tools/34.0.0" ]; then
printf 'Android SDK Platform 35 or Build-Tools 34.0.0 is unavailable.\n' >&2
exit 1
fi
if (-not (Test-Path "settings.gradle.kts") -or
-not (Test-Path "gradlew.bat")) {
throw "Run this command from the cloned project directory."
}
if (-not $env:ANDROID_HOME -or
-not (Test-Path "$env:ANDROID_HOME\platforms\android-35") -or
-not (Test-Path "$env:ANDROID_HOME\build-tools\34.0.0")) {
throw "Android SDK Platform 35 or Build-Tools 34.0.0 is unavailable."
}
Verify the project root, Java, and connected phone:
git rev-parse --show-toplevel
java -version
adb get-state
Check the huggingface_hub package with the Python executable for your operating system:
.hf-venv/bin/python -c \
'import huggingface_hub; print(huggingface_hub.__version__)'
.\.hf-venv\Scripts\python.exe -c `
"import huggingface_hub; print(huggingface_hub.__version__)"
The output includes the project root, Java version, device, and the installed huggingface_hub version. Resolve any error before continuing.
Also make sure:
- Your coding agent is installed, authenticated if needed, and allowed to read, edit, and run commands in the project workspace
- You have access to the model repository
- You have access to an approved account and access token if you’re using a gated or private repository
- You know the model runtime, tensor contract, and audio preprocessing from authoritative model documentation
- You know the tokenizer and decoding behavior from authoritative model documentation
- You have enough disk space for the complete model package and representative recordings with trusted reference transcripts
Generated code might need review and iteration. Compatibility depends on the following factors:
- The model package
- The Android runtime library
- Native operators
- Device ABI
- Preprocessing information
- Available backend support
Set the repository ID and runtime specified by the model package:
printf 'Arm model repository ID: '
read -r MODEL_ID
printf 'Model runtime: '
read -r MODEL_RUNTIME
export MODEL_ID MODEL_RUNTIME
export MODEL_FILE="$(.hf-venv/bin/python adapter-generation/download_model_package.py \
--repo-id "$MODEL_ID" \
--print-model-path)"
export MODEL_DIR="$(dirname "$MODEL_FILE")"
$MODEL_ID = Read-Host "Arm model repository ID"
$MODEL_RUNTIME = Read-Host "Model runtime"
$MODEL_FILE = .\.hf-venv\Scripts\python.exe adapter-generation/download_model_package.py `
--repo-id "$MODEL_ID" `
--print-model-path
$MODEL_DIR = Split-Path -Parent "$MODEL_FILE"
The download script retrieves the complete repository because the coding agent might need configuration, tokenizer files, preprocessing programs, or other package assets in addition to the primary model file.
The command stores the primary model binary in MODEL_FILE and the package directory in MODEL_DIR.
Inspect the package and create model-summary.json:
.hf-venv/bin/python adapter-generation/inspect_model_package.py \
--model-dir "$MODEL_DIR" \
--model-id "$MODEL_ID" \
--runtime "$MODEL_RUNTIME"
.\.hf-venv\Scripts\python.exe adapter-generation/inspect_model_package.py `
--model-dir "$MODEL_DIR" `
--model-id "$MODEL_ID" `
--runtime "$MODEL_RUNTIME"
Give your coding agent the following files from the project:
model-summary.jsonadapter-generation/generate_mobile_adapter_prompt.txtSpeechToTextAdapter.java,WhisperModelDescriptor.java,TranscriptionResult.java, andProgressListener.javaAudioRecorder.java,ModelPackageImporter.java, anddownload_model.pyAdapterRegistry.java,GeneratedAdapterRegistry.java, andapp/generated-runtime-dependencies.gradle.ktsExecuTorchWhisperAdapter.javaandLiteRtWhisperAdapter.javaas examplesWhisperFeatureExtractor.javaand bothWhisperTokenizer.javaimplementations
The prompt tells the coding agent to preserve the supplied contracts when they fit. The agent can add a documented LiteRT profile or create a runtime-specific adapter. It can register the adapter and model in GeneratedAdapterRegistry.java, update package handling, and add a required runtime dependency to app/generated-runtime-dependencies.gradle.kts.
The agent should report that the application contracts need to be extended when the package needs any of the following:
- Streaming or multi-recording input
- Language, translation, timestamp, or diarization controls
- Multiple model files selected at run time
- Another result type
The agent shouldn’t force the model into the supplied interface.
Review the generated source. The coding agent should run the included file check and report the exact command using the files it created. To run the check yourself, replace YourGeneratedSpeechAdapter.java with the generated class name:
.hf-venv/bin/python adapter-generation/validate_generated_adapter.py \
--adapter "app/src/main/java/org/arm/learningpath/whisper/YourGeneratedSpeechAdapter.java"
.\.hf-venv\Scripts\python.exe adapter-generation/validate_generated_adapter.py `
--adapter "app/src/main/java/org/arm/learningpath/whisper/YourGeneratedSpeechAdapter.java"
Create the ZIP package after reviewing the generated downloader and importer changes:
export MODEL_PACKAGE="$(.hf-venv/bin/python download_model.py \
--repo-id "$MODEL_ID" \
--print-path)"
$MODEL_PACKAGE = .\.hf-venv\Scripts\python.exe download_model.py `
--repo-id "$MODEL_ID" `
--print-path
Rebuild the application after reviewing the generated files:
./gradlew :app:assembleDebug :app:lintDebug
.\gradlew.bat :app:assembleDebug :app:lintDebug
Install the application and copy the package with the commands used on every operating system:
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb push "$MODEL_PACKAGE" /sdcard/Download/
Select the model added by the generated adapter and import the copied ZIP package. A successful build confirms that the source compiles. However, it doesn’t prove that audio preprocessing, tensor values, tokenization, or decoding are correct. Record representative utterances on the target phone and compare the transcripts with trusted references for the model package. Repeat one test in airplane mode to confirm that transcription remains on-device.
What you’ve accomplished
You’ve learned how you can extend Whisper Journal to use a speech recognition model that’s not currently supported.
You can now register a compatible model with a supplied adapter or generate, build, and validate another adapter for a model with a different contract.