# How FadCam Ensures Video Integrity with Fragmented MP4 Recording: A Technical Deep Dive

> FadCam ensures video integrity with fragmented MP4 recording. Learn how its fMP4 format preserves footage even if an app crashes, safeguarding your recordings.

- Repository: [Faded/FadCam](https://github.com/anonfaded/FadCam)
- Tags: deep-dive
- Published: 2026-05-13

---

**FadCam ensures video integrity by recording in fragmented MP4 (fMP4) format, writing self-contained 2-second fragments (moof + mdat pairs) to disk immediately instead of storing metadata at the end, ensuring that only the last incomplete fragment is lost if the app crashes while all previous footage remains playable.**

FadCam is an open-source Android camera application designed for privacy-conscious users who need reliable video recording. Unlike traditional MP4 containers that risk complete file corruption if recording terminates abruptly, the app ensures video integrity with fragmented MP4 recording by implementing a zero-corruption pipeline using Media3's `FragmentedMp4Muxer`.

## The Problem with Classic MP4 Containers

Standard MP4 files write a single `moov` atom (metadata header) only when the file is properly closed. If an application crashes, loses power, or is force-stopped before closing, the entire video becomes unplayable because the metadata never reaches the disk. This design makes classic MP4 unsuitable for critical recording scenarios where data loss is unacceptable.

## How FadCam Implements Fragmented MP4 Recording

FadCam solves this by treating each video segment as an independent, playable unit. The implementation centers on immediate persistence of fragment data rather than buffering metadata until the end of recording.

### Configuring the FragmentedMp4MuxerWrapper

In [`FragmentedMp4MuxerWrapper.java`](https://github.com/anonfaded/FadCam/blob/main/FragmentedMp4MuxerWrapper.java), the muxer is configured with `setFragmentDurationMs(2000)` to create precisely 2-second fragments. Each fragment consists of a `moof` (movie fragment) box containing metadata followed immediately by an `mdat` (media data) box containing the actual encoded samples.

The wrapper also sets the `tfhd.DEFAULT_BASE_IS_MOOF` flag during track initialization, ensuring timestamps are calculated relative to each fragment's start rather than the global timeline. This prevents timestamp drift that would otherwise corrupt playback when fragments are concatenated.

```java
// Create wrapper – the output will be a fragmented MP4
FragmentedMp4MuxerWrapper muxer = new
    FragmentedMp4MuxerWrapper(outputPath);

// Add video track (MediaFormat from the encoder)
int videoTrack = muxer.addTrack(videoFormat);

// Add audio track (optional)
int audioTrack = muxer.addTrack(audioFormat);

// Start muxer once all tracks are added
muxer.start();

// During encoding, write each sample immediately
muxer.writeSampleData(videoTrack, videoByteBuffer, videoInfo);
muxer.writeSampleData(audioTrack, audioByteBuffer, videoInfo);

// When finished
muxer.stop();
muxer.release();

```

*Relevant source:* [[`FragmentedMp4MuxerWrapper.java`](https://github.com/anonfaded/FadCam/blob/main/FragmentedMp4MuxerWrapper.java)](https://github.com/anonfaded/FadCam/blob/master/app/src/main/java/com/fadcam/media/FragmentedMp4MuxerWrapper.java)

## Enabling Seekable Playback Without Standard MP4 Structure

Standard fMP4 files lack a consolidated index, making random seeking difficult. FadCam overcomes this limitation by building a custom index at runtime.

### Building the Fragment Index

[`FragmentedMp4IndexBuilder.java`](https://github.com/anonfaded/FadCam/blob/main/FragmentedMp4IndexBuilder.java) scans the recorded file for all `moof` boxes and constructs a `FragmentIndex` that maps presentation timestamps to exact byte offsets. This index allows the player to calculate seek positions without scanning the entire file from the beginning.

### Creating a Seekable MediaSource

[`SeekableFragmentedMp4MediaSourceFactory.java`](https://github.com/anonfaded/FadCam/blob/main/SeekableFragmentedMp4MediaSourceFactory.java) uses the fragment index to create a `MediaSource` that supports random-access seeking. When a user scrubs to a specific timestamp, the factory locates the appropriate fragment using the index and injects a `SeekMapInjectingExtractor` into ExoPlayer's pipeline.

```java
// Build a seek‑able MediaSource for a file URI
SeekableFragmentedMp4MediaSourceFactory factory =
        new SeekableFragmentedMp4MediaSourceFactory(context);
MediaSource source = factory.createMediaSource(MediaItem.fromUri(fileUri));

// Feed the source to ExoPlayer
ExoPlayer player = new ExoPlayer.Builder(context).build();
player.setMediaSource(source);
player.prepare();
player.play();

```

*Relevant source:* [[`SeekableFragmentedMp4MediaSourceFactory.java`](https://github.com/anonfaded/FadCam/blob/main/SeekableFragmentedMp4MediaSourceFactory.java)](https://github.com/anonfaded/FadCam/blob/master/app/src/main/java/com/fadcam/playback/SeekableFragmentedMp4MediaSourceFactory.java)

## Converting to Standard MP4 for External Compatibility

While fMP4 ensures crash safety, many external players and editors expect a traditional MP4 structure with the `moov` atom at the beginning. FadCam handles this conversion losslessly when exporting.

### The Remuxing Process

[`FragmentedMp4Remuxer.java`](https://github.com/anonfaded/FadCam/blob/main/FragmentedMp4Remuxer.java) detects fragmented MP4 files by scanning for the `moof` box signature. When the user chooses to export or share a video, the remuxer invokes FFmpeg with the arguments `-c copy -movflags +faststart`, which copies the video and audio streams without re-encoding while moving the `moov` atom to the file's start.

```java
File input = new File(recordedPath);
FragmentedMp4Remuxer remuxer = new FragmentedMp4Remuxer(context);

// Check if conversion is required
if (remuxer.needsRemux(input)) {
    File output = remuxer.remuxSync(input);
    // `output` now holds a seek‑able, fast‑start MP4 that can be shared
}

```

*Relevant source:* [[`FragmentedMp4Remuxer.java`](https://github.com/anonfaded/FadCam/blob/main/FragmentedMp4Remuxer.java)](https://github.com/anonfaded/FadCam/blob/master/app/src/main/java/com/fadcam/playback/FragmentedMp4Remuxer.java)

## Summary

- **Crash-resistant recording**: `FragmentedMp4MuxerWrapper` writes independent 2-second fragments to disk immediately, ensuring only the final incomplete fragment is lost during abrupt termination.
- **Accurate seeking**: `FragmentedMp4IndexBuilder` and `SeekableFragmentedMp4MediaSourceFactory` construct a custom fragment index that enables ExoPlayer to seek accurately without a standard `sidx` index.
- **Timestamp integrity**: The `tfhd.DEFAULT_BASE_IS_MOOF` flag ensures each fragment contains valid,独立 decodeable timestamps relative to its own start.
- **Lossless export**: `FragmentedMp4Remuxer` converts fMP4 to standard MP4 using FFmpeg's `-c copy` mode, producing gallery-compatible files without quality degradation.

## Frequently Asked Questions

### What makes fragmented MP4 more crash-resistant than standard MP4?

Standard MP4 stores all metadata in a `moov` atom at the file's end, meaning a crash leaves the video unplayable. Fragmented MP4 writes a complete `moof` + `mdat` pair every 2 seconds, making each fragment self-contained and playable even if the recording terminates unexpectedly.

### How does FadCam enable seeking in fragmented MP4 files?

`FragmentedMp4IndexBuilder` scans for `moof` boxes to build a byte-offset index, while `SeekableFragmentedMp4MediaSourceFactory` uses this index to translate seek requests into exact fragment locations, allowing ExoPlayer to jump to specific timestamps without sequential scanning.

### Why does FadCam need to remux videos for external sharing?

Most gallery apps and editors expect a traditional MP4 structure with the `moov` atom at the start. The `FragmentedMp4Remuxer` converts the crash-safe fMP4 format to this standard layout using FFmpeg's `-movflags +faststart`, ensuring universal compatibility while preserving original quality.

### What happens to the video file if the app crashes mid-recording?

Only the actively writing 2-second fragment is lost; all previous fragments remain intact and playable. When the app restarts, the video file contains valid data up to the last complete fragment boundary, minimizing data loss to approximately 2 seconds maximum.