1use async_trait::async_trait;
8use fidl_fuchsia_images2 as images2;
9use fidl_fuchsia_media::*;
10use fuchsia_image_format::images2_image_format_from_sysmem_image_format;
11use hex::encode;
12use mundane::hash::{Digest, Hasher, Sha256};
13use std::convert::*;
14use std::fmt;
15use stream_processor_test::{ExpectedDigest, FatalError, Output, OutputPacket, OutputValidator};
16use thiserror::Error;
17
18#[derive(Debug, Error)]
19pub enum Error {
20 DataTooSmallToBeFrame { expected_size: usize, actual_size: usize },
21 FormatDetailsNotUncompressedVideo,
22 UnsupportedPixelFormat(images2::PixelFormat),
23 FormatHasNoPlane { plane_requested: usize, planes_in_format: usize },
24}
25
26impl fmt::Display for Error {
27 fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
28 fmt::Debug::fmt(&self, w)
29 }
30}
31
32struct Frame<'a> {
33 data: &'a [u8],
34 format: images2::ImageFormat,
35}
36
37trait Subsample420 {
38 const CHROMA_SUBSAMPLE_RATIO: usize = 2;
39
40 fn frame_size(format: &images2::ImageFormat) -> usize {
41 *format.bytes_per_row.as_ref().unwrap() as usize
44 * format.size.as_ref().unwrap().height as usize
45 * 3
46 / 2
47 }
48}
49
50struct Yv12Frame<'a> {
51 frame: Frame<'a>,
52}
53
54impl<'a> Subsample420 for Yv12Frame<'a> {}
55
56impl<'a> Yv12Frame<'a> {
57 fn yv12_iter(&self) -> impl Iterator<Item = u8> + 'a {
59 let luminance_plane_stride = *self.frame.format.bytes_per_row.as_ref().unwrap() as usize;
60 let luminance_plane_display_height =
61 self.frame.format.display_rect.as_ref().unwrap().height as usize;
62 let luminance_plane_display_width =
63 self.frame.format.display_rect.as_ref().unwrap().width as usize;
64 let luminance = self
65 .frame
66 .data
67 .chunks(luminance_plane_stride)
68 .take(luminance_plane_display_height)
69 .flat_map(move |row| row.iter().take(luminance_plane_display_width));
70
71 let luminance_plane_coded_size =
72 luminance_plane_stride * self.frame.format.size.as_ref().unwrap().height as usize;
73 let chroma_plane_display_height =
74 luminance_plane_display_height / Self::CHROMA_SUBSAMPLE_RATIO;
75 let chroma_plane_display_width =
76 luminance_plane_display_width / Self::CHROMA_SUBSAMPLE_RATIO;
77
78 let chroma_rows = self.frame.data[luminance_plane_coded_size..]
80 .chunks(luminance_plane_stride / Self::CHROMA_SUBSAMPLE_RATIO);
81
82 let chroma_v = chroma_rows
83 .clone()
84 .take(chroma_plane_display_height)
85 .flat_map(move |row| row.iter().take(chroma_plane_display_width));
86
87 let chroma_plane_coded_height =
88 self.frame.format.size.as_ref().unwrap().height as usize / Self::CHROMA_SUBSAMPLE_RATIO;
89 let chroma_u = chroma_rows
90 .clone()
91 .skip(chroma_plane_coded_height)
92 .take(chroma_plane_display_height)
93 .flat_map(move |row| row.iter().take(chroma_plane_display_width));
94
95 luminance.chain(chroma_v).chain(chroma_u).cloned()
96 }
97}
98
99impl<'a> TryFrom<Frame<'a>> for Yv12Frame<'a> {
100 type Error = Error;
101 fn try_from(frame: Frame<'a>) -> Result<Self, Self::Error> {
102 let expected_size = Self::frame_size(&frame.format);
103 if frame.data.len() < expected_size {
104 return Err(Error::DataTooSmallToBeFrame {
105 actual_size: frame.data.len(),
106 expected_size,
107 });
108 }
109
110 Ok(Yv12Frame { frame })
111 }
112}
113
114struct Nv12Frame<'a> {
115 frame: Frame<'a>,
116}
117
118impl<'a> Subsample420 for Nv12Frame<'a> {}
119
120impl<'a> Nv12Frame<'a> {
121 fn yv12_iter(&self) -> impl Iterator<Item = u8> + 'a {
123 let rows =
124 self.frame.data.chunks(*self.frame.format.bytes_per_row.as_ref().unwrap() as usize);
125 let luminance_row_count = self.frame.format.display_rect.as_ref().unwrap().height as usize;
126 let chroma_row_count = luminance_row_count / Self::CHROMA_SUBSAMPLE_RATIO;
127 let row_length = self.frame.format.display_rect.as_ref().unwrap().width as usize;
128
129 let luminance =
130 rows.clone().take(luminance_row_count).flat_map(move |row| row.iter().take(row_length));
131 let chroma_rows = rows
132 .skip(self.frame.format.size.as_ref().unwrap().height as usize)
133 .take(chroma_row_count);
134 let chroma_u =
135 chroma_rows.clone().flat_map(move |row| row.iter().take(row_length).step_by(2));
136 let chroma_v =
137 chroma_rows.flat_map(move |row| row.iter().take(row_length).skip(1).step_by(2));
138
139 luminance.chain(chroma_v).chain(chroma_u).cloned()
140 }
141}
142
143impl<'a> TryFrom<Frame<'a>> for Nv12Frame<'a> {
144 type Error = Error;
145 fn try_from(frame: Frame<'a>) -> Result<Self, Self::Error> {
146 let expected_size = Self::frame_size(&frame.format);
147 if frame.data.len() < expected_size {
148 return Err(Error::DataTooSmallToBeFrame {
149 actual_size: frame.data.len(),
150 expected_size,
151 });
152 }
153
154 Ok(Nv12Frame { frame })
155 }
156}
157
158fn packet_display_data<'a>(
159 src: &'a OutputPacket,
160) -> Result<Box<dyn Iterator<Item = u8> + 'a>, Error> {
161 let sysmem_format = src
162 .format
163 .format_details
164 .domain
165 .as_ref()
166 .and_then(|domain| match domain {
167 DomainFormat::Video(VideoFormat::Uncompressed(uncompressed_format)) => {
168 Some(uncompressed_format.image_format)
169 }
170 _ => None,
171 })
172 .ok_or(Error::FormatDetailsNotUncompressedVideo)?;
173 let format = images2_image_format_from_sysmem_image_format(&sysmem_format).unwrap();
174
175 Ok(match format.pixel_format.as_ref().unwrap() {
176 images2::PixelFormat::Yv12 => {
177 Box::new(Yv12Frame::try_from(Frame { data: src.data.as_slice(), format })?.yv12_iter())
178 }
179 images2::PixelFormat::Nv12 => {
180 Box::new(Nv12Frame::try_from(Frame { data: src.data.as_slice(), format })?.yv12_iter())
181 }
182 _ => Err(Error::UnsupportedPixelFormat(*format.pixel_format.as_ref().unwrap()))?,
183 })
184}
185
186pub struct VideoFrameHasher {
187 pub expected_digest: ExpectedDigest,
188}
189
190#[async_trait(?Send)]
191impl OutputValidator for VideoFrameHasher {
192 async fn validate(&self, output: &[Output]) -> Result<(), anyhow::Error> {
193 let mut hasher = Sha256::default();
194 let mut frame_ordinal = 0;
195
196 if let Some(per_frame_bytes) = &self.expected_digest.per_frame_bytes {
197 let packet_count = output
198 .iter()
199 .filter(|item| {
200 if let Output::Packet(ref _packet) = item {
201 return true;
202 }
203 false
204 })
205 .count();
206 if per_frame_bytes.len() != packet_count {
207 return Err(FatalError(format!(
208 "per_frame_bytes.len() != output.len() - {} vs {}",
209 per_frame_bytes.len(),
210 output.len()
211 ))
212 .into());
213 }
214 }
215
216 output
217 .iter()
218 .map(|output| {
219 if let Output::Packet(ref packet) = output {
220 packet_display_data(packet)?.for_each(|b| hasher.update(&[b]));
221 let tmp_hasher = hasher.clone();
222 let frame_digest = tmp_hasher.finish().bytes();
223 if let Some(per_frame_bytes) = &self.expected_digest.per_frame_bytes {
224 if per_frame_bytes[frame_ordinal] != frame_digest {
225 return Err(FatalError(format!(
226 "frame_ordinal {} digest mismatch - expected {}; got {}",
227 frame_ordinal,
228 encode(per_frame_bytes[frame_ordinal]),
229 encode(frame_digest)
230 ))
231 .into());
232 }
233 }
234
235 frame_ordinal += 1;
236 }
237 Ok(())
238 })
239 .collect::<Result<(), anyhow::Error>>()?;
240
241 let digest = hasher.finish().bytes();
242 if self.expected_digest.bytes != digest {
243 return Err(FatalError(format!(
244 "Expected {}; got {}",
245 self.expected_digest,
246 encode(digest)
247 ))
248 .into());
249 }
250
251 Ok(())
252 }
253}
254
255#[cfg(test)]
256mod test {
257 use super::*;
258 use fidl_fuchsia_images2::{self as fimages2, *};
259 use fidl_fuchsia_math::{RectU, SizeU};
260 use fuchsia_image_format::sysmem1_image_format_from_images2_image_format;
261 use fuchsia_stream_processors::{ValidPacket, ValidPacketHeader};
262 use rand::prelude::*;
263 use std::rc::Rc;
264 use stream_processor_test::ValidStreamOutputFormat;
265
266 #[derive(Debug, Copy, Clone)]
267 struct TestSpec {
268 pixel_format: PixelFormat,
269 coded_width: usize, display_width: usize,
271 display_height: usize,
272 bytes_per_row: usize,
273 }
274
275 impl TestSpec {
276 fn coded_height(&self) -> usize {
277 self.display_height * 2
279 }
280 }
281
282 impl Into<OutputPacket> for TestSpec {
283 fn into(self) -> OutputPacket {
284 let mut format_details = FormatDetails::default();
285 format_details.domain = Some(DomainFormat::Video(VideoFormat::Uncompressed(
286 new_video_uncompressed_format(ImageFormat {
287 pixel_format: Some(self.pixel_format),
288 size: Some(SizeU {
289 width: self.coded_width.try_into().unwrap(),
290 height: self.coded_height().try_into().unwrap(),
291 }),
292 bytes_per_row: Some(self.bytes_per_row.try_into().unwrap()),
293 display_rect: Some(RectU {
294 x: 0,
295 y: 0,
296 width: self.display_width.try_into().unwrap(),
297 height: self.display_height.try_into().unwrap(),
298 }),
299 color_space: Some(fimages2::ColorSpace::Rec709),
300 ..Default::default()
301 }),
302 )));
303 OutputPacket {
304 data: vec![0; (self.bytes_per_row * self.coded_height() * 3 / 2) as usize],
305 format: Rc::new(ValidStreamOutputFormat {
306 stream_lifetime_ordinal: 0,
307 format_details,
308 }),
309 packet: ValidPacket {
310 header: ValidPacketHeader { buffer_lifetime_ordinal: 0, packet_index: 0 },
311 buffer_index: 0,
312 stream_lifetime_ordinal: 0,
313 start_offset: 0,
314 valid_length_bytes: 0,
315 timestamp_ish: None,
316 start_access_unit: false,
317 known_end_access_unit: false,
318 },
319 }
320 }
321 }
322
323 fn new_video_uncompressed_format(image_format: ImageFormat) -> VideoUncompressedFormat {
324 VideoUncompressedFormat {
325 image_format: sysmem1_image_format_from_images2_image_format(&image_format).unwrap(),
326 fourcc: 0,
327 primary_width_pixels: 0,
328 primary_height_pixels: 0,
329 secondary_width_pixels: 0,
330 secondary_height_pixels: 0,
331 planar: false,
332 swizzled: false,
333 primary_line_stride_bytes: 0,
334 secondary_line_stride_bytes: 0,
335 primary_start_offset: 0,
336 secondary_start_offset: 0,
337 tertiary_start_offset: 0,
338 primary_pixel_stride: 0,
339 secondary_pixel_stride: 0,
340 primary_display_width_pixels: 0,
341 primary_display_height_pixels: 0,
342 has_pixel_aspect_ratio: false,
343 pixel_aspect_ratio_width: 0,
344 pixel_aspect_ratio_height: 0,
345 }
346 }
347
348 fn specs(pixel_format: PixelFormat) -> impl Iterator<Item = TestSpec> {
349 vec![
350 TestSpec {
351 pixel_format,
352 coded_width: 16,
353 display_width: 10,
354 display_height: 16,
355 bytes_per_row: 20,
356 },
357 TestSpec {
358 pixel_format,
359 coded_width: 16,
360 display_width: 14,
361 display_height: 8,
362 bytes_per_row: 16,
363 },
364 TestSpec {
365 pixel_format,
366 coded_width: 32,
367 display_width: 12,
368 display_height: 4,
369 bytes_per_row: 54,
370 },
371 TestSpec {
372 pixel_format,
373 coded_width: 16,
374 display_width: 2,
375 display_height: 100,
376 bytes_per_row: 1200,
377 },
378 ]
379 .into_iter()
380 }
381
382 #[fuchsia::test]
383 fn packets_of_different_formats_hash_same_with_matching_data() -> Result<(), Error> {
384 let mut rng = StdRng::seed_from_u64(45);
385
386 for (nv12_spec, yv12_spec) in specs(PixelFormat::Nv12).zip(specs(PixelFormat::Yv12)) {
387 let mut nv12_packet: OutputPacket = nv12_spec.into();
392 rng.fill(nv12_packet.data.as_mut_slice());
393
394 let mut nv12_global_index: usize = 0;
395 let mut assign_index = |b: &mut u8| {
396 *b = (nv12_global_index % 256) as u8;
397 nv12_global_index += 1;
398 };
399
400 for row in
402 nv12_packet.data.chunks_mut(nv12_spec.bytes_per_row).take(nv12_spec.display_height)
403 {
404 row.iter_mut().take(nv12_spec.display_width).for_each(&mut assign_index);
405 }
406
407 for row in nv12_packet
409 .data
410 .chunks_mut(nv12_spec.bytes_per_row)
411 .skip(nv12_spec.coded_height() as usize)
412 .take(nv12_spec.display_height / Nv12Frame::CHROMA_SUBSAMPLE_RATIO)
413 {
414 row.iter_mut()
415 .take(nv12_spec.display_width)
416 .skip(1)
417 .step_by(2)
418 .for_each(&mut assign_index)
419 }
420
421 for row in nv12_packet
423 .data
424 .chunks_mut(nv12_spec.bytes_per_row)
425 .skip(nv12_spec.coded_height() as usize)
426 .take(nv12_spec.display_height / Nv12Frame::CHROMA_SUBSAMPLE_RATIO)
427 {
428 row.iter_mut().take(nv12_spec.display_width).step_by(2).for_each(&mut assign_index)
429 }
430
431 let mut yv12_packet: OutputPacket = yv12_spec.into();
432 rng.fill(yv12_packet.data.as_mut_slice());
433
434 let mut yv12_global_index: usize = 0;
435 let mut assign_index = |b: &mut u8| {
436 *b = (yv12_global_index % 256) as u8;
437 yv12_global_index += 1;
438 };
439
440 for row in
442 yv12_packet.data.chunks_mut(yv12_spec.bytes_per_row).take(yv12_spec.display_height)
443 {
444 row.iter_mut().take(yv12_spec.display_width).for_each(&mut assign_index);
445 }
446
447 let luminance_plane_size = yv12_spec.bytes_per_row * yv12_spec.coded_height();
449 for row in yv12_packet.data[luminance_plane_size..]
450 .chunks_mut(yv12_spec.bytes_per_row / Yv12Frame::CHROMA_SUBSAMPLE_RATIO)
451 .take(yv12_spec.display_height / Yv12Frame::CHROMA_SUBSAMPLE_RATIO)
452 {
453 row.iter_mut()
454 .take(yv12_spec.display_width / Yv12Frame::CHROMA_SUBSAMPLE_RATIO)
455 .for_each(&mut assign_index)
456 }
457
458 let chrominance_plane_size =
460 luminance_plane_size / Yv12Frame::CHROMA_SUBSAMPLE_RATIO.pow(2);
461 for row in yv12_packet.data[(luminance_plane_size + chrominance_plane_size)..]
462 .chunks_mut(yv12_spec.bytes_per_row / Yv12Frame::CHROMA_SUBSAMPLE_RATIO)
463 .take(yv12_spec.display_height / Yv12Frame::CHROMA_SUBSAMPLE_RATIO)
464 {
465 row.iter_mut()
466 .take(yv12_spec.display_width / Yv12Frame::CHROMA_SUBSAMPLE_RATIO)
467 .for_each(&mut assign_index)
468 }
469
470 let from_yv12 = packet_display_data(&yv12_packet)?;
471 let from_nv12 = packet_display_data(&nv12_packet)?;
472
473 let yv12_hash = Sha256::hash(&from_yv12.collect::<Vec<u8>>().as_slice());
474 let nv12_hash = Sha256::hash(&from_nv12.collect::<Vec<u8>>().as_slice());
475
476 assert_eq!(yv12_hash, nv12_hash);
477 }
478
479 Ok(())
480 }
481
482 #[fuchsia::test]
483 fn sanity_test_that_different_packets_hash_differently() -> Result<(), Error> {
484 let mut rng = StdRng::seed_from_u64(45);
485
486 for (nv12_spec, yv12_spec) in specs(PixelFormat::Nv12).zip(specs(PixelFormat::Yv12)) {
487 let mut nv12_packet: OutputPacket = nv12_spec.into();
488 rng.fill(nv12_packet.data.as_mut_slice());
489
490 let mut yv12_packet: OutputPacket = yv12_spec.into();
491 yv12_packet.data.iter_mut().enumerate().for_each(|(i, b)| *b = nv12_packet.data[i]);
492
493 let x = rng.gen_range(0..nv12_spec.display_width);
495 let y = {
496 let y_range = [
497 (0..nv12_spec.display_height),
499 {
501 let start = nv12_spec.coded_height();
502 let end = start + (nv12_spec.display_height / 2);
503 start..end
504 },
505 ]
506 .choose(&mut rng)
507 .expect("Sampling from nonempty slice")
508 .clone();
509 rng.gen_range(y_range)
510 };
511 let idx = y * nv12_spec.bytes_per_row + x;
512 nv12_packet.data[idx] = nv12_packet.data[idx].overflowing_add(1).0;
513
514 let from_yv12 = packet_display_data(&yv12_packet)?;
515 let from_nv12 = packet_display_data(&nv12_packet)?;
516
517 let yv12_hash = Sha256::hash(&from_yv12.collect::<Vec<u8>>().as_slice());
518 let nv12_hash = Sha256::hash(&from_nv12.collect::<Vec<u8>>().as_slice());
519
520 assert_ne!(yv12_hash, nv12_hash);
521 }
522
523 Ok(())
524 }
525}