Skip to main content

codec/pixel_format/av1/
obu.rs

1//! AV1 OBU (Open Bitstream Unit) finding and byte-offset utilities.
2//!
3//! Provides the low-level OBU iteration that the higher-level parsers
4//! (`sequence`, `frame`) and Vulkan decode infrastructure depend on.
5
6use super::sequence::Av1SequenceHeader;
7use super::frame::parse_av1_frame_header;
8
9/// Find the first AV1 OBU of the given obu_type. AV1 OBU header:
10///   obu_forbidden_bit(1) | obu_type(4) | obu_extension_flag(1)
11///   | obu_has_size_field(1) | obu_reserved_1bit(1)
12/// followed by an optional 1-byte extension, optional LEB128 size,
13/// then payload. For simplicity we require obu_has_size_field=1 which
14/// all muxed AV1 satisfies.
15pub(super) fn find_av1_obu(data: &[u8], target_type: u8) -> Option<&[u8]> {
16    find_av1_obu_with_offset(data, target_type).map(|(bytes, _)| bytes)
17}
18
19/// Public re-export so the Vulkan Video decoder can extract the byte
20/// range of an OBU from a demuxed sample.
21pub fn find_av1_obu_with_offset_pub(data: &[u8], target_type: u8) -> Option<(&[u8], usize)> {
22    find_av1_obu_with_offset(data, target_type)
23}
24
25/// Returns the OBU payload slice AND the byte offset at which it
26/// starts inside `data`. The offset is what callers need to translate
27/// an in-OBU bit/byte position (e.g. tile_group start after
28/// byte_alignment()) to an absolute position in the sample buffer.
29pub(super) fn find_av1_obu_with_offset(data: &[u8], target_type: u8) -> Option<(&[u8], usize)> {
30    let mut i = 0;
31    while i < data.len() {
32        let header = data[i];
33        let obu_type = (header >> 3) & 0x0F;
34        let extension_flag = (header >> 2) & 0x01;
35        let has_size_field = (header >> 1) & 0x01;
36        i += 1;
37        if extension_flag == 1 {
38            i += 1;
39        }
40        if has_size_field == 0 {
41            return None;
42        }
43        let (size, leb_bytes) = read_leb128(&data[i..])?;
44        i += leb_bytes;
45        if obu_type == target_type {
46            let end = (i + size as usize).min(data.len());
47            return Some((&data[i..end], i));
48        }
49        i += size as usize;
50    }
51    None
52}
53
54fn read_leb128(data: &[u8]) -> Option<(u64, usize)> {
55    let mut value = 0u64;
56    for i in 0..8 {
57        if i >= data.len() {
58            return None;
59        }
60        let byte = data[i];
61        value |= ((byte & 0x7F) as u64) << (i * 7);
62        if byte & 0x80 == 0 {
63            return Some((value, i + 1));
64        }
65    }
66    None
67}
68
69/// Locate the byte offset, within `sample`, of the uncompressed_header
70/// payload of the first Frame OBU (obu_type 3 or 6). Returns None if
71/// no such OBU is found.
72///
73/// AV1 OBU layout: 1-byte header + optional 1-byte extension + LEB128
74/// size + payload. For a Frame OBU (type 6), the payload begins with
75/// uncompressed_header_obu() — so the byte offset we return is the
76/// first byte of uncompressed_header() in the original sample buffer.
77/// Vulkan `VkVideoDecodeAV1PictureInfoKHR::frameHeaderOffset` wants
78/// exactly this value.
79pub fn av1_frame_header_offset(sample: &[u8]) -> Option<u32> {
80    let mut i = 0usize;
81    while i < sample.len() {
82        let header = sample[i];
83        let obu_type = (header >> 3) & 0x0F;
84        let extension_flag = (header >> 2) & 0x01;
85        let has_size_field = (header >> 1) & 0x01;
86        let mut p = i + 1;
87        if extension_flag == 1 {
88            p += 1;
89        }
90        let (size, leb) = if has_size_field == 1 {
91            let (s, n) = read_leb128(&sample[p..])?;
92            p += n;
93            (s as usize, n)
94        } else {
95            // OBU has_size_field=0 is legal but we don't handle it
96            // (AV1 in MP4 always sets it).
97            return None;
98        };
99        let _ = leb;
100        if obu_type == 3 || obu_type == 6 {
101            return Some(p as u32);
102        }
103        p += size;
104        i = p;
105    }
106    None
107}
108
109/// Locate the byte offset of the first tile_group_obu payload within
110/// the sample buffer, used for
111/// `VkVideoDecodeAV1PictureInfoKHR::pTileOffsets`. Two shapes:
112/// - Separate Frame Header OBU (type 3) + Tile Group OBU (type 4):
113///   return the type-4 OBU payload start.
114/// - Frame OBU (type 6) (frame header + tile group in one OBU):
115///   return `frame_OBU_payload_start + tile_group_offset_in_obu`
116///   where the in-OBU offset comes from `parse_av1_frame_header`
117///   (the byte-aligned position after uncompressed_header).
118///
119/// Returns None when neither shape is found or the parser bails.
120pub fn av1_tile_group_offset(sample: &[u8], seq: &Av1SequenceHeader) -> Option<u32> {
121    // If a standalone Tile Group OBU (type 4) exists, use its payload
122    // start directly — no uncompressed_header to skip past.
123    let mut i = 0usize;
124    while i < sample.len() {
125        let header = sample[i];
126        let obu_type = (header >> 3) & 0x0F;
127        let extension_flag = (header >> 2) & 0x01;
128        let has_size_field = (header >> 1) & 0x01;
129        let mut p = i + 1;
130        if extension_flag == 1 {
131            p += 1;
132        }
133        let size = if has_size_field == 1 {
134            let (s, n) = read_leb128(&sample[p..])?;
135            p += n;
136            s as usize
137        } else {
138            return None;
139        };
140        if obu_type == 4 {
141            return Some(p as u32);
142        }
143        p += size;
144        i = p;
145    }
146    // Frame OBU (type 6): combine the OBU payload start with the
147    // in-OBU offset from the parsed frame header.
148    let (_obu_bytes, payload_offset) = find_av1_obu_with_offset(sample, 6)?;
149    let hdr = parse_av1_frame_header(sample, seq)?;
150    Some(payload_offset as u32 + hdr.tile_group_offset_in_obu)
151}
152
153/// Backwards-compatible shim — uses an empty-ish sequence header
154/// default that only works for the fallback path (standalone type-4
155/// OBU). Callers with access to the parsed sequence header should
156/// use `av1_tile_group_offset` (the seq-aware form) instead.
157pub fn av1_tile_group_offset_fallback(sample: &[u8]) -> Option<u32> {
158    let mut i = 0usize;
159    while i < sample.len() {
160        let header = sample[i];
161        let obu_type = (header >> 3) & 0x0F;
162        let extension_flag = (header >> 2) & 0x01;
163        let has_size_field = (header >> 1) & 0x01;
164        let mut p = i + 1;
165        if extension_flag == 1 {
166            p += 1;
167        }
168        let size = if has_size_field == 1 {
169            let (s, n) = read_leb128(&sample[p..])?;
170            p += n;
171            s as usize
172        } else {
173            return None;
174        };
175        if obu_type == 4 {
176            return Some(p as u32);
177        }
178        p += size;
179        i = p;
180    }
181    av1_frame_header_offset(sample)
182}