oxideav-prores
Pure-Rust Apple ProRes codec — decoder + encoder for all six ProRes video profiles (422 Proxy / LT / Standard / HQ and 4444 / 4444 XQ). 8-bit, 10-bit, and 12-bit Y'CbCr; lossless alpha plane on the 4444 / 4444 XQ profiles.
Part of the oxideav framework but usable standalone. Implemented from the spec (no C codec libraries linked or wrapped, no *-sys crates).
Status
| Profile | FourCC | Pixel formats | State |
|---|---|---|---|
| 422 Proxy | apco |
Yuv422P, Yuv422P10Le, Yuv422P12Le |
decode + encode |
| 422 LT | apcs |
Yuv422P, Yuv422P10Le, Yuv422P12Le |
decode + encode |
| 422 Standard | apcn |
Yuv422P, Yuv422P10Le, Yuv422P12Le |
decode + encode |
| 422 HQ | apch |
Yuv422P, Yuv422P10Le, Yuv422P12Le |
decode + encode |
| 4444 | ap4h |
Yuv444P, Yuv444P10Le, Yuv444P12Le (+ alpha) |
decode + encode |
| 4444 XQ | ap4x |
Yuv444P, Yuv444P10Le, Yuv444P12Le (+ alpha) |
decode + encode |
Bit-depth selection follows the stream's CodecParameters::pixel_format
— RDD 36 §5 carries no per-frame bit-depth syntax element; §7.5.1
defines the conversion from reconstructed color component values to
pixel samples of arbitrary bit depth b. Pass Yuv422P10Le /
Yuv444P10Le to get 10-bit planar output (LE u16 pairs, valid range
0..=1023); pass Yuv422P12Le / Yuv444P12Le for 12-bit
(0..=4095); pass Yuv422P / Yuv444P (or omit pixel_format) for
8-bit.
Alpha plane
The 4444 / 4444 XQ profiles support a per-pixel alpha channel coded
losslessly per RDD 36 §5.3.3 + §7.1.2 (raster-scan run-length code +
differential VLC, Tables 12-14). The alpha is exposed as a 4th
VideoPlane on the decoded VideoFrame (after Y/Cb/Cr); the
encoder accepts the same shape on input via
[encoder::encode_frame_with_alpha].
The core PixelFormat enum does not yet carry Yuva422P / Yuva444P
variants, so the pixel-format reporting stays as Yuv4(2|4)4P* — the
caller checks frame.planes.len() == 4 to detect alpha.
The 4444 and 4444 XQ profiles share the same bitstream structure as 4444 — XQ is selected when the caller requests the highest quality tier and produces a larger packet at lower quantisation.
ffmpeg-encoded prores_ks apcn / apch (4:2:2) and ap4h /
ap4x (4444 ± alpha) streams decode interop-clean across 8-/10-/12-bit
and both progressive and interlaced (-flags +ildct -top {0,1})
modes. The picture-height-not-multiple-of-16 alpha edge case is
covered: the decoder reads the full padded macroblock-row alpha
(per RDD 36 §7.5.2) and crops on output. Streams produced by this
crate's own encoder use the spec's entropy coder for color, but the
encoder emits a plain run-length alpha (alternative path permitted by
§7.1.2); the coder is bit-exact with itself and decoder-compatible.
Frame-header metadata (RDD 36 §5.1.1 / §6.2)
The encoder fills the descriptive frame-header fields
(aspect_ratio_information, frame_rate_code, color_primaries,
transfer_characteristic, matrix_coefficients) automatically from
[CodecParameters::frame_rate] — e.g. Rational::new(30_000, 1001)
maps to frame_rate_code = 4 (29.97 fps NTSC). Set every field
explicitly via [encoder::EncoderConfig::with_meta]:
use ;
use FrameMeta;
let cfg = default.with_meta;
let enc = make_encoder_with_config?;
# Ok::
The mapping from Rational to frame_rate_code lives in
[frame::frame_rate_code_from_rational] and covers the 11 spec-named
rates (RDD 36 §6.2 Table 4); any other rate yields 0
("unknown"), which RDD 36 decoders treat as a hint only.
Configurable quantisation index (RDD 36 §7.3 / Table 15)
The encoder picks one quantization_index per profile by default
(8 / 6 / 4 / 2 / 2 / 1 for Proxy / LT / Standard / HQ / 4444 /
4444 XQ — see [frame::Profile::default_quant_index]). Lower index =
finer step = higher quality + larger packet. Override the default for
custom rate/quality trade-offs:
use ;
// Highest-quality Proxy: same profile selection (bit_rate hint), but
// the qscale floor of qi=2 instead of the Proxy default qi=8.
let cfg = default.with_quantization_index;
let enc = make_encoder_with_config?;
# Ok::
The valid range is 1..=224 (rejected at encoder construction
otherwise). The override applies to every slice in every encoded frame
and roundtrips through any RDD 36 decoder.
Two-pass per-frame rate control
Set CodecParameters::bit_rate + frame_rate and call
EncoderConfig::with_rate_control() to enable per-frame binary-search
rate control. The encoder performs up to RATE_CTRL_MAX_PASSES (10)
trial encodes per frame, adjusting quantization_index to hit the
per-frame byte target derived from bit_rate / fps within
RATE_CTRL_TOLERANCE (±5 %). When the target is outside the
achievable range for the resolution the encoder returns the best
candidate (finest quality for targets above the maximum, coarsest for
targets below the minimum) — it never emits a broken stream.
use ;
// Hit the nominal 422 HQ bitrate (220 Mbit/s at 29.97 fps) within ±5%.
let cfg = default.with_rate_control;
let enc = make_encoder_with_config?;
# Ok::
Configurable quantisation matrices (RDD 36 §5.3.4 + §6.3.7 + §7.3)
The encoder defaults to the spec's flat all-4s quantisation matrix
(load_luma_qmat = load_chroma_qmat = 0, 20-byte frame header — same
as prores_ks for apcn / apch when no perceptual preset is
selected). Pass an [encoder::EncoderConfig] with a non-default
[quant::QuantMatrices] to load custom matrices into the frame header
(making the header 84 or 148 bytes per §7.3) — every RDD 36 decoder,
including ffmpeg's, uses the loaded matrices for dequantisation.
A built-in perceptual preset is provided via
[quant::QuantMatrices::perceptual]: JPEG K.1 / K.2 (ISO/IEC 10918-1
Annex K) normalised to a DC weight of 2 and clamped to the spec's
2..=63 weight range. At every quantization_index from 2 to 16 the
perceptual matrices cut packet size by 20-25% on broadband content
because the entropy coder's endOfData() semantics (RDD 36 §7.1.1)
turn HF zeros into trailing zero runs that cost no bits. PSNR trades
off slightly because flat is provably PSNR-optimal under uniform
quantisation, but JPEG-style CSF-rolloff matrices preserve perceptual
quality far better than the same byte-count flat encode.
use ;
use QuantMatrices;
let enc = make_encoder_with_config?;
// Or: EncoderConfig::default().with_quant_matrices(QuantMatrices { luma, chroma })
# Ok::
Interlaced (RDD 36 §5.1, §6.2, §7.5.3)
A frame's interlace_mode (0 = progressive, 1 = top-field-first,
2 = bottom-field-first) controls whether the encoded bitstream carries
one picture or two (one per field). Source rows interleave across
fields per §7.5.3: top field = source rows 0, 2, 4, …; bottom field =
rows 1, 3, 5, …. The encoder splits the source into two field pictures
(each at (height + 1) / 2 or height / 2 rows) and emits them in
temporal order; the decoder reverses the deinterleave. Each field
picture uses the interlaced block scan (§7.2 Figure 5) instead of the
progressive Figure 4. See [encoder::encode_frame_interlaced].
Usage
[]
= "0.1"
= "0.1"
= "0.0"
Profile selection
The encoder picks a profile from the combination of pixel_format and
bit_rate:
pixel_format |
bit_rate hint (bps) |
Picked profile |
|---|---|---|
Yuv422P |
<= 70_000_000 |
Proxy |
Yuv422P |
<= 125_000_000 |
LT |
Yuv422P |
<= 180_000_000 or None |
Standard |
Yuv422P |
> 180_000_000 |
HQ |
Yuv444P |
>= 400_000_000 |
4444 XQ |
Yuv444P |
anything else | 4444 |
use CodecRegistry;
use ;
let mut reg = new;
register;
let mut params = video;
params.width = Some;
params.height = Some;
params.pixel_format = Some;
params.bit_rate = Some; // -> 422 HQ
let mut enc = reg.make_encoder?;
# Ok::
Codec id
- Codec:
"prores"; accepted pixel formatsYuv422P,Yuv444P. - Keyframe-only (all ProRes frames are intra).
License
MIT — see LICENSE.