M1-05: Audio capture core — one continuous file that survives a kill #5
Labels
No labels
area/agent
area/capture
area/editor
area/geo
area/security
area/sync
area/transcription
area/ui
area/vault
area/voice-memos
blocked
milestone/M1
milestone/M2
milestone/M3
milestone/M4
needs-decision
needs-hardware
p0
p1
p2
type/bug
type/enhancement
type/feature
type/idea
type/infrastructure
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Stash/stash-android#5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Record audio to a single continuous file that survives the process being killed.
The requirement, unchanged from iOS
Audio is not chunked. Transcripts are chunked so the vault agent can read them mid-walk; audio
is not, because it is not synced mid-walk and there is nothing to gain. Chunking introduces a seam
problem — a click and a missing syllable every sixty seconds — and owes a concatenation step at
exactly the moment that can least afford to fail.
What must hold: a recording killed at minute 40 still yields 40 minutes of audio.
Format
AAC, mono, ~48 kbps, matching iOS. Not lossless — v2 wrote ALAC and put a 45-minute memo in the
hundreds of megabytes, which is fine when nothing syncs and not fine once M3 moves audio over a
cell connection.
The open question, and it is the substance of this issue
iOS solves truncation-tolerance with
AVAssetWriter.movieFragmentInterval, which flushes aplayable MP4 index periodically. Android's
MediaMuxerhas no equivalent worth relying on, andMediaRecorderwrites its index at the end — kill it mid-recording and the.m4ais a file full ofaudio that nothing will open. That is exactly the v2 failure this project exists to remove.
Two candidate answers, and Wes's call at the planning checkpoint:
Raw ADTS AAC (
.aac). Every frame carries its own header, so the format needs no index at alland a truncated file is simply a shorter recording. This is structurally more robust than what
iOS does — there is no flush interval to lose, no window at all. The cost is that the file on disk
stops matching iOS's
.m4a, which breaks format parity across clients that M3 has to reconcile.ADTS during capture, remuxed to
.m4aat stop. Parity is preserved and the crash artifact isstill complete. The cost is a remux step at stop and a recovery path that finds an orphaned
.aacand finishes it — which is the same shape as the transcript's assembly and recovery, so it is a
pattern the app already has to have.
The second is the recommendation, but it is a real decision with a real cost and it belongs to Wes,
not to the implementation.
Read the current
MediaCodec/MediaMuxer/AudioRecorddocumentation before building on anyof this. The iOS issue carried the same warning about
movieFragmentIntervaland it was the rightcall — the mechanism is the load-bearing claim in this issue.
Naming
YYYY-MM-DD-HHMMSS-<hash>_audio.<ext>, stamped with the original capture time. Carried overfrom v2 deliberately: the vault's
CLAUDE.mddocuments it and inbox processing depends on it. Theformat spec lives in
stash-docsand both clients must agree — if this issue changes theextension, the spec changes with it, in the same PR.
Scope
AudioRecordfeedingMediaCodec, writing to the app's files directory.Done when
A recording plays back correctly, and force-stopping the app mid-recording still leaves a playable
file containing everything up to the kill.
Milestone 1 of Stash for Android, mirroring
stash-iosM1. M1 is a functioning app on the testdevice with no sync at all — record voice memos reliably and get them off the device by hand.
Sync arrives in M3.
Test device is a Pixel 6 (Tensor G1) borrowed from a friend, until something better turns up.
Stock Android, so no OEM battery-killer behaviour to fight — worth remembering that a phone from
another manufacturer may not be as forgiving about background work.
Translation of
Stash/stash-ios#5. Where the platform forces a different answer this issue says so; where itdoes not, the iOS issue is the fuller statement of intent and should be read alongside this one.
Working agreement for every issue in this repo: feature branch, tested before the PR opens
(on-device where the label says so), then Wes reviews the PR and we walk the code together. Docs
update in the same commit. A PR that takes more than 20 minutes to review is too big — say so and
split it.