1use std::sync::Arc;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::{error::Error, fmt};
9
10use alpha2_time::{FrameRate, MediaTime};
11
12use crate::{ColorMetadata, SourceId};
13
14#[derive(Copy, Clone, Debug, Eq, PartialEq)]
16pub struct Rect {
17 pub x: u32,
19 pub y: u32,
21 pub width: u32,
23 pub height: u32,
25}
26
27#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
32pub enum Rotation {
33 #[default]
35 Deg0,
36 Deg90,
38 Deg180,
40 Deg270,
42}
43
44impl Rotation {
45 #[must_use]
47 pub const fn quarter_turns(self) -> u32 {
48 match self {
49 Self::Deg0 => 0,
50 Self::Deg90 => 1,
51 Self::Deg180 => 2,
52 Self::Deg270 => 3,
53 }
54 }
55
56 #[must_use]
58 pub const fn from_quarter_turns(quarter_turns: i32) -> Self {
59 match quarter_turns.rem_euclid(4) {
60 0 => Self::Deg0,
61 1 => Self::Deg90,
62 2 => Self::Deg180,
63 _ => Self::Deg270,
64 }
65 }
66
67 #[must_use]
69 pub const fn swaps_axes(self) -> bool {
70 matches!(self, Self::Deg90 | Self::Deg270)
71 }
72}
73
74#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)]
81pub struct Orientation {
82 pub rotation: Rotation,
84 pub mirror: bool,
86}
87
88impl Orientation {
89 pub const UPRIGHT: Self = Self {
91 rotation: Rotation::Deg0,
92 mirror: false,
93 };
94
95 #[must_use]
97 pub const fn rotated(rotation: Rotation) -> Self {
98 Self {
99 rotation,
100 mirror: false,
101 }
102 }
103
104 #[must_use]
106 pub const fn from_exif(value: u16) -> Option<Self> {
107 let (rotation, mirror) = match value {
108 1 => (Rotation::Deg0, false),
109 2 => (Rotation::Deg0, true),
110 3 => (Rotation::Deg180, false),
111 4 => (Rotation::Deg180, true),
112 5 => (Rotation::Deg270, true),
113 6 => (Rotation::Deg90, false),
114 7 => (Rotation::Deg90, true),
115 8 => (Rotation::Deg270, false),
116 _ => return None,
117 };
118 Some(Self { rotation, mirror })
119 }
120
121 #[must_use]
123 pub const fn is_upright(self) -> bool {
124 matches!(self.rotation, Rotation::Deg0) && !self.mirror
125 }
126
127 #[must_use]
129 pub const fn displayed_size(self, width: u32, height: u32) -> (u32, u32) {
130 if self.rotation.swaps_axes() {
131 (height, width)
132 } else {
133 (width, height)
134 }
135 }
136}
137
138#[derive(Copy, Clone, Debug, Eq, PartialEq)]
140pub struct SampleAspectRatio {
141 pub num: u32,
143 pub den: u32,
145}
146
147impl Default for SampleAspectRatio {
148 fn default() -> Self {
149 Self { num: 1, den: 1 }
150 }
151}
152
153impl SampleAspectRatio {
154 #[must_use]
157 pub fn new(num: u64, den: u64) -> Option<Self> {
158 if num == 0 || den == 0 {
159 return None;
160 }
161 let divisor = greatest_common_divisor(num, den);
162 Some(Self {
163 num: u32::try_from(num / divisor).ok()?,
164 den: u32::try_from(den / divisor).ok()?,
165 })
166 }
167
168 #[must_use]
175 pub fn of_display(visible: (u32, u32), displayed: (u32, u32)) -> Option<Self> {
176 let [
177 visible_width,
178 visible_height,
179 displayed_width,
180 displayed_height,
181 ] = [visible.0, visible.1, displayed.0, displayed.1].map(u128::from);
182 if visible_width == 0
183 || visible_height == 0
184 || displayed_width == 0
185 || displayed_height == 0
186 {
187 return None;
188 }
189 let slack = |shown: u128, stored: u128| u128::from(shown != stored);
191 let width_slack = slack(displayed_width, visible_width);
192 let height_slack = slack(displayed_height, visible_height);
193 let low = (
196 (2 * displayed_width - width_slack) * visible_height,
197 (2 * displayed_height + height_slack) * visible_width,
198 );
199 let high = (
200 (2 * displayed_width + width_slack) * visible_height,
201 (2 * displayed_height - height_slack) * visible_width,
202 );
203 let (num, den) = simplest_fraction_between(low, high)?;
204 Self::new(u64::try_from(num).ok()?, u64::try_from(den).ok()?)
205 }
206
207 #[must_use]
210 pub fn square_pixel_size(self, width: u32, height: u32) -> (u32, u32) {
211 let stretch = |side: u32, by: u32, of: u32| {
212 let scaled =
213 (u64::from(side) * u64::from(by) * 2 + u64::from(of)) / (2 * u64::from(of));
214 u32::try_from(scaled).unwrap_or(u32::MAX)
215 };
216 if self.num == 0 || self.den == 0 {
217 return (width, height);
218 }
219 match self.num.cmp(&self.den) {
220 std::cmp::Ordering::Greater => (stretch(width, self.num, self.den), height),
221 std::cmp::Ordering::Less => (width, stretch(height, self.den, self.num)),
222 std::cmp::Ordering::Equal => (width, height),
223 }
224 }
225}
226
227fn simplest_fraction_between(low: (u128, u128), high: (u128, u128)) -> Option<(u128, u128)> {
229 let ((low_num, low_den), (high_num, high_den)) = (low, high);
230 if low_den == 0 || high_den == 0 || low_num * high_den > high_num * low_den {
231 return None;
232 }
233 let whole = low_num / low_den;
234 if whole * low_den == low_num {
235 return Some((whole, 1));
236 }
237 if (whole + 1) * high_den <= high_num {
238 return Some((whole + 1, 1));
239 }
240 let (num, den) = simplest_fraction_between(
243 (high_den, high_num - whole * high_den),
244 (low_den, low_num - whole * low_den),
245 )?;
246 Some((whole * num + den, num))
247}
248
249fn greatest_common_divisor(mut a: u64, mut b: u64) -> u64 {
250 while b != 0 {
251 (a, b) = (b, a % b);
252 }
253 a
254}
255
256#[derive(Copy, Clone, Debug, Eq, PartialEq)]
262#[non_exhaustive]
263pub enum PixelFormat {
264 Yuv(YuvFormat),
266 Rgba8,
268 LinearRgbaF16,
272}
273
274#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
281pub struct YuvFormat {
282 pub chroma_shift_x: u8,
284 pub chroma_shift_y: u8,
286 pub bit_depth: u8,
288 pub interleaved_chroma: bool,
290 pub msb_aligned: bool,
293 pub alpha: bool,
295}
296
297impl YuvFormat {
298 pub const I420: Self = Self::planar(1, 1, 8);
300 pub const NV12: Self = Self {
302 interleaved_chroma: true,
303 ..Self::I420
304 };
305 pub const P010: Self = Self {
307 bit_depth: 10,
308 msb_aligned: true,
309 ..Self::NV12
310 };
311
312 #[must_use]
314 pub const fn planar(chroma_shift_x: u8, chroma_shift_y: u8, bit_depth: u8) -> Self {
315 Self {
316 chroma_shift_x,
317 chroma_shift_y,
318 bit_depth,
319 interleaved_chroma: false,
320 msb_aligned: false,
321 alpha: false,
322 }
323 }
324
325 #[must_use]
327 pub const fn with_alpha(self) -> Self {
328 Self {
329 alpha: true,
330 ..self
331 }
332 }
333
334 #[must_use]
337 pub const fn is_supported(self) -> bool {
338 self.bit_depth >= 8
339 && self.bit_depth <= 16
340 && self.chroma_shift_x <= 2
341 && self.chroma_shift_y <= 2
342 && (!self.msb_aligned || self.bit_depth > 8)
343 }
344
345 #[must_use]
347 pub const fn bytes_per_sample(self) -> u32 {
348 if self.bit_depth > 8 { 2 } else { 1 }
349 }
350
351 #[must_use]
353 pub const fn plane_count(self) -> usize {
354 self.color_plane_count() + if self.alpha { 1 } else { 0 }
355 }
356
357 #[must_use]
359 pub const fn color_plane_count(self) -> usize {
360 if self.interleaved_chroma { 2 } else { 3 }
361 }
362
363 #[must_use]
365 pub const fn chroma_size(self, width: u32, height: u32) -> (u32, u32) {
366 (
367 width.div_ceil(1 << self.chroma_shift_x),
368 height.div_ceil(1 << self.chroma_shift_y),
369 )
370 }
371}
372
373#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
375pub enum ChromaLocation {
376 #[default]
378 Unspecified,
379 Left,
381 Center,
383 TopLeft,
385}
386
387#[derive(Clone, Debug, Eq, PartialEq)]
391pub struct Plane {
392 pub bytes: bytes::Bytes,
394 pub stride: u32,
396 pub width: u32,
398 pub height: u32,
400}
401
402pub const MAX_FRAME_DIMENSION: u32 = 16_384;
404pub const MAX_FRAME_PIXELS: u64 = 67_108_864;
406
407#[must_use]
410pub const fn frame_size_supported(width: u32, height: u32) -> bool {
411 width <= MAX_FRAME_DIMENSION
412 && height <= MAX_FRAME_DIMENSION
413 && (width as u64) * (height as u64) <= MAX_FRAME_PIXELS
414}
415
416#[derive(Clone, Debug, PartialEq)]
418pub struct DecodedVideoFrame {
419 pub pts: MediaTime,
421 pub dts: Option<MediaTime>,
423 pub duration: Option<MediaTime>,
425 pub visible_rect: Rect,
427 pub sample_aspect_ratio: SampleAspectRatio,
429 pub orientation: Orientation,
431 pub pixel_format: PixelFormat,
433 pub chroma_location: ChromaLocation,
435 pub color: Option<ColorMetadata>,
438 pub planes: Vec<Plane>,
440}
441
442pub const MAX_AUDIO_CHANNELS: usize = 24;
445
446#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
451pub enum Speaker {
452 FrontLeft,
454 FrontRight,
456 FrontCenter,
458 LowFrequency,
460 BackLeft,
462 BackRight,
464 FrontLeftOfCenter,
466 FrontRightOfCenter,
468 BackCenter,
470 SideLeft,
472 SideRight,
474 TopCenter,
476 TopFrontLeft,
478 TopFrontCenter,
480 TopFrontRight,
482 TopBackLeft,
484 TopBackCenter,
486 TopBackRight,
488 DownmixLeft,
490 DownmixRight,
492 WideLeft,
494 WideRight,
496 SurroundDirectLeft,
498 SurroundDirectRight,
500 LowFrequency2,
502 TopSideLeft,
504 TopSideRight,
506 BottomFrontCenter,
508 BottomFrontLeft,
510 BottomFrontRight,
512 SideSurroundLeft,
514 SideSurroundRight,
516 TopSurroundLeft,
518 TopSurroundRight,
520 BinauralLeft,
522 BinauralRight,
524 Unnamed,
527}
528
529impl Speaker {
530 const TABLE: [(Self, &'static str, u32); 37] = [
533 (Self::FrontLeft, "FL", 0),
534 (Self::FrontRight, "FR", 1),
535 (Self::FrontCenter, "FC", 2),
536 (Self::LowFrequency, "LFE", 3),
537 (Self::BackLeft, "BL", 4),
538 (Self::BackRight, "BR", 5),
539 (Self::FrontLeftOfCenter, "FLC", 6),
540 (Self::FrontRightOfCenter, "FRC", 7),
541 (Self::BackCenter, "BC", 8),
542 (Self::SideLeft, "SL", 9),
543 (Self::SideRight, "SR", 10),
544 (Self::TopCenter, "TC", 11),
545 (Self::TopFrontLeft, "TFL", 12),
546 (Self::TopFrontCenter, "TFC", 13),
547 (Self::TopFrontRight, "TFR", 14),
548 (Self::TopBackLeft, "TBL", 15),
549 (Self::TopBackCenter, "TBC", 16),
550 (Self::TopBackRight, "TBR", 17),
551 (Self::DownmixLeft, "DL", 29),
552 (Self::DownmixRight, "DR", 30),
553 (Self::WideLeft, "WL", 31),
554 (Self::WideRight, "WR", 32),
555 (Self::SurroundDirectLeft, "SDL", 33),
556 (Self::SurroundDirectRight, "SDR", 34),
557 (Self::LowFrequency2, "LFE2", 35),
558 (Self::TopSideLeft, "TSL", 36),
559 (Self::TopSideRight, "TSR", 37),
560 (Self::BottomFrontCenter, "BFC", 38),
561 (Self::BottomFrontLeft, "BFL", 39),
562 (Self::BottomFrontRight, "BFR", 40),
563 (Self::SideSurroundLeft, "SSL", 41),
564 (Self::SideSurroundRight, "SSR", 42),
565 (Self::TopSurroundLeft, "TTL", 43),
566 (Self::TopSurroundRight, "TTR", 44),
567 (Self::BinauralLeft, "BIL", 61),
568 (Self::BinauralRight, "BIR", 62),
569 (Self::Unnamed, "UNK", 0x300),
570 ];
571
572 #[must_use]
574 pub fn name(self) -> &'static str {
575 Self::TABLE
576 .iter()
577 .find(|(speaker, _, _)| *speaker == self)
578 .map_or("UNK", |(_, name, _)| name)
579 }
580
581 #[must_use]
583 pub fn from_name(name: &str) -> Option<Self> {
584 Self::TABLE
585 .iter()
586 .find(|(_, known, _)| *known == name)
587 .map(|(speaker, _, _)| *speaker)
588 }
589
590 #[must_use]
592 pub fn ffmpeg_channel(self) -> u32 {
593 Self::TABLE
594 .iter()
595 .find(|(speaker, _, _)| *speaker == self)
596 .map_or(0x300, |(_, _, channel)| *channel)
597 }
598
599 #[must_use]
602 pub fn from_ffmpeg_channel(channel: i64) -> Self {
603 Self::TABLE
604 .iter()
605 .find(|(_, _, number)| i64::from(*number) == channel)
606 .map_or(Self::Unnamed, |(speaker, _, _)| *speaker)
607 }
608}
609
610#[derive(Copy, Clone, Eq, Hash, PartialEq)]
615pub struct SpeakerLayout {
616 speakers: [Speaker; MAX_AUDIO_CHANNELS],
617 count: u8,
618}
619
620impl std::fmt::Debug for SpeakerLayout {
621 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622 formatter
623 .debug_list()
624 .entries(self.speakers().iter().map(|speaker| speaker.name()))
625 .finish()
626 }
627}
628
629impl SpeakerLayout {
630 pub const MONO: Self = Self::fixed(&[Speaker::FrontCenter]);
632 pub const STEREO: Self = Self::fixed(&[Speaker::FrontLeft, Speaker::FrontRight]);
634
635 const fn fixed(speakers: &[Speaker]) -> Self {
636 let mut layout = [Speaker::Unnamed; MAX_AUDIO_CHANNELS];
637 let mut index = 0;
638 while index < speakers.len() {
639 layout[index] = speakers[index];
640 index += 1;
641 }
642 #[allow(clippy::cast_possible_truncation)]
643 Self {
644 speakers: layout,
645 count: speakers.len() as u8,
646 }
647 }
648
649 #[must_use]
652 pub fn new(speakers: &[Speaker]) -> Option<Self> {
653 if speakers.is_empty() || speakers.len() > MAX_AUDIO_CHANNELS {
654 return None;
655 }
656 let mut layout = Self {
657 speakers: [Speaker::Unnamed; MAX_AUDIO_CHANNELS],
658 count: u8::try_from(speakers.len()).ok()?,
659 };
660 layout.speakers[..speakers.len()].copy_from_slice(speakers);
661 Some(layout)
662 }
663
664 #[must_use]
669 pub fn default_for(channels: usize) -> Option<Self> {
670 const SEVEN_ONE: u64 = 0x63f;
672 let mask: u64 = match channels {
673 1 => 0x4,
674 2 => 0x3,
675 3 => 0xb,
676 4 => 0x107,
677 5 => 0x37,
678 6 => 0x3f,
679 7 => 0x70f,
680 10 => 0x2_d60f,
681 12 => 0x2_d63f,
682 14 => 0x2_d6ff,
683 16 => 0x30_0002_d6ff,
684 24 => 0x1f8_0003_ffff,
685 8..=MAX_AUDIO_CHANNELS => SEVEN_ONE,
686 _ => return None,
687 };
688 let mut layout = Self::of_mask(mask)?;
689 layout.count = u8::try_from(channels).ok()?;
691 Some(layout)
692 }
693
694 fn of_mask(mask: u64) -> Option<Self> {
696 let speakers = (0..64_i64)
697 .filter(|bit| mask & (1 << bit) != 0)
698 .map(Speaker::from_ffmpeg_channel)
699 .collect::<Vec<_>>();
700 Self::new(&speakers)
701 }
702
703 #[must_use]
706 pub fn declared_or_default(speakers: &[Speaker]) -> Option<(Self, bool)> {
707 if speakers.iter().all(|speaker| *speaker == Speaker::Unnamed) {
708 return Self::default_for(speakers.len()).map(|layout| (layout, true));
709 }
710 Self::new(speakers).map(|layout| (layout, false))
711 }
712
713 #[must_use]
715 pub fn speakers(&self) -> &[Speaker] {
716 &self.speakers[..usize::from(self.count)]
717 }
718
719 #[must_use]
721 pub fn channels(&self) -> usize {
722 usize::from(self.count)
723 }
724
725 #[must_use]
728 pub fn describe(&self) -> String {
729 self.speakers()
730 .iter()
731 .map(|speaker| speaker.name())
732 .collect::<Vec<_>>()
733 .join("+")
734 }
735}
736
737#[derive(Clone, Debug, PartialEq)]
746pub struct AudioBlock {
747 pub start_sample: i64,
749 pub sample_rate: u32,
751 pub speakers: SpeakerLayout,
753 pub channels: Vec<Vec<f32>>,
755}
756
757impl AudioBlock {
758 #[must_use]
760 pub fn frames(&self) -> usize {
761 self.channels.first().map_or(0, Vec::len)
762 }
763
764 #[must_use]
766 pub fn end_sample(&self) -> i64 {
767 self.start_sample
768 .saturating_add(i64::try_from(self.frames()).unwrap_or(i64::MAX))
769 }
770
771 #[must_use]
773 pub fn start_time(&self) -> MediaTime {
774 MediaTime::from_ticks(sample_ticks_floor(self.start_sample, self.sample_rate))
775 }
776}
777
778#[must_use]
780pub fn sample_ticks_floor(sample: i64, sample_rate: u32) -> i64 {
781 let ticks = (i128::from(sample) * i128::from(alpha2_time::TICKS_PER_SECOND))
782 .div_euclid(i128::from(sample_rate.max(1)));
783 i64::try_from(ticks).unwrap_or(if ticks < 0 { i64::MIN } else { i64::MAX })
784}
785
786#[derive(Copy, Clone, Debug, Eq, PartialEq)]
788pub enum RasterColorSpace {
789 Srgb,
791 LinearSrgb,
793}
794
795#[derive(Copy, Clone, Debug, Eq, PartialEq)]
797pub enum Premultiplication {
798 Premultiplied,
800 Straight,
802 Opaque,
804}
805
806#[derive(Clone, Debug, Eq, PartialEq)]
808pub struct HostTexture {
809 pub content_version: u64,
811 pub width: u32,
813 pub height: u32,
815 pub premultiplication: Premultiplication,
817 pub color_space: RasterColorSpace,
819}
820
821#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
823pub enum TextAlign {
824 #[default]
826 Start,
827 Center,
829 End,
831}
832
833#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
835pub enum EmojiPolicy {
836 #[default]
839 Color,
840 Monochrome,
843 Replace,
846}
847
848#[derive(Clone, Debug, PartialEq)]
853pub struct TextTexture {
854 pub texture: HostTexture,
856 pub layout_x: f32,
859 pub layout_y: f32,
861 pub layout_width: f32,
863 pub layout_height: f32,
865 pub first_baseline: f32,
867 pub ascent: f32,
869 pub descent: f32,
871 pub line_count: u32,
873}
874
875impl TextTexture {
876 #[must_use]
878 pub fn is_valid(&self) -> bool {
879 self.texture.width > 0
880 && self.texture.height > 0
881 && self.line_count > 0
882 && [
883 self.layout_x,
884 self.layout_y,
885 self.layout_width,
886 self.layout_height,
887 self.first_baseline,
888 self.ascent,
889 self.descent,
890 ]
891 .into_iter()
892 .all(|value| value.is_finite() && value >= 0.0)
893 && self.first_baseline <= self.layout_height
894 && f64::from(self.layout_x) + f64::from(self.layout_width)
895 <= f64::from(self.texture.width)
896 && f64::from(self.layout_y) + f64::from(self.layout_height)
897 <= f64::from(self.texture.height)
898 }
899}
900
901#[derive(Clone, Debug, PartialEq)]
903pub struct HostTexturePixels {
904 pub texture: HostTexture,
906 pub rgba8: Vec<u8>,
908}
909
910impl HostTexturePixels {
911 #[must_use]
913 pub fn is_valid(&self) -> bool {
914 usize::try_from(self.texture.width)
915 .ok()
916 .and_then(|width| {
917 usize::try_from(self.texture.height)
918 .ok()
919 .and_then(|height| width.checked_mul(height))
920 })
921 .and_then(|pixels| pixels.checked_mul(4))
922 == Some(self.rgba8.len())
923 }
924}
925
926#[derive(Copy, Clone, Debug, Eq, PartialEq)]
928pub enum StreamKind {
929 Video,
931 Audio,
933}
934
935#[derive(Clone, Debug, PartialEq)]
937pub struct StreamInfo {
938 pub index: usize,
940 pub kind: StreamKind,
942 pub codec: String,
944 pub duration: Option<MediaTime>,
946 pub dimensions: Option<(u32, u32)>,
948 pub frame_rate: Option<FrameRate>,
950 pub sample_rate: Option<u32>,
952 pub channels: Option<u32>,
954 pub orientation: Orientation,
956 pub sample_aspect_ratio: SampleAspectRatio,
958 pub color: Option<ColorMetadata>,
960 pub frame_bytes: Option<u64>,
963 pub alpha: Option<SourceAlpha>,
965}
966
967#[derive(Copy, Clone, Debug, Eq, PartialEq)]
969pub enum SourceAlpha {
970 Vp9SideData,
973 HevcAuxiliaryLayer,
976 Planar,
978}
979
980impl SourceAlpha {
981 #[must_use]
983 pub const fn as_str(self) -> &'static str {
984 match self {
985 Self::Vp9SideData => "vp9-side-data",
986 Self::HevcAuxiliaryLayer => "hevc-auxiliary-layer",
987 Self::Planar => "planar",
988 }
989 }
990}
991
992#[derive(Copy, Clone, Debug, Eq, PartialEq)]
994pub enum DecodeRequestKind {
995 Prefetch,
997 Seek,
999 Evicted,
1001 DeviceRecovery,
1003}
1004
1005#[derive(Clone, Debug, Eq, PartialEq)]
1007pub struct DecodeRequest {
1008 pub source: SourceId,
1010 pub time: MediaTime,
1012 pub kind: DecodeRequestKind,
1014 pub generation: u64,
1016}
1017
1018#[derive(Clone, Debug, PartialEq)]
1020pub struct DecodeResult {
1021 pub request: DecodeRequest,
1023 pub event: DecodeEvent,
1025}
1026
1027#[derive(Clone, Debug, PartialEq)]
1029pub enum DecodeEvent {
1030 Video(DecodedVideoFrame),
1032 Audio(AudioBlock),
1034 Retry,
1036 EndOfStream,
1038}
1039
1040#[derive(Clone, Debug, Default)]
1042pub struct CancelToken(Arc<AtomicBool>);
1043
1044impl CancelToken {
1045 pub fn cancel(&self) {
1047 self.0.store(true, Ordering::Release);
1048 }
1049
1050 #[must_use]
1052 pub fn is_cancelled(&self) -> bool {
1053 self.0.load(Ordering::Acquire)
1054 }
1055}
1056
1057pub const GPU_TIMEOUT_MESSAGE: &str = "GPU work did not complete within the wait timeout";
1060
1061#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1064#[non_exhaustive]
1065pub enum GpuFault {
1066 DeviceLost,
1068 DeviceHung,
1070 DeviceMemory,
1072 HostMemory,
1074 Timeout,
1076}
1077
1078impl GpuFault {
1079 #[must_use]
1081 pub const fn as_str(self) -> &'static str {
1082 match self {
1083 Self::DeviceLost => "device-lost",
1084 Self::DeviceHung => "device-hung",
1085 Self::DeviceMemory => "device-memory",
1086 Self::HostMemory => "host-memory",
1087 Self::Timeout => "timeout",
1088 }
1089 }
1090}
1091
1092#[derive(Clone, Debug, Eq, PartialEq)]
1097#[non_exhaustive]
1098pub enum DecodeError {
1099 Cancelled,
1101 OutOfRange,
1103 Unsupported(String),
1105 Adapter(String),
1107 Gpu {
1109 fault: GpuFault,
1111 message: String,
1113 },
1114}
1115
1116#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1118pub enum OpenFailureKind {
1119 Absent,
1121 Busy,
1124 Unsuitable,
1126 Unknown,
1128}
1129
1130impl OpenFailureKind {
1131 #[must_use]
1133 pub const fn as_str(self) -> &'static str {
1134 match self {
1135 Self::Absent => "absent",
1136 Self::Busy => "busy",
1137 Self::Unsuitable => "unsuitable",
1138 Self::Unknown => "unknown",
1139 }
1140 }
1141}
1142
1143#[derive(Clone, Debug, Eq, PartialEq)]
1145pub struct EncoderRefusal {
1146 pub encoder: String,
1148 pub hardware_accelerated: bool,
1150 pub kind: OpenFailureKind,
1152 pub reason: String,
1154}
1155
1156impl DecodeError {
1157 #[must_use]
1159 pub const fn is_fatal(&self) -> bool {
1160 matches!(
1161 self,
1162 Self::Unsupported(_) | Self::Adapter(_) | Self::Gpu { .. }
1163 )
1164 }
1165}
1166
1167impl fmt::Display for DecodeError {
1168 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1169 match self {
1170 Self::Cancelled => formatter.write_str("decode was cancelled"),
1171 Self::OutOfRange => formatter.write_str("decode request is out of range"),
1172 Self::Unsupported(message) => write!(formatter, "unsupported media: {message}"),
1173 Self::Adapter(message) | Self::Gpu { message, .. } => {
1174 write!(formatter, "decoder adapter failed: {message}")
1175 }
1176 }
1177 }
1178}
1179
1180impl Error for DecodeError {}
1181
1182pub trait Decoder {
1184 fn streams(&self) -> &[StreamInfo];
1186 fn flush(&mut self) -> Result<(), DecodeError>;
1191 fn seek(
1203 &mut self,
1204 request: DecodeRequest,
1205 cancel: &CancelToken,
1206 ) -> Result<DecodeResult, DecodeError>;
1207 fn decode_next(&mut self, cancel: &CancelToken) -> Result<DecodeEvent, DecodeError>;
1212}
1213
1214#[derive(Clone, Copy, Debug, PartialEq)]
1216pub struct AudioSpan<'a> {
1217 pub block: &'a AudioBlock,
1219 pub starts_source: bool,
1221 pub ends_source: bool,
1223}
1224
1225impl<'a> AudioSpan<'a> {
1226 #[must_use]
1228 pub const fn whole(block: &'a AudioBlock) -> Self {
1229 Self {
1230 block,
1231 starts_source: true,
1232 ends_source: true,
1233 }
1234 }
1235
1236 #[must_use]
1239 pub fn covers(&self, start: i64, end: i64) -> bool {
1240 (self.starts_source || self.block.start_sample <= start)
1241 && (self.ends_source || self.block.end_sample() >= end)
1242 }
1243}
1244
1245pub trait AudioBlockProvider {
1247 fn block(&self, source: &SourceId, start_sample: i64, frames: usize) -> Option<AudioSpan<'_>>;
1255}
1256
1257pub trait AudioBlockSink {
1259 fn write_audio(&mut self, block: &AudioBlock, cancel: &CancelToken) -> Result<(), EncodeError>;
1265
1266 fn finish_audio(&mut self, cancel: &CancelToken) -> Result<(), EncodeError>;
1272
1273 fn abort_audio(&mut self) -> Result<(), EncodeError>;
1278}
1279
1280#[derive(Clone, Debug, PartialEq)]
1282pub enum EncodeEvent {
1283 Video(DecodedVideoFrame),
1285 Audio(AudioBlock),
1287}
1288
1289#[derive(Clone, Debug, Eq, PartialEq)]
1291#[non_exhaustive]
1292pub enum EncodeError {
1293 Cancelled,
1295 InvalidTimestamp,
1297 Adapter(String),
1299 Output(String),
1301 OutputInUse(String),
1304 Publish {
1307 message: String,
1309 kept: Option<std::path::PathBuf>,
1311 },
1312 EncoderRejected {
1314 encoder: String,
1316 frame: u64,
1318 message: String,
1320 },
1321 Gpu {
1323 fault: GpuFault,
1325 message: String,
1327 },
1328 EncodersRefused {
1330 message: String,
1332 refusals: Vec<EncoderRefusal>,
1334 },
1335}
1336
1337impl EncodeError {
1338 #[must_use]
1340 pub fn gpu_timeout(what: impl fmt::Display) -> Self {
1341 Self::Gpu {
1342 fault: GpuFault::Timeout,
1343 message: format!("{what}: {GPU_TIMEOUT_MESSAGE}"),
1344 }
1345 }
1346}
1347
1348impl fmt::Display for EncodeError {
1349 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1350 match self {
1351 Self::Cancelled => formatter.write_str("encode was cancelled"),
1352 Self::InvalidTimestamp => formatter.write_str("encode timestamp is invalid"),
1353 Self::Adapter(message)
1354 | Self::Gpu { message, .. }
1355 | Self::EncodersRefused { message, .. } => {
1356 write!(formatter, "encoder adapter failed: {message}")
1357 }
1358 Self::EncoderRejected { message, .. } => formatter.write_str(message),
1359 Self::Output(message) | Self::OutputInUse(message) => {
1360 write!(formatter, "cannot create the output: {message}")
1361 }
1362 Self::Publish {
1363 message,
1364 kept: Some(kept),
1365 } => write!(
1366 formatter,
1367 "cannot publish the output: {message}; the finished render is kept at {}",
1368 kept.display()
1369 ),
1370 Self::Publish {
1371 message,
1372 kept: None,
1373 } => write!(
1374 formatter,
1375 "cannot publish the output: {message}; the finished render could not be kept"
1376 ),
1377 }
1378 }
1379}
1380
1381impl Error for EncodeError {}
1382
1383pub trait EncodeSink {
1385 fn write(&mut self, event: EncodeEvent, cancel: &CancelToken) -> Result<(), EncodeError>;
1390 fn finish(&mut self, cancel: &CancelToken) -> Result<(), EncodeError>;
1395 fn abort(&mut self) -> Result<(), EncodeError>;
1400}
1401
1402#[cfg(test)]
1403mod tests {
1404 use super::SampleAspectRatio;
1405
1406 #[test]
1407 fn a_pixel_aspect_ratio_is_kept_in_lowest_terms_and_shows_as_a_browser_shows_it() {
1408 let ratio = |num, den| SampleAspectRatio { num, den };
1409 assert_eq!(SampleAspectRatio::new(8, 6), Some(ratio(4, 3)));
1410 assert_eq!(SampleAspectRatio::new(0, 1), None);
1411 assert_eq!(SampleAspectRatio::new(1, 0), None);
1412 assert_eq!(SampleAspectRatio::new(u64::from(u32::MAX) + 1, 1), None);
1413 let shown = |visible, displayed| SampleAspectRatio::of_display(visible, displayed);
1415 assert_eq!(shown((128, 96), (171, 96)), Some(ratio(4, 3)));
1416 assert_eq!(shown((96, 128), (96, 171)), Some(ratio(3, 4)));
1417 assert_eq!(shown((720, 576), (1024, 576)), Some(ratio(64, 45)));
1418 assert_eq!(shown((720, 480), (655, 480)), Some(ratio(10, 11)));
1419 assert_eq!(shown((720, 480), (720, 524)), Some(ratio(11, 12)));
1420 assert_eq!(shown((1440, 1080), (1920, 1080)), Some(ratio(4, 3)));
1421 assert_eq!(shown((128, 96), (128, 96)), Some(ratio(1, 1)));
1422 for (visible, displayed) in [
1424 ((128, 96), (170, 96)),
1425 ((1000, 999), (1777, 999)),
1426 ((3, 2), (5, 2)),
1427 ] {
1428 let found = shown(visible, displayed).expect("a ratio");
1429 assert_eq!(
1430 found.square_pixel_size(visible.0, visible.1),
1431 displayed,
1432 "{visible:?} {displayed:?}"
1433 );
1434 }
1435 assert_eq!(shown((0, 96), (171, 96)), None);
1436 assert_eq!(ratio(4, 3).square_pixel_size(128, 96), (171, 96));
1438 assert_eq!(ratio(64, 45).square_pixel_size(720, 576), (1024, 576));
1439 assert_eq!(ratio(3, 4).square_pixel_size(96, 128), (96, 171));
1440 assert_eq!(ratio(1, 1).square_pixel_size(65, 49), (65, 49));
1441 assert_eq!(ratio(3, 2).square_pixel_size(3, 7), (5, 7));
1443 }
1444
1445 use super::*;
1446
1447 #[test]
1448 fn a_frame_size_is_bounded_on_each_side_and_in_area() {
1449 assert!(frame_size_supported(16_384, 4_096));
1450 assert!(frame_size_supported(8_192, 8_192));
1451 assert!(!frame_size_supported(8_193, 8_192));
1452 assert!(!frame_size_supported(16_385, 1));
1453 assert!(!frame_size_supported(1, 16_385));
1454 assert!(!frame_size_supported(u32::MAX, u32::MAX));
1455 }
1456
1457 #[test]
1458 fn cancellation_is_shared_and_monotonic() {
1459 let token = CancelToken::default();
1460 let other = token.clone();
1461 assert!(!other.is_cancelled());
1462 token.cancel();
1463 assert!(other.is_cancelled());
1464 }
1465
1466 #[test]
1467 fn a_layout_names_each_plane_by_ffmpegs_channel_names() {
1468 let lcr = SpeakerLayout::new(&[
1469 Speaker::FrontCenter,
1470 Speaker::FrontLeft,
1471 Speaker::FrontRight,
1472 ])
1473 .expect("layout");
1474 assert_eq!(lcr.channels(), 3);
1475 assert_eq!(lcr.describe(), "FC+FL+FR");
1476 assert_eq!(format!("{lcr:?}"), r#"["FC", "FL", "FR"]"#);
1477 for (speaker, name, channel) in Speaker::TABLE {
1478 assert_eq!(speaker.name(), name);
1479 assert_eq!(Speaker::from_name(name), Some(speaker));
1480 assert_eq!(speaker.ffmpeg_channel(), channel);
1481 assert_eq!(Speaker::from_ffmpeg_channel(i64::from(channel)), speaker);
1482 }
1483 for channel in [-1, 18, 0x200, 0x400, 0x401, 0x7ff, 1_000] {
1485 assert_eq!(Speaker::from_ffmpeg_channel(channel), Speaker::Unnamed);
1486 }
1487 assert_eq!(Speaker::from_name("front left"), None);
1488 assert!(SpeakerLayout::new(&[]).is_none());
1489 assert!(SpeakerLayout::new(&[Speaker::FrontLeft; MAX_AUDIO_CHANNELS + 1]).is_none());
1490 }
1491
1492 #[test]
1493 fn a_source_that_declares_no_speaker_has_ffmpegs_default_for_its_count() {
1494 let names = |channels| {
1495 SpeakerLayout::default_for(channels)
1496 .expect("a default")
1497 .describe()
1498 };
1499 assert_eq!(names(1), "FC");
1500 assert_eq!(names(3), "FL+FR+LFE");
1501 assert_eq!(names(4), "FL+FR+FC+BC");
1502 assert_eq!(names(8), "FL+FR+FC+LFE+BL+BR+SL+SR");
1503 assert_eq!(names(9), "FL+FR+FC+LFE+BL+BR+SL+SR+UNK");
1504 assert_eq!(names(10), "FL+FR+FC+LFE+SL+SR+TFL+TFR+TBL+TBR");
1505 assert_eq!(SpeakerLayout::default_for(24).expect("22.2").channels(), 24);
1506 assert!(SpeakerLayout::default_for(0).is_none());
1507 assert!(SpeakerLayout::default_for(MAX_AUDIO_CHANNELS + 1).is_none());
1508 let unnamed = [Speaker::Unnamed; 2];
1509 assert_eq!(
1510 SpeakerLayout::declared_or_default(&unnamed),
1511 Some((SpeakerLayout::default_for(2).expect("stereo"), true))
1512 );
1513 let partly = [Speaker::FrontCenter, Speaker::Unnamed];
1514 assert_eq!(
1515 SpeakerLayout::declared_or_default(&partly),
1516 Some((SpeakerLayout::new(&partly).expect("layout"), false))
1517 );
1518 }
1519
1520 #[test]
1521 fn a_span_covers_a_request_only_where_it_holds_samples_or_knows_silence() {
1522 let block = AudioBlock {
1523 start_sample: 100,
1524 sample_rate: 48_000,
1525 speakers: SpeakerLayout::default_for(1).expect("mono"),
1526 channels: vec![vec![0.5; 50]],
1527 };
1528 let partial = AudioSpan {
1529 block: &block,
1530 starts_source: false,
1531 ends_source: false,
1532 };
1533 assert!(partial.covers(100, 150));
1534 assert!(partial.covers(120, 130));
1535 assert!(!partial.covers(99, 150), "a sample before the block");
1536 assert!(!partial.covers(100, 151), "a sample after the block");
1537 let starts = AudioSpan {
1538 starts_source: true,
1539 ..partial
1540 };
1541 assert!(starts.covers(0, 150));
1542 assert!(!starts.covers(0, 151));
1543 assert!(AudioSpan::whole(&block).covers(-5, 1_000));
1544 }
1545
1546 #[test]
1547 fn text_descriptor_and_generic_pixels_validate_without_compositor_types() {
1548 let texture = HostTexture {
1549 content_version: 2,
1550 width: 4,
1551 height: 3,
1552 premultiplication: Premultiplication::Premultiplied,
1553 color_space: RasterColorSpace::Srgb,
1554 };
1555 let text = TextTexture {
1556 texture: texture.clone(),
1557 layout_x: 0.0,
1558 layout_y: 0.0,
1559 layout_width: 4.0,
1560 layout_height: 3.0,
1561 first_baseline: 2.0,
1562 ascent: 2.0,
1563 descent: 1.0,
1564 line_count: 1,
1565 };
1566 assert!(text.is_valid());
1567 assert!(
1568 HostTexturePixels {
1569 texture,
1570 rgba8: vec![0; 4 * 3 * 4],
1571 }
1572 .is_valid()
1573 );
1574 }
1575}