Skip to main content

oxideav_mjpeg/
lib.rs

1// Parallel-array index loops are idiomatic in codec code; skip the lint.
2#![allow(clippy::needless_range_loop)]
3// When built without the `registry` feature, large swathes of the
4// decoder/encoder (the JPEG entry-point + render helpers) have no
5// callers — they're only reachable via the `Decoder` / `Encoder`
6// trait implementations that live behind the `registry` feature.
7// Suppress the resulting dead-code warnings rather than gating every
8// helper.
9#![cfg_attr(not(feature = "registry"), allow(dead_code))]
10
11//! JPEG / Motion-JPEG codec, pure Rust.
12//!
13//! Each video packet is a standalone JPEG (one full SOI..EOI). The decoder
14//! recognises baseline (SOF0), extended-sequential (SOF1), progressive
15//! (SOF2), and lossless (SOF3) JPEGs with 4:2:0 / 4:2:2 / 4:4:4 chroma
16//! subsampling (DCT variants) and outputs `MjpegFrame`s in the matching
17//! `Yuv*P` pixel format or `Gray8` for 1-component streams. SOF0 and
18//! SOF1 share the same Huffman sequential scan structure at 8-bit
19//! precision; both are handled by the same scan decoder, and
20//! non-interleaved sequential scans (one SOS per component) fall back
21//! to the same coefficient-accumulator path used by the progressive
22//! decoder. Progressive scans accumulate DCT coefficients across
23//! multiple SOS segments using both spectral selection and successive
24//! approximation; the inverse DCT runs once after EOI. Restart markers
25//! (`RSTn`) and DRI segments are honoured on both paths. APP0..APP15
26//! segments (JFIF, EXIF, ICC, XMP, …) are skipped without parsing. The
27//! encoder accepts the same pixel formats and produces a standalone
28//! baseline JPEG using the Annex K "typical" Huffman tables, so its
29//! output is interoperable with any compliant JPEG decoder. A
30//! progressive (SOF2) output mode is available via
31//! `encoder::MjpegEncoder::set_progressive` or
32//! `encoder::encode_jpeg_progressive`; it emits a DC-first scan
33//! followed by two per-component AC band scans (spectral selection
34//! only, `Ah = Al = 0`). A lossless (SOF3) grayscale output mode is
35//! available via `encoder::encode_lossless_jpeg_grayscale` (or
36//! `MjpegEncoder::set_lossless(true)` on the trait-API encoder):
37//! every precision `P ∈ 2..=16` and every Annex H Table H.1 spatial
38//! predictor `1..=7` are supported, and the bitstream round-trips
39//! bit-exact through the matching SOF3 decoder.
40//!
41//! 4-component (CMYK / Adobe YCCK) JPEGs decode to packed `Cmyk` — the
42//! decoder inspects the APP14 Adobe transform flag to choose among plain
43//! CMYK, Adobe-inverted CMYK, and Adobe YCCK (which is colour-converted
44//! back to CMYK via BT.601 full-range YCbCr→RGB→CMY plus K inversion).
45//! The 4-component path covers both the sequential (SOF0 / SOF1) and the
46//! progressive (SOF2) scan decompositions at `P = 8`. 12-bit precision
47//! sequential JPEGs (SOF0/SOF1 with `P=12`) decode to `Gray12Le` /
48//! `Yuv444P12Le` / `Yuv422P12Le` / `Yuv420P12Le`; sample buffers stay
49//! 16-bit throughout the inverse DCT and the level shift uses 2048.
50//! Lossless JPEGs (SOF3) decode at every precision `P ∈ 2..=16` via
51//! Annex H predictor reconstruction (bit-exact, no DCT). Single-component
52//! grayscale output: `Gray8` at `P = 8`, `Gray10Le` / `Gray12Le` at
53//! `P = 10`/12, `Gray16Le` everywhere else. Three-component (RGB-class,
54//! `H_i = V_i = 1`) output: packed `Rgb24` at `P = 8`, planar
55//! `Gbrp10Le` / `Gbrp12Le` / `Gbrp14Le` at `P = 10`/12/14, packed
56//! `Rgb48Le` at every other precision in the valid range.
57//!
58//! Extended-sequential arithmetic (SOF9) is decoded via the Q-coder /
59//! arithmetic entropy decoder from T.81 Annex D + F.2.4. The DAC marker
60//! (Define Arithmetic Conditioning) is parsed when present; if absent the
61//! decoder uses the spec defaults `(L=0, U=1)` for DC / lossless
62//! conditioning and `Kx=5` for AC. Progressive arithmetic (SOF10) is
63//! decoded via the same Q-coder under the T.81 §G.1.3 procedures: DC
64//! first scans reuse the §F.1.4.1 model on the point-transformed
65//! values, DC refinement bits use the fixed 0.5 estimate, AC first
66//! scans run the §F.1.4 procedure per band (`Kmin = Ss`, EOB =
67//! end-of-band), and AC refinement scans follow the §G.1.3.3 model
68//! (Figures G.10 / G.11, Table G.2) — at `P = 8` and `P = 12` with
69//! the same output shaping as the Huffman progressive (SOF2) path,
70//! 4-component CMYK / YCCK included at `P = 8`. Lossless arithmetic
71//! (SOF11) is decoded via the same Q-coder under the two-dimensional
72//! statistical model of §H.1.2.3 (binary decisions conditioned on the
73//! left / above difference classifications through the Figure H.2
74//! array), covering the full Annex H surface the SOF3 path handles:
75//! every precision, every predictor, point transform and restart
76//! intervals.
77//!
78//! **Not supported** (will return `Error::Unsupported`):
79//! - Hierarchical (SOF5..SOF7, SOF13..SOF15) JPEGs
80//! - 12-bit progressive 4-component JPEGs (the workspace `PixelFormat`
81//!   enum has no 12-bit CMYK variant; `P=8` 4-component CMYK / YCCK
82//!   *is* supported on both the sequential and progressive scan
83//!   decompositions).
84//!
85//! Motion-JPEG carried over RTP (RFC 2435) is supported on the decode
86//! path via [`rtp::JpegDepacketizer`], which reassembles fragmented
87//! RTP/JPEG payloads and reconstructs the absent SOI / DQT / SOF0 / DHT /
88//! SOS / EOI marker segments (from the §3.1 main header's Q field or an
89//! in-band §3.1.8 quantization-table header) so the result is a complete
90//! JPEG interchange stream the [`decoder`] consumes directly.
91//!
92//! ## Standalone vs registry-integrated
93//!
94//! The crate's default `registry` Cargo feature pulls in `oxideav-core`
95//! and exposes the `Decoder` / `Encoder` trait surface, the JPEG-still
96//! container, and the [`registry::register`] / [`registry::register_codecs`]
97//! / [`registry::register_containers`] entry points. Disable the feature (`default-features = false`) for
98//! an oxideav-core-free build that still exposes the standalone
99//! [`decoder::decode_jpeg`] API plus crate-local [`MjpegFrame`] /
100//! [`MjpegPlane`] / [`MjpegPixelFormat`] / [`MjpegError`] types built
101//! only on `std`.
102
103pub mod decoder;
104pub mod encoder;
105pub mod error;
106pub mod image;
107pub mod jpeg;
108pub mod rtp;
109
110#[cfg(feature = "registry")]
111pub mod container;
112
113#[cfg(feature = "registry")]
114pub mod mjpeg_container;
115
116#[cfg(feature = "registry")]
117pub mod registry;
118
119pub const CODEC_ID_STR: &str = "mjpeg";
120
121// Standalone, framework-free API. Available regardless of the
122// `registry` feature.
123pub use error::{MjpegError, Result};
124pub use image::{MjpegFrame, MjpegPixelFormat, MjpegPlane};
125
126// Decode-free JPEG inspector — classifies the SOF variant + reports
127// dimensions / components / chroma-subsampling / colour hint without
128// running the entropy decoder. Standalone surface, no `oxideav-core`
129// dependency.
130pub use jpeg::inspect::{
131    inspect_jpeg, parse_adobe_app14, parse_icc_profile_app2, parse_jfif_app0, parse_jfxx_app0,
132    AdobeApp14, AdobeColorTransform, ChromaSubsampling, ColorHint, IccProfileApp2Chunk,
133    IccProfileChunks, InspectedComponent, JfifApp0, JfifUnits, JfxxApp0, JfxxThumbnail, JpegInfo,
134    SofKind,
135};
136
137// Framework-integrated API (`oxideav-core`-dependent). Gated behind
138// `registry` so image-library callers can build the crate without
139// dragging in `oxideav-core`.
140#[cfg(feature = "registry")]
141pub use registry::{__oxideav_entry, register, register_codecs, register_containers};
142
143#[cfg(all(test, feature = "registry"))]
144mod register_tests {
145    use oxideav_core::RuntimeContext;
146
147    #[test]
148    fn register_via_runtime_context_installs_factories() {
149        let mut ctx = RuntimeContext::new();
150        super::register(&mut ctx);
151        assert!(
152            ctx.codecs.decoder_ids().next().is_some(),
153            "register(ctx) should install codec decoder factories"
154        );
155        assert!(
156            ctx.containers.demuxer_names().next().is_some(),
157            "register(ctx) should install container demuxer factories"
158        );
159    }
160}