videotoolbox
Safe Rust bindings for Apple's VideoToolbox framework — hardware-accelerated encode/decode, pixel transfer/rotation, multipass helpers, HDR metadata, motion estimation, RAW processing, and VTFrameProcessor pipelines on macOS.
Status: experimental, but the crate now covers the main public
VideoToolboxsurfaces used by the doom-fish stack. Objective-C-only APIs use a small Swift bridge behind theframe_processorfeature, and executor-agnostic encode/decode/RAW-processing async helpers live invideotoolbox::async_apibehind theasyncfeature.
Features
- Hardware-accelerated encoding + decoding — H.264, HEVC, and
ProRes422/4444 - Pixel transfer / rotation / utilities —
VTPixelTransferSession,VTPixelRotationSession,VTCreateCGImageFromCVPixelBuffer - Multipass + HDR helpers —
VTFrameSilo,VTMultiPassStorage,VTHDRPerFrameMetadataGenerationSession - Advanced processing —
VTFrameProcessor,VTMotionEstimationSession,VTRAWProcessingSession - Direct
IOSurfaceinput/output — zero-copy composition withapple-cf::iosurface - Builder pattern — fluent encoder configuration for bitrate, frame rate, keyframe interval, real-time mode, profile level, the encoder specification (hardware preferred/required/disabled, encoder ID, low-latency rate control, GPU registry ID) and source pixel buffer attributes, plus per-frame properties such as forced keyframes
- Executor-agnostic async module —
videotoolbox::async_api::{AsyncCompressionSession, AsyncDecompressionSession, AsyncRawProcessingSession}bridges one-shot frame callbacks toFutures and wraps RAW-parameter change notifications as a bounded async stream viadoom-fish-utils - Mostly pure C bindings — optional Swift bridge only for Objective-C-only APIs
- Minimal dependencies —
apple-cfanddoom-fish-utils, plus optionalapple-metalforVTFrameProcessorcommand-buffer integration
Async notes
AsyncRawProcessingSession::parameter_changes(...)exposesVTRAWProcessingSessionSetParameterChangedHandleras a bounded async stream.- One-shot async decompression accepts exactly one sample per
CMSampleBuffer; multi-sample buffers returnVTError::UnexpectedSampleCountbefore native submission. VTDecompressionSessionSetMultiImageCallbackremains sync-only for now: the audited C API requires a non-null callback and exposes no clear / unsubscribe hook for an RAII async stream wrapper.encode_frame_asyncsubmits the frame when it is called. If the encoder is still holding the frame back (frame reordering is on by default) when the future is first polled, that poll forces it out withVTCompressionSessionCompleteFramesand blocks until the encoder emits it, so a sequential.awaitloop never hangs. Submit several frames before awaiting the first to keep reordering effective.
Why not bindgen?
The full VideoToolbox SDK surface is large, but the useful set for real macOS media pipelines is still small enough to hand-audit. Hand-writing those declarations gives us:
- No build-time dependency on
clang - Type-safe Rust enums for codec types (instead of raw
u32four-character codes) - Builder APIs that map ergonomically to VT's
CFDictionaryproperty bag
Installation
[]
= "0.21"
Requirements
- macOS 13.0+
- Apple Silicon or Intel Mac with hardware video encoder
APIs that need a newer macOS are looked up at run time, so binaries still load on macOS 13; on an older system they return VTError::Unsupported { api, minimum } (or false from the is_*_supported queries):
| Needs | APIs |
|---|---|
| macOS 14.0 | CompressionSession::encode_multi_image, DecompressionSession::set_multi_image_callback, TaggedBufferGroup, the MV-HEVC support queries |
| macOS 15.0 | DecompressionSession::decode_with_options with frame options, HdrMetadataSession, the decoder and RAW-processor extension-property queries, RawProcessingSession |
| macOS 15.4 / 26.0 | the VTFrameProcessor pipelines (each capability query reports what the running system supports) |
| macOS 26.0 | MotionEstimationSession, RawProcessingSession::metadata_for_sidecar_file |
The raw declarations in videotoolbox::ffi are ordinary imports. If you call one that is newer than your deployment target directly, link with -weak_framework VideoToolbox (and CoreMedia) and check availability first, or use the safe wrappers.
Sessions are Send but not Sync: VideoToolbox marks its session types non-Sendable and documents no concurrent use, so move a session to the thread that uses it or put it behind a Mutex.
Encoded H.264/HEVC frames (EncodedFrame::data) are AVCC-style length-prefixed NAL units without start codes; the parameter sets are in the sample buffer's format description (CMFormatDescription::video_parameter_sets in apple-cf).
Quick start
use *;
use CMTime;
use ;
Composes with the rest of the doom-fish stack
screencapturekit-rs ──► IOSurface ──► videotoolbox-rs ──► H.264 bytes
↓
avassetwriter-rs
↓
.mp4 file
Roadmap
-
VTCompressionSession(encoder) -
VTDecompressionSession(decoder) -
VTPixelTransferSession(pixel format / colour space conversion) -
VTPixelRotationSession -
VTMultiPassStorage+VTFrameSilo(two-pass encoding) -
VTHDRPerFrameMetadataGenerationSession(Dolby Vision metadata) -
VTFrameProcessorcapability queries (super-resolution / optical flow detection) -
VTFrameProcessorpipeline (super-resolution + motion blur + temporal noise + frame-rate conversion + optical flow + 2 low-latency variants) -
VTMotionEstimationSession -
VTRAWProcessingSession(with parameter introspection) -
VTProfessionalVideoWorkflowdecoder/encoder registration -
VTCreateCGImageFromCVPixelBuffer - HEVC profile-level helpers
- Executor-agnostic async encode/decode/RAW-processing module behind the
asyncfeature
License
Licensed under either of Apache-2.0 or MIT at your option.