Fragmented MP4 (fMP4) Recording Implementation in FadCam: Complete Technical Guide
FadCam implements fragmented MP4 recording through FragmentedMp4MuxerWrapper, which wraps AndroidX Media3's FragmentedMp4Muxer to generate 2-second streamable fragments with per-track timestamp normalization, enabling crash-safe local storage and real-time HLS streaming without file corruption.
FadCam is an open-source Android camera application that records video using fragmented MP4 (fMP4)—a container format that writes moov atom metadata incrementally rather than deferring it to the end of the file. This architecture makes recorded files instantly playable and resistant to corruption if the app crashes or the device loses power. According to the FadCam source code in anonfaded/FadCam, the implementation centers on adapting Android's MediaCodec output to Media3's muxing pipeline while handling complex timestamp normalization and live-streaming callbacks.
Core Architecture: FragmentedMp4MuxerWrapper
The recording pipeline centers on FragmentedMp4MuxerWrapper, which acts as an adapter between Android's MediaCodec encoder and Media3's fragmented MP4 muxer. This wrapper solves three critical problems: timestamp normalization, format conversion, and fragment-based streaming.
Media3 Muxer Integration
The wrapper instantiates FragmentedMp4Muxer with a callback consumer that receives each completed fragment. The muxer emits init segments (containing ftyp and moov atoms) and media fragments (containing moof and mdat atoms) every 2 seconds.
Consumer<ProcessedSegment> segmentConsumer = segment -> handleProcessedSegment(segment);
this.muxer = new FragmentedMp4Muxer.Builder(segmentConsumer)
.setFragmentDurationMs(2000) // 2-second fragments
.build();
The 2-second fragment duration is explicitly tuned for stable live-streaming and seeking behavior, as noted in the source comments regarding "CRITICAL FIX for fMP4 seeking."
Track Registration and Format Conversion
Tracks are registered via addTrack(MediaFormat), which converts Android's MediaFormat to Media3's Format object. The wrapper extracts and preserves:
- Video parameters: width, height, frame-rate, bit-rate, and color information (BT.709/BT.2020 color spaces) required for proper VLC playback of HEVC content.
- Audio parameters: Sample rate, channel count, and a cleanly generated AAC AudioSpecificConfig for proper
esdsbox creation (mp4a.40.2).
Track indices (videoTrackIndex, audioTrackIndex) are stored to handle flag conversion differently per track—ensuring AAC samples are never incorrectly marked as key frames.
Timestamp Normalization Strategy
Android MediaCodec timestamps are system-uptime based and monotonically increasing across recording sessions. Without normalization, a new recording would incorrectly appear as a 45-minute-old video. FadCam solves this through per-track timestamp offset mapping.
Offset Calculation Logic
The wrapper maintains a SparseArray<Long> named timestampOffsets. When the first sample arrives for each track, the wrapper captures its presentation timestamp (PTS) as the baseline offset. All subsequent samples subtract this offset, forcing timestamps to start at 0 microseconds for every new recording.
if (!timestampOffsetsInitialized) {
if (timestampOffsets.get(trackIndex) == null) {
timestampOffsets.put(trackIndex, bufferInfo.presentationTimeUs);
}
if (timestampOffsets.size() >= trackCount) {
timestampOffsetsInitialized = true;
}
}
long normalizedPts = bufferInfo.presentationTimeUs - timestampOffsets.get(trackIndex);
This normalization occurs within writeSampleData before forwarding buffers to the Media3 muxer.
Sample Processing and Fragment Emission
The writeSampleData method receives raw ByteBuffer objects from the GL recording pipeline. The wrapper validates that buffer.remaining() matches the reported size but does not re-position the buffer, maintaining zero-copy efficiency where possible.
Buffer Flags and Media3 Conversion
The wrapper constructs Media3 BufferInfo objects using the normalized PTS and converts Android flags to Media3 constants:
MediaCodec.BUFFER_FLAG_KEY_FRAME→C.BUFFER_FLAG_KEY_FRAMEMediaCodec.BUFFER_FLAG_END_OF_STREAM→C.BUFFER_FLAG_END_OF_STREAM
Fragment Callback Handling
The handleProcessedSegment method processes two fragment types:
- Init segments (
segment.isInitSegment): Contains file type and movie header atoms. These are sent toRemoteStreamManagerand, when inSTREAM_AND_SAVEmode, written to the local file descriptor. - Media fragments: Contains movie fragment boxes and media data. These are uploaded to the HLS server via
RemoteStreamManager.onFragmentCompleteand optionally persisted to disk.
The callback validates the underlying FileDescriptor before writing, preventing EBADF errors during abrupt shutdowns.
Metadata and Configuration
Orientation and Timestamps
Before calling start(), the wrapper injects orientation metadata using Mp4OrientationData to support video rotation hints (e.g., 90° for portrait). Creation timestamps are converted from Unix epoch to MP4 epoch (1904-01-01) using Mp4TimestampData.unixTimeToMp4TimeSeconds, ensuring compatibility with standard MP4 parsers.
Color Information Extraction
For video tracks, the wrapper extracts color space, range, and transfer characteristics from MediaFormat and populates a Media3 ColorInfo object. This metadata is essential for VLC and other players to correctly render HDR or wide-gamut content without color distortion.
Live Streaming Integration
FadCam's fMP4 implementation supports simultaneous local recording and remote streaming. The FragmentedMp4MuxerWrapper communicates with RemoteStreamManager through the ProcessedSegment callback interface.
When streaming is active:
- Init segments initialize the HLS stream manifest
- Media fragments are uploaded as discrete files for adaptive bitrate streaming
- The wrapper ensures fragments are written to disk only when
STREAM_AND_SAVEmode is active, optimizing I/O for pure streaming scenarios
Implementation Example
The following pattern demonstrates typical usage within FadCam's recording pipeline:
// Initialize wrapper with output path
FragmentedMp4MuxerWrapper muxer = new FragmentedMp4MuxerWrapper("/sdcard/Movies/fadcam.mp4");
// Register tracks from MediaCodec formats
int videoTrack = muxer.addTrack(videoMediaFormat);
int audioTrack = muxer.addTrack(audioMediaFormat);
// Set metadata
muxer.setOrientationHint(90);
muxer.setLocation(37.7749f, -122.4194f);
// Begin muxing
muxer.start();
// During encoding (called from GLRecordingPipeline)
muxer.writeSampleData(videoTrack, encodedVideoBuffer, bufferInfo);
muxer.writeSampleData(audioTrack, encodedAudioBuffer, bufferInfo);
// Finalize
muxer.stop(); // Writes final fragment
muxer.release(); // Closes FileDescriptor and persists moov
Key Source Files and Responsibilities
| File | Role |
|---|---|
app/src/main/java/com/fadcam/media/FragmentedMp4MuxerWrapper.java |
Core wrapper adapting MediaCodec to Media3's FragmentedMp4Muxer; handles timestamp normalization, format conversion, and live-stream callbacks. |
app/src/main/java/com/fadcam/streaming/RemoteStreamManager.java |
Receives fragment callbacks and manages HLS upload; handles init segments and media fragment dispatch. |
app/src/main/java/com/fadcam/playback/FragmentedMp4Remuxer.java |
Utility for re-packaging existing MP4 files into fragmented format for playback compatibility. |
app/src/main/java/com/fadcam/playback/FragmentedMp4IndexBuilder.java |
Constructs seek tables for fragmented MP4 files, enabling random access during ExoPlayer playback. |
app/src/main/java/com/fadcam/playback/SeekableFragmentedMp4MediaSourceFactory.java |
Factory creating MediaSource instances capable of seeking within fMP4 containers for PlaybackManager integration. |
Summary
- FragmentedMp4MuxerWrapper serves as the bridge between Android's
MediaCodecand Media3's fragmented MP4 muxer, handling format conversion and timestamp normalization. - 2-second fragment duration balances streaming latency with encoding efficiency, emitting
moof/mdatpairs via theProcessedSegmentcallback. - Per-track timestamp offsets reset presentation timestamps to zero for each recording, preventing "old video" artifacts caused by system uptime-based clock sources.
- Dual-path output supports both local file persistence and real-time HLS streaming through
RemoteStreamManager, with validation to prevent write errors on invalid file descriptors. - Color and AAC metadata extraction ensures compatibility with VLC and standard media players, preserving HDR color spaces and proper audio codec configuration.
Frequently Asked Questions
What makes fragmented MP4 different from standard MP4 recording?
Standard MP4 writes the moov atom (metadata index) at the end of the file, requiring the entire recording to finish before the file becomes playable. Fragmented MP4 writes the movie box incrementally alongside moof (movie fragment) headers, making each 2-second segment independently playable and streamable. In FadCam, this means recorded files survive app crashes and can be streamed to remote servers while still recording.
How does FadCam handle timestamp issues during recording?
FadCam solves the system uptime timestamp problem by maintaining a SparseArray<Long> of offsets per track. The first sample's PTS becomes the baseline for that track, and the wrapper subtracts this offset from all subsequent samples. This normalization forces every recording to start at 0 microseconds regardless of how long the device has been running.
Why does FadCam use 2-second fragments specifically?
The 2-second fragment duration (configured via setFragmentDurationMs(2000)) represents a balance between live-streaming latency and encoding overhead. Shorter fragments reduce latency for HLS streaming but increase file size due to repeated moof headers. The 2-second value is explicitly tuned for seeking stability, as noted in the FadCam source code comments regarding the critical fix for fMP4 seeking behavior.
Can FadCam record and stream simultaneously?
Yes. When configured for STREAM_AND_SAVE mode, FragmentedMp4MuxerWrapper sends init segments and media fragments to both the local file descriptor and RemoteStreamManager. The handleProcessedSegment callback validates the FileDescriptor before disk writes while simultaneously uploading fragments to the HLS server, enabling real-time broadcasting without sacrificing local recording quality.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →