Files
meetrec/android
fegger 76611af7e0 M3d: speaker labels via tinydiarize two-pass merge
- whisper-server stack: second container (port 8086) running the
  English-trained small.en-tdrz model with -tdrz; image patched
  (speaker-turn.patch) to expose speaker_turn_next per segment in
  verbose_json like the cli example does
- core/whisper Diarization: merges the tdrz pass's TURN TIMES onto the
  quality transcript as alternating 'Sprecher 1/2:' labels, splitting
  segments when a turn falls inside them; no turns detected = no
  labels (never mislabels); 6 unit tests
- RemoteWhisperEngine gains a diarize flag (sends tinydiarize=true,
  parses speaker_turn_next); WhisperEngine.Segment carries the flag
- RecorderService: optional second pass on the diarize server after the
  final pass; failures keep the unlabeled transcript
- Settings: Diarize server URL (persisted; empty disables)
- validated infrastructure locally: patched image builds, tdrz model
  downloads from akashmjn/tinydiarize-whisper.cpp, speaker_turn_next
  present in responses; synthetic espeak audio does not trigger the
  model's turn tokens — real two-person speech needed for the
  end-to-end check
2026-09-08 17:07:31 +02:00
..

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: M2.5 (remote transcription) — record on the phone while a remote whisper.cpp server (GPU) provides live and final transcripts. The phone's local whisper.cpp engine remains as the offline fallback.

Transcription targets

phone — the app's local whisper.cpp engine (JNI/NEON): pick a model (tiny/base for live, e.g. small for the final pass). Measured on the Fairphone 6: ~0.60.8× realtime, so keep local models small.

server — a remote whisper.cpp server (Docker Compose, Vulkan GPU) transcribes BOTH the live pass and the final pass; the model lives on the server (e.g. large-v3). The phone only records and displays. Set the server URL in the app (persisted; e.g. http://100.103.83.12:8085 over Tailscale — the phone needs Tailscale too). Local model selection is greyed out in this mode.

Recording library (meetrec-server)

Finished recordings (WAV + txt/srt/json) upload automatically to the meetrec-server storage service. Set the Library URL in the app (http://100.103.83.12:8090, persisted) or leave it empty to disable upload. Uploads run in the background after the final pass; the status line reports the result.

The Library tab browses everything:

  • On this phone — local recordings (with or without transcripts)
  • On the server — stored bundles with date, duration, language
  • Detail view: timestamped transcript, audio playback (streamed from the server or from the local file), share, and delete with confirmation. Refresh reloads both lists.

Summaries and agenda (Ollama)

Enter agenda items on the Record tab (one per line, persisted) before recording. After the recording uploads, meetrec-server automatically:

  1. writes a German summary (gemma4:12b via your Ollama server): topic, key points, decisions, to-dos — shown on the recording's detail page
  2. checks which agenda items were actually discussed — the detail page shows a ✓/✗ checklist with the timestamp and a quote as evidence

Both can be re-triggered from the detail page ("Neu erstellen" / "Agenda neu prüfen"). Note: very long meetings may exceed the LLM's context window (the transcript is then truncated).

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. Pick Transcribe on: phone or server. For server: enter the URL (needs Tailscale on the phone for a Tailscale-only server).
  2. Phone mode: Download + Load engine first (tiny is fine for a start).
  3. Tap Record — live transcript lines appear every few seconds.
  4. Tap Stop — the final pass runs automatically and writes <stem>.txt/.srt/.json next to the WAV.
  5. Share the transcript via the share sheet. You can also pick any PCM WAV file and transcribe it manually.

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 (record → live pass → final pass)
core/recording/ MeetingRecorder, WavWriter/WavReader, RollingWindow,
              linear resampler — pure Kotlin, unit tested on the JVM
core/whisper/ whisper.cpp JNI wrapper + WhisperEngine + TranscriptFiles
              (txt/srt/json writers, desktop-compatible formats)
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).