Skip to main content

otf_pixels_codec_avif/av1/
obu.rs

1//! The Open Bitstream Unit layer (AV1 spec §5.3).
2//!
3//! An AV1 stream — whether the configuration OBUs in an `av1C` box or the coded
4//! frame in an item's payload — is a sequence of OBUs, each a one- or two-byte
5//! header followed by an optional `leb128` size and a payload. This module
6//! splits a byte buffer into borrowed OBUs without interpreting their contents;
7//! [`super::seq`] and [`super::frame`] interpret the ones that matter.
8//!
9//! AVIF mandates the *low-overhead* framing, in which every OBU carries its own
10//! size field. This reader accepts a sizeless final OBU as well — it extends to
11//! the end of the buffer — because that is the one unambiguous case and
12//! rejecting it would turn a legal-enough stream into a spurious error.
13
14use super::bits::BitReader;
15use otf_pixels_core::{PixelsError, Result};
16
17/// The kind of an OBU (`obu_type`, §6.2.2).
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ObuType {
20    /// `OBU_SEQUENCE_HEADER` (1).
21    SequenceHeader,
22    /// `OBU_TEMPORAL_DELIMITER` (2).
23    TemporalDelimiter,
24    /// `OBU_FRAME_HEADER` (3).
25    FrameHeader,
26    /// `OBU_TILE_GROUP` (4).
27    TileGroup,
28    /// `OBU_METADATA` (5).
29    Metadata,
30    /// `OBU_FRAME` (6) — a frame header immediately followed by tile data.
31    Frame,
32    /// `OBU_REDUNDANT_FRAME_HEADER` (7).
33    RedundantFrameHeader,
34    /// `OBU_TILE_LIST` (8).
35    TileList,
36    /// `OBU_PADDING` (15).
37    Padding,
38    /// Any reserved or unknown type, carried through so a stream that uses one
39    /// is skipped rather than rejected.
40    Reserved(u8),
41}
42
43impl ObuType {
44    fn from_bits(value: u32) -> Self {
45        match value {
46            1 => Self::SequenceHeader,
47            2 => Self::TemporalDelimiter,
48            3 => Self::FrameHeader,
49            4 => Self::TileGroup,
50            5 => Self::Metadata,
51            6 => Self::Frame,
52            7 => Self::RedundantFrameHeader,
53            8 => Self::TileList,
54            15 => Self::Padding,
55            other => Self::Reserved(other as u8),
56        }
57    }
58}
59
60/// A parsed OBU header (§5.3.2).
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct ObuHeader {
63    /// The OBU type.
64    pub kind: ObuType,
65    /// `temporal_id` from the extension header, or 0 when absent.
66    pub temporal_id: u8,
67    /// `spatial_id` from the extension header, or 0 when absent.
68    pub spatial_id: u8,
69}
70
71/// A single OBU: its header and a borrow of its payload bytes.
72#[derive(Debug, Clone, Copy)]
73pub struct Obu<'a> {
74    /// The parsed header.
75    pub header: ObuHeader,
76    /// The payload, exactly `obu_size` bytes, ready for a fresh [`BitReader`].
77    pub payload: &'a [u8],
78    /// The byte offset of `payload` within the buffer `parse_stream` was given,
79    /// so a caller can locate tile data absolutely without pointer arithmetic.
80    pub payload_start: usize,
81}
82
83impl<'a> Obu<'a> {
84    /// Split a byte buffer into its OBUs.
85    ///
86    /// Stops cleanly at the end of the buffer. Rejects a header whose forbidden
87    /// bit is set and a size that would run past the buffer — the two ways OBU
88    /// framing is used to point a decoder outside its input.
89    pub fn parse_stream(data: &'a [u8]) -> Result<Vec<Obu<'a>>> {
90        let mut obus = Vec::new();
91        let mut pos = 0;
92        while pos < data.len() {
93            let Some(rest) = data.get(pos..) else {
94                break;
95            };
96            let mut reader = BitReader::new(rest);
97
98            if reader.f(1)? != 0 {
99                return Err(PixelsError::malformed(
100                    "avif",
101                    "an AV1 OBU forbidden bit was set",
102                ));
103            }
104            let kind = ObuType::from_bits(reader.f(4)?);
105            let extension_flag = reader.flag()?;
106            let has_size_field = reader.flag()?;
107            let _obu_reserved_1bit = reader.f(1)?;
108
109            let (temporal_id, spatial_id) = if extension_flag {
110                let temporal_id = reader.f(3)? as u8;
111                let spatial_id = reader.f(2)? as u8;
112                let _extension_reserved_3bits = reader.f(3)?;
113                (temporal_id, spatial_id)
114            } else {
115                (0, 0)
116            };
117
118            let payload_len = if has_size_field {
119                usize::try_from(reader.leb128()?).map_err(|_| {
120                    PixelsError::malformed("avif", "an AV1 OBU size exceeds this platform's usize")
121                })?
122            } else {
123                // A sizeless OBU runs to the end of the buffer; only the last
124                // OBU can legally be sizeless.
125                rest.len().saturating_sub(reader.byte_position())
126            };
127
128            let payload_start = pos + reader.byte_position();
129            let payload_end = payload_start
130                .checked_add(payload_len)
131                .filter(|&e| e <= data.len());
132            let Some(payload_end) = payload_end else {
133                return Err(PixelsError::malformed(
134                    "avif",
135                    "an AV1 OBU declares more bytes than the stream holds",
136                ));
137            };
138            let Some(payload) = data.get(payload_start..payload_end) else {
139                return Err(PixelsError::malformed(
140                    "avif",
141                    "an AV1 OBU payload range is invalid",
142                ));
143            };
144
145            obus.push(Obu {
146                header: ObuHeader {
147                    kind,
148                    temporal_id,
149                    spatial_id,
150                },
151                payload,
152                payload_start,
153            });
154            pos = payload_end;
155        }
156        Ok(obus)
157    }
158}
159
160#[cfg(test)]
161#[allow(
162    clippy::unwrap_used,
163    clippy::indexing_slicing,
164    clippy::panic,
165    clippy::unusual_byte_groupings,
166    reason = "tests operate on known-good values and assert shapes directly"
167)]
168mod tests {
169    use super::*;
170    use otf_pixels_core::ErrorCode;
171
172    /// Build a low-overhead OBU: header byte with the size-field flag set,
173    /// a single-byte leb128 length, then the payload.
174    fn sized_obu(kind: u8, payload: &[u8]) -> Vec<u8> {
175        let mut out = Vec::new();
176        // forbidden=0, type=kind, ext=0, has_size=1, reserved=0
177        let header = (kind << 3) | 0b0000_0010;
178        out.push(header);
179        assert!(payload.len() < 128, "test payloads stay single-byte leb128");
180        out.push(payload.len() as u8);
181        out.extend_from_slice(payload);
182        out
183    }
184
185    #[test]
186    fn splits_a_stream_into_sized_obus() {
187        let mut stream = sized_obu(2, &[]); // temporal delimiter
188        stream.extend(sized_obu(1, &[0xAA, 0xBB])); // sequence header
189        stream.extend(sized_obu(6, &[0xCC])); // frame
190
191        let obus = Obu::parse_stream(&stream).unwrap();
192        assert_eq!(obus.len(), 3);
193        assert_eq!(obus[0].header.kind, ObuType::TemporalDelimiter);
194        assert_eq!(obus[0].payload, &[] as &[u8]);
195        assert_eq!(obus[1].header.kind, ObuType::SequenceHeader);
196        assert_eq!(obus[1].payload, &[0xAA, 0xBB]);
197        assert_eq!(obus[2].header.kind, ObuType::Frame);
198        assert_eq!(obus[2].payload, &[0xCC]);
199    }
200
201    #[test]
202    fn reads_the_extension_header_ids() {
203        // forbidden=0 type=1 ext=1 has_size=1 reserved=0 -> 0b0_0001_1_1_0
204        let mut stream = vec![0b0000_1110];
205        // extension byte: temporal_id=3 (011) spatial_id=2 (10) reserved=0(000)
206        stream.push(0b011_10_000);
207        stream.push(0x01); // leb128 size = 1
208        stream.push(0x55); // payload
209        let obus = Obu::parse_stream(&stream).unwrap();
210        assert_eq!(obus.len(), 1);
211        assert_eq!(obus[0].header.temporal_id, 3);
212        assert_eq!(obus[0].header.spatial_id, 2);
213        assert_eq!(obus[0].payload, &[0x55]);
214    }
215
216    #[test]
217    fn a_sizeless_final_obu_takes_the_rest_of_the_buffer() {
218        // header: type=6 ext=0 has_size=0 -> 0b0_0110_0_0_0
219        let mut stream = vec![0b0011_0000];
220        stream.extend_from_slice(&[1, 2, 3, 4]);
221        let obus = Obu::parse_stream(&stream).unwrap();
222        assert_eq!(obus.len(), 1);
223        assert_eq!(obus[0].header.kind, ObuType::Frame);
224        assert_eq!(obus[0].payload, &[1, 2, 3, 4]);
225    }
226
227    #[test]
228    fn a_size_past_the_buffer_is_rejected() {
229        // has_size=1, size=200, but only a few bytes follow.
230        let stream = vec![0b0011_0010, 200, 1, 2, 3];
231        let err = Obu::parse_stream(&stream).unwrap_err();
232        assert_eq!(err.code(), ErrorCode::Malformed);
233    }
234
235    #[test]
236    fn a_set_forbidden_bit_is_rejected() {
237        let stream = vec![0b1011_0010, 0x00];
238        let err = Obu::parse_stream(&stream).unwrap_err();
239        assert_eq!(err.code(), ErrorCode::Malformed);
240    }
241
242    #[test]
243    fn an_unknown_type_is_carried_through_not_rejected() {
244        let stream = sized_obu(9, &[0x01]);
245        let obus = Obu::parse_stream(&stream).unwrap();
246        assert_eq!(obus[0].header.kind, ObuType::Reserved(9));
247    }
248}