Record Everything, Then Read Almost None of It
Oriel's recording philosophy fits in one sentence from its house sign: record everything, delete nothing. The game card writes about 18 GB an hour and the camera card about 5.4. A long broadcast is tens of gigabytes; the disk cap Dad approved holds a little over a hundred hours of both feeds. Yet what Pippa usually wants from all that is one frame from three seconds ago, or nine frames that summarize the last twenty seconds.
So the file layout is designed backwards, from the read. Every choice below exists so that a single frame anywhere in a hundred hours can be reached by opening one small file, seeking once and decoding a handful of frames.
The Layout, Choice by Choice
| Choice | Value | Why |
|---|---|---|
| Container | fragmented MP4 | a crash loses at most the last second; the open segment can be read while it is still being written |
| Segment length | 60 s | a read opens one modest file, never an hour-long one |
| Keyframe interval | every 15th frame | four keyframes a second at 60 fps, so a seek never lands far from a decodable frame |
| B-frames | none | frames decode in the order they are stored, which keeps a seek-and-decode short and predictable |
| Codec | HEVC through VideoToolbox | the Mac's hardware encoder; the bitrate per role was set by measurement |
The keyframe interval was Dad's suggestion, in one short line: "15프레임에 한 번씩", once every fifteen frames. It is a trade. More keyframes make files larger for the same quality, because a keyframe is a whole picture rather than a difference. Fewer keyframes make every random read slower, because a decoder must start at the last keyframe and work forward. At fifteen, the worst case is decoding fourteen frames to reach the one you want, and the architecture document says exactly that: a read seeks to the keyframe before the moment and decodes at most 14 frames.
Fragmented MP4, Briefly
A classic MP4 keeps its index (the moov box, which says where every sample is) in one place, usually written at the end when the file is finished. Kill the writer before that and the file may be unreadable. A fragmented MP4 writes a small header up front and then a series of self-contained fragments, each with its own little index. The recorder asks AVAssetWriter for a fragment every second, so a crash costs at most that last second, and a reader can open a segment that is still being written and read every fragment already on disk. That second property is what lets a "look now" read a moment from a few seconds ago without waiting for the minute to close.
The Sidecar: Facts the Index Needs
Each closed segment gets a JSON sidecar beside it: the session, the input, the wall-clock time of its first frame, its duration, frame count, measured fps, size, GOP and byte count (and, for a deck card, its audio's details). The engine indexes sidecars into one SQLite table, segments, with an index on (input_id, start_at). That table is the map of everything Oriel has ever recorded, and it is small: one row per minute per input.
Why a sidecar instead of having the engine probe each MP4? Because the recorder already knows these facts at the moment it closes the file, and probing a video file costs a process launch and a parse. Writing the facts once, next to the file, means the engine never has to open a recording just to learn what is in it. And when a segment closes without its sidecar (a recorder crash, a full disk), the recorder writes one later from the file itself, marked recovered, so no minute is ever invisible to the index.