Files
meetrec/android/README.md
T
fegger 8e079467fa Android M1: meeting recording via foreground service
- 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
2026-09-07 11:31:29 +02:00

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

  1. Launch MeetRec, pick a model (tiny is fine for a first test) and tap Download (model comes from Hugging Face; tiny is ~75 MB).
  2. Tap Load engine.
  3. Tap Record, speak, then tap Stop — the recording is saved as WAV (native device rate) and auto-loaded for transcription.
  4. 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).