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

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/:

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) receives AudioChunk structs through its add_chunk method. Each chunk triggers a call to encode_single_audio in encode.rs, which constructs and executes an FFmpeg command:

// 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:

// 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:

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:

// 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:

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:

// 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 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 Checkpoint file management and MP4 concatenation add_chunk() (lines 52-75), finalize(), merge_checkpoints() (lines 14-45)
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
  • 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 with codec selection logic and modifying 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, incremental_saver.rs, and 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 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →