Skip to content

Pure Rust MP4 muxer — zero deps, no FFmpeg. Feed it encoded frames, get a standards-compliant MP4. H.264, H.265, AV1, AAC, Opus.

License

Notifications You must be signed in to change notification settings

Michael-A-Kuykendall/muxide

Muxide
The last mile from encoder to playable MP4.

Crates.io Downloads Documentation License MSRV CI

cargo add muxide


Muxide takes correctly-timestamped, already-encoded audio/video frames and produces a standards-compliant MP4 — pure Rust, zero runtime dependencies, no FFmpeg.

Your Encoder
H.264 / HEVC / AV1
AAC / Opus
➡️ Muxide
Pure Rust
Zero deps
➡️ playable.mp4
Standards-compliant
Fast-start ready

Why Muxide Exists

If you're building a recording pipeline in Rust, you know the tradeoffs:

Approach Tradeoff
FFmpeg CLI/libs External binary, GPL licensing concerns, "which build is this?"
GStreamer Complex plugin system, C dependencies, heavy runtime
Raw MP4 writing ISO-BMFF expertise required (sample tables, interleaving, moov layout)
"Minimal" crates Often missing fast-start, strict validation, or production ergonomics

Muxide solves one job cleanly:

Take already-encoded frames with correct timestamps → produce a standards-compliant, immediately-playable MP4 → using pure Rust.

Nothing more. Nothing less.

Installation & Usage

As a Library

cargo add muxide
use muxide::api::{MuxerBuilder, VideoCodec};

let mut muxer = MuxerBuilder::new(file)
    .video(VideoCodec::H264, 1920, 1080, 30.0)?
    .build()?;

// Write your encoded frames...
muxer.write_video(0.0, &h264_frame, true)?;
muxer.finish()?;

As a CLI Tool

# Install globally
cargo install muxide

# Or download pre-built binary from releases
# Then use:
muxide --help

# Quick examples:
muxide mux --video frames/ --output output.mp4 --width 1920 --height 1080 --fps 30
muxide mux --video video.h264 --audio audio.aac --output output.mp4
muxide validate --video frames/ --audio audio.aac
muxide info input.mp4

The CLI tool accepts raw encoded frames from stdin or files and produces MP4 output.

🦀 Used By CrabCamera

Muxide powers CrabCamera - the production-ready desktop camera plugin for Tauri applications. CrabCamera uses Muxide for reliable MP4 output with perfect A/V synchronization.

Check out CrabCamera if you need camera/audio recording in your Tauri app! It provides unified access to desktop cameras and microphones with professional controls.

Core Invariant

Muxide enforces a strict contract:

Your Responsibility Muxide's Guarantee
✓ Frames are already encoded ✓ Valid ISO-BMFF (MP4)
✓ Timestamps are monotonic ✓ Correct sample tables
✓ DTS provided for B-frames ✓ Fast-start layout
✓ Codec headers in keyframes ✓ No post-processing needed

If input violates the contract, Muxide fails fast with explicit errors—no silent corruption, no guessing.


Features

Category Supported Notes
Video H.264/AVC Annex B format
H.265/HEVC Annex B with VPS/SPS/PPS
AV1 OBU stream format
Audio AAC All profiles: LC, Main, SSR, LTP, HE, HEv2
Opus Raw packets, 48kHz
Container Fast-start moov before mdat for web playback
B-frames Explicit PTS/DTS support
Fragmented MP4 For DASH/HLS streaming
Metadata Title, creation time, language
Quality World-class errors Detailed diagnostics, hex dumps, JSON output
Production tested FFmpeg compatibility verified
Comprehensive testing 80+ tests, property-based validation

Design Principles

Principle Implementation
🦀 Pure Rust No unsafe, no FFI, no C bindings
📦 Zero deps Only std — no runtime dependencies
🧵 Thread-safe Send + Sync when writer is
Well-tested Unit, integration, property tests
📜 MIT license No GPL, no copyleft concerns
🚨 Developer-friendly Exceptional error messages make debugging 10x faster

Note: no_std is not supported. Muxide requires std::io::Write.


