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 pub language: Option<String>,
967 pub title: Option<String>,
969}
970
971#[derive(Copy, Clone, Debug, Eq, PartialEq)]
973pub enum SourceAlpha {
974 Vp9SideData,
977 HevcAuxiliaryLayer,
980 Planar,
982}
983
984impl SourceAlpha {
985 #[must_use]
987 pub const fn as_str(self) -> &'static str {
988 match self {
989 Self::Vp9SideData => "vp9-side-data",
990 Self::HevcAuxiliaryLayer => "hevc-auxiliary-layer",
991 Self::Planar => "planar",
992 }
993 }
994}
995
996#[derive(Copy, Clone, Debug, Eq, PartialEq)]
998pub enum DecodeRequestKind {
999 Prefetch,
1001 Seek,
1003 Evicted,
1005 DeviceRecovery,
1007}
1008
1009#[derive(Clone, Debug, Eq, PartialEq)]
1011pub struct DecodeRequest {
1012 pub source: SourceId,
1014 pub time: MediaTime,
1016 pub kind: DecodeRequestKind,
1018 pub generation: u64,
1020}
1021
1022#[derive(Clone, Debug, PartialEq)]
1024pub struct DecodeResult {
1025 pub request: DecodeRequest,
1027 pub event: DecodeEvent,
1029}
1030
1031#[derive(Clone, Debug, PartialEq)]
1033pub enum DecodeEvent {
1034 Video(DecodedVideoFrame),
1036 Audio(AudioBlock),
1038 Retry,
1040 EndOfStream,
1042}
1043
1044#[derive(Clone, Debug, Default)]
1046pub struct CancelToken(Arc<AtomicBool>);
1047
1048impl CancelToken {
1049 pub fn cancel(&self) {
1051 self.0.store(true, Ordering::Release);
1052 }
1053
1054 #[must_use]
1056 pub fn is_cancelled(&self) -> bool {
1057 self.0.load(Ordering::Acquire)
1058 }
1059}
1060
1061pub const GPU_TIMEOUT_MESSAGE: &str = "GPU work did not complete within the wait timeout";
1064
1065#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1068#[non_exhaustive]
1069pub enum GpuFault {
1070 DeviceLost,
1072 DeviceHung,
1074 DeviceMemory,
1076 HostMemory,
1078 Timeout,
1080}
1081
1082impl GpuFault {
1083 #[must_use]
1085 pub const fn as_str(self) -> &'static str {
1086 match self {
1087 Self::DeviceLost => "device-lost",
1088 Self::DeviceHung => "device-hung",
1089 Self::DeviceMemory => "device-memory",
1090 Self::HostMemory => "host-memory",
1091 Self::Timeout => "timeout",
1092 }
1093 }
1094}
1095
1096#[derive(Clone, Debug, Eq, PartialEq)]
1101#[non_exhaustive]
1102pub enum DecodeError {
1103 Cancelled,
1105 OutOfRange,
1107 Unsupported(String),
1109 Adapter(String),
1111 Gpu {
1113 fault: GpuFault,
1115 message: String,
1117 },
1118}
1119
1120#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
1122pub enum OpenFailureKind {
1123 Absent,
1125 Busy,
1128 Unsuitable,
1130 Unknown,
1132}
1133
1134impl OpenFailureKind {
1135 #[must_use]
1137 pub const fn as_str(self) -> &'static str {
1138 match self {
1139 Self::Absent => "absent",
1140 Self::Busy => "busy",
1141 Self::Unsuitable => "unsuitable",
1142 Self::Unknown => "unknown",
1143 }
1144 }
1145}
1146
1147#[derive(Clone, Debug, Eq, PartialEq)]
1149pub struct EncoderRefusal {
1150 pub encoder: String,
1152 pub hardware_accelerated: bool,
1154 pub kind: OpenFailureKind,
1156 pub reason: String,
1158}
1159
1160impl DecodeError {
1161 #[must_use]
1163 pub const fn is_fatal(&self) -> bool {
1164 matches!(
1165 self,
1166 Self::Unsupported(_) | Self::Adapter(_) | Self::Gpu { .. }
1167 )
1168 }
1169}
1170
1171impl fmt::Display for DecodeError {
1172 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1173 match self {
1174 Self::Cancelled => formatter.write_str("decode was cancelled"),
1175 Self::OutOfRange => formatter.write_str("decode request is out of range"),
1176 Self::Unsupported(message) => write!(formatter, "unsupported media: {message}"),
1177 Self::Adapter(message) | Self::Gpu { message, .. } => {
1178 write!(formatter, "decoder adapter failed: {message}")
1179 }
1180 }
1181 }
1182}
1183
1184impl Error for DecodeError {}
1185
1186pub trait Decoder {
1188 fn streams(&self) -> &[StreamInfo];
1190 fn flush(&mut self) -> Result<(), DecodeError>;
1195 fn seek(
1207 &mut self,
1208 request: DecodeRequest,
1209 cancel: &CancelToken,
1210 ) -> Result<DecodeResult, DecodeError>;
1211 fn decode_next(&mut self, cancel: &CancelToken) -> Result<DecodeEvent, DecodeError>;
1216}
1217
1218#[derive(Clone, Copy, Debug, PartialEq)]
1220pub struct AudioSpan<'a> {
1221 pub block: &'a AudioBlock,
1223 pub starts_source: bool,
1225 pub ends_source: bool,
1227}
1228
1229impl<'a> AudioSpan<'a> {
1230 #[must_use]
1232 pub const fn whole(block: &'a AudioBlock) -> Self {
1233 Self {
1234 block,
1235 starts_source: true,
1236 ends_source: true,
1237 }
1238 }
1239
1240 #[must_use]
1243 pub fn covers(&self, start: i64, end: i64) -> bool {
1244 (self.starts_source || self.block.start_sample <= start)
1245 && (self.ends_source || self.block.end_sample() >= end)
1246 }
1247}
1248
1249pub trait AudioBlockProvider {
1251 fn block(
1262 &self,
1263 source: &SourceId,
1264 stream: Option<u32>,
1265 start_sample: i64,
1266 frames: usize,
1267 ) -> Option<AudioSpan<'_>>;
1268}
1269
1270pub trait AudioBlockSink {
1272 fn write_audio(&mut self, block: &AudioBlock, cancel: &CancelToken) -> Result<(), EncodeError>;
1278
1279 fn finish_audio(&mut self, cancel: &CancelToken) -> Result<(), EncodeError>;
1285
1286 fn abort_audio(&mut self) -> Result<(), EncodeError>;
1291}
1292
1293#[derive(Clone, Debug, PartialEq)]
1295pub enum EncodeEvent {
1296 Video(DecodedVideoFrame),
1298 Audio(AudioBlock),
1300}
1301
1302#[derive(Clone, Debug, Eq, PartialEq)]
1304#[non_exhaustive]
1305pub enum EncodeError {
1306 Cancelled,
1308 InvalidTimestamp,
1310 Adapter(String),
1312 Output(String),
1314 OutputInUse(String),
1317 Publish {
1320 message: String,
1322 kept: Option<std::path::PathBuf>,
1324 },
1325 EncoderRejected {
1327 encoder: String,
1329 frame: u64,
1331 message: String,
1333 },
1334 Gpu {
1336 fault: GpuFault,
1338 message: String,
1340 },
1341 EncodersRefused {
1343 message: String,
1345 refusals: Vec<EncoderRefusal>,
1347 },
1348}
1349
1350impl EncodeError {
1351 #[must_use]
1353 pub fn gpu_timeout(what: impl fmt::Display) -> Self {
1354 Self::Gpu {
1355 fault: GpuFault::Timeout,
1356 message: format!("{what}: {GPU_TIMEOUT_MESSAGE}"),
1357 }
1358 }
1359}
1360
1361impl fmt::Display for EncodeError {
1362 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1363 match self {
1364 Self::Cancelled => formatter.write_str("encode was cancelled"),
1365 Self::InvalidTimestamp => formatter.write_str("encode timestamp is invalid"),
1366 Self::Adapter(message)
1367 | Self::Gpu { message, .. }
1368 | Self::EncodersRefused { message, .. } => {
1369 write!(formatter, "encoder adapter failed: {message}")
1370 }
1371 Self::EncoderRejected { message, .. } => formatter.write_str(message),
1372 Self::Output(message) | Self::OutputInUse(message) => {
1373 write!(formatter, "cannot create the output: {message}")
1374 }
1375 Self::Publish {
1376 message,
1377 kept: Some(kept),
1378 } => write!(
1379 formatter,
1380 "cannot publish the output: {message}; the finished render is kept at {}",
1381 kept.display()
1382 ),
1383 Self::Publish {
1384 message,
1385 kept: None,
1386 } => write!(
1387 formatter,
1388 "cannot publish the output: {message}; the finished render could not be kept"
1389 ),
1390 }
1391 }
1392}
1393
1394impl Error for EncodeError {}
1395
1396pub trait EncodeSink {
1398 fn write(&mut self, event: EncodeEvent, cancel: &CancelToken) -> Result<(), EncodeError>;
1403 fn finish(&mut self, cancel: &CancelToken) -> Result<(), EncodeError>;
1408 fn abort(&mut self) -> Result<(), EncodeError>;
1413}
1414
1415#[cfg(test)]
1416mod tests {
1417 use super::SampleAspectRatio;
1418
1419 #[test]
1420 fn a_pixel_aspect_ratio_is_kept_in_lowest_terms_and_shows_as_a_browser_shows_it() {
1421 let ratio = |num, den| SampleAspectRatio { num, den };
1422 assert_eq!(SampleAspectRatio::new(8, 6), Some(ratio(4, 3)));
1423 assert_eq!(SampleAspectRatio::new(0, 1), None);
1424 assert_eq!(SampleAspectRatio::new(1, 0), None);
1425 assert_eq!(SampleAspectRatio::new(u64::from(u32::MAX) + 1, 1), None);
1426 let shown = |visible, displayed| SampleAspectRatio::of_display(visible, displayed);
1428 assert_eq!(shown((128, 96), (171, 96)), Some(ratio(4, 3)));
1429 assert_eq!(shown((96, 128), (96, 171)), Some(ratio(3, 4)));
1430 assert_eq!(shown((720, 576), (1024, 576)), Some(ratio(64, 45)));
1431 assert_eq!(shown((720, 480), (655, 480)), Some(ratio(10, 11)));
1432 assert_eq!(shown((720, 480), (720, 524)), Some(ratio(11, 12)));
1433 assert_eq!(shown((1440, 1080), (1920, 1080)), Some(ratio(4, 3)));
1434 assert_eq!(shown((128, 96), (128, 96)), Some(ratio(1, 1)));
1435 for (visible, displayed) in [
1437 ((128, 96), (170, 96)),
1438 ((1000, 999), (1777, 999)),
1439 ((3, 2), (5, 2)),
1440 ] {
1441 let found = shown(visible, displayed).expect("a ratio");
1442 assert_eq!(
1443 found.square_pixel_size(visible.0, visible.1),
1444 displayed,
1445 "{visible:?} {displayed:?}"
1446 );
1447 }
1448 assert_eq!(shown((0, 96), (171, 96)), None);
1449 assert_eq!(ratio(4, 3).square_pixel_size(128, 96), (171, 96));
1451 assert_eq!(ratio(64, 45).square_pixel_size(720, 576), (1024, 576));
1452 assert_eq!(ratio(3, 4).square_pixel_size(96, 128), (96, 171));
1453 assert_eq!(ratio(1, 1).square_pixel_size(65, 49), (65, 49));
1454 assert_eq!(ratio(3, 2).square_pixel_size(3, 7), (5, 7));
1456 }
1457
1458 use super::*;
1459
1460 #[test]
1461 fn a_frame_size_is_bounded_on_each_side_and_in_area() {
1462 assert!(frame_size_supported(16_384, 4_096));
1463 assert!(frame_size_supported(8_192, 8_192));
1464 assert!(!frame_size_supported(8_193, 8_192));
1465 assert!(!frame_size_supported(16_385, 1));
1466 assert!(!frame_size_supported(1, 16_385));
1467 assert!(!frame_size_supported(u32::MAX, u32::MAX));
1468 }
1469
1470 #[test]
1471 fn cancellation_is_shared_and_monotonic() {
1472 let token = CancelToken::default();
1473 let other = token.clone();
1474 assert!(!other.is_cancelled());
1475 token.cancel();
1476 assert!(other.is_cancelled());
1477 }
1478
1479 #[test]
1480 fn a_layout_names_each_plane_by_ffmpegs_channel_names() {
1481 let lcr = SpeakerLayout::new(&[
1482 Speaker::FrontCenter,
1483 Speaker::FrontLeft,
1484 Speaker::FrontRight,
1485 ])
1486 .expect("layout");
1487 assert_eq!(lcr.channels(), 3);
1488 assert_eq!(lcr.describe(), "FC+FL+FR");
1489 assert_eq!(format!("{lcr:?}"), r#"["FC", "FL", "FR"]"#);
1490 for (speaker, name, channel) in Speaker::TABLE {
1491 assert_eq!(speaker.name(), name);
1492 assert_eq!(Speaker::from_name(name), Some(speaker));
1493 assert_eq!(speaker.ffmpeg_channel(), channel);
1494 assert_eq!(Speaker::from_ffmpeg_channel(i64::from(channel)), speaker);
1495 }
1496 for channel in [-1, 18, 0x200, 0x400, 0x401, 0x7ff, 1_000] {
1498 assert_eq!(Speaker::from_ffmpeg_channel(channel), Speaker::Unnamed);
1499 }
1500 assert_eq!(Speaker::from_name("front left"), None);
1501 assert!(SpeakerLayout::new(&[]).is_none());
1502 assert!(SpeakerLayout::new(&[Speaker::FrontLeft; MAX_AUDIO_CHANNELS + 1]).is_none());
1503 }
1504
1505 #[test]
1506 fn a_source_that_declares_no_speaker_has_ffmpegs_default_for_its_count() {
1507 let names = |channels| {
1508 SpeakerLayout::default_for(channels)
1509 .expect("a default")
1510 .describe()
1511 };
1512 assert_eq!(names(1), "FC");
1513 assert_eq!(names(3), "FL+FR+LFE");
1514 assert_eq!(names(4), "FL+FR+FC+BC");
1515 assert_eq!(names(8), "FL+FR+FC+LFE+BL+BR+SL+SR");
1516 assert_eq!(names(9), "FL+FR+FC+LFE+BL+BR+SL+SR+UNK");
1517 assert_eq!(names(10), "FL+FR+FC+LFE+SL+SR+TFL+TFR+TBL+TBR");
1518 assert_eq!(SpeakerLayout::default_for(24).expect("22.2").channels(), 24);
1519 assert!(SpeakerLayout::default_for(0).is_none());
1520 assert!(SpeakerLayout::default_for(MAX_AUDIO_CHANNELS + 1).is_none());
1521 let unnamed = [Speaker::Unnamed; 2];
1522 assert_eq!(
1523 SpeakerLayout::declared_or_default(&unnamed),
1524 Some((SpeakerLayout::default_for(2).expect("stereo"), true))
1525 );
1526 let partly = [Speaker::FrontCenter, Speaker::Unnamed];
1527 assert_eq!(
1528 SpeakerLayout::declared_or_default(&partly),
1529 Some((SpeakerLayout::new(&partly).expect("layout"), false))
1530 );
1531 }
1532
1533 #[test]
1534 fn a_span_covers_a_request_only_where_it_holds_samples_or_knows_silence() {
1535 let block = AudioBlock {
1536 start_sample: 100,
1537 sample_rate: 48_000,
1538 speakers: SpeakerLayout::default_for(1).expect("mono"),
1539 channels: vec![vec![0.5; 50]],
1540 };
1541 let partial = AudioSpan {
1542 block: &block,
1543 starts_source: false,
1544 ends_source: false,
1545 };
1546 assert!(partial.covers(100, 150));
1547 assert!(partial.covers(120, 130));
1548 assert!(!partial.covers(99, 150), "a sample before the block");
1549 assert!(!partial.covers(100, 151), "a sample after the block");
1550 let starts = AudioSpan {
1551 starts_source: true,
1552 ..partial
1553 };
1554 assert!(starts.covers(0, 150));
1555 assert!(!starts.covers(0, 151));
1556 assert!(AudioSpan::whole(&block).covers(-5, 1_000));
1557 }
1558
1559 #[test]
1560 fn text_descriptor_and_generic_pixels_validate_without_compositor_types() {
1561 let texture = HostTexture {
1562 content_version: 2,
1563 width: 4,
1564 height: 3,
1565 premultiplication: Premultiplication::Premultiplied,
1566 color_space: RasterColorSpace::Srgb,
1567 };
1568 let text = TextTexture {
1569 texture: texture.clone(),
1570 layout_x: 0.0,
1571 layout_y: 0.0,
1572 layout_width: 4.0,
1573 layout_height: 3.0,
1574 first_baseline: 2.0,
1575 ascent: 2.0,
1576 descent: 1.0,
1577 line_count: 1,
1578 };
1579 assert!(text.is_valid());
1580 assert!(
1581 HostTexturePixels {
1582 texture,
1583 rgba8: vec![0; 4 * 3 * 4],
1584 }
1585 .is_valid()
1586 );
1587 }
1588}