Offline speech recognition on Android is a pipeline, not a microphone button followed by one model call. A dependable implementation records audio in a known format, validates it, divides long sessions into recoverable work units, transcribes each unit, merges results, stores provenance, and handles interruption without losing the whole recording. The model matters, but most product failures happen around it.
Start with an audio contract
Android’s official audio and video capture guidance defines platform capture APIs, but the application still owns permission timing, recording lifecycle, and the exact audio representation passed into inference.
Define one internal audio representation before choosing an inference model. Record sample rate, channel count, sample encoding, byte order, and whether the data includes a file container. Every component should either accept that contract or convert explicitly at its boundary.
A raw PCM buffer and a WAV file can contain the same samples but are not interchangeable inputs. A decoder expecting a recognized container needs headers describing the audio. Passing raw bytes and hoping the runtime infers the format produces failures that look like model-quality problems.
Your contract should also state how the recorder handles:
- microphone permission denial;
- an incoming call or audio focus loss;
- the app entering the background;
- the process being killed;
- insufficient storage;
- a session that contains silence;
- cancellation while transcription is running.
Separate recording, transcription, and note generation
Treat recording as the durable source. Save enough metadata to replay it through a later model version. Transcription is a derived artifact. Summary, title, action items, and cleanup are further derived artifacts.
This separation lets the app retry a failed transcription without rerecording. It also lets users compare raw transcript and enhanced notes. If a later prompt changes, the app can regenerate the note while preserving what was originally heard.
A practical state model might include:
recording -> recorded -> segmenting -> transcribing -> transcript-ready
-> enhancing -> complete
Errors should attach to a stage. “Processing failed” gives neither the user nor the developer enough information to recover.
Segment long audio into recoverable work
Long recordings increase working memory, processing time, and the cost of retrying after one failure. Segmenting creates checkpoints. The segment boundary should preserve order and enough timing metadata to merge results later.
The current Cove Voice TranscriptionPipeline follows this pattern. It segments PCM audio, processes segments sequentially through the shared inference engine, merges non-empty text, and leaves a visible placeholder when one segment fails instead of discarding every successful segment.
That behavior is a product decision, not the only valid policy. Another app might stop immediately on the first failure or ask the user to retry only the failed section. What matters is that partial success is represented honestly. Never close a gap by joining neighboring sentences as though nothing was missing.
Match the container to the runtime
Cove’s current shared InferenceEngine wraps PCM audio as WAV before passing audio plus a transcription instruction into LiteRT-LM. Its current engine configuration assigns the audio execution path to a CPU backend. This is a statement about the present Cove implementation, not a universal LiteRT-LM requirement or a performance claim.
At integration time, verify these boundaries independently:
- the recorder produces the expected samples;
- the container writer produces a valid file;
- the runtime accepts the artifact;
- the model returns text rather than translation or commentary;
- cancellation closes the active work safely;
- repeated segments do not leak engine state into one another.
Keep a tiny known recording in the test fixtures. It will not validate real-world accuracy, but it can catch format and lifecycle regressions.
Prompting is not an accuracy evaluation
When a multimodal language model is used for transcription, the instruction should make the task narrow: reproduce speech, keep the spoken language, avoid translation, and return only transcript text. This reduces output-format ambiguity. It does not prove recognition quality.
Quality evaluation needs a separate corpus with human reference transcripts. Include silence, background noise, clipped starts, long pauses, names, numbers, code-switching, and the languages the product promises. Decide how punctuation and casing affect scoring before comparing versions.
Do not publish a universal accuracy number from a handful of clean samples. Store the corpus definition, model artifact, runtime revision, prompt, and scoring policy next to the result.
Memory and sustained work
Peak memory cannot be inferred from the model download alone. Weights, runtime buffers, KV cache, audio tensors, application memory, system headroom, and allocator behavior all contribute. A successful short clip does not validate a long meeting.
Measure at least four phases: model load, first segment, repeated segments, and transcript enhancement. Keep the same process alive during the repeated test. This exposes accumulation that a restart-between-runs benchmark would hide.
Also test cancellation. A user who stops a long transcription should not leave native inference, audio buffers, or database transactions alive indefinitely.
Privacy: verify traffic during the complete workflow
Local inference removes the need to send the recording to a remote inference server, but it does not prove the application makes no other network requests. Verify the entire task in airplane mode and observe traffic when connectivity is restored.
A useful audit distinguishes:
- audio content;
- transcript content;
- model delivery;
- purchase or entitlement checks;
- optional diagnostics;
- user-triggered export or sharing.
The first two are the sensitive payloads in a transcription product. Export and sharing should be explicit user actions, not hidden side effects of processing.
Test matrix
| Layer | Minimum cases |
|---|---|
| Recording | permission denial, interruption, background, silence |
| Container | empty buffer, truncated data, valid known fixture |
| Segmentation | short input, boundary input, multiple segments |
| Inference | success, cancellation, one failed segment, repeated work |
| Merge | whitespace, missing segment, language changes |
| Persistence | restart before and after transcript completion |
| Privacy | airplane mode, traffic capture, explicit export |
| Memory | load, representative session, repeated session, cleanup |
Release checklist
- Record the exact audio format in code and documentation.
- Preserve original audio until the user deletes it or the retention rule runs.
- Keep transcription and enhancement as separate states.
- Represent failed segments visibly.
- Test the actual runtime artifact on target hardware.
- Evaluate quality on a versioned reference corpus.
- Measure sustained memory, not only first-run success.
- Verify the full workflow in airplane mode.
- Audit network traffic after connectivity returns.
- Make sharing and export explicit user actions.
Cove’s current implementation is still moving through testing and store preparation, so this page documents architecture and testable code paths rather than claiming released-user results. See private Android meeting recording for the user scenario, RAM requirements for measurement, and on-device AI vs cloud AI for the data boundary.
Last reviewed: 2026-09-16.