# Meetily RecordingSaver Audio File Formats: MP4 (AAC) Implementation Guide

> Learn how Meetily's RecordingSaver implements MP4 AAC audio format saving using FFmpeg. Discover incremental checkpoints and final merge processes for efficient audio recording.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: implementation-guide
- Published: 2026-08-01

---

**Meetily's `RecordingSaver` exclusively supports MP4 container format with AAC audio encoding, using FFmpeg-powered incremental checkpoints and a final merge process.**

The `RecordingSaver` component in Zackriya-Solutions/meetily handles audio persistence for meeting recordings. This Rust-based Tauri module implements a robust two-stage saving strategy that always outputs **MP4 (AAC)** files, with no alternative container formats available. Understanding this architecture helps developers customize recording workflows and troubleshoot audio export issues.

## Supported Audio Format: MP4 (AAC)

Meetily's audio pipeline produces exactly one output format: **MP4 container with AAC audio codec**.

This design decision is embedded across three core modules in `frontend/src-tauri/src/audio/`:

- **[`encode.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/encode.rs)** – Encodes raw PCM to AAC/MP4 via FFmpeg
- **[`incremental_saver.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/incremental_saver.rs)** – Manages checkpoint files and final merge
- **[`recording_saver.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/recording_saver.rs)** – Orchestrates the overall saving lifecycle

No WAV, OGG, FLAC, or other containers are generated at any stage. The final output file is always named `audio.mp4`.

## How RecordingSaver Processes Audio: The Two-Stage Pipeline

The `RecordingSaver` implements **incremental checkpointing** to prevent data loss during long recordings. This approach splits work between runtime encoding and post-recording consolidation.

### Stage 1: Incremental MP4 Checkpoints

During active recording, raw audio chunks are encoded into short MP4 files on-the-fly.

The `IncrementalAudioSaver` struct (defined in [`incremental_saver.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/incremental_saver.rs)) receives `AudioChunk` structs through its `add_chunk` method. Each chunk triggers a call to `encode_single_audio` in [`encode.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/encode.rs), which constructs and executes an FFmpeg command:

```rust
// From encode.rs (lines 18-54)
pub fn encode_single_audio(
    input_path: &Path,
    output_path: &Path,
    sample_rate: u32,
    channels: u16,
) -> Result<(), AudioError> {
    let ffmpeg = find_ffmpeg()?;
    
    let status = std::process::Command::new(ffmpeg)
        .arg("-f").arg("f32le")          // Input format: 32-bit float PCM
        .arg("-ar").arg(sample_rate.to_string())
        .arg("-ac").arg(channels.to_string())
        .arg("-i").arg(input_path)        // Raw PCM input
        .arg("-c:a").arg("aac")           // AAC codec
        .arg("-b:a").arg("128k")          // Bitrate
        .arg("-movflags").arg("+faststart")
        .arg(output_path)                 // Output: MP4 checkpoint
        .status()?;
        
    // Validation logic follows...
}

```

Each checkpoint receives a sequential filename: `audio_chunk_000.mp4`, `audio_chunk_001.mp4`, etc. These files accumulate in the meeting folder until recording stops.

### Stage 2: Final MP4 Merge

When `stop_and_save` is called, `IncrementalAudioSaver::finalize` merges all checkpoints into a single output file.

The merge operation uses FFmpeg's **concat demuxer**, which avoids re-encoding and preserves audio quality:

```rust
// From incremental_saver.rs (lines 31-34, 47-55)
pub async fn finalize(&mut self) -> Result<PathBuf, AudioError> {
    // ... validation and list file creation ...
    
    let output_path = self.output_dir.join("audio.mp4");
    self.merge_checkpoints(&output_path).await?;
    Ok(output_path)
}

async fn merge_checkpoints(&self, output_path: &Path) -> Result<(), AudioError> {
    let concat_list = self.create_concat_list()?;  // Generates file list for demuxer
    
    let status = std::process::Command::new(&self.ffmpeg)
        .arg("-f").arg("concat")
        .arg("-safe").arg("0")
        .arg("-i").arg(&concat_list)      // Input: list of MP4 checkpoints
        .arg("-c").arg("copy")            // Stream copy: no re-encoding
        .arg(output_path)                 // Final merged MP4
        .status()?;
        
    // Cleanup and validation follow...
}

```

The `-c copy` flag ensures **lossless concatenation**—audio frames are copied directly without transcoding, preserving the original AAC encoding from each checkpoint.

## Configuring Audio Saving in Your Code

### Enable MP4 Recording (Auto-Save)

To capture audio with incremental MP4 checkpoints:

```rust
let mut saver = RecordingSaver::new();
saver.set_meeting_name(Some("Team Sync".into()));

// `true` enables incremental MP4 checkpoint saving
let audio_sender = saver.start_accumulation(true);

```

This call:
- Creates the meeting folder structure
- Initializes an `IncrementalAudioSaver` instance
- Spawns the accumulation task that processes `AudioChunk` data

Audio chunks flow through the pipeline automatically:

```rust
// Inside recording_saver.rs accumulation task
if save_audio {
    // Calls encode_single_audio via IncrementalAudioSaver::add_chunk
    saver_arc.lock().await.add_chunk(chunk)?;
}

```

### Retrieve Final MP4 Output

After stopping recording, await the final merged file:

```rust
let final_path = saver
    .stop_and_save(&app_handle, Some(recording_seconds))
    .await?
    .expect("audio file path");

println!("Recording saved to {}", final_path);
// Output: /path/to/meeting/Team_Sync_2024-01-15/audio.mp4

```

### Disable Audio Saving (Transcripts Only)

To skip MP4 generation entirely—storing only transcript data:

```rust
// From recording_saver.rs (lines 36-44)
let audio_sender = saver.start_accumulation(false); // No MP4 files created

```

When `auto_save` is `false`, the `IncrementalAudioSaver` is never initialized. The `save_audio` boolean prevents checkpoint encoding, and `stop_and_save` returns `Ok(None)` for the audio path.

## Key Source Files and Responsibilities

| File | Purpose | Critical Functions |
|------|---------|------------------|
| [`frontend/src-tauri/src/audio/recording_saver.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/recording_saver.rs) | High-level orchestration; decides whether to enable audio saving based on `auto_save` parameter | `start_accumulation()` (lines 36-44), `stop_and_save()` (lines 73-80) |
| [`frontend/src-tauri/src/audio/incremental_saver.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/incremental_saver.rs) | Checkpoint file management and MP4 concatenation | `add_chunk()` (lines 52-75), `finalize()`, `merge_checkpoints()` (lines 14-45) |
| [`frontend/src-tauri/src/audio/encode.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/encode.rs) | FFmpeg wrapper for AAC/MP4 encoding | `encode_single_audio()` (lines 18-54) |

