Hardware Video Compression in Rust on macOS — ffmpeg with VideoToolbox
DEV Community Grade 8

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 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 (()) } 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 } } } 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" ] } } 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

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 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 { 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(()) } Fallback to software VideoToolbox isn't available on all systems. Fallback gracefully: async fn compress_with_fallback(input: &Path, output: &Path) -> Result { // 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 } } } 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"] } } 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)

Comments

No comments yet. Start the discussion.