1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
//! H.264/H.265 slice header types and parsing utilities.
//!
//! Provides the [`SliceType`] enum, [`SliceHeader`] struct, and
//! [`SliceHeaderReader`] helper that extracts slice type and frame number
//! from a raw bitstream prefix.
#![allow(dead_code)]
/// H.264 slice types as defined in Table 7-6.
///
/// The spec defines values 0–9; values 5–9 are equivalent to 0–4 but
/// signal that all slices in the picture are the same type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SliceType {
/// Predictive slice (inter-predicted from reference frames).
P,
/// Bi-predictive slice (two reference frames).
B,
/// Intra-only slice.
I,
/// Switching P slice.
Sp,
/// Switching I slice.
Si,
}
impl SliceType {
/// Returns `true` when this slice type uses only intra prediction.
pub fn is_intra(self) -> bool {
matches!(self, Self::I | Self::Si)
}
/// Returns `true` when this is a bi-predictive slice.
pub fn is_bipredictive(self) -> bool {
self == Self::B
}
/// Returns `true` when this is a switching slice (SP or SI).
pub fn is_switching(self) -> bool {
matches!(self, Self::Sp | Self::Si)
}
/// Decodes a raw ue(v) slice_type value (0–9) into a [`SliceType`].
///
/// Values 5–9 are mapped to their equivalents 0–4.
pub fn from_raw(raw: u8) -> Option<Self> {
match raw % 5 {
0 => Some(Self::P),
1 => Some(Self::B),
2 => Some(Self::I),
3 => Some(Self::Sp),
4 => Some(Self::Si),
_ => None,
}
}
}
/// Parsed fields from a slice header.
#[derive(Debug, Clone)]
pub struct SliceHeader {
/// Slice type.
pub slice_type: SliceType,
/// `frame_num` syntax element (wraps at `MaxFrameNum`).
pub frame_num: u16,
/// PPS id referenced by this slice.
pub pic_parameter_set_id: u8,
/// `field_pic_flag`: `true` when this slice belongs to a field (not a frame).
pub field_pic_flag: bool,
/// `bottom_field_flag`: relevant only when `field_pic_flag` is `true`.
pub bottom_field_flag: bool,
/// IDR picture id (only meaningful for IDR slices).
pub idr_pic_id: Option<u16>,
/// `nal_ref_idc` from the enclosing NAL unit (0 = non-reference).
pub nal_ref_idc: u8,
}
impl SliceHeader {
/// Returns `true` if this slice is a reference slice (nal_ref_idc > 0).
pub fn is_reference(&self) -> bool {
self.nal_ref_idc > 0
}
/// Returns `true` if this slice is an IDR slice.
pub fn is_idr(&self) -> bool {
self.idr_pic_id.is_some()
}
/// Returns `true` if this is a key-frame (IDR or I-slice that is reference).
pub fn is_keyframe(&self) -> bool {
self.is_idr() || (self.slice_type.is_intra() && self.is_reference())
}
}
/// Reads slice header fields from a raw byte buffer.
///
/// This is a simplified reader that expects the buffer to begin immediately
/// after the NAL unit start code and header byte. Full Exp-Golomb parsing
/// is approximated with a fixed layout suitable for testing.
#[derive(Debug, Default)]
pub struct SliceHeaderReader {
/// Log2 of `MaxFrameNum` (from SPS); used to mask `frame_num`.
pub log2_max_frame_num: u8,
}
impl SliceHeaderReader {
/// Creates a new reader with the given `log2_max_frame_num`.
pub fn new(log2_max_frame_num: u8) -> Self {
Self { log2_max_frame_num }
}
/// Reads the slice type from the first byte of `data`.
///
/// Returns `None` if `data` is empty or the value is out of range.
pub fn read_type(&self, data: &[u8]) -> Option<SliceType> {
let raw = data.first().copied()?;
SliceType::from_raw(raw % 10)
}
/// Reads the `frame_num` from bytes 1–2 of `data` (big-endian u16),
/// masked to `(1 << log2_max_frame_num) - 1`.
///
/// Returns `None` if `data` has fewer than 3 bytes.
pub fn frame_num(&self, data: &[u8]) -> Option<u16> {
if data.len() < 3 {
return None;
}
let raw = u16::from_be_bytes([data[1], data[2]]);
let mask = if self.log2_max_frame_num >= 16 {
u16::MAX
} else {
((1u32 << self.log2_max_frame_num) - 1) as u16
};
Some(raw & mask)
}
/// Full parse: reads slice type, frame_num, and other fixed fields.
///
/// Returns `None` if `data` is too short (requires at least 5 bytes).
pub fn parse(&self, nal_ref_idc: u8, data: &[u8]) -> Option<SliceHeader> {
if data.len() < 5 {
return None;
}
let slice_type = self.read_type(data)?;
let frame_num = self.frame_num(data)?;
let pic_parameter_set_id = data[3];
let flags = data[4];
let field_pic_flag = (flags & 0x80) != 0;
let bottom_field_flag = (flags & 0x40) != 0;
let is_idr = (flags & 0x20) != 0;
let idr_pic_id = if is_idr {
Some(u16::from_be_bytes([data[3], data[4]]) & 0x0FFF)
} else {
None
};
Some(SliceHeader {
slice_type,
frame_num,
pic_parameter_set_id,
field_pic_flag,
bottom_field_flag,
idr_pic_id,
nal_ref_idc,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slice_type_is_intra_i() {
assert!(SliceType::I.is_intra());
}
#[test]
fn test_slice_type_is_intra_si() {
assert!(SliceType::Si.is_intra());
}
#[test]
fn test_slice_type_p_is_not_intra() {
assert!(!SliceType::P.is_intra());
}
#[test]
fn test_slice_type_b_is_bipredictive() {
assert!(SliceType::B.is_bipredictive());
}
#[test]
fn test_slice_type_p_is_not_bipredictive() {
assert!(!SliceType::P.is_bipredictive());
}
#[test]
fn test_slice_type_is_switching() {
assert!(SliceType::Sp.is_switching());
assert!(SliceType::Si.is_switching());
assert!(!SliceType::I.is_switching());
}
#[test]
fn test_slice_type_from_raw_0_to_4() {
assert_eq!(SliceType::from_raw(0), Some(SliceType::P));
assert_eq!(SliceType::from_raw(1), Some(SliceType::B));
assert_eq!(SliceType::from_raw(2), Some(SliceType::I));
assert_eq!(SliceType::from_raw(3), Some(SliceType::Sp));
assert_eq!(SliceType::from_raw(4), Some(SliceType::Si));
}
#[test]
fn test_slice_type_from_raw_5_to_9_maps_same() {
assert_eq!(SliceType::from_raw(5), Some(SliceType::P));
assert_eq!(SliceType::from_raw(7), Some(SliceType::I));
assert_eq!(SliceType::from_raw(9), Some(SliceType::Si));
}
#[test]
fn test_slice_header_is_reference() {
let hdr = SliceHeader {
slice_type: SliceType::I,
frame_num: 0,
pic_parameter_set_id: 0,
field_pic_flag: false,
bottom_field_flag: false,
idr_pic_id: None,
nal_ref_idc: 1,
};
assert!(hdr.is_reference());
}
#[test]
fn test_slice_header_non_reference() {
let hdr = SliceHeader {
slice_type: SliceType::B,
frame_num: 3,
pic_parameter_set_id: 0,
field_pic_flag: false,
bottom_field_flag: false,
idr_pic_id: None,
nal_ref_idc: 0,
};
assert!(!hdr.is_reference());
}
#[test]
fn test_slice_header_is_idr() {
let hdr = SliceHeader {
slice_type: SliceType::I,
frame_num: 0,
pic_parameter_set_id: 0,
field_pic_flag: false,
bottom_field_flag: false,
idr_pic_id: Some(0),
nal_ref_idc: 3,
};
assert!(hdr.is_idr());
assert!(hdr.is_keyframe());
}
#[test]
fn test_reader_read_type_i_slice() {
let reader = SliceHeaderReader::new(4);
// raw = 2 → I
let ty = reader.read_type(&[2, 0, 0, 0, 0]);
assert_eq!(ty, Some(SliceType::I));
}
#[test]
fn test_reader_read_type_empty_returns_none() {
let reader = SliceHeaderReader::new(4);
assert!(reader.read_type(&[]).is_none());
}
#[test]
fn test_reader_frame_num_masked() {
let reader = SliceHeaderReader::new(4); // mask = 0x000F
// raw frame_num bytes: 0x01, 0xFF → 0x01FF & 0x000F = 0x000F
let fn_ = reader.frame_num(&[0, 0x01, 0xFF, 0, 0]);
assert_eq!(fn_, Some(0x000F));
}
#[test]
fn test_reader_frame_num_short_returns_none() {
let reader = SliceHeaderReader::new(4);
assert!(reader.frame_num(&[0, 1]).is_none());
}
#[test]
fn test_reader_parse_full() {
let reader = SliceHeaderReader::new(8);
// slice_type=2 (I), frame_num=0x0005, pps_id=0, flags=0x00
let data = [2u8, 0x00, 0x05, 0x00, 0x00];
let hdr = reader.parse(1, &data).expect("parse should succeed");
assert_eq!(hdr.slice_type, SliceType::I);
assert_eq!(hdr.frame_num, 5);
assert!(hdr.is_reference());
}
#[test]
fn test_reader_parse_too_short_returns_none() {
let reader = SliceHeaderReader::new(4);
assert!(reader.parse(1, &[2, 0, 5]).is_none());
}
}