# How Time Lapse Recording Works with Sampling Intervals and MP4 Conversion in atomcam_tools

> Discover how atomcam_tools time lapse recording works. Learn about sampling intervals, temporary storage, and on-the-fly MP4 conversion with ISO-BMFF metadata.

- Repository: [Mitsuru Nakada/atomcam_tools](https://github.com/mnakada/atomcam_tools)
- Tags: how-to-guide
- Published: 2026-03-07

---

**The atomcam_tools time-lapse feature captures frames at user-defined sampling intervals, stores them in a temporary binary container, and converts them to standards-compliant MP4 files by constructing ISO-BMFF metadata boxes on the fly.**

The time-lapse subsystem is implemented in [`libcallback/timelapse.c`](https://github.com/mnakada/atomcam_tools/blob/main/libcallback/timelapse.c) and operates as a background thread that bridges raw H.264 frame acquisition with final MP4 packaging. This implementation allows the Atomcam camera to record long-duration events by sampling frames at configurable intervals while maintaining precise control over the playback frame rate in the resulting video file.

## Overview of the Time-Lapse Architecture

The architecture follows a three-stage pipeline designed to minimize memory usage while maintaining data integrity. All operations are handled by the **`TimelapseThread`** background thread, synchronized via mutexes to ensure safe operation during start, stop, and restart commands.

- **Stage 1: Initialization**. The `Timelapse()` function (lines 33-78) parses user parameters and creates two files: a binary metadata table (`*.stsz`) containing recording parameters, and a temporary video container (`*._mp4`) pre-structured with `ftyp` and placeholder `mdat` boxes.

- **Stage 2: Recording Loop**. At each sampling interval, the thread grabs raw H.264 NAL units via `video_get_frame()`, writes them to the temporary MP4, and logs each frame's offset and size in the `*.stsz` file (lines 81-124).

- **Stage 3: MP4 Finalization**. When recording completes, the `AppendMoov()` routine (lines 586-737) reads the metadata table, constructs the missing MP4 index boxes (moov, stsd, stts, stsc, stsz, stco), embeds H.264 codec configuration data, and renames the file to `*.mp4`.

## How Sampling Intervals and FPS Are Configured

### Command Syntax and Parameter Parsing

Users initiate recording through the command-line interface parsed in `Timelapse()` at lines 33-51:

```bash
timelapse <file> <interval> <count> [<out-fps>] [<schedule-no>]

```

- **`<interval>`**: Seconds between frame captures (sampling interval).
- **`<count>`**: Total number of snapshots to acquire.
- **`[<out-fps>]`**: Playback frame rate for the final MP4 (defaults to **20 fps** if unspecified).
- **`[<schedule-no>]`**: Optional schedule identifier for automated triggers.

These parameters populate the **`StszHeaderSt`** structure written to the `.stsz` file header (lines 64-71), preserving the configuration across potential recording interruptions.

### Frame Acquisition Timing Loop

The recording thread calculates target wall-clock timestamps to maintain the sampling interval. In lines 72-78, the code computes the next capture second:

```c
int targetTime = ProcessingInfo.endTime -
     ((ProcessingInfo.endTime - now.tv_sec) * 1000 - 500) /
     (ProcessingInfo.interval * 1000) *
     ProcessingInfo.interval;

```

The thread sleeps in one-second increments until `now.tv_sec` reaches `targetTime`, ensuring frames are spaced by the user-defined interval with ±1 second granularity. This approach balances precision with power efficiency suitable for low-rate time-lapse capture.

### Output Playback Frame Rate

The header's `fps` field determines the time-scale for the final video. During finalization (lines 554-558), `AppendMoov()` calculates:

```c
unsigned int timeScale = 1000;          // milliseconds per second
unsigned int sampleDelta = timeScale / ProcessingInfo.fps;

```

This `sampleDelta` value populates the **`stts`** (Time to Sample) box, dictating that each frame displays for `1000/fps` milliseconds. Consequently, a recording with a 10-second sampling interval and 30 fps output plays back 300 times faster than real-time.

## MP4 Conversion and Finalization Process

### Container Structure and Temporary Files

Upon initialization (lines 61-69), the system creates `*._mp4` containing an **ftyp** (File Type) box identifying the file as MP4, followed by an **mdat** (Media Data) box with a placeholder size field. As frames arrive during recording, NAL units are appended immediately after the mdat header, and the size field updates dynamically to reflect the growing data payload.

### H.264 Parameter Extraction

When converting to final MP4 format, `AppendMoov()` (lines 227-241) scans the temporary file for **SPS** (Sequence Parameter Set, NAL type `0x27`) and **PPS** (Picture Parameter Set, NAL type `0x28`) units. These essential decoder configuration parameters embed into the **`avcC`** box within the sample description table, ensuring playback compatibility across standard video players.

### ISO-BMFF Box Construction

The `AppendMoov()` function (lines 254-560 and 586-737) manually constructs the MP4 container hierarchy byte-by-byte:

- **moov** (Movie) box contains the metadata track
- **trak** (Track) box defines the video stream
- **mdia** (Media) and **minf** (Media Information) boxes wrap the sample tables
- **stbl** (Sample Table) box contains:
  - **`stsd`**: Sample description with H.264 configuration
  - **`stts`**: Time-to-sample mappings using the calculated `sampleDelta`
  - **`stsc`**: Sample-to-chunk mappings (optimizing with 8 samples per chunk)
  - **`stsz`**: Individual sample sizes retrieved from the `.stsz` metadata file
  - **`stco`**: Chunk offset locations within the file (lines 617-702)

### File Finalization

After writing all index tables, the system performs atomic completion (lines 734-737):

1. Renames `*._mp4` to `*.mp4`
2. Removes the auxiliary `*.stsz` metadata file
3. Calls `sync()` to flush filesystem buffers

The result is a self-contained, seekable MP4 file compliant with ISO Base Media File Format specifications.

## Practical Usage Examples

### Start a New Time-Lapse Session

```bash

# Capture every 10 seconds, 120 frames total, playback at 30 fps

timelapse /media/mmc/timelapse 10 120 30

```

This creates `timelapse.stsz` with metadata and `timelapse._mp4` for raw H.264 storage, then begins the background acquisition thread.

### Stop Recording Early

```bash
timelapse stop

```

The thread receives `Directive_Stop`, exits the acquisition loop while preserving captured data, and keeps the partial `._mp4` file available for finalization.

### Convert Raw Data to MP4

```bash
timelapse mp4 /media/mmc/timelapse

```

`AppendMoov()` processes the existing `.stsz` table and `._mp4` payload, constructing the complete MP4 index and producing `timelapse.mp4`.

### Resume Interrupted Sessions

```bash
timelapse restart

```

The system reloads the existing `.stsz` file, updates internal pointers, and continues appending frames where the previous session ended.

## Key Source Files and Functions

- **[`libcallback/timelapse.c`](https://github.com/mnakada/atomcam_tools/blob/main/libcallback/timelapse.c)**: Core implementation containing `Timelapse()`, `TimelapseThread()`, and `AppendMoov()`.
- **[`libcallback/video_callback.c`](https://github.com/mnakada/atomcam_tools/blob/main/libcallback/video_callback.c)**: Provides `video_get_frame()` for H.264 NAL unit acquisition.
- **[`libcallback/mp4write.c`](https://github.com/mnakada/atomcam_tools/blob/main/libcallback/mp4write.c)**: Utility functions for binary box writing referenced by the finalization routines.
- **[`timelapse_samples/timelapse_hook.sh`](https://github.com/mnakada/atomcam_tools/blob/main/timelapse_samples/timelapse_hook.sh)**: Example shell integration demonstrating event-triggered time-lapse initiation.

## Summary

- atomcam_tools implements time-lapse recording in [`libcallback/timelapse.c`](https://github.com/mnakada/atomcam_tools/blob/main/libcallback/timelapse.c) using a dedicated background thread and mutex synchronization.
- The system stores frame offsets in a proprietary `.stsz` metadata file while buffering raw H.264 data in a temporary `._mp4` container.
- Sampling intervals are enforced through wall-clock time calculations with one-second precision, independent of the output playback frame rate.
- Final MP4 conversion constructs ISO-BMFF boxes manually, including critical H.264 SPS/PPS extraction and time-scale calculations based on the user-specified output fps.
- The architecture supports interruption recovery through the `restart` command and metadata persistence in the `StszHeaderSt` structure.

## Frequently Asked Questions

### What file formats does atomcam_tools use during time-lapse recording?

During active recording, atomcam_tools maintains two files: a binary metadata table with the `.stsz` extension containing frame sizes and offsets, and a temporary video file ending in `._mp4` holding raw H.264 NAL units. Upon completion, the `AppendMoov()` function consolidates these into a standards-compliant `.mp4` file and removes the auxiliary metadata.

### How does the sampling interval affect the final MP4 playback speed?

The sampling interval determines how much wall-clock time passes between captured frames, while the output fps parameter (default 20) controls playback speed. For example, a 60-second sampling interval with 30 fps output produces video playing 1800 times faster than reality. The `stts` box in the final MP4 uses `sampleDelta = 1000 / fps` to enforce this timing.

### Can I recover a time-lapse recording if the camera loses power?

Yes, provided the `.stsz` metadata file and `._mp4` temporary file remain intact on the storage medium. The `timelapse restart` command reloads the existing `StszHeaderSt` structure from the `.stsz` file and resumes appending frames. If recording cannot continue, `timelapse mp4 <file>` finalizes the partial data into a playable video up to the point of interruption.

### Where does atomcam_tools store H.264 codec configuration data?

During MP4 finalization, `AppendMoov()` scans the temporary video file for SPS (NAL type `0x27`) and PPS (NAL type `0x28`) units (lines 227-241). These parameters embed into the `avcC` configuration record within the `stsd` (Sample Description) box, ensuring the final MP4 contains the necessary decoder initialization data for standard playback.