1use alloc::format;
6use alloc::string::{String, ToString};
7use alloc::sync::Arc;
8use alloc::vec::Vec;
9use core::fmt;
10
11use enough::{Stop, StopReason};
12use zencodec::decode::{AnimationFrameDecoder, Input};
13use zencodec::encode::AnimationFrameEncoder;
14use zencodec::how_far::NoPulse;
15use zencodec::source::{ReadAt, SourceError};
16use zencodec::time::{FrameDuration, TimeBase, Timestamp};
17use zencodec_io::{DriveFailure, DriveOptions, ReadAtCursor, SliceCursor, Yield};
18use zencodec_media::adapt::{
19 AnimationVideoEncoder, CaptureFrame, RenderFrame, VideoAnimationDecoder,
20};
21use zencodec_media::container::{
22 DemuxEvent, DemuxStage, Demuxer, MuxHint, Muxer, SeekStatus, SeekTarget,
23};
24use zencodec_media::frame::{Frame, PlaneSamples};
25use zencodec_media::packet::{Discard, Packet, RandomAccess};
26use zencodec_media::session::{
27 CodecConfig, ProtocolViolation, Receive, SendStatus, SessionError, UnsupportedMedia,
28 VideoDecoder, VideoEncoder, erase_video_decoder,
29};
30use zencodec_media::track::TrackInfo;
31use zenpixels::PixelBuffer;
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct Failure {
36 pub check: &'static str,
38 pub detail: String,
40}
41
42impl fmt::Display for Failure {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(f, "[{}] {}", self.check, self.detail)
45 }
46}
47
48impl core::error::Error for Failure {}
49
50pub type Conformance = Result<(), Failure>;
52
53fn fail(check: &'static str, detail: impl Into<String>) -> Failure {
54 Failure {
55 check,
56 detail: detail.into(),
57 }
58}
59
60struct Tripped;
62
63impl Stop for Tripped {
64 fn check(&self) -> Result<(), StopReason> {
65 Err(StopReason::Cancelled)
66 }
67}
68
69fn frame_pixels(frame: &Frame) -> Result<Vec<u8>, String> {
71 let mapped = frame.map().map_err(|e| e.to_string())?;
72 let mut out = Vec::new();
73 for i in 0..mapped.plane_count() {
74 let plane = mapped.plane(i).ok_or("plane disappeared")?;
75 for y in 0..plane.height() {
76 match plane.row(y).ok_or("row out of range")? {
77 PlaneSamples::U8(r) => out.extend_from_slice(r),
78 PlaneSamples::U16(r) => {
79 for v in r {
80 out.extend_from_slice(&v.to_le_bytes());
81 }
82 }
83 }
84 }
85 }
86 Ok(out)
87}
88
89#[derive(Debug, PartialEq, Eq)]
91struct Shown {
92 pts: Option<Timestamp>,
93 keyframe: bool,
94 show_existing: bool,
95 pixels: Vec<u8>,
96}
97
98fn shown(frame: &Frame) -> Result<Shown, String> {
99 Ok(Shown {
100 pts: frame.timing().pts,
101 keyframe: frame.presentation().keyframe,
102 show_existing: frame.presentation().show_existing,
103 pixels: frame_pixels(frame)?,
104 })
105}
106
107fn decode_eager<D: VideoDecoder>(dec: &mut D, packets: &[Packet]) -> Result<Vec<Frame>, String> {
109 let mut frames = Vec::new();
110 for p in packets {
111 let mut p = p.clone();
112 zencodec_media::session::send_packet_until_accepted(dec, &mut p, Some(&NoPulse), |f| {
113 frames.push(f);
114 Ok::<(), ()>(())
115 })
116 .map_err(|e| format!("{e:?}"))?;
117 }
118 zencodec_media::session::drain_decoder(dec, Some(&NoPulse), |f| {
119 frames.push(f);
120 Ok::<(), ()>(())
121 })
122 .map_err(|e| format!("{e:?}"))?;
123 Ok(frames)
124}
125
126pub fn check_video_decoder_contract<D, F>(
146 mut make: F,
147 config: &CodecConfig,
148 packets: &[Packet],
149) -> Conformance
150where
151 D: VideoDecoder + Send + 'static,
152 F: FnMut() -> D,
153{
154 const CHECK: &str = "video_decoder_contract";
155 let configured = |make: &mut F| -> Result<D, Failure> {
156 let mut d = make();
157 d.configure(config, Some(&NoPulse))
158 .map_err(|e| fail(CHECK, format!("configure: {e:?}")))?;
159 Ok(d)
160 };
161 if packets.is_empty() {
162 return Err(fail(CHECK, "no packets supplied"));
163 }
164
165 let mut dec = configured(&mut make)?;
166 let eager = decode_eager(&mut dec, packets).map_err(|e| fail(CHECK, e))?;
167 if eager.is_empty() {
168 return Err(fail(CHECK, "the stream decoded to no frames"));
169 }
170 let want: Vec<Shown> = eager
171 .iter()
172 .map(shown)
173 .collect::<Result<_, _>>()
174 .map_err(|e| fail(CHECK, e))?;
175
176 let mut dec = configured(&mut make)?;
178 let mut lazy = Vec::new();
179 for (i, p) in packets.iter().enumerate() {
180 let mut p = p.clone();
181 loop {
182 match dec.send(&mut p, Some(&NoPulse)) {
183 Ok(SendStatus::Accepted) => break,
184 Ok(SendStatus::ReceivePending) => {
185 if p.is_taken() {
186 return Err(fail(
187 CHECK,
188 format!("packet {i}: ReceivePending took the payload"),
189 ));
190 }
191 match dec.receive(Some(&NoPulse)) {
192 Ok(Receive::Ready(f)) => lazy.push(f),
193 other => {
194 return Err(fail(
195 CHECK,
196 format!("packet {i}: ReceivePending, then {other:?}"),
197 ));
198 }
199 }
200 }
201 Ok(other) => return Err(fail(CHECK, format!("packet {i}: send {other:?}"))),
202 Err(e) => return Err(fail(CHECK, format!("packet {i}: send {e:?}"))),
203 }
204 }
205 }
206 dec.end_input()
207 .map_err(|e| fail(CHECK, format!("end_input: {e:?}")))?;
208 dec.end_input()
209 .map_err(|e| fail(CHECK, format!("second end_input: {e:?}")))?;
210 loop {
211 match dec.receive(Some(&NoPulse)) {
212 Ok(Receive::Ready(f)) => lazy.push(f),
213 Ok(Receive::EndOfStream) => break,
214 other => return Err(fail(CHECK, format!("drain: {other:?}"))),
215 }
216 }
217 if !matches!(dec.receive(Some(&NoPulse)), Ok(Receive::EndOfStream)) {
218 return Err(fail(CHECK, "EndOfStream is not stable"));
219 }
220 let got: Vec<Shown> = lazy
221 .iter()
222 .map(shown)
223 .collect::<Result<_, _>>()
224 .map_err(|e| fail(CHECK, e))?;
225 if got != want {
226 return Err(fail(
227 CHECK,
228 format!(
229 "lazy receive gave {} frames, eager {}, or they differ",
230 got.len(),
231 want.len()
232 ),
233 ));
234 }
235 let mut again = packets[0].clone();
236 match dec.send(&mut again, Some(&NoPulse)) {
237 Err(SessionError::Protocol(ProtocolViolation::SendAfterEnd)) => {}
238 other => return Err(fail(CHECK, format!("send after end_input: {other:?}"))),
239 }
240
241 let mut dec = configured(&mut make)?;
243 let mut other_epoch = packets[0]
244 .clone()
245 .with_config_epoch(config.epoch.wrapping_add(1));
246 match dec.send(&mut other_epoch, Some(&NoPulse)) {
247 Err(SessionError::Protocol(ProtocolViolation::EpochMismatch)) => {}
248 other => {
249 return Err(fail(
250 CHECK,
251 format!("a packet from another epoch: {other:?}"),
252 ));
253 }
254 }
255
256 let mut dec = configured(&mut make)?;
259 let first = decode_eager(&mut dec, packets).map_err(|e| fail(CHECK, e))?;
260 let held = first[0].clone();
261 let mapping = held.map().map_err(|e| fail(CHECK, e.to_string()))?;
262 dec.reset()
263 .map_err(|e| fail(CHECK, format!("reset: {e:?}")))?;
264 let after_reset = decode_eager(&mut dec, packets).map_err(|e| fail(CHECK, e))?;
265 drop(dec);
266 drop(mapping);
267 let after: Vec<Shown> = after_reset
268 .iter()
269 .map(shown)
270 .collect::<Result<_, _>>()
271 .map_err(|e| fail(CHECK, e))?;
272 if after != want {
273 return Err(fail(
274 CHECK,
275 "after reset the same packets decode to different frames",
276 ));
277 }
278 if shown(&held).map_err(|e| fail(CHECK, e))? != want[0] {
279 return Err(fail(
280 CHECK,
281 "a frame changed after its decoder decoded more, reset and dropped",
282 ));
283 }
284
285 let mut dec = erase_video_decoder(configured(&mut make)?);
287 match dec.receive(Some(&zencodec::how_far::StopOnly::borrowed(&Tripped))) {
288 Err(SessionError::Cancelled(_)) => {}
289 other => return Err(fail(CHECK, format!("a tripped stop: {other:?}"))),
290 }
291 match dec.receive(Some(&NoPulse)) {
292 Err(SessionError::Finished) => {}
293 other => return Err(fail(CHECK, format!("after cancellation: {other:?}"))),
294 }
295 dec.reset()
296 .map_err(|e| fail(CHECK, format!("reset after cancellation: {e:?}")))?;
297 if !matches!(dec.receive(Some(&NoPulse)), Ok(Receive::NeedInput)) {
298 return Err(fail(CHECK, "reset did not reopen the session"));
299 }
300 Ok(())
301}
302
303pub fn check_video_encoder_contract<E, F>(mut make: F, frames: &[Frame]) -> Conformance
316where
317 E: VideoEncoder,
318 F: FnMut() -> E,
319{
320 const CHECK: &str = "video_encoder_contract";
321 if frames.is_empty() {
322 return Err(fail(CHECK, "no frames supplied"));
323 }
324 type Out = (Vec<u8>, Option<Timestamp>, RandomAccess);
325 let run = |enc: &mut E, eager: bool| -> Result<Vec<Out>, Failure> {
326 let mut out = Vec::new();
327 let keep = |p: Packet| -> Out { (p.bytes().to_vec(), p.pts(), p.random_access()) };
328 for (i, f) in frames.iter().enumerate() {
329 loop {
330 match enc.send(f, Some(&NoPulse)) {
331 Ok(SendStatus::Accepted) => break,
332 Ok(SendStatus::ReceivePending) => match enc.receive(Some(&NoPulse)) {
333 Ok(Receive::Ready(p)) => out.push(keep(p)),
334 other => {
335 return Err(fail(
336 CHECK,
337 format!("frame {i}: ReceivePending, then {other:?}"),
338 ));
339 }
340 },
341 other => return Err(fail(CHECK, format!("frame {i}: send {other:?}"))),
342 }
343 }
344 if eager {
345 while let Ok(Receive::Ready(p)) = enc.receive(Some(&NoPulse)) {
346 out.push(keep(p));
347 }
348 }
349 }
350 enc.end_input()
351 .and_then(|()| enc.end_input())
352 .map_err(|e| fail(CHECK, format!("end_input: {e:?}")))?;
353 loop {
354 match enc.receive(Some(&NoPulse)) {
355 Ok(Receive::Ready(p)) => out.push(keep(p)),
356 Ok(Receive::EndOfStream) => break,
357 other => return Err(fail(CHECK, format!("drain: {other:?}"))),
358 }
359 }
360 if !matches!(enc.receive(Some(&NoPulse)), Ok(Receive::EndOfStream)) {
361 return Err(fail(CHECK, "EndOfStream is not stable"));
362 }
363 match enc.send(&frames[0], Some(&NoPulse)) {
364 Err(SessionError::Protocol(ProtocolViolation::SendAfterEnd)) => {}
365 other => return Err(fail(CHECK, format!("send after end_input: {other:?}"))),
366 }
367 Ok(out)
368 };
369 let mut enc = make();
370 let Some(config) = enc.output_config().cloned() else {
371 return Err(fail(CHECK, "no output_config before the first frame"));
372 };
373 let eager = run(&mut enc, true)?;
374 let lazy = run(&mut make(), false)?;
375 if eager != lazy {
376 return Err(fail(CHECK, "lazy and eager receive gave different packets"));
377 }
378 let mut got: Vec<Timestamp> = Vec::new();
379 for (i, (_, pts, _)) in eager.iter().enumerate() {
380 got.push(pts.ok_or_else(|| fail(CHECK, format!("packet {i} has no timestamp")))?);
381 }
382 let mut want: Vec<Timestamp> = frames.iter().filter_map(|f| f.timing().pts).collect();
383 got.sort_by(|a, b| a.compare(*b));
384 want.sort_by(|a, b| a.compare(*b));
385 if got != want {
386 return Err(fail(
387 CHECK,
388 "packet timestamps are not exactly the frames' timestamps",
389 ));
390 }
391 let mut enc = make();
393 let other_base = if config.time_base == TimeBase::MILLIS {
394 TimeBase::SECONDS
395 } else {
396 TimeBase::MILLIS
397 };
398 let mut timing = frames[0].timing();
399 timing.pts = Some(Timestamp::new(0, other_base));
400 let stray = frames[0].represent(timing, frames[0].presentation());
401 match enc.send(&stray, Some(&NoPulse)) {
402 Err(SessionError::Protocol(ProtocolViolation::ClockMismatch)) => Ok(()),
403 other => Err(fail(CHECK, format!("a frame on another clock: {other:?}"))),
404 }
405}
406
407struct Remote<'a>(&'a [u8]);
410
411impl ReadAt for Remote<'_> {
412 type Error = SourceError;
413
414 fn len(&mut self) -> Result<u64, SourceError> {
415 Ok(self.0.len() as u64)
416 }
417
418 fn read_exact_at(&mut self, offset: u64, out: &mut [u8]) -> Result<(), SourceError> {
419 if self.read_at(offset, out)? == out.len() {
420 Ok(())
421 } else {
422 Err(SourceError::UnexpectedEof {
423 offset,
424 len: out.len() as u64,
425 total: Some(self.0.len() as u64),
426 })
427 }
428 }
429
430 fn read_at(&mut self, offset: u64, out: &mut [u8]) -> Result<usize, SourceError> {
431 let start = (offset as usize).min(self.0.len());
432 let n = out.len().min(self.0.len() - start);
433 out[..n].copy_from_slice(&self.0[start..start + n]);
434 Ok(n)
435 }
436}
437
438#[derive(Debug, PartialEq, Eq)]
440struct Demuxed {
441 track: u32,
442 bytes: Vec<u8>,
443 pts: Option<Timestamp>,
444 dts: Option<Timestamp>,
445 duration_ticks: Option<u64>,
446 discard: Option<Discard>,
447 random_access: RandomAccess,
448 epoch: u32,
449}
450
451#[derive(Debug, PartialEq, Eq)]
452enum Seen {
453 Headers(usize),
454 Packet(Demuxed),
455 Config(u32, u32),
456}
457
458fn seen(ev: DemuxEvent, tracks: usize) -> Option<Seen> {
459 match ev {
460 DemuxEvent::Headers => Some(Seen::Headers(tracks)),
461 DemuxEvent::Packet(p) => Some(Seen::Packet(Demuxed {
462 track: p.track(),
463 bytes: p.bytes().to_vec(),
464 pts: p.pts(),
465 dts: p.dts(),
466 duration_ticks: p.duration_ticks(),
467 discard: p.discard(),
468 random_access: p.random_access(),
469 epoch: p.config_epoch(),
470 })),
471 DemuxEvent::ConfigChanged { track, epoch } => Some(Seen::Config(track, epoch)),
472 _ => None,
473 }
474}
475
476type DemuxErr<D> = DriveFailure<SessionError<<D as Demuxer>::Error>, SourceError>;
477
478fn demux_all<D: Demuxer>(
479 dem: &mut D,
480 mut next: impl FnMut(&mut D) -> Result<Yield<DemuxEvent>, DemuxErr<D>>,
481) -> Result<Vec<Seen>, DemuxErr<D>> {
482 let mut out = Vec::new();
483 loop {
484 let y = next(dem)?;
485 let finished = y.is_finished();
486 if let Some(s) = seen(y.into_inner(), dem.tracks().len()) {
487 out.push(s);
488 }
489 if finished {
490 return Ok(out);
491 }
492 }
493}
494
495pub fn check_demuxer_contract<D, F>(mut make: F, file: &[u8]) -> Conformance
512where
513 D: Demuxer,
514 F: FnMut() -> D,
515{
516 const CHECK: &str = "demuxer_contract";
517 let slice = |make: &mut F, bytes: &[u8], window: usize| {
518 let mut dem = make();
519 let mut cursor = SliceCursor::new(window);
520 demux_all(&mut dem, |d| {
521 cursor
522 .next(bytes, &mut DemuxStage(d), Some(&NoPulse))
523 .map_err(DriveFailure::widen)
524 })
525 };
526 let want =
527 slice(&mut make, file, file.len().max(1)).map_err(|e| fail(CHECK, format!("{e:?}")))?;
528 if !want.iter().any(|s| matches!(s, Seen::Packet(_))) {
529 return Err(fail(CHECK, "the file yielded no packets"));
530 }
531 for window in [1usize, 7, 64] {
532 let got = slice(&mut make, file, window)
533 .map_err(|e| fail(CHECK, format!("window {window}: {e:?}")))?;
534 if got != want {
535 return Err(fail(
536 CHECK,
537 format!("window {window} gave different events than the whole file"),
538 ));
539 }
540 }
541 for read_size in [64usize, 4096] {
542 let mut dem = make();
543 let mut src = Remote(file);
544 let mut cursor = ReadAtCursor::new(DriveOptions::new(0, read_size));
545 let got = demux_all(&mut dem, |d| {
546 cursor.next(&mut src, &mut DemuxStage(d), Some(&NoPulse))
547 })
548 .map_err(|e| fail(CHECK, format!("read_at {read_size}: {e:?}")))?;
549 if got != want {
550 return Err(fail(
551 CHECK,
552 format!("the ReadAt driver at {read_size} bytes gave different events"),
553 ));
554 }
555 }
556
557 let mut dem = make();
559 let at = file.len() / 2;
560 let p = dem
561 .step(Input::new(at as u64 + 1, &file[at..]), Some(&NoPulse))
562 .map_err(|e| fail(CHECK, format!("an unrequested input: {e:?}")))?;
563 if p.consumed != 0 || !matches!(p.event, DemuxEvent::Need) || dem.requested().is_empty() {
564 return Err(fail(
565 CHECK,
566 "input at an offset nobody asked for was used, or no request was named",
567 ));
568 }
569
570 for k in 1..=13usize {
572 let cut = file.len() * k / 14;
573 match slice(&mut make, &file[..cut], 7) {
574 Ok(got) => {
575 if got.len() > want.len() || got[..] != want[..got.len()] {
576 return Err(fail(
577 CHECK,
578 format!("a file cut at {cut} gave events the whole file does not"),
579 ));
580 }
581 }
582 Err(DriveFailure::Step(_)) => {}
583 Err(e) => {
584 return Err(fail(
585 CHECK,
586 format!("a file cut at {cut} broke the protocol: {e:?}"),
587 ));
588 }
589 }
590 }
591 Ok(())
592}
593
594pub fn check_demuxer_seek<D, F>(mut make: F, file: &[u8], targets: &[SeekTarget]) -> Conformance
602where
603 D: Demuxer,
604 F: FnMut() -> D,
605{
606 const CHECK: &str = "demuxer_seek";
607 let mut dem = make();
608 let mut cursor = SliceCursor::new(16);
609 loop {
610 match cursor
611 .next(file, &mut DemuxStage(&mut dem), Some(&NoPulse))
612 .map_err(|e| fail(CHECK, format!("{e:?}")))?
613 .into_inner()
614 {
615 DemuxEvent::Headers => break,
616 DemuxEvent::Done => return Err(fail(CHECK, "no headers")),
617 _ => {}
618 }
619 }
620 for (i, target) in targets.iter().enumerate() {
621 let result = match dem.seek(target) {
622 Ok(SeekStatus::Positioned(r)) => r,
623 Ok(SeekStatus::Scanning) => loop {
624 match cursor
625 .next(file, &mut DemuxStage(&mut dem), Some(&NoPulse))
626 .map_err(|e| fail(CHECK, format!("target {i}: {e:?}")))?
627 .into_inner()
628 {
629 DemuxEvent::Seeked(r) => break r,
630 DemuxEvent::Done => {
631 return Err(fail(CHECK, format!("target {i}: scan ended unpositioned")));
632 }
633 _ => {}
634 }
635 },
636 Ok(other) => return Err(fail(CHECK, format!("target {i}: {other:?}"))),
637 Err(SessionError::Unsupported(UnsupportedMedia::Seek)) => {
638 return Err(fail(CHECK, "seek is unsupported"));
639 }
640 Err(e) => return Err(fail(CHECK, format!("target {i}: {e:?}"))),
641 };
642 let next = loop {
643 match cursor
644 .next(file, &mut DemuxStage(&mut dem), Some(&NoPulse))
645 .map_err(|e| fail(CHECK, format!("target {i}: {e:?}")))?
646 .into_inner()
647 {
648 DemuxEvent::Packet(p) if p.track() == target.track => break p,
649 DemuxEvent::Done => {
650 return Err(fail(CHECK, format!("target {i}: no packet after the seek")));
651 }
652 _ => {}
653 }
654 };
655 if next.pts().is_none_or(|p| !p.same_instant(result.pts)) {
656 return Err(fail(
657 CHECK,
658 format!(
659 "target {i}: reported {:?} but the next packet is at {:?}",
660 result.pts,
661 next.pts()
662 ),
663 ));
664 }
665 if next.random_access() != result.random_access {
666 return Err(fail(
667 CHECK,
668 format!("target {i}: reported random access differs from the packet's"),
669 ));
670 }
671 if target.scope == zencodec_media::container::SeekScope::RandomAccessPoints
672 && result.random_access != RandomAccess::Yes
673 {
674 return Err(fail(
675 CHECK,
676 format!("target {i}: a random-access seek landed elsewhere"),
677 ));
678 }
679 }
680 Ok(())
681}
682
683#[derive(Clone, Copy, PartialEq, Eq)]
685enum Drain {
686 Never,
687 All,
688 Partial,
689 Take,
690}
691
692pub fn check_muxer_output_honesty<M, F>(
706 mut make: F,
707 tracks: &[TrackInfo],
708 packets: &[Packet],
709) -> Conformance
710where
711 M: Muxer,
712 F: FnMut() -> M,
713{
714 const CHECK: &str = "muxer_output_honesty";
715 let run = |make: &mut F, drain: Drain, hint: MuxHint| -> Result<Vec<u8>, Failure> {
716 let mut m = make();
717 let timing = m
718 .begin(tracks, hint)
719 .map_err(|e| fail(CHECK, format!("begin: {e:?}")))?;
720 let mut out = Vec::new();
721 let mut rng = 0x2545_f491u32;
722 for (i, p) in packets.iter().enumerate() {
723 m.write_packet(p.clone(), Some(&NoPulse))
724 .map_err(|e| fail(CHECK, format!("packet {i}: {e:?}")))?;
725 let pending = m.pending_output().len();
726 match drain {
727 Drain::Never => {}
728 Drain::All => {
729 out.extend_from_slice(m.pending_output());
730 m.consume_output(pending);
731 }
732 Drain::Partial => {
733 rng ^= rng << 13;
734 rng ^= rng >> 17;
735 rng ^= rng << 5;
736 let n = rng as usize % (pending + 2);
737 out.extend_from_slice(&m.pending_output()[..n.min(pending)]);
738 m.consume_output(n);
739 if m.pending_output().len() != pending - n.min(pending) {
740 return Err(fail(CHECK, "consume_output did not clamp"));
741 }
742 }
743 Drain::Take => m.take_output(&mut out),
744 }
745 if !timing.streams() && !m.pending_output().is_empty() {
746 return Err(fail(
747 CHECK,
748 "pending output before finish from a muxer that reported AT_FINISH",
749 ));
750 }
751 }
752 let fin = m
753 .finish(Some(&NoPulse))
754 .map_err(|e| fail(CHECK, format!("finish: {e:?}")))?;
755 if !timing.patches_at_finish() && !fin.patches.is_empty() {
756 return Err(fail(
757 CHECK,
758 "patches from a muxer whose timing said it would not patch",
759 ));
760 }
761 fin.apply_to(&mut out)
762 .map_err(|e| fail(CHECK, e.to_string()))?;
763 Ok(out)
764 };
765 let hint = MuxHint::default();
766 let want = run(&mut make, Drain::Never, hint)?;
767 for (name, drain) in [
768 ("draining all", Drain::All),
769 ("uneven prefixes", Drain::Partial),
770 ("take_output", Drain::Take),
771 ] {
772 if run(&mut make, drain, hint)? != want {
773 return Err(fail(CHECK, format!("{name} changed the file")));
774 }
775 }
776 let mut m = make();
777 m.begin(tracks, hint)
778 .map_err(|e| fail(CHECK, format!("begin: {e:?}")))?;
779 match m.begin(tracks, hint) {
780 Err(SessionError::Protocol(ProtocolViolation::BeginTwice)) => {}
781 other => return Err(fail(CHECK, format!("a second begin: {other:?}"))),
782 }
783 let mut m = make();
784 match m.begin(tracks, MuxHint::forward_only()) {
785 Err(SessionError::Unsupported(UnsupportedMedia::ForwardOnlyLayout)) => {}
786 Ok(t) if t.patches_at_finish() => {
787 return Err(fail(
788 CHECK,
789 "a forward-only destination got a layout that patches",
790 ));
791 }
792 Ok(_) => {
793 run(&mut make, Drain::Take, MuxHint::forward_only())?;
794 }
795 Err(e) => return Err(fail(CHECK, format!("forward-only begin: {e:?}"))),
796 }
797 Ok(())
798}
799
800#[allow(clippy::too_many_arguments)]
811pub fn check_animation_over_video<E, M, D, C, P, R>(
812 mut make_encoder: impl FnMut() -> E,
813 mut make_muxer: impl FnMut() -> M,
814 lag_frames: u32,
815 mut capture: impl FnMut() -> P,
816 make_demuxer: impl FnOnce() -> D,
817 make_decoder: impl FnOnce() -> C,
818 render: R,
819 frames: &[PixelBuffer],
820 durations: &[FrameDuration],
821) -> Conformance
822where
823 E: VideoEncoder + Send,
824 M: Muxer + Send,
825 D: Demuxer + Send,
826 C: VideoDecoder + Send,
827 P: CaptureFrame + Send,
828 R: RenderFrame + Send,
829{
830 const CHECK: &str = "animation_over_video";
831 if frames.is_empty() || frames.len() != durations.len() {
832 return Err(fail(
833 CHECK,
834 "supply as many durations as frames, at least one",
835 ));
836 }
837 let (w, h, desc) = (
838 frames[0].width(),
839 frames[0].height(),
840 frames[0].descriptor(),
841 );
842 let count = frames.len() as u32;
843 type Encoded = (Vec<u8>, Vec<usize>, Option<u32>);
845 let mut encode = |drain: bool| -> Result<Encoded, Failure> {
846 let mut a = AnimationVideoEncoder::new(make_encoder(), make_muxer(), capture(), lag_frames)
847 .map_err(|e| fail(CHECK, format!("new: {e:?}")))?;
848 let timing = a
849 .begin(w, h, desc, Some(count))
850 .map_err(|e| fail(CHECK, format!("begin: {e:?}")))?;
851 let mut out = Vec::new();
852 let mut points = Vec::new();
853 if drain {
854 a.take_output(&mut out);
855 }
856 for (f, d) in frames.iter().zip(durations) {
857 a.push_frame_exact(f.as_slice(), *d, None)
858 .map_err(|e| fail(CHECK, format!("push: {e:?}")))?;
859 points.push(out.len() + a.pending_output().len());
860 if drain {
861 a.take_output(&mut out);
862 }
863 }
864 out.extend_from_slice(
865 a.finish(None)
866 .map_err(|e| fail(CHECK, format!("finish: {e:?}")))?
867 .data(),
868 );
869 Ok((out, points, timing.lag_frames()))
870 };
871 let (drained, points, lag) = encode(true)?;
872 let (undrained, _, _) = encode(false)?;
873 if drained != undrained {
874 return Err(fail(CHECK, "draining changed the animation's bytes"));
875 }
876 if let Some(lag) = lag {
877 let lag = lag as usize;
878 let bounded = &points[..points.len().saturating_sub(1)];
879 for i in 0..bounded.len() {
880 if let Some(&later) = bounded.get(i + lag + 1)
881 && later <= bounded[i]
882 {
883 return Err(fail(
884 CHECK,
885 format!(
886 "a lag of {lag} frames, but no bytes from frame {i} to {}",
887 i + lag + 1
888 ),
889 ));
890 }
891 }
892 }
893
894 let mut anim = VideoAnimationDecoder::new(
895 Arc::<[u8]>::from(drained),
896 make_demuxer(),
897 make_decoder(),
898 render,
899 durations[durations.len() - 1],
900 7,
901 );
902 let mut index = 0usize;
903 while let Some(f) = anim
904 .render_next_frame(None)
905 .map_err(|e| fail(CHECK, format!("decode: {e:?}")))?
906 {
907 let Some(want) = frames.get(index) else {
908 return Err(fail(CHECK, "more frames decoded than encoded"));
909 };
910 if f.duration() != durations[index] {
911 return Err(fail(
912 CHECK,
913 format!(
914 "frame {index} lasts {:?}, encoded {:?}",
915 f.duration(),
916 durations[index]
917 ),
918 ));
919 }
920 for y in 0..h {
921 if f.pixels().row(y) != want.as_slice().row(y) {
922 return Err(fail(CHECK, format!("frame {index} row {y} differs")));
923 }
924 }
925 if f.pts().is_none() {
926 return Err(fail(CHECK, format!("frame {index} has no timestamp")));
927 }
928 index += 1;
929 }
930 if index != frames.len() {
931 return Err(fail(
932 CHECK,
933 format!("{index} frames decoded, {} encoded", frames.len()),
934 ));
935 }
936
937 let mut a = AnimationVideoEncoder::new(make_encoder(), make_muxer(), capture(), lag_frames)
939 .map_err(|e| fail(CHECK, format!("new: {e:?}")))?;
940 a.begin(w, h, desc, Some(count + 1))
941 .map_err(|e| fail(CHECK, format!("begin: {e:?}")))?;
942 for (f, d) in frames.iter().zip(durations) {
943 a.push_frame_exact(f.as_slice(), *d, None)
944 .map_err(|e| fail(CHECK, format!("push: {e:?}")))?;
945 }
946 if a.finish(None).is_ok() {
947 return Err(fail(
948 CHECK,
949 "finish short of the declared frame count was accepted",
950 ));
951 }
952 Ok(())
953}