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. No C libraries, no FFI wrappers, 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 (4444 +
alpha) streams decode interop-clean — both progressive and interlaced
(-flags +ildct -top {0,1}). 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.
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.