Skip to content

Add ProRes 422 parser layer to oximedia-codec#20

Open
vbasky wants to merge 1 commit into
cool-japan:masterfrom
vbasky:feat/prores-422-parser
Open

Add ProRes 422 parser layer to oximedia-codec#20
vbasky wants to merge 1 commit into
cool-japan:masterfrom
vbasky:feat/prores-422-parser

Conversation

@vbasky

@vbasky vbasky commented May 17, 2026

Copy link
Copy Markdown

Description

First half of Apple ProRes 422 decode support — the parser layer up to but not including entropy decode + IDCT. Implements the bitstream syntax defined in SMPTE RDD 36-2015 ("Apple ProRes Bitstream Syntax").

ProRes is an intra-only 4:2:2 10-bit codec widely used as a post-production intermediate. Every video editor, colorist, and broadcast pipeline touches ProRes daily.

Module: oximedia_codec::prores

What's in Phase 1 (this PR)

File Surface
frame.rs Frame container parsing (4-byte size + 'icpf' marker). Frame header recognises every profile FourCC (apco / apcs / apcn / apch / ap4h / ap4x), both chroma formats (4:2:2 / 4:4:4), all three interlace modes, custom and default quant matrices, alpha-channel detection.
picture.rs Picture header + per-slice header parsing. Slice header carries the quantization scale plus three (or four, with alpha) compressed plane sizes that bound the slice's data region.
quant.rs Default 8×8 luma and chroma quantization matrices from RDD 36 §6.5.4 Table 10, with tests pinning the spec invariants (DC quantizer = 4; chroma HF > luma HF; diagonal monotonicity).
bitreader.rs MSB-first bit-level reader for the entropy stage. read_bits(1..=32), read_bit, count_leading_ones (for unary prefixes of exp-Golomb codes), align_to_byte. Byte-boundary handling, out-of-input errors, and >32-bit guard tested.
decode.rs::split_slice_planes Fully implemented helper that takes a slice's bytes-after-header plus the sizes from SliceHeader and returns SliceData { luma, cb, cr, alpha } per-plane sub-slices. Last step before entropy decode; catches truncated streams / bit-flip corruption at slice boundaries.

Phase 2 extension point

decode::decode_slice_to_yuv422 has a stable signature, docstring, and structured I/O — the function body returns DecodeError::NotImplemented so callers fail loudly rather than silently producing zeros. A test pins this contract so the public surface can't accidentally drift into a no-op decoder.

The Phase 2 PR fills in:

  1. Entropy decode (RDD 36 §6.5.5–6.5.6) — DC differential coding + AC run/level with adaptive Rice parameter.
  2. Inverse zigzag scan (RDD 36 §6.5.7 Table 11).
  3. Dequantizationcoeff[i] = quantized[i] * matrix[i] * qscale.
  4. 8×8 integer IDCT (RDD 36 §6.5.7).
  5. Plane assembly to 10-bit YUV422 destination buffers.

Motivation

ProRes is one of the largest remaining FFmpeg-parity gaps that's actually closeable in pure Rust. Unlike H.264 / HEVC (multi-quarter projects), ProRes is intra-only, well-documented, and tractable — FFmpeg's libavcodec/proresdec*.c is ~2K lines per component, and the bitstream is published as SMPTE RDD 36-2015.

This PR lands the half that's safe to ship now (parsing — no pixel decode) so reviewers can audit the bitstream surface independently of the IDCT/entropy work. The decode pipeline can then be reviewed in isolation in a follow-up.

Fixes: no tracking issue — feature work.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Performance improvement
  • Code refactoring
  • Documentation update
  • Build/CI configuration change
  • Other (please describe):

Changes Made

  • 6 new files under crates/oximedia-codec/src/prores/: mod.rs, frame.rs, picture.rs, quant.rs, bitreader.rs, decode.rs.
  • crates/oximedia-codec/src/lib.rs gains one line: pub mod prores;.
  • No other crate is touched. Strictly additive — no existing API is modified or removed.

