- core/recording: MeetingRecorder (AudioRecord at the device's native rate, WAV on disk + 60 s rolling window resampled to 16 kHz), streaming WavWriter, thread-safe RollingWindow, linear resampler; WavReader moved here from the app - app: RecorderService (foreground, type microphone) with ongoing notification and StateFlow state; Record/Stop UI with timer and level meter; runtime permission flow; finished recordings auto-load for transcription - launcher icon (mic + waveform, matching the desktop brand) and notification glyph - fix real M0 bug caught by the new unit tests: WavReader parsed 16-bit fmt fields (audioFormat/channels/bitsPerSample) with 32-bit reads - JVM tests: WAV round trip, native-rate header, resampler, rolling window — 5/5 green; assembleDebug and aapt2 APK checks pass - validated on-device on a Fairphone 6 (Android 16): model download, engine load, recording and transcription all working
2.8 KiB
MeetRec for Android
Native Android app with the same functionality as the desktop meetrec: record meetings and transcribe them on-device with Whisper. Nothing leaves the phone — no cloud, no telemetry.
Status: M1 (recording) — record a meeting with a foreground service (native sample rate, timer, level meter), then transcribe it on device. Model download + engine load are from M0; the live rolling transcript arrives with M2 (see the milestone plan in the project docs).
Requirements
- Android Studio (or: SDK Platform 36, Build Tools 36, NDK 27.1, CMake 3.22.1)
- JDK 17+
- Device running Android 10+ (developed against a Fairphone 6 / Snapdragon 7s Gen 3, Android 15+)
Build
cd android
./tools/fetch-whisper.sh # vendors whisper.cpp v1.9.3 into third_party/
./gradlew :app:assembleDebug # or open the android/ folder in Android Studio
adb install -r app/build/outputs/apk/debug/app-debug.apk
third_party/whisper.cpp is gitignored — the fetch script pins the exact
release tag so the JNI layer never breaks on upstream churn.
Test on device
- Launch MeetRec, pick a model (
tinyis fine for a first test) and tap Download (model comes from Hugging Face; tiny is ~75 MB). - Tap Load engine.
- Tap Record, speak, then tap Stop — the recording is saved as WAV (native device rate) and auto-loaded for transcription.
- Tap Transcribe — segments with timestamps appear; share via the share sheet. You can also pick any PCM WAV file instead of recording.
Expect roughly realtime transcription with tiny/base on the
Fairphone 6's CPU; small is noticeably slower — use it for final passes
only (the live/final split comes with the recorder milestones).
Performance notes
- The engine runs on CPU via NEON, using up to 4 threads.
- GPU/NPU acceleration (e.g. Snapdragon NPU via the QNN backend) is a stretch goal, not wired up yet.
Module layout
app/ Compose UI + RecorderService (foreground mic recording)
core/recording/ MeetingRecorder, WavWriter/WavReader, RollingWindow,
linear resampler — pure Kotlin, unit tested on the JVM
core/whisper/ whisper.cpp JNI wrapper: CMake build + LibWhisper.kt +
WhisperEngine.kt (the on-device transcription API)
tools/ fetch-whisper.sh — vendor the pinned whisper.cpp release
Recording mirrors the desktop app: audio is captured at the input's native rate (WAV on disk keeps it), while the 60 s rolling window is resampled to Whisper's 16 kHz for the upcoming live pass. Recording continues while the app is backgrounded via the microphone foreground service.
Models live in the app's private storage (filesDir/models), shared files
with the desktop app's ~/.cache/meetrec/whisper-cpp naming
(ggml-tiny.bin … ggml-large-v3.bin).