Skip to main content

oxideav_bitstream/
lib.rs

1//! Minimal IDR / keyframe header parsing for H.264, HEVC and AV1.
2//!
3//! This crate is the parser layer that the oxideav HW-accel bridge
4//! crates (`oxideav-vaapi`, `oxideav-vdpau`, `oxideav-vulkan-video`)
5//! call before submitting parameter buffers to the GPU. It is **not**
6//! a full codec: there is no DCT, no entropy decode, no inverse
7//! transform, no motion compensation. Just enough syntax parsing to
8//! populate `VAPictureParameterBufferH264` / `VdpPictureInfoH264` /
9//! `VkVideoDecodeH264PictureInfoKHR` and the equivalents for HEVC and
10//! AV1.
11//!
12//! Each codec lives in its own module:
13//!
14//! - [`h264`] — Annex-B framing, SPS, PPS and minimal slice header.
15//! - [`hevc`] — Annex-B framing, VPS, SPS, PPS and minimal slice
16//!   segment header.
17//! - [`h266`] — Annex-B framing, 2-byte NAL header decode, VCL /
18//!   IRAP / parameter-set classifiers. Parameter-set parsing
19//!   deferred to future rounds.
20//! - [`av1`] — leb128 reader/writer, OBU walker + emitter, sequence
21//!   header and key-frame OBU parsing.
22//!
23//! There is no shared cross-codec abstraction in v0 — each module
24//! exposes its own `parse_*` entry points and result structs. The
25//! shared [`bit_reader::BitReader`] type provides `u(n)`, `ue(v)` and
26//! `se(v)` primitives; [`bit_writer::BitWriter`] is its MSB-first
27//! inverse for emitting the same fields back out.
28//!
29//! ## No `unsafe`
30//!
31//! This crate contains zero `unsafe` blocks. Bit reading is software,
32//! no FFI involved.
33//!
34//! ## Workspace clean-room policy
35//!
36//! Bitstream syntax is taken from the relevant ITU-T / Khronos /
37//! AOMedia spec PDFs only. We deliberately do not consult ffmpeg /
38//! libavcodec, x264, x265, libde265, libaom, dav1d, rav1e or libgav1
39//! source. ffmpeg / aomenc are used only as black-box CLI tools to
40//! generate test fixtures.
41
42#![forbid(unsafe_code)]
43#![deny(rust_2018_idioms)]
44
45pub mod av1;
46pub mod bit_reader;
47pub mod bit_writer;
48pub mod h264;
49pub mod h266;
50pub mod hevc;
51pub mod ivf;
52pub mod mpeg2;
53pub mod vc1;
54pub mod vp8;
55pub mod vp9;
56
57/// Errors produced by the parsers in this crate.
58///
59/// We deliberately do not depend on `oxideav_core::Error` — keeping
60/// this crate dep-free means `oxideav-vdpau` / `oxideav-vaapi` /
61/// `oxideav-vulkan-video` can pull it in without tugging on the rest
62/// of the framework.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum BitstreamError {
65    /// The byte stream ended in the middle of a header we expected
66    /// to be complete (NAL too short, OBU truncated, etc.).
67    UnexpectedEnd(String),
68
69    /// Syntax error inside an otherwise well-framed header (an
70    /// Exp-Golomb code with too many leading zeros, an out-of-range
71    /// field value, a forbidden bit set, …).
72    InvalidData(String),
73
74    /// The input parses successfully but uses a feature this crate
75    /// deliberately does not handle (scaling lists, FMO, AV1 film
76    /// grain, …). HW backends should reject the fixture rather than
77    /// submit a partial parameter buffer.
78    Unsupported(String),
79}
80
81impl core::fmt::Display for BitstreamError {
82    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83        match self {
84            BitstreamError::UnexpectedEnd(msg) => write!(f, "unexpected end of stream: {msg}"),
85            BitstreamError::InvalidData(msg) => write!(f, "invalid bitstream data: {msg}"),
86            BitstreamError::Unsupported(msg) => write!(f, "unsupported bitstream feature: {msg}"),
87        }
88    }
89}
90
91impl std::error::Error for BitstreamError {}
92
93impl BitstreamError {
94    pub fn invalid(msg: impl Into<String>) -> Self {
95        BitstreamError::InvalidData(msg.into())
96    }
97
98    pub fn unsupported(msg: impl Into<String>) -> Self {
99        BitstreamError::Unsupported(msg.into())
100    }
101
102    pub fn unexpected_end(msg: impl Into<String>) -> Self {
103        BitstreamError::UnexpectedEnd(msg.into())
104    }
105}