macOS Backend
VideoToolbox, AVFoundation, Core Video, Metal, and native audio execution in Passtape.
passtape-macos connects Passtape’s backend-neutral execution contracts to
macOS media surfaces.
video file -> MacVideoSourceProvider
-> MacVideoSurface (retained CVPixelBuffer)
-> prepared Metal passes through MacVideoBackend
-> MacMediaOutput (GPU surface, packed CPU frame, or tensor)
-> VideoOutputSession
-> MacVideoFile -> H.264, HEVC, or ProRes
audio file -> MacAudioSourceProvider
-> MacAudioBlock (shared planar f32 samples)
-> prepared CPU work through MacAudioBackend
-> AudioOutputSession
-> MacAudioFile -> AAC, Apple Lossless, FLAC, Opus, or PCM
video and/or audio -> MediaOutputSession
-> MacMediaFile -> encoded video and/or audio
new_runtime() configures both paths. Video and audio sources are registered
independently because a graph may use either stream or assign different source
identities and timeline ranges to them.
MacVideoSourceProvider::add_file opens a video and registers it under the
identity used by a graph’s VideoSource. AVFoundation reads compressed samples
from the container, and a reusable VideoToolbox session decodes them into
Metal-compatible Core Video surfaces. The returned descriptor copies the
identity and decoded format into the graph without storing a path or decoder
object there. VideoToolbox prefers dedicated decoder hardware and may use its
codec fallback when that hardware does not support the stream.
add_file_stream selects a zero-based video track when a container holds more
than one; MacAudioSourceProvider provides the equivalent audio-track API.
MacVideoSourceProvider::add_image_file decodes a still image once with
ImageIO, applies its stored orientation, and keeps the resulting
Metal-compatible surface. An ImageSource reuses those pixels for every frame
in its graph node’s active timeline range.
add_bgra_file is the alpha-preserving alternative for foreground media.
Ordinary files prefer native bi-planar 4:2:0 YUV surfaces and fall back to BGRA
when a decoder does not declare how its chroma samples are positioned. Alpha
foregrounds decode to straight-alpha BGRA, and preparation converts them as
operations require.
The provider reuses its active compressed reader and VideoToolbox decoder for nearby forward requests. On a distant or backward request, it reopens them near the requested source time and decodes forward. If that preroll does not include the preceding frame, it restarts at the beginning to preserve frame selection.
Each registered file owns one serial decode worker. After nearby forward requests establish sequential playback, that worker retains at most three future frames while the calling thread renders and submits the current frame. The VideoToolbox decoder separately retains a bounded set of codec reference, reordering, and callback frames. VideoToolbox compression and movie writing also have their own bounded queues, so these stages can overlap without an unbounded frame backlog. A request still waits synchronously when its frame has not finished decoding, and file registration waits for initial inspection.
Source descriptors do not assume that files stay unchanged. Call
deterministic_descriptor(content_revision) when the application controls the
file and updates the revision whenever its contents change; this enables
decoded-frame and rendered-output reuse.
MacVideoSurface owns each Core Video pixel buffer through Apple’s reference
counted handle, so moving a frame through Passtape does not copy its pixels.
Metal uses texture views over that storage and allocates intermediate frames
from reusable Core Video pools.
AVFoundation color tags are stored in each source’s VideoFormat and preserved
through prepared surfaces and format-preserving operations. Missing color
characteristics remain unspecified; the missing full-range flag means video
range according to Core Media. Encoders write the prepared tags. PQ and HLG
are preserved by HEVC Main 10 and ProRes output.
When complete color metadata or pixel storage differs between inputs or outputs, preparation adds an explicit conversion pass. Built-in video operations declare the working representation they need: most use BGRA, while color adjustment and HDR tone mapping use linear half-float RGB. Source-only encoded output stays in its bi-planar YUV storage and avoids unnecessary conversion.
The conversion shaders support 8- and 10-bit 4:2:0 YUV, 10-bit 4:2:2 YUV,
BGRA, and half-float RGBA. YUV surfaces retain their Core Video chroma
location. PQ and HLG values pass through unchanged when their metadata is
unchanged. Their transfer functions can also be decoded into linear light for
an explicit media.video.tone_map operation before SDR output conversion.
Gaussian blur uses Metal Performance Shaders. Constant radii keep their exact value; animated radii use prebuilt half-pixel steps so rendering never creates a filter on the frame path. A zero radius uses the existing Metal copy path.
2D transforms use a bilinear Metal pass. They preserve the frame size and sample transparent pixels beyond the source bounds.
Alpha compositing runs as a Metal pass over premultiplied BGRA inputs. The foreground alpha selects how much of the background remains at each pixel.
Before an output session starts, the backend reads the prepared surface working set and prewarms each Core Video pool for the session’s bounded in-flight frames. The bound accounts for frame count and estimated surface bytes, including surfaces created only for output resizing. AVFoundation separately owns the codec’s reference surfaces.
When run_with_control is cancelled, the macOS sink tells AVAssetWriter to
abandon the incomplete file. Cancellation is also observed while the writer is
finalizing.
Output materialization can resize each supported pixel format with bilinear Metal kernels. YUV resizing samples its luma and chroma planes directly and accounts for 4:2:0 or 4:2:2 subsampling and chroma location. The resized surface comes from the same reusable Core Video pool system and stays on the GPU path before display or encoding.
Passtape’s custom Metal kernels live in src/shaders and are compiled into one
library when the backend initializes. Gaussian blur uses Apple’s optimized
Metal Performance Shaders implementation instead of a custom sampling loop.
For each rendered frame, the backend records its operations and output resizing into at most one Metal command buffer. Sequential output sessions retain a bounded FIFO of submitted frames and wait for the oldest frame before handing it to the destination. This overlaps Metal execution with later decoding and command recording without allowing surface residency to grow without limit. Direct and interactive frame requests still wait before returning. A source-only frame that needs no GPU work creates no command buffer.
Supported graph operations and their parameters are listed in the operations guide. The macOS backend executes video operations with Metal while retaining frame storage in Core Video surfaces.
Application-defined Metal effects
MacRuntimeConfiguration pairs a platform-neutral OperationDescriptor with
a MetalEffectImplementation before creating a runtime generation. The graph
continues to store only the semantic operation identity, inputs, outputs, and
parameters; Metal remains confined to this crate.
let descriptor = OperationDescriptor::new(
OpRef::new("editor.effect.agent-created", 1),
"Agent-created Effect",
&[MediaType::Video],
OutputDescriptor::new("output", MediaType::Video),
)
.with_parameter_schema(&[
ParameterDescriptor::new("amount", ParameterType::Scalar),
]);
let metal = MetalEffectImplementation::from_source(
descriptor.op_ref().clone(),
shader_source,
"agent_created_effect",
);
let mut configuration = MacRuntimeConfiguration::new();
configuration.register_effect(descriptor, metal)?;
let pending = configuration.prepare_concurrent_generation(graph, &request)?;
// Native preparation runs on the candidate's video and audio owners while the
// currently published generation remains available for playback.
let next_generation = pending.wait()?;
let generations = MacRuntimeGenerationSlot::new(next_generation);
Custom kernels receive required video inputs at consecutive texture indices,
followed by their output texture. Buffer zero contains one float4 per
parameter in descriptor order: scalar values use .x, and points use .xy.
Textures are straight-alpha linear-light RGBA16Float. Candidate preparation
compiles only effects used by its graph, caches identical pipelines across
generations from that configuration on the same Metal device, and preserves
Metal’s compiler diagnostic in MacRuntimeGenerationError.
Each call that creates a runtime freezes a generation-specific registry and
backend identity. An editor may continue using its existing generation while
an agent adds an effect to the configuration, creates and validates a later
generation, reprepares the graph, and then publishes the ready runtime and plan
together with MacRuntimeGenerationSlot::publish. Readers that already loaded
the previous generation can finish with it; previously created runtimes are
not mutated.
A request with VideoOutputDestination::CpuBuffer returns a MacMediaOutput
containing a CpuVideoFrame; callers access it with as_cpu_frame or
into_cpu_frame. The backend waits for Metal, copies only the pixel bytes from
each Core Video row, and removes platform-specific row padding. This path is
useful for thumbnails and pixel analysis; encoded-file targets keep surfaces
on the GPU and do not perform the copy.
MacVideoFile supports H.264, HEVC Main/Main 10, HEVC with alpha, and the
ProRes 422 and 4444 families. Main 10 and ProRes 422 use native 10-bit YUV
surfaces; alpha codecs use an alpha-preserving RGB surface. H.264 and ordinary
HEVC support MOV and MP4. HEVC with alpha and ProRes use MOV.
MacH264Mov remains a convenience preset.
VideoOutputSession handles frame submission, backpressure, and finalization.
Finalization blocks, so the session should run on a worker thread.
MacVideoFileSpecification holds the codec, container, and preparation
request without a path. Calling at(path) creates the execution target. Audio
and combined outputs provide matching specification types, which lets an
application prepare once and choose an atomic staging path only when execution
starts.
VideoToolbox compresses rendered Core Video surfaces asynchronously.
AVAssetWriter only places the compressed samples into the MOV or MP4
container. Passtape’s generic encoded-video pipeline moves packets between
those two components in order. Both its packet queue and VideoToolbox’s input
window are bounded; when either fills, the output session pauses rendering
until space is available.
MacVideoFileEncoder is the lower-level sink for callers that
need to drive prepared frames themselves.
Audio
MacAudioSourceProvider uses AVFoundation to decode native planar f32 PCM.
Nearby requests reuse the decoder; distant or backward requests reopen it near
the requested time. When a file declares its speaker order, Passtape preserves
that order. Missing or unrecognized layout metadata remains discrete rather
than being guessed from channel count alone.
Preparation inserts CPU passes when the sample rate, speaker layout, or channel
storage order must change. Common mono, stereo, 5.1, and 7.1 layouts are
supported. Downmixing omits LFE and routes center and surround channels at
-3 dB; upmixing leaves speakers absent from the source silent.
MacAudioBackend executes these conversions and audio graph operations. A
matching source-only block can pass through without copying its samples.
MacAudioFile writes AAC, Apple Lossless, FLAC, Opus, or linear PCM. Supported
audio containers are M4A, CAF, WAV, AIFF, native FLAC, MOV, and MP4, subject to
the selected codec. AAC and Apple Lossless support 5.1, and linear PCM supports
7.1. FLAC and Opus output currently support mono and stereo. MacAacM4a
remains the AAC convenience target.
AudioToolbox compresses audio before container writing. PCM is explicitly
interleaved and packetized. MOV, MP4, and M4A use AVAssetWriter; CAF, WAV,
AIFF, and FLAC use Core Audio file services. Codec priming and final padding are
stored as container metadata rather than becoming audible samples.
MacAacM4a is an audio output target. Its PCM format becomes part of the
preparation request. It accepts mono, stereo, or 5.1 planar f32 blocks from
AudioOutputSession and writes AAC in an .m4a file.
Combined movies
MacMediaFileSpecification connects a non-empty selection of prepared video
and/or audio outputs to one MacMediaFile. Keeping the specification attached
to the destination ensures the encoder consumes the exact formats requested
during preparation. VideoToolbox compresses video, while AVAssetWriter muxes
the selected tracks. MOV and MP4 support depends on the chosen codecs.
MacH264AacMov remains a convenience preset.
MediaOutputSession feeds each selected track according to its own
backpressure and timeline.
MOV and MP4 outputs can also carry multiple selectable WebVTT caption tracks.
Attach text graph outputs with RequestedMediaOutput::with_text_tracks; each
track can declare a BCP 47 language and at most one track can be enabled by
default. Text cues are compiled during preparation and muxed without entering
the per-frame render loop.
MacAudioVideoFileEncoder exposes the same two-track writer for callers that
drive prepared media themselves. Each writer input is marked complete when its
stream ends, even when the other stream is longer.