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}