An MP4 is a box of boxes
An MP4 file is built from boxes. (The specification's older name for them, atoms, is the one that stuck in error messages.) Every box starts with the same 8-byte header: 4 bytes that say how big the box is, then 4 characters that say what kind of box it is. That's the whole grammar. Boxes can contain other boxes, and a parser walks the file by reading a header, deciding whether to look inside, and skipping ahead by the declared size to the next one.
Open any finished recording (from a phone, a GoPro, OBS, a drone) and at the top level you'll find the same three boxes doing all the work:
Declares "this is an MP4" and which flavor of the format players should expect. The first thing in the file.
The compressed audio and video frames, packed back to back in the order they were recorded. No labels, no timestamps, no frame boundaries you can see from outside, just payload.
The map of everything above: where each frame starts, how big it is, when it plays. Boxes inside boxes:
MOV files, the QuickTime format MP4 was derived from, share this exact structure, which is why a MOV that won't open and an MP4 that won't open are nearly always the same problem wearing different extensions.
The moov atom is the map
Here's the part that surprises people: the data block is opaque. Compressed frames sit back to back in mdat with nothing separating them, no markers saying "frame 412 starts here." Every question a player needs answered lives in the moov atom instead, in a set of structures called the sample tables:
- Where is each frame? The chunk-offset table (
stco) records byte positions inside the file. - How big is each frame? The sample-size table (
stsz) lists every frame's length, one entry per frame. - When does each frame play? The time-to-sample table (
stts) maps frames onto the movie's clock, so audio and video stay in lockstep. - Where can playback jump to? The sync-sample table (
stss) lists the keyframes, the only frames a decoder can start from, which is what makes seeking possible.
Even the decoder's configuration (the parameters that tell it how the video was compressed) is stored in the moov atom, not in the stream. A player without the moov atom isn't just missing chapter markers; it doesn't know where a single frame begins, how long the video is, or how to set up its decoder. Think of a library where the books are shelved with no spines, no titles, and no catalog: the information all exists, and none of it is reachable. That's mdat without moov, and it's why players give up completely instead of playing "most of" a damaged file.
Why the index is written last
The moov atom describes every frame: its position, its size, its timing. While you're recording, none of that is knowable: the camera has no idea whether you'll stop in ten seconds or two hours, and a frame's byte offset can't be recorded before the frame exists. So every recorder does the only thing it can. It streams compressed frames into mdat as they're encoded, keeps the running bookkeeping in memory, and writes the index in one pass at the moment you press stop.
During recording: frames are appended as they're encoded. The index can't exist yet; its contents aren't known until the last frame is written.
Finalization: the recorder measures everything it wrote and appends the moov atom. Only now is the file playable.
This design is efficient and safe, right up until the recording doesn't reach the finalization step. A dead battery, an OBS crash, a drone in a lake: the process writing the file stops, and the index is never written. What's left on disk is a file with a healthy size, hours of intact footage in mdat, and no map. Every player refuses it; ffmpeg prints moov atom not found. The footage isn't gone; it's stranded. (The very last seconds, still in the camera's memory buffer and not yet flushed to storage, are gone. Honest tools say so.) That stranded-but-intact state is precisely what moov-atom repair exists for, and it's the standard failure mode of crashed OBS recordings.
faststart and fragmented MP4
The index-at-the-end design has two well-known consequences, and the industry built a fix for each.
faststart: move the index to the front, after the fact
A browser streaming a video from the web reads the file front to back. If the moov atom is at the end, playback can't start until the whole file has downloaded; the player is holding the book and waiting for the catalog. The fix is a post-processing pass (ffmpeg's -movflags +faststart) that rewrites the finished file with the moov atom moved up front:
A “faststart” file: index first, so streaming playback can begin immediately. This is why some web videos start instantly and others stall.
Note what faststart is not: crash protection. It runs on a complete, healthy file after encoding ends. A recording that dies mid-write never reaches that step.
Fragmented MP4: many small indexes instead of one big one
The second consequence (lose the finalization, lose the file) is addressed by fragmented MP4. Instead of one mdat and one moov at the end, the file is written as a chain of short self-describing segments, each a slice of data with its own mini-index (a moof box). In ffmpeg terms, -movflags frag_keyframe+empty_moov: a skeleton moov up front, then fragment after fragment. If the recorder dies, you lose at most the final fragment, and everything before it is already indexed and playable. Streaming services deliver video this way for the same reason. The tradeoff is compatibility: some editors and older players handle fragmented files poorly, which is why cameras still write the classic layout. OBS's answer (in OBS 30.2 and later) is "Hybrid MP4," which keeps fragment-style crash resistance while finalizing into a widely compatible file, worth enabling if OBS crashes have burned you before.
How moov reconstruction works
So a crash left you with ftyp, a giant mdat, and no index. Rebuilding it is possible because the "opaque" data block isn't perfectly opaque to a parser that knows the codecs. Inside mdat, video frames are stored as length-prefixed units, and each codec's bitstream has recognizable patterns: headers that mark a frame's type, dimensions, and boundaries. Reconstruction tools (the open-source untrunc established the approach, and it's the class of repair IntactFile's video engine performs) walk the raw data byte by byte:
- Find the frame boundaries. Scan
mdat, using the length prefixes and codec signatures to identify where each video and audio frame starts and ends. - Classify and measure. Assign each frame to its stream, and record its size and position, rebuilding sample by sample the raw material of
stszandstco. - Reconstruct timing. Derive frame durations from the encoder's settings. This is why
untruncasks for a healthy reference file recorded with the same settings, to learn what "normal" looks like for your device. - Write a new moov and finalize. Assemble the sample tables into a fresh index and write out a spec-correct file.
The result plays everything that physically survived, ending where the data ends. The honest limitations follow directly from the mechanism: recordings with highly irregular frame timing can come back with minor audio/video drift, and if mdat itself is zeros (the storage failed rather than the recorder) there are no frames to find, and no tool can conjure them. For the broader picture of which damage is which, see how video files get corrupted and what's actually repairable.
FAQ
What is the moov atom in an MP4 file?
The moov atom is the MP4's index: a structured block that records where every video and audio frame sits in the file, how large each one is, when it should be displayed, and how the streams synchronize. It also carries the decoder configuration. A player reads the moov atom first and uses it as a map into the media data. Without a valid moov atom, the file won't open, even when the footage itself is intact.
Why is the moov atom at the end of the file?
Because its contents aren't known until recording ends. The moov atom lists the size, position, and timing of every frame, information that only exists once the last frame has been written. So recorders append frames as they go and write the index in one pass at finalization, which lands it after the media data. Files prepared for web streaming often have the moov moved to the front afterward ("faststart"), but that's a post-processing step, not how the file is born.
Can a missing moov atom be rebuilt?
Usually yes, provided the media data (the mdat block) survived. Reconstruction tools scan the raw stream, detect the boundaries of each frame, measure sizes and positions, and rebuild the sample tables the moov atom would have contained. The result is a playable file covering everything up to the point of interruption. If the mdat block is missing, zeroed out, or overwritten, there is nothing to index, and no tool can rebuild the file. You can check a file in your browser to see which case yours is.
What is the difference between moov and mdat?
mdat holds the payload: the compressed audio and video frames, packed back to back, typically 99% of the file's size. moov holds the metadata: the index describing where each frame in mdat begins, its size, its timing, and the decoder settings needed to play it. mdat without moov is footage without a map: the data is there but no player can navigate it. moov without mdat is a map of a territory that no longer exists.
What does movflags faststart do?
In ffmpeg, -movflags +faststart relocates the moov atom from the end of a finished file to the front, right after the file-type header. Players can then start playback while the rest of the file is still downloading, which is why it's standard for web video. It requires rewriting the file (ffmpeg does a second pass) and it only works on a healthy, complete file. It does not protect a recording against crashes and can't fix a file whose moov was never written.
Also in this series: how video files get corrupted — and what's actually repairable.