Quick Start

use muxide::api::{MuxerBuilder, VideoCodec, AudioCodec, Metadata};
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = File::create("recording.mp4")?;
    
    let mut muxer = MuxerBuilder::new(file)
        .video(VideoCodec::H264, 1920, 1080, 30.0)
        .audio(AudioCodec::Aac, 48000, 2)
        .with_metadata(Metadata::new().with_title("My Recording"))
        .with_fast_start(true)
        .build()?;

    // Write encoded frames (from your encoder)
    // muxer.write_video(pts_seconds, h264_annex_b_bytes, is_keyframe)?;
    // muxer.write_audio(pts_seconds, aac_adts_bytes)?;

    let stats = muxer.finish_with_stats()?;
    println!("Wrote {} frames, {} bytes", stats.video_frames, stats.bytes_written);
    Ok(())
}
📹 More Examples: HEVC, AV1, Opus, Fragmented MP4

HEVC/H.265 (4K)

// Requires VPS, SPS, PPS in first keyframe
let mut muxer = MuxerBuilder::new(file)
    .video(VideoCodec::H265, 3840, 2160, 30.0)
    .build()?;
muxer.write_video(0.0, &hevc_annexb_with_vps_sps_pps, true)?;

AV1

// Requires Sequence Header OBU in first keyframe
let mut muxer = MuxerBuilder::new(file)
    .video(VideoCodec::Av1, 1920, 1080, 60.0)
    .build()?;
muxer.write_video(0.0, &av1_obu_with_sequence_header, true)?;

Opus Audio

// Opus always uses 48kHz internally (per spec)
let mut muxer = MuxerBuilder::new(file)
    .video(VideoCodec::H264, 1920, 1080, 30.0)
    .audio(AudioCodec::Opus, 48000, 2)
    .build()?;
muxer.write_audio(0.0, &opus_packet)?;

Fragmented MP4 (DASH/HLS)

let mut muxer = MuxerBuilder::new(file)
    .video(VideoCodec::H264, 1920, 1080, 30.0)
    .fragmented(true)  // Enable fMP4 mode
    .build()?;

// Write frames, then flush fragments periodically
muxer.write_video(0.0, &frame, true)?;
muxer.flush_fragment()?;  // Writes an moof+mdat pair

B-Frames with Explicit DTS

// When encoder produces B-frames, provide both PTS and DTS
muxer.write_video_with_dts(
    pts_seconds,  // Presentation timestamp
    dts_seconds,  // Decode timestamp (for B-frame ordering)
    &frame_data,
    is_keyframe
)?;

Command Line Tool

Muxide includes a command-line tool for quick testing and development workflows:

# Install the CLI tool
cargo install muxide

# Basic video-only muxing
muxide mux \
  --video keyframes.h264 \
  --width 1920 --height 1080 --fps 30 \
  --output recording.mp4

# Video + audio with metadata
muxide mux \
  --video stream.h264 \
  --audio stream.aac \
  --video-codec h264 \
  --audio-codec aac-he \
  --width 1920 --height 1080 --fps 30 \
  --sample-rate 44100 --channels 2 \
  --title "My Recording" \
  --language eng \
  --output final.mp4

# JSON output for automation
muxide mux --json [args...] > stats.json

# Validate input files without muxing
muxide validate --video input.h264 --audio input.aac

# Get info about supported codecs
muxide info

Supported Codecs:

  • Video: H.264 (AVC), H.265 (HEVC), AV1
  • Audio: AAC (all profiles), Opus

Features:

  • Progress reporting with --verbose
  • JSON output for CI/CD integration
  • Comprehensive error messages
  • Fast-start MP4 layout by default
  • Metadata support (title, language, creation time)

What Muxide Is Not

Muxide is intentionally focused. It does not:

Not Supported Why
Encoding/decoding Use openh264, x264, rav1e, etc.
Transcoding Not a codec library
Demuxing/reading MP4 Write-only by design
Timestamp correction Garbage in = error out
Non-MP4 containers MKV, WebM, AVI not supported
DRM/encryption Out of scope

