fMP4 Recording in FadCam: Code Examples and Implementation Guide
FadCam records fragmented MP4 (fMP4) videos using the Media3 FragmentedMp4Muxer wrapped in a custom FragmentedMp4MuxerWrapper class, enabling crash-safe, stream-ready recordings with automatic 2-second fragments.
FadCam is an open-source Android camera app that implements fragmented MP4 recording through the Media3 ExoPlayer library. Unlike traditional MP4 containers that require header finalization at the end of recording, fMP4 writes self-contained moof/mdat fragments on-the-fly, making videos resilient to crashes and immediately compatible with HTTP Live Streaming (HLS). The implementation centers around a custom wrapper class that handles track registration, timestamp normalization, and seamless segment roll-over for continuous recording.
What Is fMP4 Recording and Why FadCam Uses It
Fragmented MP4 (fMP4) splits video data into independent segments containing their own metadata headers. Each fragment is typically 2 seconds long and includes all information necessary for decoding, eliminating the need for a central index that traditional MP4 files require at the end of the file.
Key advantages for FadCam users:
- Crash safety: If the app terminates unexpectedly, recorded fragments remain playable because each fragment is self-contained.
- Live streaming readiness: The 2-second fragments align with HLS segment requirements, enabling real-time streaming capabilities.
- Seekable recordings: Fragment boundaries allow the player to seek without scanning the entire file.
Core Architecture for fMP4 Recording
The fMP4 implementation in FadCam spans three primary components: the muxer wrapper that interfaces with Media3, the OpenGL recording pipeline that manages encoders, and the service layer that orchestrates the recording lifecycle.
The FragmentedMp4MuxerWrapper Class
Located at app/src/main/java/com/fadcam/media/FragmentedMp4MuxerWrapper.java, this class encapsulates androidx.media3.muxer.FragmentedMp4Muxer and adds Android-specific conveniences for track management and metadata handling.
The wrapper configures 2-second fragments via .setFragmentDurationMs(2000) (lines 80‑88), which ensures stable streaming and proper tfdt (fragment decode time) timestamps. It also handles timestamp normalization (lines 58‑62) by tracking per-track offsets to restart timestamps from 0 ms for each new recording segment, preventing decode errors during playback.
Additional responsibilities include:
- Converting
MediaFormatobjects to Media3Formatinstances inaddTrack()(lines 45‑58) - Injecting orientation hints and GPS location metadata before calling
start()(lines 82‑99)
GLRecordingPipeline Integration
The GLRecordingPipeline class at app/src/main/java/com/fadcam/opengl/GLRecordingPipeline.java instantiates the muxer wrapper and drives the recording process. The setupMuxer() method (lines 46‑56) creates a new FragmentedMp4MuxerWrapper instance for each output file or file descriptor, passing location metadata when available.
During the render loop, the pipeline drains encoder output buffers and writes samples to the muxer. When segment size limits are reached or manual roll-over is requested, the pipeline finalizes the current fragment and reinitializes the wrapper (lines 76‑84), preserving monotonic timestamps across segments.
RecordingService Orchestration
RecordingService.java at app/src/main/java/com/fadcam/services/RecordingService.java manages the lifecycle of the GL pipeline, ensuring the fMP4 muxer is utilized for both camera and screen recording paths. The service handles resource cleanup and guarantees that the muxer receives stop signals even if the recording ends abruptly.
fMP4 Recording Code Examples
The following Java snippets demonstrate the essential steps for recording fragmented MP4 video in FadCam, extracted from the actual source code.
Initializing the Fragmented Muxer
The setupMuxer() method in GLRecordingPipeline creates the wrapper and attaches GPS metadata:
// Called for each new segment
private void setupMuxer() throws IOException {
// Use FragmentedMp4MuxerWrapper for fMP4 output
if (currentOutputFd != null) {
mediaMuxer = new FragmentedMp4MuxerWrapper(currentOutputFd);
} else {
mediaMuxer = new FragmentedMp4MuxerWrapper(currentOutputFilePath);
}
// Attach location metadata if available
if (locationLatitude != null && locationLongitude != null) {
mediaMuxer.setLocation(locationLatitude.floatValue(),
locationLongitude.floatValue());
}
// Reset track indices for the new segment
audioTrackIndex = -1;
videoTrackIndex = -1;
}
Registering Audio and Video Tracks
After creating the muxer, the pipeline registers encoder formats and starts the fragmented container:
int videoTrack = mediaMuxer.addTrack(videoFormat); // videoFormat = MediaFormat from MediaCodec
int audioTrack = mediaMuxer.addTrack(audioFormat); // audioFormat = MediaFormat from AudioRecorder
mediaMuxer.start(); // Begins writing fragmented MP4
Writing Samples in the Render Loop
Each iteration drains encoder buffers and writes timestamped samples to the appropriate track:
// Drain video encoder
ByteBuffer videoBuf = videoEncoder.getOutputBuffer(bufIndex);
long pts = videoEncoder.getOutputBufferInfo(bufIndex).presentationTimeUs;
mediaMuxer.writeSampleData(videoTrack, videoBuf, pts, false, false);
// Drain audio encoder (similar)
ByteBuffer audioBuf = audioEncoder.getOutputBuffer(bufIndex);
long pts = audioEncoder.getOutputBufferInfo(bufIndex).presentationTimeUs;
mediaMuxer.writeSampleData(audioTrack, audioBuf, pts, false, false);
Handling Segment Roll-over
When splitting recordings (due to size limits or user request), the pipeline finalizes the current fragment and creates a fresh muxer:
if (shouldSplitSegment()) {
// Finalize current fragment
mediaMuxer.stop(); // Flushes the last fragment
// Create a fresh muxer for the next segment
setupMuxer();
// Re‑add tracks and start again
videoTrack = mediaMuxer.addTrack(videoFormat);
audioTrack = mediaMuxer.addTrack(audioFormat);
mediaMuxer.start();
}
Exporting and Remuxing fMP4 Files
While fMP4 is optimal for recording and streaming, some players require traditional seekable MP4 containers. FadCam includes FragmentedMp4Remuxer at app/src/main/java/com/fadcam/playback/FragmentedMp4Remuxer.java to convert recordings:
FragmentedMp4Remuxer remuxer = new FragmentedMp4Remuxer();
remuxer.remux(fmp4Path, mp4OutputPath);
This utility reads the fragmented input and produces a standard MP4 with centralized moov headers, enabling compatibility with editing software and older media players.
Key Files in the fMP4 Pipeline
Understanding the complete recording flow requires familiarity with these source files:
FragmentedMp4MuxerWrapper.java– Core wrapper around Media3's fragmented muxer; handles track addition, orientation hints, and timestamp normalization.GLRecordingPipeline.java– Manages MediaCodec encoders, creates the muxer per segment, and drives sample writing in the render thread.RecordingService.java– Service that orchestrates the GL pipeline, handles start/stop commands, and ensures fMP4 muxer usage for all recording modes.FragmentedMp4Remuxer.java– Utility class for converting fMP4 recordings to standard MP4 format for export.FragmentedMp4IndexBuilder.java– Builds timestamp indexes for fast seeking within fMP4 files without requiringsidxboxes.SeekableFragmentedMp4MediaSourceFactory.java– Factory for creating ExoPlayer media sources that support seeking within fragmented recordings during playback.
Summary
- FadCam implements fMP4 recording through a custom
FragmentedMp4MuxerWrapperthat encapsulates Media3'sFragmentedMp4Muxerwith Android-specific metadata handling. - 2-second fragments are configured via
setFragmentDurationMs(2000), producing self-containedmoof/mdatboxes that ensure crash safety and HLS compatibility. - Segment roll-over is handled by the
GLRecordingPipelinefinalizing the current muxer and instantiating a new wrapper, with timestamp normalization preventing decode gaps. - Source code locations include
FragmentedMp4MuxerWrapper.javafor muxer logic andGLRecordingPipeline.javafor the recording workflow. - Export capability is provided by
FragmentedMp4Remuxer.java, which converts fMP4 to standard MP4 for compatibility with non-streaming players.
Frequently Asked Questions
What makes fMP4 recording crash-safe compared to traditional MP4?
Traditional MP4 files write a central moov header at the end of recording containing all sample offsets; if the app crashes before finalization, the file becomes unplayable. FadCam's fMP4 implementation writes self-contained 2-second fragments with embedded moof headers, so each fragment is independently playable even if the recording terminates unexpectedly.
How does FadCam handle video segmentation during long recordings?
The GLRecordingPipeline monitors recording duration and file size, triggering a segment roll-over when limits are reached. This process calls mediaMuxer.stop() to flush the current fragment, then creates a new FragmentedMp4MuxerWrapper instance via setupMuxer(), re-registers tracks, and continues recording with normalized timestamps starting from zero.
Can fMP4 recordings from FadCam be played in standard video players?
Most modern players and browsers support fMP4 playback, but older software may require conversion. FadCam includes FragmentedMp4Remuxer, which remuxes the fragmented container into a traditional MP4 with centralized headers, ensuring compatibility with legacy editors and players that expect non-fragmented structures.
What is the default fragment duration in FadCam's fMP4 implementation?
The default fragment duration is 2 seconds (2000 milliseconds), configured in FragmentedMp4MuxerWrapper.java via the builder method .setFragmentDurationMs(2000). This duration balances streaming latency with encoding efficiency, providing stable tfdt timestamps suitable for HLS delivery.
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 →