1use super::obu::{parse_obu, ObuType};
25use super::sequence::SequenceHeader;
26
27#[derive(Clone, Debug, Default)]
33pub struct ValidationResult {
34 pub is_valid: bool,
36 pub errors: Vec<String>,
38 pub warnings: Vec<String>,
41}
42
43impl ValidationResult {
44 #[must_use]
46 fn new() -> Self {
47 Self {
48 is_valid: true,
49 errors: Vec::new(),
50 warnings: Vec::new(),
51 }
52 }
53
54 fn error(&mut self, msg: impl Into<String>) {
56 self.errors.push(msg.into());
57 self.is_valid = false;
58 }
59
60 fn warn(&mut self, msg: impl Into<String>) {
62 self.warnings.push(msg.into());
63 }
64}
65
66pub struct SequenceHeaderValidator;
79
80impl SequenceHeaderValidator {
81 #[must_use]
85 pub fn validate(header: &SequenceHeader) -> ValidationResult {
86 let mut result = ValidationResult::new();
87
88 if header.profile > 2 {
90 result.error(format!(
91 "seq_profile {} is out of range; must be 0, 1, or 2",
92 header.profile
93 ));
94 }
95
96 let bd = header.color_config.bit_depth;
98 if bd != 8 && bd != 10 && bd != 12 {
99 result.error(format!("bit_depth {bd} is invalid; must be 8, 10, or 12"));
100 }
101
102 match header.profile {
104 0 => {
105 if !header.color_config.subsampling_x || !header.color_config.subsampling_y {
107 result.error(
108 "profile 0 (Main) requires 4:2:0 subsampling \
109 (subsampling_x and subsampling_y must both be true)"
110 .to_string(),
111 );
112 }
113 if bd == 12 {
114 result.error("profile 0 (Main) does not allow 12-bit depth".to_string());
115 }
116 }
117 1 => {
118 if header.color_config.mono_chrome {
120 result.error("profile 1 (High) forbids mono_chrome".to_string());
121 }
122 if header.color_config.subsampling_x || header.color_config.subsampling_y {
123 result.error(
124 "profile 1 (High) requires 4:4:4 \
125 (subsampling_x and subsampling_y must both be false)"
126 .to_string(),
127 );
128 }
129 if bd == 12 {
130 result.error("profile 1 (High) does not allow 12-bit depth".to_string());
131 }
132 }
133 2 => {
134 }
138 _ => {
139 }
141 }
142
143 if header.order_hint_bits > 8 {
145 result.error(format!(
146 "order_hint_bits {} exceeds maximum of 8",
147 header.order_hint_bits
148 ));
149 }
150 if !header.enable_order_hint && header.order_hint_bits != 0 {
151 result.warn(format!(
152 "enable_order_hint is false but order_hint_bits is {}; \
153 expected 0 when order hints are disabled",
154 header.order_hint_bits
155 ));
156 }
157
158 if header.film_grain_params_present && header.color_config.num_planes != 3 {
162 result.warn(
163 "film_grain_params_present is set but num_planes != 3; \
164 film grain synthesis requires luma and chroma planes"
165 .to_string(),
166 );
167 }
168
169 if header.film_grain_params_present
172 && header.profile == 1
173 && header.color_config.mono_chrome
174 {
175 result.warn(
176 "film_grain_params_present with profile 1 and mono_chrome \
177 is contradictory (profile 1 forbids mono_chrome)"
178 .to_string(),
179 );
180 }
181
182 result
183 }
184}
185
186pub struct ObuValidator;
197
198impl ObuValidator {
199 #[must_use]
205 pub fn validate_bitstream(data: &[u8]) -> ValidationResult {
206 let mut result = ValidationResult::new();
207
208 if data.is_empty() {
209 result.warn("bitstream is empty".to_string());
210 return result;
211 }
212
213 let mut offset = 0usize;
214 let mut first_obu = true;
215 let mut seen_sequence_header = false;
216
217 while offset < data.len() {
218 let remaining = &data[offset..];
219
220 match parse_obu(remaining) {
221 Err(e) => {
222 result.error(format!("OBU parse error at byte offset {offset}: {e}"));
223 break;
225 }
226 Ok((header, _payload, total_size)) => {
227 if first_obu {
229 match header.obu_type {
230 ObuType::TemporalDelimiter | ObuType::SequenceHeader => {
231 }
233 _ => {
234 result.warn(format!(
235 "first OBU at offset 0 is {:?}; \
236 expected TemporalDelimiter or SequenceHeader",
237 header.obu_type
238 ));
239 }
240 }
241 first_obu = false;
242 }
243
244 if matches!(header.obu_type, ObuType::SequenceHeader) {
246 seen_sequence_header = true;
247 }
248
249 if !seen_sequence_header {
251 match header.obu_type {
252 ObuType::FrameHeader
253 | ObuType::Frame
254 | ObuType::RedundantFrameHeader => {
255 result.error(format!(
256 "{:?} OBU at byte offset {offset} appears \
257 before any SequenceHeader",
258 header.obu_type
259 ));
260 }
261 _ => {}
262 }
263 }
264
265 offset += total_size;
266 }
267 }
268 }
269
270 result
271 }
272
273 #[must_use]
277 pub fn count_obus(data: &[u8]) -> usize {
278 let mut count = 0usize;
279 let mut offset = 0usize;
280
281 while offset < data.len() {
282 match parse_obu(&data[offset..]) {
283 Err(_) => break,
284 Ok((_header, _payload, total_size)) => {
285 count += 1;
286 offset += total_size;
287 }
288 }
289 }
290
291 count
292 }
293
294 #[must_use]
297 pub fn find_sequence_header(data: &[u8]) -> Option<usize> {
298 let mut offset = 0usize;
299
300 while offset < data.len() {
301 match parse_obu(&data[offset..]) {
302 Err(_) => break,
303 Ok((header, _payload, total_size)) => {
304 if matches!(header.obu_type, ObuType::SequenceHeader) {
305 return Some(offset);
306 }
307 offset += total_size;
308 }
309 }
310 }
311
312 None
313 }
314}
315
316#[cfg(test)]
321mod tests {
322 use super::super::film_grain::{FilmGrainParams, ScalingPoint};
323 use super::super::sequence::{ColorConfig, SequenceHeader};
324 use super::{ObuValidator, SequenceHeaderValidator};
325
326 fn valid_profile0_header() -> SequenceHeader {
327 SequenceHeader {
328 profile: 0,
329 still_picture: false,
330 reduced_still_picture_header: false,
331 max_frame_width_minus_1: 1919,
332 max_frame_height_minus_1: 1079,
333 enable_order_hint: true,
334 order_hint_bits: 7,
335 enable_superres: false,
336 enable_cdef: true,
337 enable_restoration: true,
338 color_config: ColorConfig {
339 bit_depth: 8,
340 mono_chrome: false,
341 num_planes: 3,
342 color_primaries: 1,
343 transfer_characteristics: 1,
344 matrix_coefficients: 1,
345 color_range: false,
346 subsampling_x: true,
347 subsampling_y: true,
348 separate_uv_delta_q: false,
349 },
350 film_grain_params_present: false,
351 }
352 }
353
354 #[test]
355 fn test_sequence_header_validator_valid_profile0() {
356 let header = valid_profile0_header();
357 let result = SequenceHeaderValidator::validate(&header);
358 assert!(
359 result.is_valid,
360 "expected valid profile-0 header; errors: {:?}",
361 result.errors
362 );
363 assert!(result.errors.is_empty());
364 }
365
366 #[test]
367 fn test_sequence_header_validator_profile1_requires_444() {
368 let header = SequenceHeader {
371 profile: 1,
372 color_config: ColorConfig {
373 bit_depth: 8,
374 mono_chrome: false,
375 num_planes: 3,
376 color_primaries: 1,
377 transfer_characteristics: 1,
378 matrix_coefficients: 1,
379 color_range: false,
380 subsampling_x: true, subsampling_y: false,
382 separate_uv_delta_q: false,
383 },
384 ..valid_profile0_header()
385 };
386 let result = SequenceHeaderValidator::validate(&header);
387 assert!(
388 !result.is_valid,
389 "expected validation failure for profile 1 with 4:2:2 subsampling"
390 );
391 assert!(
392 !result.errors.is_empty(),
393 "expected at least one error for profile 1 subsampling violation"
394 );
395 }
396
397 #[test]
398 fn test_sequence_header_validator_invalid_bit_depth() {
399 let header = SequenceHeader {
400 profile: 0,
401 color_config: ColorConfig {
402 bit_depth: 7, ..valid_profile0_header().color_config
404 },
405 ..valid_profile0_header()
406 };
407 let result = SequenceHeaderValidator::validate(&header);
408 assert!(!result.is_valid, "bit_depth=7 should be invalid");
409 assert!(result.errors.iter().any(|e| e.contains("bit_depth")));
410 }
411
412 #[test]
413 fn test_sequence_header_validator_order_hint_bits_too_large() {
414 let header = SequenceHeader {
415 enable_order_hint: true,
416 order_hint_bits: 9, ..valid_profile0_header()
418 };
419 let result = SequenceHeaderValidator::validate(&header);
420 assert!(!result.is_valid, "order_hint_bits=9 should be invalid");
421 assert!(result.errors.iter().any(|e| e.contains("order_hint_bits")));
422 }
423
424 #[test]
425 fn test_sequence_header_validator_order_hint_disabled_nonzero_bits_warns() {
426 let header = SequenceHeader {
427 enable_order_hint: false,
428 order_hint_bits: 4, ..valid_profile0_header()
430 };
431 let result = SequenceHeaderValidator::validate(&header);
432 assert!(
434 result.is_valid,
435 "non-zero order_hint_bits with enable_order_hint=false should only warn"
436 );
437 assert!(
438 !result.warnings.is_empty(),
439 "expected a warning for inconsistent order_hint_bits"
440 );
441 }
442
443 #[test]
444 fn test_sequence_header_validator_profile0_rejects_12bit() {
445 let header = SequenceHeader {
446 profile: 0,
447 color_config: ColorConfig {
448 bit_depth: 12,
449 ..valid_profile0_header().color_config
450 },
451 ..valid_profile0_header()
452 };
453 let result = SequenceHeaderValidator::validate(&header);
454 assert!(!result.is_valid, "profile 0 should reject 12-bit depth");
455 }
456
457 #[test]
458 fn test_obu_validator_count_obus_empty() {
459 assert_eq!(ObuValidator::count_obus(&[]), 0);
460 }
461
462 #[test]
463 fn test_obu_validator_find_sequence_header_none() {
464 assert_eq!(ObuValidator::find_sequence_header(&[]), None);
465 }
466
467 #[test]
468 fn test_obu_validator_minimal_valid_bitstream() {
469 let bitstream: &[u8] = &[0x12, 0x00, 0x0A, 0x00];
474
475 let count = ObuValidator::count_obus(bitstream);
476 assert_eq!(count, 2, "expected 2 OBUs in minimal bitstream");
477
478 let seq_offset = ObuValidator::find_sequence_header(bitstream);
479 assert_eq!(
480 seq_offset,
481 Some(2),
482 "SequenceHeader should be at byte offset 2"
483 );
484 }
485
486 #[test]
487 fn test_obu_validator_validate_bitstream_empty_warns() {
488 let result = ObuValidator::validate_bitstream(&[]);
489 assert!(
491 result.is_valid,
492 "empty bitstream should yield is_valid=true with warnings"
493 );
494 assert!(
495 !result.warnings.is_empty(),
496 "empty bitstream should produce at least one warning"
497 );
498 }
499
500 #[test]
501 fn test_obu_validator_validate_bitstream_minimal() {
502 let bitstream: &[u8] = &[0x12, 0x00, 0x0A, 0x00];
503 let result = ObuValidator::validate_bitstream(bitstream);
504 assert!(
505 result.is_valid,
506 "minimal TD+SH bitstream should be valid; errors: {:?}",
507 result.errors
508 );
509 }
510
511 #[test]
512 fn test_obu_validator_validate_bitstream_frame_before_sequence_header() {
513 let bitstream: &[u8] = &[0x32, 0x00];
516 let result = ObuValidator::validate_bitstream(bitstream);
517 assert!(
518 !result.is_valid,
519 "Frame OBU before SequenceHeader should produce an error"
520 );
521 assert!(
522 result
523 .errors
524 .iter()
525 .any(|e| e.contains("before any SequenceHeader")),
526 "error should mention SequenceHeader ordering; got: {:?}",
527 result.errors
528 );
529 }
530
531 #[test]
532 fn test_av1_film_grain_params_serialize_deserialize() {
533 let mut params = FilmGrainParams::new();
534 params.apply_grain = true;
535 params.grain_seed = 0xDEAD;
536 params.update_grain = true;
537 params.film_grain_params_present = true;
538 params.num_y_points = 2;
539 params.y_points[0] = ScalingPoint::new(0, 64);
540 params.y_points[1] = ScalingPoint::new(128, 128);
541
542 let cloned = params.clone();
544
545 assert_eq!(params.grain_seed, cloned.grain_seed);
546 assert_eq!(params.num_y_points, cloned.num_y_points);
547 assert_eq!(params.y_points[0], cloned.y_points[0]);
548 assert_eq!(params.y_points[1], cloned.y_points[1]);
549 assert!(params.apply_grain);
550 assert!(params.update_grain);
551 assert!(params.film_grain_params_present);
552
553 assert!(
555 params.validate(),
556 "constructed FilmGrainParams should pass validate()"
557 );
558 }
559
560 #[test]
561 fn test_av1_film_grain_params_default_valid() {
562 let params = FilmGrainParams::default();
563 assert!(params.validate(), "default FilmGrainParams should be valid");
564 }
565
566 #[test]
567 fn test_av1_film_grain_params_invalid_ar_coeff_lag() {
568 let mut params = FilmGrainParams::new();
569 params.ar_coeff_lag = 4; assert!(!params.validate(), "ar_coeff_lag=4 should fail validate()");
571 }
572}