Skip to main content

oxideav_h261/
lib.rs

1//! Pure-Rust ITU-T H.261 video codec (decoder + encoder).
2//!
3//! Scope (ITU-T Rec. H.261 03/93):
4//! * Picture header — PSC (20 bits: `0000 0000 0000 0001 0000`), TR (5),
5//!   PTYPE (6) with `source format` bit (0 = QCIF, 1 = CIF), PEI/PSPARE loop.
6//! * GOB header — GBSC (16 bits), GN (4), GQUANT (5), GEI/GSPARE loop.
7//! * Macroblock layer — MBA VLC (Table 1), MTYPE VLC (Table 2), MQUANT (5),
8//!   MVD VLC (Table 3), CBP VLC (Table 4).
9//! * Block layer — TCOEFF VLC (Table 5) + EOB + 20-bit `(escape | run | level)`
10//!   escape, zigzag scan per Figure 12, dequantisation per §4.2.4, 8x8 IDCT,
11//!   INTRA DC as 8-bit FLC per Table 6.
12//! * INTRA / INTER / INTER+MC / INTER+MC+FIL macroblock types.
13//! * Integer-pel motion compensation (range +-15 pels). Chroma MVs are the
14//!   luma MV halved then truncated toward zero (§3.2.2).
15//! * Loop filter (FIL bit of MTYPE) — separable 1/4, 1/2, 1/4 filter, §3.2.3.
16//! * Output is YUV 4:2:0 in an `oxideav_core::VideoFrame`. Two picture sizes:
17//!   QCIF 176x144 and CIF 352x288.
18//! * Encoder: I + P pictures, integer-pel MC (spiral+diamond ME), per-GOB
19//!   MQUANT rate control, FIL loop-filter RDO, full §4.2.3.4 MV-pred.
20//! * BCH (511,493) error-correction framing (§5.4): the [`bch`] module
21//!   wraps / unwraps the outer FEC multiframe layer (alignment pattern,
22//!   `Fi` bit, BCH parity computation and per-frame syndrome check), and
23//!   implements the spec-mandated `t = 1` single-bit error correction
24//!   via [`bch::locate_single_error`] /
25//!   [`bch::decode_multiframe_with_correction`]. Sweep-tested across all
26//!   511 protected bit positions per frame.
27//! * Hypothetical Reference Decoder buffer model (§5.2 + Annex B): the
28//!   [`hrd`] module exposes the per-picture bit cap (256 kbits CIF /
29//!   64 kbits QCIF) and the HRD buffer-occupancy walk used to verify
30//!   a coded sequence won't underflow a conforming decoder's receiving
31//!   buffer at a given channel rate.
32//! * RTP payload-format wrapping (RFC 4587): the [`rtp`] module packs an
33//!   elementary stream into a sequence of GOB-aligned H.261 RTP payloads
34//!   (4-byte header + bitstream slice) and unpacks them back, plus the
35//!   §4.2 RECOMMENDED MB-level fragmentation (`packetize_mb_fragmented`)
36//!   that parses the Huffman layer to split oversize GOBs on macroblock
37//!   boundaries with the full §4.1 GOBN/MBAP/QUANT/HMVD/VMVD context.
38//!   The `RtpPacketizer` glue wraps each payload in a full RFC 3550 §5.1
39//!   RTP fixed header (V/P/X/CC/M/PT/seq/timestamp/SSRC) so callers can
40//!   hand `pack_frame` output straight to UDP / DTLS / SRTP.
41//! * RTCP report packets (RFC 3550 §6.4): the [`rtcp`] module builds and
42//!   parses Sender Report (SR, PT=200) and Receiver Report (RR, PT=201)
43//!   packets — the control-channel companions to the [`rtp`] data path.
44//!   `RtpPacketizer` tracks the sender's running packet / octet counts so a
45//!   conformant SR can be emitted directly from its session state.
46//! * RTCP SDES / BYE / APP packets (RFC 3550 §6.5 / §6.6 / §6.7): the
47//!   [`rtcp`] module also builds and parses Source Description (SDES,
48//!   PT=202), Goodbye (BYE, PT=203), and Application-Defined (APP, PT=204)
49//!   packets, plus compound-packet (§6.1) concatenation and walking. APP
50//!   carries a 4-byte ASCII name + 32-bit-aligned application-dependent
51//!   data for application-layer extensions that do not warrant their own
52//!   IANA-registered packet type.
53//! * SDP media-type signalling (RFC 4587 §6.1.1 / §6.2): the [`sdp`] module
54//!   maps the `video/H261` media type and its three optional parameters
55//!   (`CIF`, `QCIF`, `D`) to the SDP `a=rtpmap` / `a=fmtp` attribute lines,
56//!   with the §6.2.1 offer/answer helpers (per-format MPI frame-rate bound,
57//!   the "at least one picture size" invariant, the RFC-2032 QCIF fallback).
58//! * Annex D still-image transmission (§D.2 + §D.3): the [`annex_d`] module
59//!   exposes the [`annex_d::SubImageIndex`] mapping to / from the 5-bit `TR`
60//!   field, the still-image dimensions (4× the currently transmitted video
61//!   format), and the Figure D.1 2:1 × 2:1 sub-sample / reassemble transform
62//!   between a full-resolution still image and its four sub-images.
63//!
64//! Out of scope:
65//! * Multi-bit (weight ≥ 2) BCH (511,493) error correction. The
66//!   generator polynomial has minimum distance `d = 3`, so the spec's
67//!   `t = (d − 1) / 2 = 1` correction capability is the upper bound;
68//!   weight-2 or denser error patterns are flagged
69//!   (`uncorrectable_frames` in [`bch::DecodedMultiframe`]) but cannot
70//!   be uniquely resolved by this code on its own — the inner H.261
71//!   video VLC layer handles those via GOB resync.
72//!
73//! No runtime dependencies beyond `oxideav-core`, `oxideav-codec`, and
74//! `oxideav-pixfmt`.
75
76#![allow(clippy::needless_range_loop)]
77#![allow(clippy::too_many_arguments)]
78#![allow(clippy::unusual_byte_groupings)]
79#![allow(clippy::unnecessary_cast)]
80
81pub mod annex_d;
82pub mod bch;
83pub mod block;
84pub mod decoder;
85pub mod encoder;
86pub mod fdct;
87pub mod gob;
88pub mod hrd;
89pub mod idct;
90pub mod mb;
91pub mod picture;
92pub mod quant;
93pub mod rtcp;
94pub mod rtp;
95pub mod sdp;
96pub mod start_code;
97pub mod tables;
98
99use oxideav_core::{CodecCapabilities, CodecId, CodecTag};
100use oxideav_core::{CodecInfo, CodecRegistry, RuntimeContext};
101
102/// The canonical oxideav codec id for ITU-T H.261 video.
103///
104/// AVI FourCC `H261` maps to this id; raw `.h261` elementary-stream files
105/// probe to it as well.
106pub const CODEC_ID_STR: &str = "h261";
107
108/// Register the H.261 decoder and encoder with a codec registry.
109pub fn register_codecs(reg: &mut CodecRegistry) {
110    let caps = CodecCapabilities::video("h261_sw")
111        .with_lossy(true)
112        .with_intra_only(false)
113        .with_max_size(352, 288);
114    reg.register(
115        CodecInfo::new(CodecId::new(CODEC_ID_STR))
116            .capabilities(caps)
117            .decoder(decoder::make_decoder)
118            .encoder(encoder::make_encoder)
119            .tags([CodecTag::fourcc(b"H261"), CodecTag::fourcc(b"h261")]),
120    );
121}
122
123/// Unified registration entry point: install the H.261 codec factories
124/// into the codec sub-registry of a [`RuntimeContext`].
125///
126/// This is the preferred entry point for new code — it matches the
127/// convention every sibling crate now follows. Direct callers that need
128/// only the codec sub-registry can keep using [`register_codecs`].
129///
130/// Also wired into [`oxideav_meta::register_all`] via the
131/// [`oxideav_core::register!`] macro below.
132pub fn register(ctx: &mut RuntimeContext) {
133    register_codecs(&mut ctx.codecs);
134}
135
136oxideav_core::register!("h261", register);
137
138#[cfg(test)]
139mod register_tests {
140    use super::*;
141    use oxideav_core::{CodecId, CodecParameters, RuntimeContext};
142
143    #[test]
144    fn register_via_runtime_context_installs_codec_factory() {
145        let mut ctx = RuntimeContext::new();
146        register(&mut ctx);
147        let params = CodecParameters::video(CodecId::new(CODEC_ID_STR));
148        let dec = ctx
149            .codecs
150            .first_decoder(&params)
151            .expect("h261 decoder factory");
152        assert_eq!(dec.codec_id().as_str(), CODEC_ID_STR);
153    }
154
155    #[test]
156    fn register_via_runtime_context_installs_encoder_factory() {
157        let mut ctx = RuntimeContext::new();
158        register(&mut ctx);
159        let params = CodecParameters::video(CodecId::new(CODEC_ID_STR));
160        let enc = ctx
161            .codecs
162            .first_encoder(&params)
163            .expect("h261 encoder factory");
164        assert_eq!(enc.codec_id().as_str(), CODEC_ID_STR);
165    }
166
167    #[test]
168    fn encoder_factory_qcif_defaults() {
169        let mut ctx = RuntimeContext::new();
170        register(&mut ctx);
171        let mut params = CodecParameters::video(CodecId::new(CODEC_ID_STR));
172        params.width = Some(176);
173        params.height = Some(144);
174        let enc = ctx
175            .codecs
176            .first_encoder(&params)
177            .expect("h261 encoder factory qcif");
178        assert_eq!(enc.codec_id().as_str(), CODEC_ID_STR);
179        let out = enc.output_params();
180        assert_eq!(out.width, Some(176));
181        assert_eq!(out.height, Some(144));
182    }
183
184    #[test]
185    fn encoder_factory_cif() {
186        let mut ctx = RuntimeContext::new();
187        register(&mut ctx);
188        let mut params = CodecParameters::video(CodecId::new(CODEC_ID_STR));
189        params.width = Some(352);
190        params.height = Some(288);
191        let enc = ctx
192            .codecs
193            .first_encoder(&params)
194            .expect("h261 encoder factory cif");
195        assert_eq!(enc.codec_id().as_str(), CODEC_ID_STR);
196        let out = enc.output_params();
197        assert_eq!(out.width, Some(352));
198        assert_eq!(out.height, Some(288));
199    }
200
201    #[test]
202    fn encoder_factory_rejects_bad_dimensions() {
203        let mut ctx = RuntimeContext::new();
204        register(&mut ctx);
205        let mut params = CodecParameters::video(CodecId::new(CODEC_ID_STR));
206        params.width = Some(320);
207        params.height = Some(240);
208        assert!(
209            ctx.codecs.first_encoder(&params).is_err(),
210            "should reject 320x240"
211        );
212    }
213}