oxideav-mjpeg 0.1.2

Pure-Rust JPEG / Motion-JPEG codec for oxideav — baseline, extended-sequential, and progressive decode; baseline and progressive encode
Documentation

oxideav-mjpeg

Pure-Rust JPEG / Motion-JPEG codec and still-image container — decodes baseline (SOF0), extended-sequential (SOF1) and progressive (SOF2) 8-bit JPEGs, encodes baseline and progressive JPEG using the Annex K "typical" Huffman tables. YUV 4:4:4 / 4:2:2 / 4:2:0 and grayscale. Zero C dependencies.

Part of the oxideav framework but usable standalone.

Installation

[dependencies]
oxideav-core = "0.1"
oxideav-codec = "0.1"
oxideav-container = "0.1"
oxideav-mjpeg = "0.1"

Quick use

A JPEG file is a single SOI..EOI byte stream, so the still-image container is a pass-through: open the file, pull one packet, decode. Motion-JPEG streams (inside AVI / MOV / AMV / etc.) reuse the same codec — each video packet is a full JPEG.

use oxideav_codec::CodecRegistry;
use oxideav_container::ContainerRegistry;
use oxideav_core::Frame;

let mut codecs = CodecRegistry::new();
let mut containers = ContainerRegistry::new();
oxideav_mjpeg::register(&mut codecs);
oxideav_mjpeg::register_containers(&mut containers);

let input: Box<dyn oxideav_container::ReadSeek> = Box::new(
    std::io::Cursor::new(std::fs::read("photo.jpg")?),
);
let mut dmx = containers.open("jpeg", input)?;
let stream = &dmx.streams()[0];
let mut dec = codecs.make_decoder(&stream.params)?;

let pkt = dmx.next_packet()?;
dec.send_packet(&pkt)?;
if let Ok(Frame::Video(vf)) = dec.receive_frame() {
    // vf.format is Yuv444P / Yuv422P / Yuv420P / Gray8
    // vf.planes[0..] carry the planar samples.
}
# Ok::<(), Box<dyn std::error::Error>>(())

Encoder

use oxideav_core::{CodecId, CodecParameters, Frame, PixelFormat};

let mut params = CodecParameters::video(CodecId::new("mjpeg"));
params.width = Some(w);
params.height = Some(h);
params.pixel_format = Some(PixelFormat::Yuv420P);
let mut enc = codecs.make_encoder(&params)?;
enc.send_frame(&Frame::Video(frame_yuv420))?;
let pkt = enc.receive_packet()?;

The encoder accepts Yuv444P, Yuv422P, or Yuv420P planar input and emits a standalone baseline JPEG per frame: SOI, JFIF APP0, DQT, SOF0, DHT (Annex K typical tables), optional DRI, SOS, entropy scan, EOI. Default quality factor is 75 (libjpeg style); encoder::encode_jpeg(frame, quality) is also exposed for sibling crates that wrap the same bitstream in custom containers.

Restart markers (RSTn + DRI) are supported for interop and bitstream resiliency. They are off by default — call MjpegEncoder::set_restart_interval(n_mcus) (or use encoder::encode_jpeg_with_opts(frame, quality, n_mcus)) to enable them. A non-zero value writes a DRI segment before SOS and cycles RST0..=RST7 every n_mcus macroblocks in the scan, resetting DC predictors at each marker. Passing 0 preserves the historical no-restart behaviour.

Progressive (SOF2) encode

Toggle progressive emission via MjpegEncoder::set_progressive(true) on the concrete encoder (construct it with MjpegEncoder::from_params), or call encoder::encode_jpeg_progressive(frame, quality) directly. The output is a standalone progressive JPEG with this scan decomposition:

  1. Interleaved DC-first scan (Ss=0, Se=0, Ah=0, Al=0) covering every component.
  2. Per-component low-band AC scan (Ss=1, Se=5, Ah=0, Al=0) — luma, Cb, Cr.
  3. Per-component high-band AC scan (Ss=6, Se=63, Ah=0, Al=0).

That's 1 + 3 + 3 = 7 SOS segments. Successive-approximation refinement scans (Ah ≥ 1) are not emitted; coefficients reach the decoder in a single initial scan per band and round-trip through our own progressive decoder. Restart markers are not emitted on the progressive path. Compressed size is typically ~10% larger than the equivalent baseline encode due to the extra SOS/DHT overhead and per-scan EOB handling (no EOBn runs).

Codec / container IDs

  • Codec: "mjpeg". Decoder output / encoder input pixel formats: Yuv444P, Yuv422P, Yuv420P, plus Gray8 on the decode side.
  • Container: "jpeg", matches .jpg / .jpeg / .jpe / .jfif by extension and by FF D8 FF magic bytes. One frame per file; muxing is a pass-through of the codec packet.

Format coverage

Decoder:

  • SOF0 (baseline sequential, Huffman, 8-bit).
  • SOF1 (extended sequential, Huffman, 8-bit) — same scan structure as SOF0 at 8-bit, so the same code path handles it.
  • SOF2 (progressive, Huffman, 8-bit) — multi-scan spectral selection and successive approximation (DC first + refinement, AC first + refinement with EOB-run).
  • Non-interleaved sequential scans (SOF0/SOF1 with one SOS per component) — transparently routed through the shared coefficient accumulator.
  • 12-bit precision sequential JPEGs (SOF0/SOF1, P=12) → 16-bit-LE Gray12Le for grayscale and Yuv420P12Le for 4:2:0 YUV. Level shift is 2048 as per the spec.
  • Lossless JPEG (SOF3) — single-component grayscale at any precision P ∈ 2..=16. Annex H predictor reconstruction (bit-exact). Output: Gray8 at P=8, Gray10Le / Gray12Le at P=10/12, else Gray16Le. Point transform (Pt = Al) honoured.
  • CMYK / YCCK 4-component JPEGs → packed PixelFormat::Cmyk. Adobe APP14 transform flag honoured: transform=0 (Adobe CMYK, stored inverted) un-inverts on decode; transform=2 (YCCK) converts back to CMYK via BT.601 YCbCr→RGB→CMY and K inversion; no APP14 → plain ("regular", C=0 = no ink) pass-through.
  • Chroma subsampling: 4:4:4, 4:2:2, 4:2:0.
  • Grayscale (single-component → Gray8).
  • Restart markers (RSTn) + DRI.
  • APP0..APP15 segments skipped cleanly (EXIF/XMP/ICC preserved at the container level, not parsed).
  • Trailing garbage past EOI is stripped by the demuxer.

Encoder:

  • SOF0 (baseline sequential) — 8-bit Huffman, Annex K tables.
  • SOF2 (progressive) — spectral-selection decomposition: one interleaved DC-first scan + per-component AC scans over bands Ss=1..=5 and Ss=6..=63 (Ah=0, Al=0). No successive-approximation refinement.
  • 4:4:4 / 4:2:2 / 4:2:0 input.
  • Optional DRI + RSTn emission on the baseline path (off by default; see the Encoder section above).

Not supported (decoder returns Error::Unsupported):

  • Hierarchical (SOF5+), arithmetic-coded (SOF9..SOF15).
  • 12-bit progressive (SOF2 with P=12), 12-bit 4:2:2 / 4:4:4 YUV.
  • Progressive 4-component JPEGs.
  • Multi-component lossless JPEGs.

License

MIT — see LICENSE.