## Limitations and Design Constraints

The `RecordingSaver` format support is intentionally narrow:

- **No format selection**: Users cannot choose WAV for uncompressed audio or OGG for smaller files
- **Fixed filename**: Output is always `audio.mp4` in the meeting folder
- **Hardcoded AAC parameters**: 128 kbps bitrate and faststart movflags in [`encode.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/encode.rs)
- **FFmpeg dependency**: All operations require FFmpeg binary available at runtime

These constraints simplify the codebase and ensure consistent output across platforms. Future format support would require extending [`encode.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/encode.rs) with codec selection logic and modifying [`incremental_saver.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/incremental_saver.rs) to handle heterogeneous checkpoint types.

## Summary

- **Meetily `RecordingSaver` supports exactly one audio format: MP4 container with AAC codec**
- Two-stage pipeline: incremental MP4 checkpoints during recording, FFmpeg concat merge on stop
- Enable with `start_accumulation(true)`; disable with `start_accumulation(false)` for transcript-only mode
- Core implementation spans [`recording_saver.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/recording_saver.rs), [`incremental_saver.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/incremental_saver.rs), and [`encode.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/encode.rs) in `frontend/src-tauri/src/audio/`
- No alternative formats (WAV, OGG, FLAC) are generated; extension requires source modification

## Frequently Asked Questions

### Can RecordingSaver output WAV or uncompressed audio files?

No. The `encode_single_audio` function in [`encode.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/encode.rs) hardcodes AAC encoding with the `-c:a aac` FFmpeg flag. To obtain uncompressed audio, you would need to fork the repository and modify the encoder to use `-c:a pcm_s16le` with a WAV container extension.

### Why does Meetily use incremental checkpoints instead of writing one continuous file?

Incremental checkpoints prevent data loss if the application crashes during long recordings. Each chunk is independently encoded to a valid MP4 file, so only the most recent unwritten chunk is lost on failure. The final merge using FFmpeg's concat demuxer efficiently stitches these together without quality degradation.

### How does disabling auto-save affect the recording workflow?

When `start_accumulation(false)` is called, `RecordingSaver` skips `IncrementalAudioSaver` initialization entirely. The `save_audio` boolean remains false throughout the accumulation task, so `add_chunk` is never invoked. Upon stopping, `stop_and_save` returns `Ok(None)` for the audio path, and only transcript data persists.