DEV Community

Cover image for Hardware Video Compression in Rust on macOS — ffmpeg with VideoToolbox
hiyoyo
hiyoyo

Posted on

Hardware Video Compression in Rust on macOS — ffmpeg with VideoToolbox

All tests run on an 8-year-old MacBook Air. All results from shipping 7 Mac apps as a solo developer. No sponsored opinion.

HiyokoAutoSync optionally compresses videos during sync. Software compression on an 8-year-old MacBook Air is slow. Hardware compression via VideoToolbox is fast. Here's how to use ffmpeg's hardware acceleration from Rust.


VideoToolbox basics

VideoToolbox is Apple's hardware video encoding framework. ffmpeg supports it via the h264_videotoolbox encoder. The difference on Intel MacBook Air:

  • Software (libx264): 2-3 minutes for a 1-minute 4K video
  • Hardware (h264_videotoolbox): 15-30 seconds for the same video

For a sync app where users want to not think about it, this matters.


The ffmpeg command

ffmpeg -i input.mp4 \
  -c:v h264_videotoolbox \
  -b:v 5M \
  -c:a aac \
  -b:a 128k \
  output.mp4
Enter fullscreen mode Exit fullscreen mode

h264_videotoolbox uses the hardware encoder. -b:v 5M sets video bitrate — adjust based on your quality/size tradeoff.


From Rust

use std::process::Command;

pub async fn compress_video(
    input: &Path,
    output: &Path,
    bitrate_mbps: u32,
) -> Result<(), AppError> {
    let bitrate = format!("{}M", bitrate_mbps);

    let status = tokio::task::spawn_blocking({
        let input = input.to_owned();
        let output = output.to_owned();
        move || {
            Command::new("ffmpeg")
                .args([
                    "-i", input.to_str().unwrap(),
                    "-c:v", "h264_videotoolbox",
                    "-b:v", &bitrate,
                    "-c:a", "aac",
                    "-b:a", "128k",
                    "-y", // overwrite output
                    output.to_str().unwrap(),
                ])
                .status()
        }
    }).await??;

    if !status.success() {
        return Err(AppError::Compression("ffmpeg failed".into()));
    }

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Fallback to software

VideoToolbox isn't available on all systems. Fallback gracefully:

async fn compress_with_fallback(input: &Path, output: &Path) -> Result<(), AppError> {
    // Try hardware first
    match compress_video_hw(input, output).await {
        Ok(()) => Ok(()),
        Err(_) => {
            log::warn!("Hardware encoding failed, falling back to software");
            compress_video_sw(input, output).await
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Software fallback with libx264 is slower but reliable on any system.


Bundling ffmpeg

ffmpeg needs to be bundled with your Tauri app or assumed to be installed. I bundle a universal binary ffmpeg in the app resources. In tauri.conf.json:

{
  "bundle": {
    "resources": ["bin/ffmpeg"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Then access it via the resource directory at runtime.


TL;DR: Use ffmpeg's h264_videotoolbox encoder for hardware-accelerated video compression on macOS — 5-10x faster than libx264 on Intel Macs. Call via spawn_blocking in Rust, add a software fallback for systems where VideoToolbox isn't available, and bundle ffmpeg as a Tauri resource.


If this was useful, a ❤️ helps more than you'd think — thanks!

HiyokoAutoSync | X → @hiyoyok

Top comments (0)