1use crate::{
2 AudioBuffer, Codec, ColorRange, Error, ErrorKind, FrameIndex, FrameSource, Limits, PixelFormat,
3 Plane, Result, VideoDimensions, VideoFrame,
4};
5use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
6use std::future::Future;
7use std::pin::Pin;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::time::Duration;
11
12pub type EncoderFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + 'a>>;
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum TrackKind {
18 Video,
19 Audio,
20}
21
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct SampleDependency {
25 pub is_leading: u8,
26 pub depends_on: u8,
27 pub is_depended_on: u8,
28 pub has_redundancy: u8,
29}
30
31impl SampleDependency {
32 pub const INDEPENDENT: Self = Self {
33 is_leading: 0,
34 depends_on: 2,
35 is_depended_on: 0,
36 has_redundancy: 0,
37 };
38
39 pub const DEPENDENT: Self = Self {
40 is_leading: 0,
41 depends_on: 1,
42 is_depended_on: 0,
43 has_redundancy: 0,
44 };
45
46 #[doc(hidden)]
47 pub fn to_sdtp(self) -> u8 {
48 ((self.is_leading & 3) << 6)
49 | ((self.depends_on & 3) << 4)
50 | ((self.is_depended_on & 3) << 2)
51 | (self.has_redundancy & 3)
52 }
53}
54
55#[derive(Clone, Debug, Eq, PartialEq)]
57pub struct EncodedSample {
58 pub data: Vec<u8>,
59 pub dts: i64,
60 pub pts: i64,
61 pub duration: u32,
62 pub is_sync: bool,
63 pub dependency: SampleDependency,
64}
65
66#[derive(Clone, Debug, Eq, PartialEq)]
68pub struct EncoderConfig {
69 pub codec: Codec,
70 pub timescale: u32,
71 pub decoder_config: Vec<u8>,
73}
74
75#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77pub struct VideoEncoderFormat {
78 pub dimensions: VideoDimensions,
79 pub pixel_format: PixelFormat,
80}
81
82#[derive(Clone, Copy, Debug, Eq, PartialEq)]
84pub struct AudioEncoderFormat {
85 pub sample_rate: u32,
86 pub channels: u16,
87}
88
89#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
91pub struct AudioGapless {
92 pub priming: u32,
93 pub padding: u32,
94}
95
96#[derive(Clone, Debug, Default, Eq, PartialEq)]
98pub struct AudioDrain {
99 pub samples: Vec<EncodedSample>,
100 pub gapless: AudioGapless,
101}
102
103pub trait VideoEncoder {
105 fn config(&self) -> &EncoderConfig;
106 fn format(&self) -> VideoEncoderFormat;
107 fn implementation(&self) -> CodecImplementation {
116 CodecImplementation::Software
117 }
118 fn backend_name(&self) -> &str {
122 "zvidlib"
123 }
124 fn encode<'a>(
125 &'a mut self,
126 index: FrameIndex,
127 frame: FrameSource<'a>,
128 ) -> EncoderFuture<'a, Vec<EncodedSample>>;
129 fn finish<'a>(&'a mut self) -> EncoderFuture<'a, Vec<EncodedSample>>;
130}
131
132pub trait AudioEncoder {
162 fn config(&self) -> &EncoderConfig;
163 fn format(&self) -> AudioEncoderFormat;
164 fn encode<'a>(
165 &'a mut self,
166 index: FrameIndex,
167 buffer: AudioBuffer,
168 ) -> EncoderFuture<'a, Vec<EncodedSample>>;
169 fn finish<'a>(&'a mut self) -> EncoderFuture<'a, AudioDrain>;
170}
171
172#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
174#[non_exhaustive]
175pub enum CodecProfile {
176 UncompressedGray8,
177 HevcMain,
178 HevcMain10,
179 Av1Main,
180 Av1High,
181 Av1Professional,
182 Vp8,
185 Vp9Profile0,
187 Vp9Profile1,
189 Vp9Profile2,
191 Vp9Profile3,
193 AacLowComplexity,
194 Opus,
196 Vorbis,
198}
199
200#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
202#[non_exhaustive]
203pub enum HardwarePreference {
204 Require,
205 Prefer,
206 Avoid,
207}
208
209#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
211#[non_exhaustive]
212pub enum CodecImplementation {
213 Software,
214 Hardware,
215}
216
217#[derive(Clone, Debug, Eq, PartialEq)]
219#[non_exhaustive]
220pub enum CodecSupport {
221 Supported { implementation: CodecImplementation },
222 UnsupportedCodec,
223 UnsupportedProfile,
224 InvalidConfiguration { reason: String },
225 HardwareUnavailable,
226}
227
228impl CodecSupport {
229 pub const fn is_supported(&self) -> bool {
230 matches!(self, Self::Supported { .. })
231 }
232}
233
234#[derive(Clone, Debug, Eq, PartialEq)]
236pub struct VideoDecoderConfig {
237 pub codec: Codec,
238 pub profile: CodecProfile,
239 pub coded_dimensions: VideoDimensions,
240 pub output_format: PixelFormat,
241 pub color_range: ColorRange,
242 pub hardware: HardwarePreference,
243 pub configuration: Vec<u8>,
245}
246
247#[derive(Clone, Debug, Eq, PartialEq)]
249pub struct VideoEncoderConfig {
250 pub codec: Codec,
251 pub profile: CodecProfile,
252 pub coded_dimensions: VideoDimensions,
253 pub input_format: PixelFormat,
254 pub color_range: ColorRange,
255 pub hardware: HardwarePreference,
256 pub timescale: u32,
258 pub frame_duration: u32,
260 pub configuration: Vec<u8>,
261}
262
263#[derive(Clone, Debug, Eq, PartialEq)]
265pub struct EncodedVideoSample {
266 pub presentation_index: FrameIndex,
267 pub random_access: bool,
268 pub data: Vec<u8>,
269}
270
271#[derive(Clone, Debug, Eq, PartialEq)]
273pub struct DecodedVideoFrame {
274 pub presentation_index: FrameIndex,
275 pub frame: VideoFrame,
276}
277
278#[derive(Clone, Debug, Default)]
280pub struct CancellationToken {
281 cancelled: Arc<AtomicBool>,
282}
283
284impl CancellationToken {
285 pub fn new() -> Self {
286 Self::default()
287 }
288
289 pub fn cancel(&self) {
290 self.cancelled.store(true, Ordering::Release);
291 }
292
293 pub fn is_cancelled(&self) -> bool {
294 self.cancelled.load(Ordering::Acquire)
295 }
296
297 #[doc(hidden)]
298 pub fn check(&self) -> Result<()> {
299 if self.is_cancelled() {
300 Err(Error::new(
301 ErrorKind::Cancelled,
302 "codec operation cancelled",
303 ))
304 } else {
305 Ok(())
306 }
307 }
308}
309
310pub trait VideoDecoder: Send {
315 fn submit(
316 &mut self,
317 sample: &EncodedVideoSample,
318 cancellation: &CancellationToken,
319 ) -> Result<Vec<DecodedVideoFrame>>;
320 fn drain(&mut self, cancellation: &CancellationToken) -> Result<Vec<DecodedVideoFrame>>;
321 fn reset(&mut self) -> Result<()>;
322
323 fn set_output_wanted(&mut self, _wanted: bool) {}
344}
345
346pub trait VideoDecoderFactory {
348 fn capability(&self, configuration: &VideoDecoderConfig) -> CodecSupport;
349 fn create(
350 &self,
351 configuration: &VideoDecoderConfig,
352 limits: &Limits,
353 ) -> Result<Box<dyn VideoDecoder>>;
354}
355
356pub trait VideoEncoderFactory {
358 fn capability(&self, configuration: &VideoEncoderConfig) -> CodecSupport;
359 fn create(
360 &self,
361 configuration: &VideoEncoderConfig,
362 limits: &Limits,
363 ) -> Result<Box<dyn VideoEncoder>>;
364}
365
366#[derive(Clone, Debug, Eq, PartialEq)]
368pub struct AudioEncoderConfig {
369 pub codec: Codec,
370 pub profile: CodecProfile,
371 pub sample_rate: u32,
372 pub channels: u16,
373 pub timescale: u32,
375 pub configuration: Vec<u8>,
376}
377
378pub trait AudioEncoderFactory {
387 fn capability(&self, configuration: &AudioEncoderConfig) -> CodecSupport;
388 fn create(
389 &self,
390 configuration: &AudioEncoderConfig,
391 limits: &Limits,
392 ) -> Result<Box<dyn AudioEncoder>>;
393}
394
395#[doc(hidden)]
401pub fn parse_audio_bit_rate(configuration: &[u8]) -> Option<Option<u32>> {
402 match configuration {
403 [] => Some(None),
404 [a, b, c, d] => match u32::from_be_bytes([*a, *b, *c, *d]) {
405 0 => None,
406 bits_per_second => Some(Some(bits_per_second)),
407 },
408 _ => None,
409 }
410}
411
412#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
414pub struct DecodeStatistics {
415 pub samples_submitted: u64,
416 pub samples_skipped: u64,
419 pub cache_hits: u64,
420 pub resets: u64,
421 pub drains: u64,
422}
423
424pub struct ExactFrameReader {
426 configuration: VideoDecoderConfig,
427 decoder: Box<dyn VideoDecoder>,
428 samples: Vec<EncodedVideoSample>,
429 decode_position_by_presentation: HashMap<FrameIndex, usize>,
430 cache: BTreeMap<FrameIndex, VideoFrame>,
431 lru: VecDeque<FrameIndex>,
432 published_since_reset: HashSet<FrameIndex>,
440 suppressed_since_reset: HashSet<FrameIndex>,
444 in_flight_since_reset: HashSet<FrameIndex>,
449 output_wanted: bool,
452 next_decode_position: Option<usize>,
453 session_start: Option<usize>,
456 seek_previews: Option<Arc<dyn SeekPreviewSource>>,
458 limits: Limits,
459 statistics: DecodeStatistics,
460}
461
462pub const SEEK_LATENCY_BUDGET: Duration = Duration::from_millis(50);
467
468pub trait SeekPreviewSource: Send + Sync {
480 fn nearest_at(&self, frame: FrameIndex) -> Option<(FrameIndex, VideoFrame)>;
483}
484
485#[derive(Clone, Debug, Eq, PartialEq)]
494pub enum Seek {
495 Exact(VideoFrame),
497 Preview {
499 picture: VideoFrame,
500 frame: FrameIndex,
503 },
504 Pending,
507}
508
509#[derive(Clone, Copy, Debug, Eq, PartialEq)]
516enum Request {
517 Destination,
520 Step,
523}
524
525impl Request {
526 fn cache_tail(self, limits: &Limits) -> u64 {
528 match self {
529 Self::Destination => u64::from(limits.max_cached_frames),
530 Self::Step => 0,
531 }
532 }
533}
534
535impl ExactFrameReader {
536 pub fn new(
537 factory: &dyn VideoDecoderFactory,
538 configuration: VideoDecoderConfig,
539 samples: Vec<EncodedVideoSample>,
540 limits: Limits,
541 ) -> Result<Self> {
542 if limits.max_cached_frames == 0 || limits.max_decode_samples_per_seek == 0 {
543 return Err(Error::new(
544 ErrorKind::ResourceLimit,
545 "exact-frame limits must permit cached frames and decode work",
546 ));
547 }
548 let capability = factory.capability(&configuration);
549 if !capability.is_supported() {
550 return Err(capability_error(capability));
551 }
552 if samples.is_empty() {
553 return Err(Error::new(
554 ErrorKind::InvalidInput,
555 "an exact-frame reader requires at least one sample",
556 ));
557 }
558 if !samples[0].random_access {
559 return Err(Error::new(
560 ErrorKind::InvalidInput,
561 "the first decode-order sample must be a random-access point",
562 ));
563 }
564 let mut positions = HashMap::with_capacity(samples.len());
565 for (position, sample) in samples.iter().enumerate() {
566 if positions
567 .insert(sample.presentation_index, position)
568 .is_some()
569 {
570 return Err(Error::new(
571 ErrorKind::InvalidInput,
572 "presentation frame identities must be unique",
573 ));
574 }
575 }
576 let decoder = factory.create(&configuration, &limits)?;
577 Ok(Self {
578 configuration,
579 decoder,
580 samples,
581 decode_position_by_presentation: positions,
582 cache: BTreeMap::new(),
583 lru: VecDeque::new(),
584 published_since_reset: HashSet::new(),
585 suppressed_since_reset: HashSet::new(),
586 in_flight_since_reset: HashSet::new(),
587 output_wanted: true,
588 next_decode_position: None,
589 session_start: None,
590 seek_previews: None,
591 limits,
592 statistics: DecodeStatistics::default(),
593 })
594 }
595
596 pub fn set_seek_previews(&mut self, previews: Option<Arc<dyn SeekPreviewSource>>) {
602 self.seek_previews = previews;
603 }
604
605 pub fn seek(&mut self, presentation_index: FrameIndex) -> Seek {
618 if let Some(frame) = self.cache.get(&presentation_index).cloned() {
619 self.statistics.cache_hits = self.statistics.cache_hits.saturating_add(1);
620 self.touch(presentation_index);
621 return Seek::Exact(frame);
622 }
623 match self
624 .seek_previews
625 .as_ref()
626 .and_then(|previews| previews.nearest_at(presentation_index))
627 {
628 Some((frame, picture)) => Seek::Preview { picture, frame },
629 None => Seek::Pending,
630 }
631 }
632
633 pub fn get(
642 &mut self,
643 presentation_index: FrameIndex,
644 cancellation: &CancellationToken,
645 ) -> Result<VideoFrame> {
646 self.get_request(presentation_index, Request::Destination, cancellation)
647 }
648
649 pub fn get_step(
664 &mut self,
665 presentation_index: FrameIndex,
666 cancellation: &CancellationToken,
667 ) -> Result<VideoFrame> {
668 self.get_request(presentation_index, Request::Step, cancellation)
669 }
670
671 fn get_request(
672 &mut self,
673 presentation_index: FrameIndex,
674 request: Request,
675 cancellation: &CancellationToken,
676 ) -> Result<VideoFrame> {
677 cancellation.check()?;
678 if let Some(frame) = self.cache.get(&presentation_index).cloned() {
679 self.statistics.cache_hits = self.statistics.cache_hits.saturating_add(1);
680 self.touch(presentation_index);
681 return Ok(frame);
682 }
683 let target_position = *self
684 .decode_position_by_presentation
685 .get(&presentation_index)
686 .ok_or_else(|| {
687 Error::new(ErrorKind::InvalidInput, "presentation frame is not indexed")
688 })?;
689 let random_access_position = self.nearest_random_access(target_position);
690 let can_reuse = self
710 .next_decode_position
711 .is_some_and(|position| position >= random_access_position)
712 && self
713 .session_start
714 .is_some_and(|start| start <= random_access_position)
715 && !self.published_since_reset.contains(&presentation_index)
716 && !self.suppressed_since_reset.contains(&presentation_index);
717 if !can_reuse {
718 self.decoder.reset()?;
719 self.statistics.resets = self.statistics.resets.saturating_add(1);
720 self.published_since_reset.clear();
721 self.suppressed_since_reset.clear();
722 self.in_flight_since_reset.clear();
723 self.next_decode_position = Some(random_access_position);
724 self.session_start = Some(random_access_position);
725 }
726
727 let mut work = 0_u32;
728 while let Some(position) = self.next_decode_position {
729 cancellation.check()?;
730 if work >= self.limits.max_decode_samples_per_seek {
731 return Err(Error::new(
732 ErrorKind::ResourceLimit,
733 "exact-frame request exceeded the configured decode-work limit",
734 ));
735 }
736 if position == self.samples.len() {
737 self.set_output_wanted(true);
738 self.drain_internal(cancellation)?;
739 break;
740 }
741 let cache_tail = request.cache_tail(&self.limits);
764 let wanted = position >= target_position
765 || self.samples[position].presentation_index.0
766 >= presentation_index.0.saturating_sub(cache_tail);
767 self.set_output_wanted(wanted);
768 let suppressed = !self.output_wanted;
769 let outputs = self.decoder.submit(&self.samples[position], cancellation)?;
770 self.statistics.samples_submitted = self.statistics.samples_submitted.saturating_add(1);
771 if suppressed {
772 self.statistics.samples_skipped = self.statistics.samples_skipped.saturating_add(1);
773 self.suppressed_since_reset
774 .insert(self.samples[position].presentation_index);
775 } else {
776 self.in_flight_since_reset
777 .insert(self.samples[position].presentation_index);
778 }
779 self.next_decode_position = Some(position + 1);
780 work += 1;
781 self.publish(outputs)?;
782 if let Some(frame) = self.cache.get(&presentation_index).cloned() {
783 self.touch(presentation_index);
784 return Ok(frame);
785 }
786 }
787 if let Some(frame) = self.cache.get(&presentation_index).cloned() {
788 self.touch(presentation_index);
789 Ok(frame)
790 } else {
791 Err(Error::new(
792 ErrorKind::MalformedMedia,
793 "decoder did not produce the requested presentation frame",
794 ))
795 }
796 }
797
798 pub fn drain(&mut self, cancellation: &CancellationToken) -> Result<usize> {
800 cancellation.check()?;
801 self.set_output_wanted(true);
802 self.drain_internal(cancellation)
803 }
804
805 fn set_output_wanted(&mut self, wanted: bool) {
817 if self.output_wanted == wanted {
818 return;
819 }
820 if !wanted {
821 for frame in self.in_flight_since_reset.drain() {
822 self.suppressed_since_reset.insert(frame);
823 }
824 }
825 self.decoder.set_output_wanted(wanted);
826 self.output_wanted = wanted;
827 }
828
829 pub fn reset(&mut self) -> Result<()> {
831 self.set_output_wanted(true);
832 self.decoder.reset()?;
833 self.statistics.resets = self.statistics.resets.saturating_add(1);
834 self.next_decode_position = None;
835 self.session_start = None;
836 self.cache.clear();
837 self.lru.clear();
838 self.published_since_reset.clear();
839 self.suppressed_since_reset.clear();
840 self.in_flight_since_reset.clear();
841 Ok(())
842 }
843
844 pub const fn statistics(&self) -> DecodeStatistics {
845 self.statistics
846 }
847
848 pub fn cached_frames(&self) -> usize {
849 self.cache.len()
850 }
851
852 fn nearest_random_access(&self, target_position: usize) -> usize {
860 let target = self.samples[target_position].presentation_index;
861 (0..=target_position)
862 .rev()
863 .find(|position| {
864 let sample = &self.samples[*position];
865 sample.random_access && sample.presentation_index <= target
866 })
867 .unwrap_or(0)
868 }
869
870 fn drain_internal(&mut self, cancellation: &CancellationToken) -> Result<usize> {
871 let outputs = self.decoder.drain(cancellation)?;
872 let count = outputs.len();
873 self.statistics.drains = self.statistics.drains.saturating_add(1);
874 self.next_decode_position = Some(self.samples.len());
875 self.publish(outputs)?;
876 Ok(count)
877 }
878
879 fn publish(&mut self, outputs: Vec<DecodedVideoFrame>) -> Result<()> {
880 for output in outputs {
881 self.suppressed_since_reset
885 .remove(&output.presentation_index);
886 self.in_flight_since_reset
887 .remove(&output.presentation_index);
888 self.published_since_reset.insert(output.presentation_index);
898 if !self
899 .decode_position_by_presentation
900 .contains_key(&output.presentation_index)
901 {
902 return Err(Error::new(
903 ErrorKind::MalformedMedia,
904 "decoder produced an unindexed presentation frame",
905 ));
906 }
907 if output.frame.dimensions != self.configuration.coded_dimensions
908 || output.frame.pixel_format != self.configuration.output_format
909 || output.frame.color_range != self.configuration.color_range
910 {
911 return Err(Error::new(
912 ErrorKind::MalformedMedia,
913 "decoder output does not match its normalized configuration",
914 ));
915 }
916 self.insert_cache(output.presentation_index, output.frame);
917 }
918 Ok(())
919 }
920
921 fn insert_cache(&mut self, index: FrameIndex, frame: VideoFrame) {
922 self.cache.insert(index, frame);
923 self.touch(index);
924 while self.cache.len() > self.limits.max_cached_frames as usize {
925 if let Some(evicted) = self.lru.pop_front() {
926 self.cache.remove(&evicted);
927 }
928 }
929 }
930
931 fn touch(&mut self, index: FrameIndex) {
932 self.lru.retain(|candidate| *candidate != index);
933 self.lru.push_back(index);
934 }
935}
936
937fn capability_error(capability: CodecSupport) -> Error {
938 let (kind, message) = match capability {
939 CodecSupport::UnsupportedCodec => (
940 ErrorKind::Unsupported,
941 "decoder does not support the requested codec".into(),
942 ),
943 CodecSupport::UnsupportedProfile => (
944 ErrorKind::Unsupported,
945 "decoder does not support the requested profile".into(),
946 ),
947 CodecSupport::InvalidConfiguration { reason } => (ErrorKind::InvalidInput, reason),
948 CodecSupport::HardwareUnavailable => (
949 ErrorKind::Unsupported,
950 "requested hardware decoder is unavailable".into(),
951 ),
952 CodecSupport::Supported { .. } => (
953 ErrorKind::Internal,
954 "decoder capability changed unexpectedly".into(),
955 ),
956 };
957 Error::new(kind, message)
958}
959
960pub fn uncompressed_video_decoder_factory() -> impl VideoDecoderFactory {
965 UncompressedVideoDecoderFactory
966}
967
968struct UncompressedVideoDecoderFactory;
969
970impl VideoDecoderFactory for UncompressedVideoDecoderFactory {
971 fn capability(&self, configuration: &VideoDecoderConfig) -> CodecSupport {
972 if configuration.codec != Codec::UncompressedVideo {
973 return CodecSupport::UnsupportedCodec;
974 }
975 if configuration.profile != CodecProfile::UncompressedGray8 {
976 return CodecSupport::UnsupportedProfile;
977 }
978 if configuration.output_format != PixelFormat::Gray8
979 || !configuration.configuration.is_empty()
980 {
981 return CodecSupport::InvalidConfiguration {
982 reason: "uncompressed Gray8 requires Gray8 output and no configuration bytes"
983 .into(),
984 };
985 }
986 if configuration.hardware == HardwarePreference::Require {
987 return CodecSupport::HardwareUnavailable;
988 }
989 CodecSupport::Supported {
990 implementation: CodecImplementation::Software,
991 }
992 }
993
994 fn create(
995 &self,
996 configuration: &VideoDecoderConfig,
997 limits: &Limits,
998 ) -> Result<Box<dyn VideoDecoder>> {
999 let capability = self.capability(configuration);
1000 if !capability.is_supported() {
1001 return Err(capability_error(capability));
1002 }
1003 if configuration.coded_dimensions.width > limits.max_width
1004 || configuration.coded_dimensions.height > limits.max_height
1005 {
1006 return Err(Error::new(
1007 ErrorKind::ResourceLimit,
1008 "decoded dimensions exceed the configured limits",
1009 ));
1010 }
1011 let required = u64::from(configuration.coded_dimensions.width)
1012 .checked_mul(u64::from(configuration.coded_dimensions.height))
1013 .ok_or_else(|| Error::new(ErrorKind::ResourceLimit, "decoded frame size overflow"))?;
1014 if required > limits.max_allocation_bytes {
1015 return Err(Error::new(
1016 ErrorKind::ResourceLimit,
1017 "decoded frame exceeds the configured allocation limit",
1018 ));
1019 }
1020 Ok(Box::new(UncompressedVideoDecoder {
1021 configuration: configuration.clone(),
1022 limits: *limits,
1023 }))
1024 }
1025}
1026
1027struct UncompressedVideoDecoder {
1028 configuration: VideoDecoderConfig,
1029 limits: Limits,
1030}
1031
1032impl VideoDecoder for UncompressedVideoDecoder {
1033 fn submit(
1034 &mut self,
1035 sample: &EncodedVideoSample,
1036 cancellation: &CancellationToken,
1037 ) -> Result<Vec<DecodedVideoFrame>> {
1038 cancellation.check()?;
1039 let width = usize::try_from(self.configuration.coded_dimensions.width)
1040 .map_err(|_| Error::new(ErrorKind::ResourceLimit, "decoded width is too large"))?;
1041 let height = usize::try_from(self.configuration.coded_dimensions.height)
1042 .map_err(|_| Error::new(ErrorKind::ResourceLimit, "decoded height is too large"))?;
1043 let length = width
1044 .checked_mul(height)
1045 .ok_or_else(|| Error::new(ErrorKind::ResourceLimit, "decoded frame size overflow"))?;
1046 if sample.data.len() != length {
1047 return Err(Error::new(
1048 ErrorKind::MalformedMedia,
1049 "uncompressed Gray8 packet size does not match coded dimensions",
1050 ));
1051 }
1052 let frame = VideoFrame::new(
1053 self.configuration.coded_dimensions,
1054 PixelFormat::Gray8,
1055 self.configuration.color_range,
1056 vec![Plane {
1057 data: sample.data.clone(),
1058 stride: width,
1059 }],
1060 &self.limits,
1061 )?;
1062 Ok(vec![DecodedVideoFrame {
1063 presentation_index: sample.presentation_index,
1064 frame,
1065 }])
1066 }
1067
1068 fn drain(&mut self, cancellation: &CancellationToken) -> Result<Vec<DecodedVideoFrame>> {
1069 cancellation.check()?;
1070 Ok(Vec::new())
1071 }
1072
1073 fn reset(&mut self) -> Result<()> {
1074 Ok(())
1075 }
1076}
1077
1078#[cfg(test)]
1079mod tests {
1080 use super::*;
1081 use std::sync::Mutex;
1082
1083 fn config() -> VideoDecoderConfig {
1084 VideoDecoderConfig {
1085 codec: Codec::UncompressedVideo,
1086 profile: CodecProfile::UncompressedGray8,
1087 coded_dimensions: VideoDimensions::new(1, 1, &Limits::default()).unwrap(),
1088 output_format: PixelFormat::Gray8,
1089 color_range: ColorRange::Full,
1090 hardware: HardwarePreference::Avoid,
1091 configuration: Vec::new(),
1092 }
1093 }
1094
1095 fn sample(index: u64, value: u8, random_access: bool) -> EncodedVideoSample {
1096 EncodedVideoSample {
1097 presentation_index: FrameIndex(index),
1098 random_access,
1099 data: vec![value],
1100 }
1101 }
1102
1103 fn value(frame: &VideoFrame) -> u8 {
1104 frame.planes[0].data[0]
1105 }
1106
1107 fn single_group_reader(frames: u64) -> ExactFrameReader {
1111 let samples = (0..frames)
1112 .map(|index| sample(index, (index % 251) as u8, index == 0))
1113 .collect();
1114 ExactFrameReader::new(
1115 &uncompressed_video_decoder_factory(),
1116 config(),
1117 samples,
1118 Limits::default(),
1119 )
1120 .unwrap()
1121 }
1122
1123 struct StridedPreviews {
1127 stride: u64,
1128 frames: u64,
1129 }
1130
1131 impl SeekPreviewSource for StridedPreviews {
1132 fn nearest_at(&self, frame: FrameIndex) -> Option<(FrameIndex, VideoFrame)> {
1133 if self.frames == 0 {
1134 return None;
1135 }
1136 let at = (frame.0 / self.stride * self.stride).min(self.frames - 1);
1137 let picture = VideoFrame::new(
1138 config().coded_dimensions,
1139 PixelFormat::Gray8,
1140 ColorRange::Full,
1141 vec![Plane {
1142 data: vec![(at % 251) as u8],
1143 stride: 1,
1144 }],
1145 &Limits::default(),
1146 )
1147 .ok()?;
1148 Some((FrameIndex(at), picture))
1149 }
1150 }
1151
1152 #[test]
1156 fn a_seek_with_nothing_decoded_is_pending_and_submits_no_samples() {
1157 let mut reader = single_group_reader(512);
1158 assert_eq!(reader.seek(FrameIndex(511)), Seek::Pending);
1159 assert_eq!(reader.statistics().samples_submitted, 0);
1160 assert_eq!(reader.statistics().resets, 0);
1161 }
1162
1163 #[test]
1165 fn a_seek_into_the_cache_answers_exactly() {
1166 let mut reader = single_group_reader(64);
1167 let cancellation = CancellationToken::new();
1168 reader.get(FrameIndex(40), &cancellation).unwrap();
1169 let submitted = reader.statistics().samples_submitted;
1170 let Seek::Exact(frame) = reader.seek(FrameIndex(40)) else {
1171 panic!("a cached frame seeks exactly");
1172 };
1173 assert_eq!(value(&frame), 40);
1174 assert_eq!(
1175 reader.statistics().samples_submitted,
1176 submitted,
1177 "an exact seek decodes nothing"
1178 );
1179 }
1180
1181 #[test]
1185 fn a_seek_anywhere_on_a_single_group_track_decodes_nothing() {
1186 let mut reader = single_group_reader(512);
1187 reader.set_seek_previews(Some(Arc::new(StridedPreviews {
1188 stride: 12,
1189 frames: 512,
1190 })));
1191 for target in [0_u64, 1, 255, 400, 511] {
1192 match reader.seek(FrameIndex(target)) {
1193 Seek::Preview { frame, .. } => assert!(
1194 frame.0.abs_diff(target) < 12,
1195 "frame {target} answered by {frame:?}, further than one stride away"
1196 ),
1197 Seek::Exact(_) => {}
1198 Seek::Pending => panic!("frame {target} had no answer"),
1199 }
1200 }
1201 assert_eq!(
1202 reader.statistics().samples_submitted,
1203 0,
1204 "seeking is not decoding"
1205 );
1206 assert_eq!(reader.statistics().resets, 0);
1207 }
1208
1209 #[test]
1213 fn a_seek_to_any_position_answers_inside_the_latency_budget() {
1214 let frames = 768;
1215 let mut reader = single_group_reader(frames);
1216 reader.set_seek_previews(Some(Arc::new(StridedPreviews { stride: 12, frames })));
1217 let mut worst = Duration::ZERO;
1218 for step in 0..=100_u64 {
1219 let target = FrameIndex(step * (frames - 1) / 100);
1220 let started = std::time::Instant::now();
1221 let answer = reader.seek(target);
1222 worst = worst.max(started.elapsed());
1223 assert_ne!(answer, Seek::Pending, "frame {} had no answer", target.0);
1224 }
1225 assert!(
1226 worst < SEEK_LATENCY_BUDGET,
1227 "the worst seek took {worst:?}, over the {SEEK_LATENCY_BUDGET:?} budget"
1228 );
1229 }
1230
1231 #[test]
1234 fn detaching_the_preview_source_leaves_only_the_cache() {
1235 let mut reader = single_group_reader(64);
1236 reader.set_seek_previews(Some(Arc::new(StridedPreviews {
1237 stride: 12,
1238 frames: 64,
1239 })));
1240 assert!(matches!(reader.seek(FrameIndex(63)), Seek::Preview { .. }));
1241 reader.set_seek_previews(None);
1242 assert_eq!(reader.seek(FrameIndex(63)), Seek::Pending);
1243 }
1244
1245 #[test]
1250 fn a_leading_picture_is_decoded_from_the_random_access_point_before_it() {
1251 let samples = vec![
1252 sample(0, 10, true),
1253 sample(1, 11, false),
1254 sample(2, 12, false),
1255 sample(4, 14, true),
1256 sample(3, 13, false),
1257 sample(5, 15, false),
1258 ];
1259 let mut reader = ExactFrameReader::new(
1260 &uncompressed_video_decoder_factory(),
1261 config(),
1262 samples.clone(),
1263 Limits::default(),
1264 )
1265 .unwrap();
1266 let cancellation = CancellationToken::new();
1267 assert_eq!(
1268 value(&reader.get(FrameIndex(3), &cancellation).unwrap()),
1269 13
1270 );
1271 assert_eq!(reader.statistics().samples_submitted, 5);
1272
1273 let mut reader = ExactFrameReader::new(
1274 &uncompressed_video_decoder_factory(),
1275 config(),
1276 samples,
1277 Limits::default(),
1278 )
1279 .unwrap();
1280 assert_eq!(
1281 value(&reader.get(FrameIndex(5), &cancellation).unwrap()),
1282 15
1283 );
1284 assert_eq!(reader.statistics().samples_submitted, 3);
1285 }
1286
1287 #[test]
1288 fn capability_distinguishes_codec_profile_configuration_and_hardware() {
1289 let factory = uncompressed_video_decoder_factory();
1290 assert!(factory.capability(&config()).is_supported());
1291 let mut candidate = config();
1292 candidate.codec = Codec::Av1;
1293 assert_eq!(
1294 factory.capability(&candidate),
1295 CodecSupport::UnsupportedCodec
1296 );
1297 candidate = config();
1298 candidate.profile = CodecProfile::Av1Main;
1299 assert_eq!(
1300 factory.capability(&candidate),
1301 CodecSupport::UnsupportedProfile
1302 );
1303 candidate = config();
1304 candidate.output_format = PixelFormat::Rgba8;
1305 assert!(matches!(
1306 factory.capability(&candidate),
1307 CodecSupport::InvalidConfiguration { .. }
1308 ));
1309 candidate = config();
1310 candidate.hardware = HardwarePreference::Require;
1311 assert_eq!(
1312 factory.capability(&candidate),
1313 CodecSupport::HardwareUnavailable
1314 );
1315
1316 candidate = config();
1317 candidate.output_format = PixelFormat::Rgba8;
1318 assert_eq!(
1319 factory
1320 .create(&candidate, &Limits::default())
1321 .err()
1322 .unwrap()
1323 .kind(),
1324 ErrorKind::InvalidInput
1325 );
1326
1327 let restrictive = Limits {
1328 max_width: 0,
1329 ..Limits::default()
1330 };
1331 assert_eq!(
1332 factory
1333 .create(&config(), &restrictive)
1334 .err()
1335 .unwrap()
1336 .kind(),
1337 ErrorKind::ResourceLimit
1338 );
1339 }
1340
1341 #[test]
1345 fn a_request_before_the_open_session_start_resets_the_decoder() {
1346 let samples = (0..4)
1347 .map(|index| sample(index, index as u8 + 10, true))
1348 .collect();
1349 let mut reader = ExactFrameReader::new(
1350 &uncompressed_video_decoder_factory(),
1351 config(),
1352 samples,
1353 Limits {
1354 max_cached_frames: 1,
1355 ..Limits::default()
1356 },
1357 )
1358 .unwrap();
1359 let cancellation = CancellationToken::new();
1360 assert_eq!(
1361 value(&reader.get(FrameIndex(2), &cancellation).unwrap()),
1362 12
1363 );
1364 assert_eq!(
1365 value(&reader.get(FrameIndex(0), &cancellation).unwrap()),
1366 10
1367 );
1368 assert_eq!(reader.statistics().resets, 2);
1369 }
1370
1371 #[test]
1372 fn conformance_vector_reorders_exact_frames_and_reuses_bounded_state() {
1373 let samples = vec![
1374 sample(0, 10, true),
1375 sample(3, 40, false),
1376 sample(1, 20, false),
1377 sample(2, 30, false),
1378 sample(4, 50, true),
1379 sample(6, 70, false),
1380 sample(5, 60, false),
1381 ];
1382 let factory = uncompressed_video_decoder_factory();
1383 let mut reader = ExactFrameReader::new(
1384 &factory,
1385 config(),
1386 samples,
1387 Limits {
1388 max_cached_frames: 3,
1389 ..Limits::default()
1390 },
1391 )
1392 .unwrap();
1393 let cancellation = CancellationToken::new();
1394 assert_eq!(
1395 value(&reader.get(FrameIndex(1), &cancellation).unwrap()),
1396 20
1397 );
1398 let first = reader.statistics();
1399 assert_eq!(
1400 value(&reader.get(FrameIndex(2), &cancellation).unwrap()),
1401 30
1402 );
1403 assert_eq!(reader.statistics().resets, first.resets);
1404 assert_eq!(
1405 value(&reader.get(FrameIndex(3), &cancellation).unwrap()),
1406 40
1407 );
1408 assert!(reader.statistics().cache_hits > 0);
1409 assert_eq!(
1410 value(&reader.get(FrameIndex(5), &cancellation).unwrap()),
1411 60
1412 );
1413 assert_eq!(
1414 value(&reader.get(FrameIndex(0), &cancellation).unwrap()),
1415 10
1416 );
1417 assert!(reader.statistics().resets > first.resets);
1418 assert!(reader.cached_frames() <= 3);
1419 }
1420
1421 struct HintFactory {
1424 wanted: Arc<Mutex<Vec<(u64, bool)>>>,
1425 }
1426
1427 struct HintDecoder {
1428 configuration: VideoDecoderConfig,
1429 limits: Limits,
1430 wanted: Arc<Mutex<Vec<(u64, bool)>>>,
1431 output_wanted: bool,
1432 }
1433
1434 impl VideoDecoderFactory for HintFactory {
1435 fn capability(&self, _: &VideoDecoderConfig) -> CodecSupport {
1436 CodecSupport::Supported {
1437 implementation: CodecImplementation::Software,
1438 }
1439 }
1440
1441 fn create(
1442 &self,
1443 configuration: &VideoDecoderConfig,
1444 limits: &Limits,
1445 ) -> Result<Box<dyn VideoDecoder>> {
1446 Ok(Box::new(HintDecoder {
1447 configuration: configuration.clone(),
1448 limits: *limits,
1449 wanted: Arc::clone(&self.wanted),
1450 output_wanted: true,
1451 }))
1452 }
1453 }
1454
1455 impl VideoDecoder for HintDecoder {
1456 fn submit(
1457 &mut self,
1458 sample: &EncodedVideoSample,
1459 cancellation: &CancellationToken,
1460 ) -> Result<Vec<DecodedVideoFrame>> {
1461 cancellation.check()?;
1462 self.wanted
1463 .lock()
1464 .expect("hint log poisoned")
1465 .push((sample.presentation_index.0, self.output_wanted));
1466 if !self.output_wanted {
1467 return Ok(Vec::new());
1468 }
1469 Ok(vec![DecodedVideoFrame {
1470 presentation_index: sample.presentation_index,
1471 frame: VideoFrame::new(
1472 self.configuration.coded_dimensions,
1473 PixelFormat::Gray8,
1474 ColorRange::Full,
1475 vec![Plane {
1476 data: sample.data.clone(),
1477 stride: 1,
1478 }],
1479 &self.limits,
1480 )?,
1481 }])
1482 }
1483
1484 fn drain(&mut self, _: &CancellationToken) -> Result<Vec<DecodedVideoFrame>> {
1485 Ok(Vec::new())
1486 }
1487
1488 fn reset(&mut self) -> Result<()> {
1489 Ok(())
1490 }
1491
1492 fn set_output_wanted(&mut self, wanted: bool) {
1493 self.output_wanted = wanted;
1494 }
1495 }
1496
1497 struct ReplayFactory;
1502
1503 struct ReplayDecoder {
1504 configuration: VideoDecoderConfig,
1505 limits: Limits,
1506 previous: Option<(FrameIndex, u8)>,
1507 }
1508
1509 impl ReplayDecoder {
1510 fn produce(&self, presentation_index: FrameIndex, value: u8) -> Result<DecodedVideoFrame> {
1511 Ok(DecodedVideoFrame {
1512 presentation_index,
1513 frame: VideoFrame::new(
1514 self.configuration.coded_dimensions,
1515 PixelFormat::Gray8,
1516 ColorRange::Full,
1517 vec![Plane {
1518 data: vec![value],
1519 stride: 1,
1520 }],
1521 &self.limits,
1522 )?,
1523 })
1524 }
1525 }
1526
1527 impl VideoDecoderFactory for ReplayFactory {
1528 fn capability(&self, _: &VideoDecoderConfig) -> CodecSupport {
1529 CodecSupport::Supported {
1530 implementation: CodecImplementation::Software,
1531 }
1532 }
1533
1534 fn create(
1535 &self,
1536 configuration: &VideoDecoderConfig,
1537 limits: &Limits,
1538 ) -> Result<Box<dyn VideoDecoder>> {
1539 Ok(Box::new(ReplayDecoder {
1540 configuration: configuration.clone(),
1541 limits: *limits,
1542 previous: None,
1543 }))
1544 }
1545 }
1546
1547 impl VideoDecoder for ReplayDecoder {
1548 fn submit(
1549 &mut self,
1550 sample: &EncodedVideoSample,
1551 cancellation: &CancellationToken,
1552 ) -> Result<Vec<DecodedVideoFrame>> {
1553 cancellation.check()?;
1554 let value = sample.data[0];
1555 let mut outputs = Vec::new();
1556 if let Some((index, previous)) = self.previous {
1557 outputs.push(self.produce(index, previous)?);
1558 }
1559 outputs.push(self.produce(sample.presentation_index, value)?);
1560 self.previous = Some((sample.presentation_index, value));
1561 Ok(outputs)
1562 }
1563
1564 fn drain(&mut self, _: &CancellationToken) -> Result<Vec<DecodedVideoFrame>> {
1565 Ok(Vec::new())
1566 }
1567
1568 fn reset(&mut self) -> Result<()> {
1569 self.previous = None;
1570 Ok(())
1571 }
1572 }
1573
1574 #[test]
1578 fn a_frame_the_decoder_emits_twice_is_cached_again_rather_than_refused() {
1579 let mut reader = ExactFrameReader::new(
1580 &ReplayFactory,
1581 config(),
1582 single_group_of_pictures(),
1583 Limits::default(),
1584 )
1585 .unwrap();
1586 let cancellation = CancellationToken::new();
1587
1588 assert_eq!(
1591 value(&reader.get(FrameIndex(3), &cancellation).unwrap()),
1592 30
1593 );
1594 assert_eq!(
1595 value(&reader.get(FrameIndex(6), &cancellation).unwrap()),
1596 60
1597 );
1598
1599 let resets = reader.statistics().resets;
1602 let hits = reader.statistics().cache_hits;
1603 assert_eq!(
1604 value(&reader.get(FrameIndex(5), &cancellation).unwrap()),
1605 50
1606 );
1607 assert_eq!(reader.statistics().resets, resets);
1608 assert!(reader.statistics().cache_hits > hits);
1609 }
1610
1611 fn single_group_of_pictures() -> Vec<EncodedVideoSample> {
1614 (0..10)
1615 .map(|index| sample(index, (index * 10) as u8, index == 0))
1616 .collect()
1617 }
1618
1619 fn small_cache() -> Limits {
1622 Limits {
1623 max_cached_frames: 2,
1624 ..Limits::default()
1625 }
1626 }
1627
1628 #[test]
1629 fn walking_to_a_distant_frame_does_not_ask_for_the_pictures_it_passes() {
1630 let log = Arc::new(Mutex::new(Vec::new()));
1631 let factory = HintFactory {
1632 wanted: Arc::clone(&log),
1633 };
1634 let mut reader = ExactFrameReader::new(
1635 &factory,
1636 config(),
1637 single_group_of_pictures(),
1638 small_cache(),
1639 )
1640 .unwrap();
1641 let cancellation = CancellationToken::new();
1642
1643 assert_eq!(
1644 value(&reader.get(FrameIndex(8), &cancellation).unwrap()),
1645 80
1646 );
1647 assert_eq!(
1648 *log.lock().unwrap(),
1649 vec![
1650 (0, false),
1651 (1, false),
1652 (2, false),
1653 (3, false),
1654 (4, false),
1655 (5, false),
1656 (6, true),
1657 (7, true),
1658 (8, true),
1659 ],
1660 "only the target and the tail the cache can hold are produced"
1661 );
1662 let statistics = reader.statistics();
1663 assert_eq!(statistics.samples_submitted, 9);
1664 assert_eq!(statistics.samples_skipped, 6);
1665 assert_eq!(statistics.resets, 1, "the walk itself is one decode");
1666 }
1667
1668 #[test]
1669 fn a_frame_the_walk_went_past_is_decoded_again_rather_than_reported_missing() {
1670 let log = Arc::new(Mutex::new(Vec::new()));
1671 let factory = HintFactory {
1672 wanted: Arc::clone(&log),
1673 };
1674 let mut reader = ExactFrameReader::new(
1675 &factory,
1676 config(),
1677 single_group_of_pictures(),
1678 small_cache(),
1679 )
1680 .unwrap();
1681 let cancellation = CancellationToken::new();
1682 reader.get(FrameIndex(8), &cancellation).unwrap();
1683 let resets = reader.statistics().resets;
1684
1685 assert_eq!(
1689 value(&reader.get(FrameIndex(4), &cancellation).unwrap()),
1690 40
1691 );
1692 assert_eq!(reader.statistics().resets, resets + 1);
1693 assert_eq!(
1695 value(&reader.get(FrameIndex(4), &cancellation).unwrap()),
1696 40
1697 );
1698 assert_eq!(reader.statistics().resets, resets + 1);
1699 }
1700
1701 #[test]
1702 fn a_reordered_frame_at_or_past_the_target_is_kept_for_the_request_that_follows() {
1703 let samples = vec![
1707 sample(0, 0, true),
1708 sample(4, 40, false),
1709 sample(2, 20, false),
1710 sample(1, 10, false),
1711 sample(3, 30, false),
1712 sample(8, 80, false),
1713 sample(6, 60, false),
1714 sample(5, 50, false),
1715 sample(7, 70, false),
1716 ];
1717 let log = Arc::new(Mutex::new(Vec::new()));
1718 let factory = HintFactory {
1719 wanted: Arc::clone(&log),
1720 };
1721 let mut reader = ExactFrameReader::new(&factory, config(), samples, small_cache()).unwrap();
1722 let cancellation = CancellationToken::new();
1723
1724 assert_eq!(
1725 value(&reader.get(FrameIndex(4), &cancellation).unwrap()),
1726 40
1727 );
1728 let resets = reader.statistics().resets;
1729 for (index, expected) in [(1_u64, 10_u8), (2, 20), (3, 30)] {
1730 assert_eq!(
1731 value(&reader.get(FrameIndex(index), &cancellation).unwrap()),
1732 expected,
1733 "frame {index} is still available"
1734 );
1735 }
1736 assert_eq!(
1737 reader.statistics().resets,
1738 resets,
1739 "a frame after the target in decode order is not skipped"
1740 );
1741 assert_eq!(
1742 reader.statistics().samples_skipped,
1743 1,
1744 "only frame 0, the random-access point the walk starts from, is behind frame 4"
1745 );
1746 }
1747
1748 #[test]
1749 fn a_walk_leaves_the_frames_behind_its_target_cached_so_stepping_back_is_free() {
1750 let log = Arc::new(Mutex::new(Vec::new()));
1754 let factory = HintFactory {
1755 wanted: Arc::clone(&log),
1756 };
1757 let samples: Vec<_> = (0..40)
1758 .map(|index| sample(index, index as u8, index == 0))
1759 .collect();
1760 let mut reader = ExactFrameReader::new(
1761 &factory,
1762 config(),
1763 samples,
1764 Limits {
1765 max_cached_frames: 4,
1766 ..Limits::default()
1767 },
1768 )
1769 .unwrap();
1770 let cancellation = CancellationToken::new();
1771 reader.get(FrameIndex(32), &cancellation).unwrap();
1772 let after_walk = reader.statistics();
1773 assert_eq!(after_walk.samples_skipped, 28, "frames 0 to 27 are skipped");
1774
1775 for index in [31_u64, 30, 29] {
1776 assert_eq!(
1777 value(&reader.get(FrameIndex(index), &cancellation).unwrap()),
1778 index as u8
1779 );
1780 }
1781 assert_eq!(
1782 reader.statistics().resets,
1783 after_walk.resets,
1784 "the tail the walk kept is in the cache, so stepping back decodes nothing"
1785 );
1786 assert_eq!(
1787 reader.statistics().samples_submitted,
1788 after_walk.samples_submitted
1789 );
1790 }
1791
1792 #[test]
1793 fn a_step_converts_its_own_picture_and_no_tail_behind_it() {
1794 let log = Arc::new(Mutex::new(Vec::new()));
1799 let factory = HintFactory {
1800 wanted: Arc::clone(&log),
1801 };
1802 let samples: Vec<_> = (0..40)
1803 .map(|index| sample(index, index as u8, index == 0))
1804 .collect();
1805 let mut reader = ExactFrameReader::new(
1806 &factory,
1807 config(),
1808 samples,
1809 Limits {
1810 max_cached_frames: 4,
1811 ..Limits::default()
1812 },
1813 )
1814 .unwrap();
1815 let cancellation = CancellationToken::new();
1816
1817 assert_eq!(
1818 value(&reader.get_step(FrameIndex(32), &cancellation).unwrap()),
1819 32
1820 );
1821 let after_step = reader.statistics();
1822 assert_eq!(
1823 after_step.samples_skipped, 32,
1824 "frames 0 to 31 are all passed through, tail included"
1825 );
1826 assert_eq!(
1827 reader.cached_frames(),
1828 1,
1829 "only the picture the step asked for was converted"
1830 );
1831 }
1832
1833 #[test]
1834 fn a_walk_of_steps_converts_what_it_asks_for_rather_than_what_it_passes() {
1835 let log = Arc::new(Mutex::new(Vec::new()));
1840 let factory = HintFactory {
1841 wanted: Arc::clone(&log),
1842 };
1843 let samples: Vec<_> = (0..64)
1844 .map(|index| sample(index, index as u8, index == 0))
1845 .collect();
1846 let mut reader = ExactFrameReader::new(
1847 &factory,
1848 config(),
1849 samples,
1850 Limits {
1851 max_cached_frames: 8,
1852 ..Limits::default()
1853 },
1854 )
1855 .unwrap();
1856 let cancellation = CancellationToken::new();
1857
1858 let steps: Vec<u64> = (4..60).step_by(4).collect();
1860 for step in &steps {
1861 reader.get_step(FrameIndex(*step), &cancellation).unwrap();
1862 }
1863 let converted = reader.statistics().samples_submitted - reader.statistics().samples_skipped;
1864 assert_eq!(
1865 converted,
1866 steps.len() as u64,
1867 "one conversion per published step, not one per frame passed"
1868 );
1869 assert_eq!(
1870 reader.statistics().resets,
1871 1,
1872 "only the cold start resets; a forward walk of steps never starts over again"
1873 );
1874 }
1875
1876 #[test]
1877 fn a_step_walk_that_ends_in_a_destination_still_leaves_a_cache_tail_to_step_back_through() {
1878 let log = Arc::new(Mutex::new(Vec::new()));
1882 let factory = HintFactory {
1883 wanted: Arc::clone(&log),
1884 };
1885 let samples: Vec<_> = (0..40)
1886 .map(|index| sample(index, index as u8, index == 0))
1887 .collect();
1888 let mut reader = ExactFrameReader::new(
1889 &factory,
1890 config(),
1891 samples,
1892 Limits {
1893 max_cached_frames: 4,
1894 ..Limits::default()
1895 },
1896 )
1897 .unwrap();
1898 let cancellation = CancellationToken::new();
1899
1900 for step in [8_u64, 16, 24] {
1901 reader.get_step(FrameIndex(step), &cancellation).unwrap();
1902 }
1903 reader.get(FrameIndex(32), &cancellation).unwrap();
1904 let after_walk = reader.statistics();
1905
1906 for index in [31_u64, 30, 29] {
1907 assert_eq!(
1908 value(&reader.get(FrameIndex(index), &cancellation).unwrap()),
1909 index as u8
1910 );
1911 }
1912 assert_eq!(
1913 reader.statistics().resets,
1914 after_walk.resets,
1915 "the destination kept its tail, so stepping back decodes nothing"
1916 );
1917 assert_eq!(
1918 reader.statistics().samples_submitted,
1919 after_walk.samples_submitted
1920 );
1921 }
1922
1923 struct LateFactory;
1926
1927 struct LateDecoder {
1928 configuration: VideoDecoderConfig,
1929 limits: Limits,
1930 held: Option<EncodedVideoSample>,
1931 output_wanted: bool,
1932 }
1933
1934 impl VideoDecoderFactory for LateFactory {
1935 fn capability(&self, _: &VideoDecoderConfig) -> CodecSupport {
1936 CodecSupport::Supported {
1937 implementation: CodecImplementation::Software,
1938 }
1939 }
1940
1941 fn create(
1942 &self,
1943 configuration: &VideoDecoderConfig,
1944 limits: &Limits,
1945 ) -> Result<Box<dyn VideoDecoder>> {
1946 Ok(Box::new(LateDecoder {
1947 configuration: configuration.clone(),
1948 limits: *limits,
1949 held: None,
1950 output_wanted: true,
1951 }))
1952 }
1953 }
1954
1955 impl LateDecoder {
1956 fn release(&self, sample: EncodedVideoSample) -> Result<Vec<DecodedVideoFrame>> {
1957 if !self.output_wanted {
1958 return Ok(Vec::new());
1959 }
1960 Ok(vec![DecodedVideoFrame {
1961 presentation_index: sample.presentation_index,
1962 frame: VideoFrame::new(
1963 self.configuration.coded_dimensions,
1964 PixelFormat::Gray8,
1965 ColorRange::Full,
1966 vec![Plane {
1967 data: sample.data,
1968 stride: 1,
1969 }],
1970 &self.limits,
1971 )?,
1972 }])
1973 }
1974 }
1975
1976 impl VideoDecoder for LateDecoder {
1977 fn submit(
1978 &mut self,
1979 sample: &EncodedVideoSample,
1980 cancellation: &CancellationToken,
1981 ) -> Result<Vec<DecodedVideoFrame>> {
1982 cancellation.check()?;
1983 match self.held.replace(sample.clone()) {
1984 Some(previous) => self.release(previous),
1985 None => Ok(Vec::new()),
1986 }
1987 }
1988
1989 fn drain(&mut self, _: &CancellationToken) -> Result<Vec<DecodedVideoFrame>> {
1990 match self.held.take() {
1991 Some(held) => self.release(held),
1992 None => Ok(Vec::new()),
1993 }
1994 }
1995
1996 fn reset(&mut self) -> Result<()> {
1997 self.held = None;
1998 Ok(())
1999 }
2000
2001 fn set_output_wanted(&mut self, wanted: bool) {
2002 self.output_wanted = wanted;
2003 }
2004 }
2005
2006 #[test]
2007 fn a_frame_still_inside_a_reordering_decoder_when_output_stops_is_written_off_too() {
2008 let mut reader = ExactFrameReader::new(
2013 &LateFactory,
2014 config(),
2015 single_group_of_pictures(),
2016 small_cache(),
2017 )
2018 .unwrap();
2019 let cancellation = CancellationToken::new();
2020
2021 assert_eq!(
2025 value(&reader.get(FrameIndex(4), &cancellation).unwrap()),
2026 40
2027 );
2028 assert_eq!(
2029 value(&reader.get(FrameIndex(9), &cancellation).unwrap()),
2030 90
2031 );
2032 assert_eq!(
2033 value(&reader.get(FrameIndex(5), &cancellation).unwrap()),
2034 50,
2035 "the frame that was dropped in flight is decoded again, not reported missing"
2036 );
2037 }
2038
2039 #[test]
2040 fn a_decoder_that_ignores_the_hint_still_answers_every_frame() {
2041 let factory = uncompressed_video_decoder_factory();
2044 let mut reader = ExactFrameReader::new(
2045 &factory,
2046 config(),
2047 single_group_of_pictures(),
2048 small_cache(),
2049 )
2050 .unwrap();
2051 let cancellation = CancellationToken::new();
2052 reader.get(FrameIndex(8), &cancellation).unwrap();
2053 assert_eq!(reader.statistics().samples_skipped, 6);
2054 for index in 0..9_u64 {
2055 assert_eq!(
2056 value(&reader.get(FrameIndex(index), &cancellation).unwrap()),
2057 (index * 10) as u8,
2058 "frame {index} came back from the decoder that ignored the hint"
2059 );
2060 }
2061 }
2062
2063 #[test]
2064 fn malformed_packets_and_cancellation_are_reported() {
2065 let factory = uncompressed_video_decoder_factory();
2066 let mut reader = ExactFrameReader::new(
2067 &factory,
2068 config(),
2069 vec![EncodedVideoSample {
2070 presentation_index: FrameIndex(0),
2071 random_access: true,
2072 data: Vec::new(),
2073 }],
2074 Limits::default(),
2075 )
2076 .unwrap();
2077 assert_eq!(
2078 reader
2079 .get(FrameIndex(0), &CancellationToken::new())
2080 .unwrap_err()
2081 .kind(),
2082 ErrorKind::MalformedMedia
2083 );
2084 let cancelled = CancellationToken::new();
2085 cancelled.cancel();
2086 assert_eq!(
2087 reader.get(FrameIndex(0), &cancelled).unwrap_err().kind(),
2088 ErrorKind::Cancelled
2089 );
2090 }
2091
2092 #[test]
2093 fn random_access_decode_work_obeys_the_configured_bound() {
2094 let factory = uncompressed_video_decoder_factory();
2095 let mut reader = ExactFrameReader::new(
2096 &factory,
2097 config(),
2098 vec![sample(0, 1, true), sample(1, 2, false), sample(2, 3, false)],
2099 Limits {
2100 max_decode_samples_per_seek: 2,
2101 ..Limits::default()
2102 },
2103 )
2104 .unwrap();
2105 let error = reader
2106 .get(FrameIndex(2), &CancellationToken::new())
2107 .unwrap_err();
2108 assert_eq!(error.kind(), ErrorKind::ResourceLimit);
2109 assert_eq!(reader.statistics().samples_submitted, 2);
2110 }
2111
2112 struct DelayedFactory;
2113 impl VideoDecoderFactory for DelayedFactory {
2114 fn capability(&self, _: &VideoDecoderConfig) -> CodecSupport {
2115 CodecSupport::Supported {
2116 implementation: CodecImplementation::Software,
2117 }
2118 }
2119 fn create(
2120 &self,
2121 configuration: &VideoDecoderConfig,
2122 limits: &Limits,
2123 ) -> Result<Box<dyn VideoDecoder>> {
2124 Ok(Box::new(DelayedDecoder {
2125 configuration: configuration.clone(),
2126 limits: *limits,
2127 delayed: None,
2128 }))
2129 }
2130 }
2131 struct DelayedDecoder {
2132 configuration: VideoDecoderConfig,
2133 limits: Limits,
2134 delayed: Option<EncodedVideoSample>,
2135 }
2136 impl DelayedDecoder {
2137 fn decode(&self, sample: EncodedVideoSample) -> Result<DecodedVideoFrame> {
2138 Ok(DecodedVideoFrame {
2139 presentation_index: sample.presentation_index,
2140 frame: VideoFrame::new(
2141 self.configuration.coded_dimensions,
2142 PixelFormat::Gray8,
2143 ColorRange::Full,
2144 vec![Plane {
2145 data: sample.data,
2146 stride: 1,
2147 }],
2148 &self.limits,
2149 )?,
2150 })
2151 }
2152 }
2153 impl VideoDecoder for DelayedDecoder {
2154 fn submit(
2155 &mut self,
2156 sample: &EncodedVideoSample,
2157 cancellation: &CancellationToken,
2158 ) -> Result<Vec<DecodedVideoFrame>> {
2159 cancellation.check()?;
2160 let previous = self.delayed.replace(sample.clone());
2161 previous
2162 .map(|sample| self.decode(sample).map(|frame| vec![frame]))
2163 .unwrap_or(Ok(Vec::new()))
2164 }
2165 fn drain(&mut self, cancellation: &CancellationToken) -> Result<Vec<DecodedVideoFrame>> {
2166 cancellation.check()?;
2167 self.delayed
2168 .take()
2169 .map(|sample| self.decode(sample).map(|frame| vec![frame]))
2170 .unwrap_or(Ok(Vec::new()))
2171 }
2172 fn reset(&mut self) -> Result<()> {
2173 self.delayed = None;
2174 Ok(())
2175 }
2176 }
2177
2178 #[test]
2179 fn delayed_output_drains_and_reset_clears_state() {
2180 let mut reader = ExactFrameReader::new(
2181 &DelayedFactory,
2182 config(),
2183 vec![sample(0, 11, true), sample(1, 22, false)],
2184 Limits::default(),
2185 )
2186 .unwrap();
2187 let cancellation = CancellationToken::new();
2188 assert_eq!(
2189 value(&reader.get(FrameIndex(1), &cancellation).unwrap()),
2190 22
2191 );
2192 assert_eq!(reader.statistics().drains, 1);
2193 reader.reset().unwrap();
2194 assert_eq!(reader.cached_frames(), 0);
2195 assert_eq!(
2196 value(&reader.get(FrameIndex(0), &cancellation).unwrap()),
2197 11
2198 );
2199 }
2200
2201 #[test]
2202 fn sequential_playback_through_a_pipelined_decoder_does_not_reset_every_frame() {
2203 let mut reader = ExactFrameReader::new(
2209 &DelayedFactory,
2210 config(),
2211 vec![
2212 sample(0, 11, true),
2213 sample(1, 22, false),
2214 sample(2, 33, false),
2215 sample(3, 44, false),
2216 sample(4, 55, false),
2217 ],
2218 Limits::default(),
2219 )
2220 .unwrap();
2221 let cancellation = CancellationToken::new();
2222
2223 assert_eq!(
2224 value(&reader.get(FrameIndex(0), &cancellation).unwrap()),
2225 11
2226 );
2227 assert_eq!(reader.statistics().resets, 1);
2228
2229 for (index, expected) in [(1, 22_u8), (2, 33), (3, 44), (4, 55)] {
2230 let frame = reader.get(FrameIndex(index), &cancellation).unwrap();
2231 assert_eq!(value(&frame), expected);
2232 assert_eq!(
2233 reader.statistics().resets,
2234 1,
2235 "sequential forward playback must not reset once decoding has started"
2236 );
2237 }
2238 assert_eq!(reader.statistics().samples_submitted, 5);
2239 assert_eq!(reader.statistics().drains, 1);
2240 }
2241
2242 struct MalformedOutputFactory;
2243
2244 impl VideoDecoderFactory for MalformedOutputFactory {
2245 fn capability(&self, _: &VideoDecoderConfig) -> CodecSupport {
2246 CodecSupport::Supported {
2247 implementation: CodecImplementation::Software,
2248 }
2249 }
2250
2251 fn create(
2252 &self,
2253 configuration: &VideoDecoderConfig,
2254 limits: &Limits,
2255 ) -> Result<Box<dyn VideoDecoder>> {
2256 let factory = UncompressedVideoDecoderFactory;
2257 factory.create(configuration, limits).map(|decoder| {
2258 Box::new(MalformedOutputDecoder {
2259 decoder,
2260 previous: None,
2261 }) as Box<dyn VideoDecoder>
2262 })
2263 }
2264 }
2265
2266 struct MalformedOutputDecoder {
2267 decoder: Box<dyn VideoDecoder>,
2268 previous: Option<FrameIndex>,
2269 }
2270
2271 impl VideoDecoder for MalformedOutputDecoder {
2272 fn submit(
2273 &mut self,
2274 sample: &EncodedVideoSample,
2275 cancellation: &CancellationToken,
2276 ) -> Result<Vec<DecodedVideoFrame>> {
2277 let mut output = self.decoder.submit(sample, cancellation)?;
2278 if let (Some(previous), Some(frame)) = (self.previous, output.first_mut()) {
2279 frame.presentation_index = previous;
2280 }
2281 self.previous = Some(sample.presentation_index);
2282 Ok(output)
2283 }
2284
2285 fn drain(&mut self, cancellation: &CancellationToken) -> Result<Vec<DecodedVideoFrame>> {
2286 self.decoder.drain(cancellation)
2287 }
2288
2289 fn reset(&mut self) -> Result<()> {
2290 self.previous = None;
2291 self.decoder.reset()
2292 }
2293 }
2294
2295 #[test]
2296 fn malformed_backend_output_is_rejected() {
2297 let mut reader = ExactFrameReader::new(
2298 &MalformedOutputFactory,
2299 config(),
2300 vec![sample(0, 1, true), sample(1, 2, false)],
2301 Limits::default(),
2302 )
2303 .unwrap();
2304 let error = reader
2305 .get(FrameIndex(1), &CancellationToken::new())
2306 .unwrap_err();
2307 assert_eq!(error.kind(), ErrorKind::MalformedMedia);
2308 }
2309}