Skip to main content

zvidlib_container/
conformance.rs

1//! Backend-independent video codec conformance helpers.
2//!
3//! Decoder vectors carry encoded samples plus fingerprints of the expected
4//! presentation frames. Encoder vectors are decoded by a separately supplied
5//! decoder backend and checked against their source frames with a PSNR floor.
6
7use crate::io::ByteSource;
8use crate::{
9    CancellationToken, CodecSupport, CpuFrameSource, DecodeStatistics, EncodedSample,
10    EncodedVideoSample, Error, ErrorKind, ExactFrameReader, FrameIndex, FrameSource, Limits,
11    Mp4Demuxer, Mp4DemuxerOptions, Orientation, PixelFormat, Result, VideoDecoderConfig,
12    VideoDecoderFactory, VideoEncoderConfig, VideoEncoderFactory, VideoFrame,
13};
14use std::collections::{HashMap, HashSet};
15
16/// A SHA-256 fingerprint of a frame's normalized metadata and active pixels.
17#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
18pub struct FrameDigest(pub [u8; 32]);
19
20impl FrameDigest {
21    /// Fingerprints active rows only, so backend-specific stride padding does
22    /// not affect conformance results.
23    pub fn from_frame(frame: &VideoFrame) -> Result<Self> {
24        let mut bytes = Vec::new();
25        bytes.extend_from_slice(&frame.dimensions.width.to_be_bytes());
26        bytes.extend_from_slice(&frame.dimensions.height.to_be_bytes());
27        bytes.push(pixel_format_tag(frame.pixel_format));
28        bytes.push(match frame.color_range {
29            crate::ColorRange::Full => 0,
30            crate::ColorRange::Limited => 1,
31            _ => u8::MAX,
32        });
33        bytes.push(u8::try_from(frame.planes.len()).map_err(|_| {
34            Error::new(ErrorKind::ResourceLimit, "video frame has too many planes")
35        })?);
36        append_active_pixels(frame, &mut bytes)?;
37        Ok(Self(sha256(&bytes)))
38    }
39
40    pub fn to_hex(self) -> String {
41        const HEX: &[u8; 16] = b"0123456789abcdef";
42        let mut output = String::with_capacity(64);
43        for byte in self.0 {
44            output.push(char::from(HEX[usize::from(byte >> 4)]));
45            output.push(char::from(HEX[usize::from(byte & 0x0f)]));
46        }
47        output
48    }
49
50    pub fn from_hex(value: &str) -> Result<Self> {
51        if value.len() != 64 || !value.is_ascii() {
52            return Err(invalid(
53                "frame fingerprints must contain 64 hexadecimal digits",
54            ));
55        }
56        let mut digest = [0_u8; 32];
57        for (index, byte) in digest.iter_mut().enumerate() {
58            let start = index * 2;
59            *byte = u8::from_str_radix(&value[start..start + 2], 16)
60                .map_err(|_| invalid("frame fingerprints must contain hexadecimal digits"))?;
61        }
62        Ok(Self(digest))
63    }
64}
65
66/// One expected presentation frame in a decoder conformance vector.
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub struct ExpectedVideoFrame {
69    pub presentation_index: FrameIndex,
70    pub digest: FrameDigest,
71}
72
73/// Encoded input and expected output for a decoder backend.
74#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct VideoDecoderConformanceVector {
76    pub name: String,
77    pub configuration: VideoDecoderConfig,
78    pub samples: Vec<EncodedVideoSample>,
79    pub expected_frames: Vec<ExpectedVideoFrame>,
80}
81
82impl VideoDecoderConformanceVector {
83    /// Builds a complete vector from one checked-in MP4 track and its expected
84    /// presentation fingerprints. Container configuration and decode-order
85    /// samples come from the same bounded demux path used by applications.
86    pub async fn from_mp4<S: ByteSource + ?Sized>(
87        name: impl Into<String>,
88        source: &S,
89        options: Mp4DemuxerOptions,
90        track_id: u32,
91        mut configuration: VideoDecoderConfig,
92        expected_digests: &[FrameDigest],
93    ) -> Result<Self> {
94        let limits = options.limits;
95        let movie = Mp4Demuxer::open(source, options).await?;
96        let track = movie.track(track_id).ok_or_else(|| {
97            Error::new(
98                ErrorKind::InvalidInput,
99                "codec vector MP4 track does not exist",
100            )
101        })?;
102        if track.codec != configuration.codec
103            || track.dimensions != Some(configuration.coded_dimensions)
104        {
105            return Err(invalid(
106                "codec vector MP4 track does not match its decoder configuration",
107            ));
108        }
109        let samples = track.to_encoded_video_samples(source, &limits).await?;
110        if samples.len() != expected_digests.len() {
111            return Err(invalid(
112                "codec vector MP4 requires one fingerprint per presentation frame",
113            ));
114        }
115        configuration.configuration = track.decoder_config.clone();
116        let expected_frames = expected_digests
117            .iter()
118            .copied()
119            .enumerate()
120            .map(|(index, digest)| ExpectedVideoFrame {
121                presentation_index: FrameIndex(index as u64),
122                digest,
123            })
124            .collect();
125        Ok(Self {
126            name: name.into(),
127            configuration,
128            samples,
129            expected_frames,
130        })
131    }
132}
133
134/// Work completed while verifying a decoder vector.
135#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
136pub struct VideoDecoderConformanceReport {
137    pub frames_verified: u64,
138    pub access_patterns_verified: u8,
139    pub statistics: DecodeStatistics,
140}
141
142/// Verifies a decoder against sequential, reverse, and seek-heavy access.
143pub fn verify_video_decoder_conformance(
144    factory: &dyn VideoDecoderFactory,
145    vector: &VideoDecoderConformanceVector,
146    limits: Limits,
147) -> Result<VideoDecoderConformanceReport> {
148    validate_decoder_vector(vector)?;
149    let mut sequential: Vec<_> = vector
150        .expected_frames
151        .iter()
152        .map(|frame| frame.presentation_index)
153        .collect();
154    sequential.sort_by_key(|index| index.0);
155    let mut reverse = sequential.clone();
156    reverse.reverse();
157    let mut seek_heavy = Vec::with_capacity(sequential.len());
158    let (mut low, mut high) = (0_usize, sequential.len());
159    while low < high {
160        high -= 1;
161        seek_heavy.push(sequential[high]);
162        if low < high {
163            seek_heavy.push(sequential[low]);
164            low += 1;
165        }
166    }
167
168    let expected: HashMap<_, _> = vector
169        .expected_frames
170        .iter()
171        .map(|frame| (frame.presentation_index, frame.digest))
172        .collect();
173    let mut report = VideoDecoderConformanceReport::default();
174    for pattern in [&sequential, &reverse, &seek_heavy] {
175        let mut reader = ExactFrameReader::new(
176            factory,
177            vector.configuration.clone(),
178            vector.samples.clone(),
179            limits,
180        )?;
181        let cancellation = CancellationToken::new();
182        for &index in pattern {
183            let frame = reader.get(index, &cancellation)?;
184            let actual = FrameDigest::from_frame(&frame)?;
185            let wanted = expected[&index];
186            if actual != wanted {
187                return Err(Error::new(
188                    ErrorKind::Codec,
189                    format!(
190                        "codec vector '{}' frame {} fingerprint mismatch: expected {}, got {}",
191                        vector.name,
192                        index.0,
193                        wanted.to_hex(),
194                        actual.to_hex()
195                    ),
196                ));
197            }
198            report.frames_verified = report.frames_verified.saturating_add(1);
199        }
200        report.statistics = add_statistics(report.statistics, reader.statistics());
201        report.access_patterns_verified = report.access_patterns_verified.saturating_add(1);
202    }
203    Ok(report)
204}
205
206/// Source frames and quality requirements for an encoder conformance run.
207#[derive(Clone, Debug, PartialEq)]
208pub struct VideoEncoderConformanceVector {
209    pub name: String,
210    pub configuration: VideoEncoderConfig,
211    /// The decoder configuration is completed with the encoder's emitted
212    /// standardized configuration bytes before round-trip decoding.
213    pub decoder_configuration: VideoDecoderConfig,
214    pub frames: Vec<VideoFrame>,
215    pub minimum_psnr_db: f64,
216}
217
218/// Work completed while verifying an encoder vector.
219#[derive(Clone, Copy, Debug, Default, PartialEq)]
220pub struct VideoEncoderConformanceReport {
221    pub frames_encoded: u64,
222    pub packets_emitted: u64,
223    pub minimum_observed_psnr_db: f64,
224}
225
226/// Encodes a vector, validates packet timing/configuration, then decodes it
227/// with an independently supplied conforming decoder and checks image quality.
228pub async fn verify_video_encoder_conformance(
229    encoder_factory: &dyn VideoEncoderFactory,
230    decoder_factory: &dyn VideoDecoderFactory,
231    vector: &VideoEncoderConformanceVector,
232    limits: Limits,
233) -> Result<VideoEncoderConformanceReport> {
234    validate_encoder_vector(vector)?;
235    require_supported(
236        encoder_factory.capability(&vector.configuration),
237        "encoder does not support the conformance vector",
238    )?;
239    let mut encoder = encoder_factory.create(&vector.configuration, &limits)?;
240    let format = encoder.format();
241    if format.dimensions != vector.configuration.coded_dimensions
242        || format.pixel_format != vector.configuration.input_format
243    {
244        return Err(invalid(
245            "encoder format does not match its conformance configuration",
246        ));
247    }
248
249    let mut packets = Vec::new();
250    for (index, frame) in vector.frames.iter().enumerate() {
251        let index =
252            FrameIndex(u64::try_from(index).map_err(|_| {
253                Error::new(ErrorKind::ResourceLimit, "encoder frame index overflow")
254            })?);
255        packets.extend(
256            encoder
257                .encode(
258                    index,
259                    FrameSource::Cpu(CpuFrameSource {
260                        frame,
261                        orientation: Orientation::TopLeft,
262                    }),
263                )
264                .await?,
265        );
266    }
267    packets.extend(encoder.finish().await?);
268    let encoder_config = encoder.config().clone();
269    validate_encoded_packets(&packets, vector.frames.len(), &encoder_config, vector)?;
270    let packet_count = packets.len();
271
272    let mut decoder_configuration = vector.decoder_configuration.clone();
273    decoder_configuration.configuration = encoder_config.decoder_config;
274    let samples = packets_to_video_samples(packets)?;
275    let mut reader =
276        ExactFrameReader::new(decoder_factory, decoder_configuration, samples, limits)?;
277    let cancellation = CancellationToken::new();
278    let mut minimum_observed = f64::INFINITY;
279    for (index, source) in vector.frames.iter().enumerate() {
280        let index = FrameIndex(index as u64);
281        let decoded = reader.get(index, &cancellation)?;
282        let psnr = frame_psnr(source, &decoded)?;
283        minimum_observed = minimum_observed.min(psnr);
284        if psnr < vector.minimum_psnr_db {
285            return Err(Error::new(
286                ErrorKind::Codec,
287                format!(
288                    "codec vector '{}' frame {} PSNR {psnr:.3} dB is below {:.3} dB",
289                    vector.name, index.0, vector.minimum_psnr_db
290                ),
291            ));
292        }
293    }
294    Ok(VideoEncoderConformanceReport {
295        frames_encoded: vector.frames.len() as u64,
296        packets_emitted: packet_count as u64,
297        minimum_observed_psnr_db: minimum_observed,
298    })
299}
300
301fn validate_decoder_vector(vector: &VideoDecoderConformanceVector) -> Result<()> {
302    if vector.name.trim().is_empty() || vector.samples.is_empty() {
303        return Err(invalid(
304            "decoder conformance vectors require a name and samples",
305        ));
306    }
307    if vector.expected_frames.len() != vector.samples.len() {
308        return Err(invalid(
309            "decoder conformance vectors require one expected frame per sample",
310        ));
311    }
312    let sample_indexes: HashSet<_> = vector
313        .samples
314        .iter()
315        .map(|sample| sample.presentation_index)
316        .collect();
317    let expected_indexes: HashSet<_> = vector
318        .expected_frames
319        .iter()
320        .map(|frame| frame.presentation_index)
321        .collect();
322    if sample_indexes.len() != vector.samples.len()
323        || expected_indexes.len() != vector.expected_frames.len()
324        || sample_indexes != expected_indexes
325    {
326        return Err(invalid(
327            "decoder conformance frame identities must be unique and complete",
328        ));
329    }
330    Ok(())
331}
332
333fn validate_encoder_vector(vector: &VideoEncoderConformanceVector) -> Result<()> {
334    if vector.name.trim().is_empty() || vector.frames.is_empty() {
335        return Err(invalid(
336            "encoder conformance vectors require a name and frames",
337        ));
338    }
339    if !vector.minimum_psnr_db.is_finite() || vector.minimum_psnr_db < 0.0 {
340        return Err(invalid(
341            "encoder conformance PSNR must be finite and nonnegative",
342        ));
343    }
344    if vector.configuration.timescale == 0 || vector.configuration.frame_duration == 0 {
345        return Err(invalid(
346            "encoder conformance requires a nonzero timescale and frame duration",
347        ));
348    }
349    if vector.decoder_configuration.codec != vector.configuration.codec
350        || vector.decoder_configuration.coded_dimensions != vector.configuration.coded_dimensions
351        || vector.decoder_configuration.output_format != vector.configuration.input_format
352        || vector.decoder_configuration.color_range != vector.configuration.color_range
353    {
354        return Err(invalid(
355            "encoder and decoder conformance configurations must describe the same codec and frame format",
356        ));
357    }
358    for frame in &vector.frames {
359        if frame.dimensions != vector.configuration.coded_dimensions
360            || frame.pixel_format != vector.configuration.input_format
361            || frame.color_range != vector.configuration.color_range
362        {
363            return Err(invalid("encoder conformance source frame format changed"));
364        }
365    }
366    Ok(())
367}
368
369fn require_supported(support: CodecSupport, message: &str) -> Result<()> {
370    if support.is_supported() {
371        Ok(())
372    } else {
373        Err(Error::new(
374            ErrorKind::Unsupported,
375            format!("{message}: {support:?}"),
376        ))
377    }
378}
379
380fn validate_encoded_packets(
381    packets: &[EncodedSample],
382    frame_count: usize,
383    configuration: &crate::EncoderConfig,
384    vector: &VideoEncoderConformanceVector,
385) -> Result<()> {
386    if configuration.codec != vector.configuration.codec
387        || configuration.timescale != vector.configuration.timescale
388        || configuration.timescale == 0
389    {
390        return Err(Error::new(
391            ErrorKind::Codec,
392            "encoder emitted invalid codec configuration",
393        ));
394    }
395    if packets.len() != frame_count {
396        return Err(Error::new(
397            ErrorKind::Codec,
398            "encoder must emit exactly one video access unit per conformance frame",
399        ));
400    }
401    if packets.first().is_none_or(|packet| !packet.is_sync) {
402        return Err(Error::new(
403            ErrorKind::Codec,
404            "encoder conformance output must begin with a random-access sample",
405        ));
406    }
407    let mut pts = HashSet::new();
408    for (decode_index, packet) in packets.iter().enumerate() {
409        let expected_dts = i64::try_from(decode_index)
410            .ok()
411            .and_then(|value| value.checked_mul(i64::from(vector.configuration.frame_duration)))
412            .ok_or_else(|| Error::new(ErrorKind::ResourceLimit, "encoder DTS overflow"))?;
413        if packet.data.is_empty()
414            || packet.duration != vector.configuration.frame_duration
415            || packet.dts != expected_dts
416            || !pts.insert(packet.pts)
417        {
418            return Err(Error::new(
419                ErrorKind::Codec,
420                "encoder emitted invalid data, duration, DTS, or duplicate PTS",
421            ));
422        }
423        let duration = i64::from(vector.configuration.frame_duration);
424        if packet.pts < 0 || packet.pts % duration != 0 {
425            return Err(Error::new(
426                ErrorKind::Codec,
427                "encoder sample PTS is outside the configured frame clock",
428            ));
429        }
430    }
431    for presentation_index in 0..frame_count {
432        let expected_pts = i64::try_from(presentation_index)
433            .ok()
434            .and_then(|value| value.checked_mul(i64::from(vector.configuration.frame_duration)))
435            .ok_or_else(|| Error::new(ErrorKind::ResourceLimit, "encoder PTS overflow"))?;
436        if !pts.contains(&expected_pts) {
437            return Err(Error::new(
438                ErrorKind::Codec,
439                "encoder sample PTS values do not cover the input timeline",
440            ));
441        }
442    }
443    Ok(())
444}
445
446fn packets_to_video_samples(packets: Vec<EncodedSample>) -> Result<Vec<EncodedVideoSample>> {
447    let mut presentation = (0..packets.len()).collect::<Vec<_>>();
448    presentation.sort_by_key(|&index| (packets[index].pts, packets[index].dts, index));
449    let mut indexes = vec![FrameIndex(0); packets.len()];
450    for (presentation_index, decode_index) in presentation.into_iter().enumerate() {
451        indexes[decode_index] =
452            FrameIndex(u64::try_from(presentation_index).map_err(|_| {
453                Error::new(ErrorKind::ResourceLimit, "presentation index overflow")
454            })?);
455    }
456    Ok(packets
457        .into_iter()
458        .enumerate()
459        .map(|(decode_index, packet)| EncodedVideoSample {
460            presentation_index: indexes[decode_index],
461            random_access: packet.is_sync,
462            data: packet.data,
463        })
464        .collect())
465}
466
467fn frame_psnr(reference: &VideoFrame, candidate: &VideoFrame) -> Result<f64> {
468    if reference.dimensions != candidate.dimensions
469        || reference.pixel_format != candidate.pixel_format
470        || reference.color_range != candidate.color_range
471    {
472        return Err(Error::new(
473            ErrorKind::Codec,
474            "round-trip decoder changed frame metadata",
475        ));
476    }
477    let mut a = Vec::new();
478    let mut b = Vec::new();
479    append_active_pixels(reference, &mut a)?;
480    append_active_pixels(candidate, &mut b)?;
481    if a.len() != b.len() || a.is_empty() {
482        return Err(Error::new(
483            ErrorKind::Codec,
484            "round-trip frame layout changed",
485        ));
486    }
487    let squared_error: f64 = a
488        .iter()
489        .zip(&b)
490        .map(|(&left, &right)| {
491            let difference = f64::from(left) - f64::from(right);
492            difference * difference
493        })
494        .sum();
495    if squared_error == 0.0 {
496        return Ok(f64::INFINITY);
497    }
498    let mse = squared_error / a.len() as f64;
499    Ok(10.0 * ((255.0 * 255.0) / mse).log10())
500}
501
502fn append_active_pixels(frame: &VideoFrame, output: &mut Vec<u8>) -> Result<()> {
503    let layouts = plane_layouts(frame)?;
504    for (plane, (row_bytes, rows)) in frame.planes.iter().zip(layouts) {
505        for row in 0..rows {
506            let start = row.checked_mul(plane.stride).ok_or_else(|| {
507                Error::new(ErrorKind::ResourceLimit, "video plane offset overflow")
508            })?;
509            let end = start
510                .checked_add(row_bytes)
511                .ok_or_else(|| Error::new(ErrorKind::ResourceLimit, "video plane row overflow"))?;
512            let bytes = plane.data.get(start..end).ok_or_else(|| {
513                Error::new(
514                    ErrorKind::InvalidInput,
515                    "video plane is shorter than its active rows",
516                )
517            })?;
518            output.extend_from_slice(bytes);
519        }
520    }
521    Ok(())
522}
523
524fn plane_layouts(frame: &VideoFrame) -> Result<Vec<(usize, usize)>> {
525    let width = usize::try_from(frame.dimensions.width)
526        .map_err(|_| Error::new(ErrorKind::ResourceLimit, "video width is too large"))?;
527    let height = usize::try_from(frame.dimensions.height)
528        .map_err(|_| Error::new(ErrorKind::ResourceLimit, "video height is too large"))?;
529    let packed = |components: usize| {
530        width
531            .checked_mul(components)
532            .map(|row_bytes| vec![(row_bytes, height)])
533    };
534    let layouts = match frame.pixel_format {
535        PixelFormat::Rgba8 | PixelFormat::Bgra8 => packed(4),
536        PixelFormat::Rgb8 => packed(3),
537        PixelFormat::Gray8 => packed(1),
538        PixelFormat::Yuv420p8 => {
539            let chroma_width = width.div_ceil(2);
540            let chroma_height = height.div_ceil(2);
541            Some(vec![
542                (width, height),
543                (chroma_width, chroma_height),
544                (chroma_width, chroma_height),
545            ])
546        }
547        _ => {
548            return Err(Error::new(
549                ErrorKind::Unsupported,
550                "unsupported pixel format",
551            ));
552        }
553    }
554    .ok_or_else(|| Error::new(ErrorKind::ResourceLimit, "video row size overflow"))?;
555    if layouts.len() != frame.planes.len() {
556        return Err(invalid("video plane count does not match its pixel format"));
557    }
558    Ok(layouts)
559}
560
561fn pixel_format_tag(format: PixelFormat) -> u8 {
562    match format {
563        PixelFormat::Rgba8 => 0,
564        PixelFormat::Bgra8 => 1,
565        PixelFormat::Rgb8 => 2,
566        PixelFormat::Gray8 => 3,
567        PixelFormat::Yuv420p8 => 4,
568        _ => u8::MAX,
569    }
570}
571
572fn add_statistics(left: DecodeStatistics, right: DecodeStatistics) -> DecodeStatistics {
573    DecodeStatistics {
574        samples_submitted: left
575            .samples_submitted
576            .saturating_add(right.samples_submitted),
577        samples_skipped: left.samples_skipped.saturating_add(right.samples_skipped),
578        cache_hits: left.cache_hits.saturating_add(right.cache_hits),
579        resets: left.resets.saturating_add(right.resets),
580        drains: left.drains.saturating_add(right.drains),
581    }
582}
583
584fn invalid(message: &str) -> Error {
585    Error::new(ErrorKind::InvalidInput, message)
586}
587
588#[doc(hidden)]
589pub fn sha256(input: &[u8]) -> [u8; 32] {
590    const INITIAL: [u32; 8] = [
591        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
592        0x5be0cd19,
593    ];
594    const K: [u32; 64] = [
595        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
596        0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
597        0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
598        0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
599        0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
600        0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
601        0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
602        0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
603        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
604        0xc67178f2,
605    ];
606    let bit_length = (input.len() as u64).wrapping_mul(8);
607    let mut padded = input.to_vec();
608    padded.push(0x80);
609    while padded.len() % 64 != 56 {
610        padded.push(0);
611    }
612    padded.extend_from_slice(&bit_length.to_be_bytes());
613    let mut state = INITIAL;
614    for chunk in padded.chunks_exact(64) {
615        let mut words = [0_u32; 64];
616        for (index, word) in words[..16].iter_mut().enumerate() {
617            *word = u32::from_be_bytes(chunk[index * 4..index * 4 + 4].try_into().unwrap());
618        }
619        for index in 16..64 {
620            let s0 = words[index - 15].rotate_right(7)
621                ^ words[index - 15].rotate_right(18)
622                ^ (words[index - 15] >> 3);
623            let s1 = words[index - 2].rotate_right(17)
624                ^ words[index - 2].rotate_right(19)
625                ^ (words[index - 2] >> 10);
626            words[index] = words[index - 16]
627                .wrapping_add(s0)
628                .wrapping_add(words[index - 7])
629                .wrapping_add(s1);
630        }
631        let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = state;
632        for index in 0..64 {
633            let upper = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
634            let choice = (e & f) ^ ((!e) & g);
635            let first = h
636                .wrapping_add(upper)
637                .wrapping_add(choice)
638                .wrapping_add(K[index])
639                .wrapping_add(words[index]);
640            let lower = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
641            let majority = (a & b) ^ (a & c) ^ (b & c);
642            let second = lower.wrapping_add(majority);
643            h = g;
644            g = f;
645            f = e;
646            e = d.wrapping_add(first);
647            d = c;
648            c = b;
649            b = a;
650            a = first.wrapping_add(second);
651        }
652        for (target, value) in state.iter_mut().zip([a, b, c, d, e, f, g, h]) {
653            *target = target.wrapping_add(value);
654        }
655    }
656    let mut output = [0_u8; 32];
657    for (chunk, value) in output.chunks_exact_mut(4).zip(state) {
658        chunk.copy_from_slice(&value.to_be_bytes());
659    }
660    output
661}
662
663#[cfg(test)]
664mod tests {
665    use super::*;
666    use crate::{
667        Codec, CodecImplementation, CodecProfile, ColorRange, HardwarePreference, Plane,
668        SampleDependency, VideoDimensions, VideoEncoder, VideoEncoderFormat,
669        uncompressed_video_decoder_factory,
670    };
671    use std::future::Future;
672    use std::pin::Pin;
673    use std::task::{Context, Poll, Waker};
674
675    fn block_on<T>(future: impl Future<Output = T>) -> T {
676        let waker = Waker::noop();
677        let mut context = Context::from_waker(waker);
678        let mut future = Box::pin(future);
679        loop {
680            match Pin::new(&mut future).poll(&mut context) {
681                Poll::Ready(value) => return value,
682                Poll::Pending => std::thread::yield_now(),
683            }
684        }
685    }
686
687    fn config() -> VideoDecoderConfig {
688        VideoDecoderConfig {
689            codec: Codec::UncompressedVideo,
690            profile: CodecProfile::UncompressedGray8,
691            coded_dimensions: VideoDimensions::new(2, 1, &Limits::default()).unwrap(),
692            output_format: PixelFormat::Gray8,
693            color_range: ColorRange::Full,
694            hardware: HardwarePreference::Avoid,
695            configuration: Vec::new(),
696        }
697    }
698
699    fn frame(value: u8) -> VideoFrame {
700        VideoFrame::new(
701            config().coded_dimensions,
702            PixelFormat::Gray8,
703            ColorRange::Full,
704            vec![Plane {
705                data: vec![value, value + 1],
706                stride: 2,
707            }],
708            &Limits::default(),
709        )
710        .unwrap()
711    }
712
713    fn decoder_vector() -> VideoDecoderConformanceVector {
714        let values = [10_u8, 30, 20];
715        VideoDecoderConformanceVector {
716            name: "uncompressed reorder".into(),
717            configuration: config(),
718            samples: values
719                .iter()
720                .enumerate()
721                .map(|(decode_index, &value)| EncodedVideoSample {
722                    presentation_index: FrameIndex([0, 2, 1][decode_index]),
723                    random_access: decode_index == 0,
724                    data: vec![value, value + 1],
725                })
726                .collect(),
727            expected_frames: [10_u8, 20, 30]
728                .iter()
729                .enumerate()
730                .map(|(index, &value)| ExpectedVideoFrame {
731                    presentation_index: FrameIndex(index as u64),
732                    digest: FrameDigest::from_frame(&frame(value)).unwrap(),
733                })
734                .collect(),
735        }
736    }
737
738    #[test]
739    fn sha256_matches_published_empty_string_vector() {
740        assert_eq!(
741            FrameDigest(sha256(b"")).to_hex(),
742            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
743        );
744    }
745
746    #[test]
747    fn frame_digest_hex_round_trips_and_rejects_bad_input() {
748        let digest = FrameDigest::from_frame(&frame(11)).unwrap();
749        assert_eq!(FrameDigest::from_hex(&digest.to_hex()).unwrap(), digest);
750        assert_eq!(
751            FrameDigest::from_hex("xyz").unwrap_err().kind(),
752            ErrorKind::InvalidInput
753        );
754    }
755
756    #[test]
757    fn frame_digest_ignores_stride_padding() {
758        let mut padded = frame(7);
759        padded.planes[0] = Plane {
760            data: vec![7, 8, 99, 99],
761            stride: 4,
762        };
763        assert_eq!(
764            FrameDigest::from_frame(&frame(7)).unwrap(),
765            FrameDigest::from_frame(&padded).unwrap()
766        );
767    }
768
769    #[test]
770    fn decoder_runner_checks_three_access_patterns() {
771        let report = verify_video_decoder_conformance(
772            &uncompressed_video_decoder_factory(),
773            &decoder_vector(),
774            Limits::default(),
775        )
776        .unwrap();
777        assert_eq!(report.frames_verified, 9);
778        assert_eq!(report.access_patterns_verified, 3);
779        assert!(report.statistics.resets > 0);
780    }
781
782    struct PassThroughEncoder {
783        config: crate::EncoderConfig,
784        format: VideoEncoderFormat,
785        frame_duration: u32,
786    }
787
788    impl VideoEncoder for PassThroughEncoder {
789        fn config(&self) -> &crate::EncoderConfig {
790            &self.config
791        }
792
793        fn format(&self) -> VideoEncoderFormat {
794            self.format
795        }
796
797        fn encode<'a>(
798            &'a mut self,
799            index: FrameIndex,
800            source: FrameSource<'a>,
801        ) -> crate::EncoderFuture<'a, Vec<EncodedSample>> {
802            Box::pin(async move {
803                let FrameSource::Cpu(source) = source else {
804                    return Err(invalid("fixture requires CPU frames"));
805                };
806                Ok(vec![EncodedSample {
807                    data: source.frame.planes[0].data.clone(),
808                    dts: i64::try_from(index.0)
809                        .map_err(|_| invalid("fixture timestamp overflow"))?
810                        * i64::from(self.frame_duration),
811                    pts: i64::try_from(index.0)
812                        .map_err(|_| invalid("fixture timestamp overflow"))?
813                        * i64::from(self.frame_duration),
814                    duration: self.frame_duration,
815                    is_sync: true,
816                    dependency: SampleDependency::INDEPENDENT,
817                }])
818            })
819        }
820
821        fn finish<'a>(&'a mut self) -> crate::EncoderFuture<'a, Vec<EncodedSample>> {
822            Box::pin(async { Ok(Vec::new()) })
823        }
824    }
825
826    struct PassThroughEncoderFactory;
827
828    impl VideoEncoderFactory for PassThroughEncoderFactory {
829        fn capability(&self, configuration: &VideoEncoderConfig) -> CodecSupport {
830            if configuration.codec == Codec::UncompressedVideo {
831                CodecSupport::Supported {
832                    implementation: CodecImplementation::Software,
833                }
834            } else {
835                CodecSupport::UnsupportedCodec
836            }
837        }
838
839        fn create(
840            &self,
841            configuration: &VideoEncoderConfig,
842            _limits: &Limits,
843        ) -> Result<Box<dyn VideoEncoder>> {
844            Ok(Box::new(PassThroughEncoder {
845                config: crate::EncoderConfig {
846                    codec: Codec::UncompressedVideo,
847                    timescale: configuration.timescale,
848                    decoder_config: Vec::new(),
849                },
850                format: VideoEncoderFormat {
851                    dimensions: configuration.coded_dimensions,
852                    pixel_format: configuration.input_format,
853                },
854                frame_duration: configuration.frame_duration,
855            }))
856        }
857    }
858
859    #[test]
860    fn encoder_runner_validates_independent_round_trip() {
861        let decoder = config();
862        let vector = VideoEncoderConformanceVector {
863            name: "pass through".into(),
864            configuration: VideoEncoderConfig {
865                codec: Codec::UncompressedVideo,
866                profile: CodecProfile::UncompressedGray8,
867                coded_dimensions: decoder.coded_dimensions,
868                input_format: PixelFormat::Gray8,
869                color_range: ColorRange::Full,
870                hardware: HardwarePreference::Avoid,
871                timescale: 1,
872                frame_duration: 1,
873                configuration: Vec::new(),
874            },
875            decoder_configuration: decoder,
876            frames: vec![frame(1), frame(3)],
877            minimum_psnr_db: 60.0,
878        };
879        let report = block_on(verify_video_encoder_conformance(
880            &PassThroughEncoderFactory,
881            &uncompressed_video_decoder_factory(),
882            &vector,
883            Limits::default(),
884        ))
885        .unwrap();
886        assert_eq!(report.frames_encoded, 2);
887        assert!(report.minimum_observed_psnr_db.is_infinite());
888    }
889}