Muxide is the last mile: encoder output → playable file.


Use Cases

Muxide is a great fit for:

  • 🎥 Screen recorders — capture → encode → mux → ship
  • 📹 Camera apps — webcam/IP camera recording pipelines
  • 🎬 Video editors — export timeline to MP4
  • 📡 Streaming — generate fMP4 segments for DASH/HLS
  • 🏭 Embedded systems — single binary, no external deps
  • 🔬 Scientific apps — deterministic, reproducible output

Probably not a fit if you need encoding, demuxing, or legacy codecs (MPEG-2, etc.).


Example: Fast-Start Proof

The faststart_proof example demonstrates a structural MP4 invariant:

  • Two MP4 files are generated from the same encoded inputs
  • One with fast-start enabled, one without
  • No external tools are used at any stage
$ cargo run --example faststart_proof --release

output: recording_faststart.mp4
    layout invariant: moov before mdat = YES

output: recording_normal.mp4
    layout invariant: moov before mdat = NO

When served over HTTP, the fast-start file can begin playback without waiting for the full download (player behavior varies, but the layout property is deterministic).

This example is intentionally minimal:

  • Timestamps are generated in-code
  • No B-frames/DTS paths are exercised
  • The goal is container layout correctness, not encoding quality

Performance

Muxide is designed for minimal overhead. Muxing should never be your bottleneck.

Scenario Time Throughput
1000 H.264 frames 264 µs 3.7M frames/sec
1000 H.264 + fast-start 362 µs 2.8M frames/sec
1000 video + 1500 audio 457 µs 2.2M frames/sec
100 4K frames (~6.5 MB) 14 ms 464 MB/sec
Run benchmarks yourself
cargo bench

Benchmarks run on standard development hardware. In practice, encoding is always the bottleneck — muxing overhead is negligible.


Input Format Requirements

📋 Codec-specific requirements (click to expand)

H.264/AVC

  • Format: Annex B (start codes: 00 00 00 01 or 00 00 01)
  • First keyframe must contain: SPS and PPS NAL units
  • NAL unit types: IDR (keyframe), non-IDR, SPS, PPS

H.265/HEVC

  • Format: Annex B (start codes)
  • First keyframe must contain: VPS, SPS, and PPS NAL units
  • NAL unit types: IDR_W_RADL, IDR_N_LP, CRA, VPS, SPS, PPS

AV1

  • Format: OBU (Open Bitstream Unit) stream
  • First keyframe must contain: Sequence Header OBU
  • OBU types: Sequence Header, Frame, Frame Header, Tile Group

AAC

  • Format: ADTS (Audio Data Transport Stream)
  • Header: 7-byte ADTS header per frame
  • Profiles: LC-AAC recommended

Opus

  • Format: Raw Opus packets (no container)
  • Sample rate: Always 48000 Hz (Opus specification)
  • Channels: 1 (mono) or 2 (stereo)

Documentation

Resource Description
📚 API Reference Complete API documentation
📜 Design Charter Architecture decisions and rationale
📋 API Contract Input/output guarantees

FAQ

Why not just use FFmpeg?

FFmpeg is excellent, but:

  • External binary dependency (distribution complexity)
  • GPL licensing concerns for some builds
  • Process orchestration overhead
  • "What flags was this built with?" debugging

Muxide is a single cargo add with zero external dependencies.

Can Muxide encode video?

No. Muxide is muxing only. For encoding, use:

  • openh264 — H.264 encoding (BSD)
  • rav1e — AV1 encoding (BSD)
  • x264/x265 — H.264/HEVC (GPL, via FFI)
What if my timestamps are wrong?

Muxide will reject non-monotonic timestamps with a clear error. It does not attempt to "fix" broken input — this is by design to ensure predictable output.

Is Muxide production-ready?

Yes. Muxide has an extensive test suite (unit, integration, property-based tests) and is designed for predictable, deterministic behavior.


License

MIT — no GPL, no copyleft, no surprises.


Muxide is designed to be boring in the best way:
predictable, strict, fast, and invisible once integrated.

# Test CI trigger