Testing

Test Plan

  • Unit tests added/updated
  • Integration tests added/updated (not yet meaningful — parser-only has nothing to integrate against; integration tests come with Phase 2 when there's a decoded frame to assert on)
  • Benchmarks added/updated (parser is too cheap to be a bottleneck; benchmarks land with Phase 2's IDCT/entropy path)
  • Fuzz targets added/updated (natural follow-up — the frame/picture/slice header parsers are obvious fuzz candidates and will get a target alongside Phase 2)
  • Manual testing performed

Test Evidence

Locally on macOS aarch64:

$ cargo test -p oximedia-codec --lib prores
running 34 tests
test prores::bitreader::tests::align_to_byte_advances_to_boundary ... ok
test prores::bitreader::tests::count_leading_ones_counts_unary_prefix ... ok
test prores::bitreader::tests::count_leading_ones_zero_when_first_bit_zero ... ok
test prores::bitreader::tests::out_of_input_errors_clean ... ok
test prores::bitreader::tests::read_bits_packs_correctly_across_byte_boundary ... ok
test prores::bitreader::tests::read_single_bits_msb_first ... ok
test prores::bitreader::tests::read_too_many_bits_errors ... ok
test prores::bitreader::tests::read_zero_bits_returns_zero ... ok
test prores::decode::tests::decode_slice_phase2_stub_fails_loudly ... ok
test prores::decode::tests::split_planes_no_alpha ... ok
test prores::decode::tests::split_planes_size_mismatch_errors ... ok
test prores::decode::tests::split_planes_with_alpha ... ok
test prores::frame::tests::container_bad_tag_errors ... ok
test prores::frame::tests::container_parses_and_splits_payload ... ok
test prores::frame::tests::container_short_buffer_errors ... ok
test prores::frame::tests::container_with_trailing_bytes ... ok
test prores::frame::tests::frame_header_parses_minimal ... ok
test prores::frame::tests::frame_header_recognises_every_profile ... ok
test prores::frame::tests::frame_header_rejects_nonzero_version ... ok
test prores::frame::tests::frame_header_rejects_unknown_profile ... ok
test prores::frame::tests::frame_header_with_both_custom_matrices ... ok
test prores::frame::tests::frame_header_with_custom_luma_matrix ... ok
test prores::frame::tests::interlace_mode_picture_count ... ok
test prores::frame::tests::profile_is_4444_classification ... ok
test prores::picture::tests::picture_header_round_trip ... ok
test prores::picture::tests::picture_header_too_short_errors ... ok
test prores::picture::tests::picture_header_with_trailing_bytes_returns_remainder ... ok
test prores::picture::tests::slice_header_no_alpha ... ok
test prores::picture::tests::slice_header_truncated_errors ... ok
test prores::picture::tests::slice_header_with_alpha ... ok
test prores::quant::tests::chroma_quantizes_high_frequency_more_than_luma ... ok
test prores::quant::tests::dc_quantizers_match_rdd_36 ... ok
test prores::quant::tests::matrices_are_64_entries ... ok
test prores::quant::tests::matrices_monotonically_increase_along_diagonal ... ok

test result: ok. 34 passed; 0 failed

$ cargo build -p oximedia-codec 2>&1 | grep -E "warning|error"   # silent
$ cargo clippy -p oximedia-codec --all-targets --no-deps 2>&1 | grep -E "^(warning|error)"   # silent

The full workspace also builds clean with the prores module included.

Performance Impact

  • No performance impact (new code on a new path; nothing existing is touched. Parser is O(header_size) per frame, irrelevant compared to even hypothetical decode cost.)
  • Performance improvement (provide benchmarks below)
  • Performance regression (justified because...):

Checklist

Code Quality

  • My code follows the project's style guidelines
  • I have run cargo fmt and cargo clippy
  • I have addressed all compiler warnings (zero warnings on cargo build and cargo clippy --all-targets --no-deps)
  • My code compiles without errors on Rust stable, beta, and nightly (verified locally on stable; CI will exercise beta/nightly)
  • I have added appropriate error handling (structured FrameError, DecodeError, BitReaderError enums via thiserror; no unwrap/expect in non-test code)

Documentation

  • I have updated documentation comments (rustdoc) (every public type and function carries a docstring; module-level docs explain the spec section reference and the bitstream layout in ASCII art)
  • I have updated the relevant README.md files (no README change required — this is a new internal module)
  • I have added/updated examples (module-level docs describe the API shape and Phase 2 hand-off; per-method # Example blocks are deferred until Phase 2 when there's a usable end-to-end flow to demonstrate)
  • Public APIs have comprehensive documentation

Testing

  • I have added tests that prove my fix/feature works (34 unit tests covering every parser branch + Phase-2-stub contract)
  • All existing tests pass locally with my changes (verified — no regressions in oximedia-codec or other crates)
  • I have tested on multiple platforms (macOS aarch64 only locally; the code is pure Rust with no platform-conditional logic so CI should be uneventful)
  • I have verified no memory leaks or unsafe behavior (no unsafe blocks anywhere in the new code)

Patent/Legal Compliance

  • Not clear-cut. Apple holds patents on the ProRes encoding format. Decoder implementations have been treated as not-asserted-against in industry practice — FFmpeg has shipped ProRes decode since 2010 and Apple has never moved against decoder implementations. The bitstream syntax this PR parses is publicly published (SMPTE RDD 36-2015). However, this isn't patent-free in the same sense AV1 or Opus are. Reviewers should make their own call on whether this fits the workspace's codec policy.
  • I have not introduced any code from restrictively licensed sources (implementation written from the SMPTE spec; no third-party source vendored)
  • I have the right to contribute this code under the Apache-2.0 license

Breaking Changes

  • This PR includes breaking changes
  • I have updated the CHANGELOG (if applicable)
  • I have documented the migration path

Strictly additive — one new module, no existing API touched.

Dependencies

  • I have not added new dependencies (uses only thiserror which is already a workspace dependency)
  • All new dependencies are compatible with Apache-2.0
  • Dependencies are necessary and well-maintained

Additional Notes

Reviewer hint: the most consequential files to read are frame.rs (the bulk of the parsing logic + profile enumeration) and decode.rs (the documented Phase 2 extension point). quant.rs and bitreader.rs are short and self-contained.

Out of scope (explicit follow-ups):

  • Phase 2 decode pipeline (see decode.rs module doc): entropy decode, inverse zigzag, dequantization, IDCT, plane assembly.
  • ProRes 4444 alpha-plane decode (parser already recognises the format; IDCT/dequant/plane assembly will share Phase 2's machinery once it lands).
  • ProRes encoder.
  • Integration with oximedia_codec::VideoDecoder trait + CodecId::ProRes variant — that wiring lands when the decoder is end-to-end, not before.
  • Fuzz target against the frame/picture/slice header parsers — natural follow-up.

cool-japan added a commit that referenced this pull request May 19, 2026
Add `prores` Cargo feature flag to oximedia-codec; gate the prores module
and re-export its public types behind it, consistent with the existing
pattern for av1/vp9/vp8/mjpeg/apv feature-gated codecs.

ProRes 422 parser (SMPTE RDD 36-2015) + full decode pipeline (entropy,
dequant, IDCT) implemented in PR #20/#21 by vbasky; not on the project
Green List but decoding is unencumbered in practice (FFmpeg ships it
since 2010 without consequence).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cool-japan added a commit that referenced this pull request May 19, 2026
- Mark AutoCaption pipeline (Wave 2 Slice C) as complete
- Mark oximedia-neural oxionnx wiring (Wave 4 Slice D) as complete
- Add 0.1.7 PR integration log: #18 RTSP, #22 FLV, #23 Windows,
  #20/#21 ProRes; note PR #19 HW-accel deferred to 2027+

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
First half of Apple ProRes 422 decode support. Implements the
bitstream syntax defined in SMPTE RDD 36-2015 ("Apple ProRes
Bitstream Syntax") up to but not including entropy decode + IDCT.

Module: oximedia_codec::prores

ProRes is an intra-only 4:2:2 10-bit codec widely used as a
post-production intermediate. Patent licensing for encoding is held
by Apple; decoding is unencumbered in practice — FFmpeg has shipped
a decoder since 2010 without consequence.

Phase 1 (this revision) — parser + scaffolding

- frame.rs: Frame container parsing (4-byte size + 'icpf' marker)
  and frame header parsing. Recognises every profile FourCC (apco /
  apcs / apcn / apch / ap4h / ap4x), both chroma formats (4:2:2 /
  4:4:4), all three interlace modes, custom and default quant
  matrices, alpha-channel detection.

- picture.rs: Picture header and per-slice header parsing. Slice
  header carries the quantization scale plus three (or four, with
  alpha) compressed plane sizes that bound the slice's data region.

- quant.rs: Default 8x8 luma and chroma quantization matrices from
  RDD 36 §6.5.4 Table 10, with tests pinning the spec invariants
  (DC quantizer = 4; chroma HF > luma HF; diagonal monotonicity).

- bitreader.rs: MSB-first bit-level reader for the entropy stage.
  Supports read_bits(1..=32), read_bit, count_leading_ones (for
  unary prefixes of exp-Golomb codes), align_to_byte. Byte-boundary
  handling, out-of-input errors, and >32-bit guard are all tested.

- decode.rs::split_slice_planes: Fully implemented helper that takes
  the post-header bytes of a slice and the sizes from SliceHeader
  and returns SliceData { luma, cb, cr, alpha } per-plane sub-slices.
  This is the last step before entropy decode and catches truncated
  streams / bit-flip corruption at slice boundaries.

Phase 2 extension point

decode::decode_slice_to_yuv422 has a stable signature, docstring,
and structured I/O — the function body returns
DecodeError::NotImplemented so callers fail loudly rather than
silently producing zeros. A test pins this contract so the public
surface can't accidentally drift into a no-op decoder.

The Phase 2 PR fills in:

1. Entropy decode (RDD 36 §6.5.5–6.5.6): DC differential coding +
   AC run/level with adaptive Rice parameter.
2. Inverse zigzag scan (RDD 36 §6.5.7 Table 11).
3. Dequantization (coeff[i] = quantized[i] * matrix[i] * qscale).
4. 8×8 integer IDCT (RDD 36 §6.5.7).
5. Plane assembly to 10-bit YUV422 destination buffers.

Tests

34 unit tests covering: frame container parse + bad tag + truncated
buffer, frame header parse including every profile FourCC + version
guard + custom luma + custom luma+chroma matrices + interlace and
profile classification, picture header parse + trailing data, slice
header parse with and without alpha + truncated, default quant
matrix invariants, bit reader MSB-first single-bit + multi-bit +
zero-bit + too-many-bits + out-of-input + count_leading_ones +
align_to_byte, plane splitter with/without alpha + size mismatch,
and the Phase-2-stub-fails-loudly contract.

Build is warning-free and clippy-clean.

Out of scope

- Phase 2 decode pipeline (see decode.rs module doc).
- ProRes 4444 alpha-plane decode (parser already recognises it; the
  IDCT + dequant + plane assembly will share Phase 2's machinery
  once it lands).
- Encoder.
- Integration with oximedia_codec::VideoDecoder trait + CodecId::ProRes
  variant — slot for that lands when the decoder is end-to-end.
@vbasky vbasky force-pushed the feat/prores-422-parser branch from 06e9d34 to 3f4c059 Compare May 28, 2026 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant