1#![doc = include_str!("../README.md")]
2
3use std::{
4 collections::VecDeque,
5 sync::{
6 Arc, Mutex, RwLock,
7 atomic::{AtomicU64, Ordering},
8 },
9 time::{Duration, Instant},
10};
11
12use styx_core::prelude::*;
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
25pub enum CodecKind {
26 Encoder,
28 Decoder,
30}
31
32#[derive(Debug, Clone)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize))]
50#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
51pub struct CodecDescriptor {
52 pub kind: CodecKind,
54 pub input: FourCc,
56 pub output: FourCc,
58 pub name: &'static str,
60 pub impl_name: &'static str,
62}
63
64pub trait Codec: Send + Sync + 'static {
83 fn descriptor(&self) -> &CodecDescriptor;
85
86 fn process(&self, input: FrameLease) -> Result<FrameLease, CodecError>;
90}
91
92#[derive(Debug, thiserror::Error)]
106pub enum CodecError {
107 #[error("format mismatch: expected {expected}, got {actual}")]
109 FormatMismatch {
110 expected: FourCc,
112 actual: FourCc,
114 },
115 #[error("codec error: {0}")]
117 Codec(String),
118 #[error("codec backpressure")]
120 Backpressure,
121}
122
123#[derive(Debug, thiserror::Error)]
134pub enum RegistryError {
135 #[error("codec not registered for {0}")]
137 NotFound(FourCc),
138 #[error(transparent)]
140 Codec(#[from] CodecError),
141}
142
143#[derive(Debug, Clone, Default)]
154pub struct CodecStats {
155 processed: Arc<AtomicU64>,
156 errors: Arc<AtomicU64>,
157 backpressure: Arc<AtomicU64>,
158 last_nanos: Arc<AtomicU64>,
159 window: Arc<Mutex<WindowState>>,
160}
161
162#[derive(Debug, Clone)]
163struct WindowState {
164 samples: VecDeque<(Instant, u64)>,
165 max: usize,
166}
167
168impl Default for WindowState {
169 fn default() -> Self {
170 Self {
171 samples: VecDeque::new(),
172 max: DEFAULT_WINDOW,
173 }
174 }
175}
176
177const DEFAULT_WINDOW: usize = 120;
178
179impl CodecStats {
180 pub fn inc_processed(&self) {
182 self.processed.fetch_add(1, Ordering::Relaxed);
183 }
184
185 pub fn inc_errors(&self) {
187 self.errors.fetch_add(1, Ordering::Relaxed);
188 }
189
190 pub fn inc_backpressure(&self) {
192 self.backpressure.fetch_add(1, Ordering::Relaxed);
193 }
194
195 pub fn processed(&self) -> u64 {
197 self.processed.load(Ordering::Relaxed)
198 }
199
200 pub fn errors(&self) -> u64 {
202 self.errors.load(Ordering::Relaxed)
203 }
204
205 pub fn backpressure(&self) -> u64 {
207 self.backpressure.load(Ordering::Relaxed)
208 }
209
210 pub fn samples(&self) -> u64 {
212 self.window
213 .lock()
214 .map(|w| w.samples.len() as u64)
215 .unwrap_or(0)
216 }
217
218 pub fn record_duration(&self, dur: Duration) {
220 let nanos = dur.as_nanos().min(u64::MAX as u128) as u64;
221 self.last_nanos.store(nanos, Ordering::Relaxed);
222 if let Ok(mut win) = self.window.lock() {
223 if win.max == 0 {
224 win.max = DEFAULT_WINDOW;
225 }
226 win.samples.push_back((Instant::now(), nanos));
227 while win.samples.len() > win.max {
228 win.samples.pop_front();
229 }
230 }
231 }
232
233 pub fn set_window_size(&self, window: usize) {
235 let window = window.max(1);
236 if let Ok(mut win) = self.window.lock() {
237 win.max = window;
238 while win.samples.len() > win.max {
239 win.samples.pop_front();
240 }
241 }
242 }
243
244 pub fn avg_millis(&self) -> Option<f64> {
246 self.window.lock().ok().and_then(|w| {
247 let count = w.samples.len();
248 if count == 0 {
249 return None;
250 }
251 let total: u128 = w.samples.iter().map(|(_, n)| *n as u128).sum();
252 Some(total as f64 / 1_000_000.0 / count as f64)
253 })
254 }
255
256 pub fn last_millis(&self) -> Option<f64> {
258 let last = self.last_nanos.load(Ordering::Relaxed);
259 if last == 0 {
260 None
261 } else {
262 Some(last as f64 / 1_000_000.0)
263 }
264 }
265
266 pub fn fps(&self) -> Option<f64> {
268 self.window.lock().ok().and_then(|w| {
269 if w.samples.len() < 2 {
270 return None;
271 }
272 let first = w.samples.front()?.0;
273 let last = w.samples.back()?.0;
274 let span = last.saturating_duration_since(first).as_secs_f64();
275 if span > 0.0 {
276 Some(w.samples.len() as f64 / span)
277 } else {
278 None
279 }
280 })
281 }
282}
283
284struct RegistryInner {
285 codecs: std::collections::HashMap<FourCc, Vec<Arc<dyn Codec>>>,
286 preferences: std::collections::HashMap<FourCc, Preference>,
287 impl_priority: std::collections::HashMap<(FourCc, String), i32>,
288 default_prefer_hardware: bool,
289 policies: std::collections::HashMap<FourCc, CodecPolicy>,
290}
291
292impl RegistryInner {
293 fn new() -> Self {
294 Self {
295 codecs: std::collections::HashMap::new(),
296 preferences: std::collections::HashMap::new(),
297 impl_priority: std::collections::HashMap::new(),
298 default_prefer_hardware: true,
299 policies: std::collections::HashMap::new(),
300 }
301 }
302}
303
304fn sort_backends_for(
305 priorities: &std::collections::HashMap<(FourCc, String), i32>,
306 default_prefer_hardware: bool,
307 fourcc: FourCc,
308 list: &mut Vec<Arc<dyn Codec>>,
309) {
310 list.sort_by_key(|c| {
311 let impl_name = c.descriptor().impl_name.to_ascii_lowercase();
312 let prio = priorities
313 .get(&(fourcc, impl_name.clone()))
314 .copied()
315 .unwrap_or(i32::MAX);
316 let hw_bias = if default_prefer_hardware && is_hardware_impl(&impl_name) {
317 0
318 } else {
319 1
320 };
321 (prio, hw_bias, impl_name)
322 });
323}
324
325fn is_hardware_impl(name: &str) -> bool {
326 let n = name.to_ascii_lowercase();
327 [
328 "vaapi",
329 "nvenc",
330 "nvdec",
331 "cuvid",
332 "qsv",
333 "v4l2",
334 "videotoolbox",
335 "v4l2m2m",
336 ]
337 .iter()
338 .any(|tok| n.contains(tok))
339}
340
341#[derive(Clone)]
376pub struct CodecRegistryHandle {
377 inner: Arc<RwLock<RegistryInner>>,
378 stats: CodecStats,
379}
380
381impl CodecRegistryHandle {
382 pub fn lookup(&self, fourcc: FourCc) -> Result<Arc<dyn Codec>, RegistryError> {
384 let guard = self.inner.read().unwrap();
385 guard
386 .codecs
387 .get(&fourcc)
388 .and_then(|v| v.first().cloned())
389 .ok_or(RegistryError::NotFound(fourcc))
390 }
391
392 pub fn lookup_named(
394 &self,
395 fourcc: FourCc,
396 impl_name: &str,
397 ) -> Result<Arc<dyn Codec>, RegistryError> {
398 let guard = self.inner.read().unwrap();
399 guard
400 .codecs
401 .get(&fourcc)
402 .and_then(|v| {
403 v.iter()
404 .find(|c| c.descriptor().impl_name.eq_ignore_ascii_case(impl_name))
405 .cloned()
406 })
407 .ok_or(RegistryError::NotFound(fourcc))
408 }
409
410 pub fn lookup_named_kind(
412 &self,
413 fourcc: FourCc,
414 kind: CodecKind,
415 impl_name: &str,
416 ) -> Result<Arc<dyn Codec>, RegistryError> {
417 let guard = self.inner.read().unwrap();
418 guard
419 .codecs
420 .get(&fourcc)
421 .and_then(|v| {
422 v.iter()
423 .find(|c| {
424 c.descriptor().kind == kind
425 && c.descriptor().impl_name.eq_ignore_ascii_case(impl_name)
426 })
427 .cloned()
428 })
429 .ok_or(RegistryError::NotFound(fourcc))
430 }
431
432 pub fn lookup_preferred(
434 &self,
435 fourcc: FourCc,
436 preferred_impls: &[&str],
437 prefer_hardware: bool,
438 ) -> Result<Arc<dyn Codec>, RegistryError> {
439 let guard = self.inner.read().unwrap();
440 let list = guard
441 .codecs
442 .get(&fourcc)
443 .ok_or(RegistryError::NotFound(fourcc))?;
444
445 if !preferred_impls.is_empty() {
446 for pref in preferred_impls {
447 if let Some(c) = list
448 .iter()
449 .find(|c| c.descriptor().impl_name.eq_ignore_ascii_case(pref))
450 {
451 return Ok(c.clone());
452 }
453 }
454 }
455
456 if prefer_hardware
457 && let Some(c) = list
458 .iter()
459 .find(|c| is_hardware_impl(c.descriptor().impl_name))
460 {
461 return Ok(c.clone());
462 }
463
464 list.first().cloned().ok_or(RegistryError::NotFound(fourcc))
465 }
466
467 pub fn lookup_by_impl(
469 &self,
470 kind: CodecKind,
471 impl_name: &str,
472 ) -> Result<(FourCc, Arc<dyn Codec>), RegistryError> {
473 let guard = self.inner.read().unwrap();
474 for (fcc, list) in guard.codecs.iter() {
475 if let Some(c) = list.iter().find(|c| {
476 c.descriptor().kind == kind
477 && c.descriptor().impl_name.eq_ignore_ascii_case(impl_name)
478 }) {
479 return Ok((*fcc, c.clone()));
480 }
481 }
482 Err(RegistryError::NotFound(FourCc::new(*b" ")))
483 }
484
485 pub fn process(&self, fourcc: FourCc, frame: FrameLease) -> Result<FrameLease, RegistryError> {
487 let start = Instant::now();
488 let codec = self.lookup(fourcc)?;
489 self.run_codec(start, codec, frame)
490 }
491
492 pub fn process_named(
494 &self,
495 fourcc: FourCc,
496 impl_name: &str,
497 frame: FrameLease,
498 ) -> Result<FrameLease, RegistryError> {
499 let start = Instant::now();
500 let codec = self.lookup_named(fourcc, impl_name)?;
501 self.run_codec(start, codec, frame)
502 }
503
504 pub fn process_preferred(
506 &self,
507 fourcc: FourCc,
508 preferred_impls: &[&str],
509 prefer_hardware: bool,
510 frame: FrameLease,
511 ) -> Result<FrameLease, RegistryError> {
512 let start = Instant::now();
513 let codec = self.lookup_preferred(fourcc, preferred_impls, prefer_hardware)?;
514 self.run_codec(start, codec, frame)
515 }
516
517 pub fn set_preference(&self, fourcc: FourCc, preference: Preference) {
519 let mut guard = self.inner.write().unwrap();
520 guard.preferences.insert(fourcc, preference);
521 }
522
523 pub fn disable_impl(&self, fourcc: FourCc, impl_name: &str) {
525 let mut guard = self.inner.write().unwrap();
526 if let Some(list) = guard.codecs.get_mut(&fourcc) {
527 list.retain(|c| !c.descriptor().impl_name.eq_ignore_ascii_case(impl_name));
528 }
529 }
530
531 pub fn enable_only(&self, fourcc: FourCc, impl_names: &[&str]) {
533 let mut guard = self.inner.write().unwrap();
534 let priorities = guard.impl_priority.clone();
535 let prefer_hw = guard.default_prefer_hardware;
536 if let Some(list) = guard.codecs.get_mut(&fourcc) {
537 let names: Vec<String> = impl_names.iter().map(|s| s.to_ascii_lowercase()).collect();
538 list.retain(|c| {
539 names
540 .iter()
541 .any(|n| c.descriptor().impl_name.eq_ignore_ascii_case(n))
542 });
543 sort_backends_for(&priorities, prefer_hw, fourcc, list);
544 }
545 }
546
547 pub fn register_dynamic(&self, fourcc: FourCc, codec: Arc<dyn Codec>) {
549 let mut guard = self.inner.write().unwrap();
550 let priorities = guard.impl_priority.clone();
551 let prefer_hw = guard.default_prefer_hardware;
552 let list = guard.codecs.entry(fourcc).or_default();
553 list.push(codec);
554 sort_backends_for(&priorities, prefer_hw, fourcc, list);
555 }
556
557 pub fn set_impl_priority(&self, fourcc: FourCc, impl_name: &str, priority: i32) {
559 let mut guard = self.inner.write().unwrap();
560 guard
561 .impl_priority
562 .insert((fourcc, impl_name.to_ascii_lowercase()), priority);
563 let priorities = guard.impl_priority.clone();
564 let prefer_hw = guard.default_prefer_hardware;
565 if let Some(list) = guard.codecs.get_mut(&fourcc) {
566 sort_backends_for(&priorities, prefer_hw, fourcc, list);
567 }
568 }
569
570 pub fn set_default_hardware_bias(&self, prefer: bool) {
572 let mut guard = self.inner.write().unwrap();
573 guard.default_prefer_hardware = prefer;
574 }
575
576 pub fn set_policy(&self, policy: CodecPolicy) {
578 let mut guard = self.inner.write().unwrap();
579 guard.default_prefer_hardware = policy.prefer_hardware;
580 guard.impl_priority.extend(
581 policy
582 .priorities
583 .clone()
584 .into_iter()
585 .map(|(k, v)| ((policy.fourcc, k), v)),
586 );
587 if !policy.ordered_impls.is_empty() {
588 guard.preferences.insert(
589 policy.fourcc,
590 Preference {
591 impls: policy.ordered_impls.clone(),
592 prefer_hardware: policy.prefer_hardware,
593 },
594 );
595 }
596 guard.policies.insert(policy.fourcc, policy);
597 }
598
599 pub fn lookup_auto(&self, fourcc: FourCc) -> Result<Arc<dyn Codec>, RegistryError> {
601 let guard = self.inner.read().unwrap();
602 let list = guard
603 .codecs
604 .get(&fourcc)
605 .ok_or(RegistryError::NotFound(fourcc))?;
606
607 let policy = guard.policies.get(&fourcc);
608 let prefer_hw = policy
609 .map(|p| p.prefer_hardware)
610 .unwrap_or(guard.default_prefer_hardware);
611
612 if let Some(pref) = guard.preferences.get(&fourcc) {
614 if !pref.impls.is_empty() {
615 for name in &pref.impls {
616 if let Some(c) = list
617 .iter()
618 .find(|c| c.descriptor().impl_name.eq_ignore_ascii_case(name))
619 {
620 return Ok(c.clone());
621 }
622 }
623 }
624 if pref.prefer_hardware
625 && let Some(c) = list
626 .iter()
627 .find(|c| is_hardware_impl(c.descriptor().impl_name))
628 {
629 return Ok(c.clone());
630 }
631 }
632
633 let impl_prio = &guard.impl_priority;
635 let best = list
636 .iter()
637 .min_by_key(|c| {
638 let name = c.descriptor().impl_name.to_ascii_lowercase();
639 let prio = impl_prio
640 .get(&(fourcc, name.clone()))
641 .copied()
642 .unwrap_or(i32::MAX);
643 let hw_bias = if prefer_hw && is_hardware_impl(&name) {
644 0
645 } else {
646 1
647 };
648 (prio, hw_bias, name)
649 })
650 .cloned();
651
652 best.ok_or(RegistryError::NotFound(fourcc))
653 }
654
655 pub fn lookup_auto_kind(
657 &self,
658 fourcc: FourCc,
659 kind: CodecKind,
660 ) -> Result<Arc<dyn Codec>, RegistryError> {
661 let guard = self.inner.read().unwrap();
662 let list_all = guard
663 .codecs
664 .get(&fourcc)
665 .ok_or(RegistryError::NotFound(fourcc))?;
666 let list: Vec<&Arc<dyn Codec>> = list_all
667 .iter()
668 .filter(|c| c.descriptor().kind == kind)
669 .collect();
670 if list.is_empty() {
671 return Err(RegistryError::NotFound(fourcc));
672 }
673
674 let policy = guard.policies.get(&fourcc);
675 let prefer_hw = policy
676 .map(|p| p.prefer_hardware)
677 .unwrap_or(guard.default_prefer_hardware);
678
679 if let Some(pref) = guard.preferences.get(&fourcc) {
680 if !pref.impls.is_empty() {
681 for name in &pref.impls {
682 if let Some(c) = list
683 .iter()
684 .find(|c| c.descriptor().impl_name.eq_ignore_ascii_case(name))
685 {
686 return Ok((*c).clone());
687 }
688 }
689 }
690 if pref.prefer_hardware
691 && let Some(c) = list
692 .iter()
693 .find(|c| is_hardware_impl(c.descriptor().impl_name))
694 {
695 return Ok((*c).clone());
696 }
697 }
698
699 let impl_prio = &guard.impl_priority;
700 let best = list
701 .iter()
702 .min_by_key(|c| {
703 let name = c.descriptor().impl_name.to_ascii_lowercase();
704 let prio = impl_prio
705 .get(&(fourcc, name.clone()))
706 .copied()
707 .unwrap_or(i32::MAX);
708 let hw_bias = if prefer_hw && is_hardware_impl(&name) {
709 0
710 } else {
711 1
712 };
713 (prio, hw_bias, name)
714 })
715 .cloned();
716
717 best.cloned().ok_or(RegistryError::NotFound(fourcc))
718 }
719
720 pub fn lookup_auto_kind_by_name(
722 &self,
723 fourcc: FourCc,
724 kind: CodecKind,
725 codec_name: &str,
726 ) -> Result<Arc<dyn Codec>, RegistryError> {
727 let guard = self.inner.read().unwrap();
728 let list_all = guard
729 .codecs
730 .get(&fourcc)
731 .ok_or(RegistryError::NotFound(fourcc))?;
732 let list: Vec<&Arc<dyn Codec>> = list_all
733 .iter()
734 .filter(|c| c.descriptor().kind == kind && c.descriptor().name.eq_ignore_ascii_case(codec_name))
735 .collect();
736 if list.is_empty() {
737 return Err(RegistryError::NotFound(fourcc));
738 }
739
740 let policy = guard.policies.get(&fourcc);
741 let prefer_hw = policy
742 .map(|p| p.prefer_hardware)
743 .unwrap_or(guard.default_prefer_hardware);
744
745 if let Some(pref) = guard.preferences.get(&fourcc) {
746 if !pref.impls.is_empty() {
747 for name in &pref.impls {
748 if let Some(c) = list
749 .iter()
750 .find(|c| c.descriptor().impl_name.eq_ignore_ascii_case(name))
751 {
752 return Ok((*c).clone());
753 }
754 }
755 }
756 if pref.prefer_hardware
757 && let Some(c) = list
758 .iter()
759 .find(|c| is_hardware_impl(c.descriptor().impl_name))
760 {
761 return Ok((*c).clone());
762 }
763 }
764
765 let impl_prio = &guard.impl_priority;
766 let best = list
767 .iter()
768 .min_by_key(|c| {
769 let name = c.descriptor().impl_name.to_ascii_lowercase();
770 let prio = impl_prio
771 .get(&(fourcc, name.clone()))
772 .copied()
773 .unwrap_or(i32::MAX);
774 let hw_bias = if prefer_hw && is_hardware_impl(&name) {
775 0
776 } else {
777 1
778 };
779 (prio, hw_bias, name)
780 })
781 .cloned();
782
783 best.cloned().ok_or(RegistryError::NotFound(fourcc))
784 }
785
786 pub fn process_auto(
788 &self,
789 fourcc: FourCc,
790 frame: FrameLease,
791 ) -> Result<FrameLease, RegistryError> {
792 let start = Instant::now();
793 let codec = self.lookup_auto(fourcc)?;
794 self.run_codec(start, codec, frame)
795 }
796
797 pub fn process_auto_kind(
799 &self,
800 fourcc: FourCc,
801 kind: CodecKind,
802 frame: FrameLease,
803 ) -> Result<FrameLease, RegistryError> {
804 let start = Instant::now();
805 let codec = self.lookup_auto_kind(fourcc, kind)?;
806 self.run_codec(start, codec, frame)
807 }
808
809 pub fn process_auto_kind_by_name(
811 &self,
812 fourcc: FourCc,
813 kind: CodecKind,
814 codec_name: &str,
815 frame: FrameLease,
816 ) -> Result<FrameLease, RegistryError> {
817 let start = Instant::now();
818 let codec = self.lookup_auto_kind_by_name(fourcc, kind, codec_name)?;
819 self.run_codec(start, codec, frame)
820 }
821
822 pub fn stats(&self) -> CodecStats {
824 self.stats.clone()
825 }
826
827 pub fn list_registered(&self) -> Vec<(FourCc, Vec<CodecDescriptor>)> {
829 let guard = self.inner.read().unwrap();
830 guard
831 .codecs
832 .iter()
833 .map(|(fourcc, list)| {
834 let descs = list.iter().map(|c| c.descriptor().clone()).collect();
835 (*fourcc, descs)
836 })
837 .collect()
838 }
839
840 pub fn list_registered_by_kind(&self, kind: CodecKind) -> Vec<(FourCc, Vec<CodecDescriptor>)> {
842 self.list_registered()
843 .into_iter()
844 .filter_map(|(fcc, descs)| {
845 let filtered: Vec<_> = descs.into_iter().filter(|d| d.kind == kind).collect();
846 if filtered.is_empty() {
847 None
848 } else {
849 Some((fcc, filtered))
850 }
851 })
852 .collect()
853 }
854}
855
856impl CodecRegistryHandle {
857 fn run_codec(
858 &self,
859 start: Instant,
860 codec: Arc<dyn Codec>,
861 frame: FrameLease,
862 ) -> Result<FrameLease, RegistryError> {
863 let expected = codec.descriptor().input;
864 let actual = frame.meta().format.code;
865
866 let frame = if actual != expected {
867 if let Some(converter) = self.lookup_converter(actual, expected) {
868 match converter.process(frame) {
869 Ok(converted) => converted,
870 Err(err) => {
871 self.stats.inc_errors();
872 return Err(RegistryError::Codec(err));
873 }
874 }
875 } else {
876 frame
877 }
878 } else {
879 frame
880 };
881
882 match codec.process(frame) {
883 Ok(out) => {
884 self.stats.inc_processed();
885 self.stats.record_duration(start.elapsed());
886 Ok(out)
887 }
888 Err(err) => {
889 if matches!(err, CodecError::Backpressure) {
890 self.stats.inc_backpressure();
891 } else {
892 self.stats.inc_errors();
893 }
894 Err(RegistryError::Codec(err))
895 }
896 }
897 }
898
899 fn lookup_converter(&self, actual: FourCc, expected: FourCc) -> Option<Arc<dyn Codec>> {
900 let guard = self.inner.read().unwrap();
901 let list = guard.codecs.get(&actual)?;
902 list.iter()
903 .find(|c| c.descriptor().output == expected)
904 .cloned()
905 }
906}
907
908pub struct CodecRegistry {
919 handle: CodecRegistryHandle,
920}
921
922const DEFAULT_CODEC_MAX_WIDTH: u32 = 1920;
923const DEFAULT_CODEC_MAX_HEIGHT: u32 = 1080;
924
925impl Default for CodecRegistry {
926 fn default() -> Self {
927 Self::new()
928 }
929}
930
931impl CodecRegistry {
932 pub fn new() -> Self {
934 let inner = RegistryInner::new();
935 let handle = CodecRegistryHandle {
936 inner: Arc::new(RwLock::new(inner)),
937 stats: CodecStats::default(),
938 };
939 Self { handle }
940 }
941
942 pub fn handle(&self) -> CodecRegistryHandle {
944 self.handle.clone()
945 }
946
947 pub fn register(&self, fourcc: FourCc, codec: Arc<dyn Codec>) {
949 let mut guard = self.handle.inner.write().unwrap();
950 let priorities = guard.impl_priority.clone();
951 let prefer_hw = guard.default_prefer_hardware;
952 let list = guard.codecs.entry(fourcc).or_default();
953 list.push(codec);
954 sort_backends_for(&priorities, prefer_hw, fourcc, list);
955 }
956
957 pub fn with_enabled_codecs() -> Result<Self, CodecError> {
959 Self::with_enabled_codecs_for_max(DEFAULT_CODEC_MAX_WIDTH, DEFAULT_CODEC_MAX_HEIGHT)
960 }
961
962 pub fn with_enabled_codecs_for_max(
964 max_width: u32,
965 max_height: u32,
966 ) -> Result<Self, CodecError> {
967 let registry = Self::new();
968 registry.register_enabled_codecs(max_width, max_height)?;
969 Ok(registry)
970 }
971
972 pub fn register_enabled_codecs_default(&self) -> Result<(), CodecError> {
974 self.register_enabled_codecs(DEFAULT_CODEC_MAX_WIDTH, DEFAULT_CODEC_MAX_HEIGHT)
975 }
976
977 pub fn register_enabled_codecs(
979 &self,
980 max_width: u32,
981 max_height: u32,
982 ) -> Result<(), CodecError> {
983 let max_width = max_width.max(1);
984 let max_height = max_height.max(1);
985
986 self.register(
988 FourCc::new(*b"MJPG"),
989 Arc::new(mjpeg::MjpegDecoder::new(FourCc::new(*b"RG24"))),
990 );
991 self.register(
993 FourCc::new(*b"JPEG"),
994 Arc::new(mjpeg::MjpegDecoder::new_for_input(
995 FourCc::new(*b"JPEG"),
996 FourCc::new(*b"RG24"),
997 )),
998 );
999 self.register(
1000 FourCc::new(*b"BGR3"),
1001 Arc::new(decoder::raw::BgrToRgbDecoder::new(max_width, max_height)),
1002 );
1003 self.register(
1004 FourCc::new(*b"BGRA"),
1005 Arc::new(decoder::raw::BgraToRgbDecoder::new(max_width, max_height)),
1006 );
1007 self.register(
1008 FourCc::new(*b"RGBA"),
1009 Arc::new(decoder::raw::RgbaToRgbDecoder::new(max_width, max_height)),
1010 );
1011 self.register(
1012 FourCc::new(*b"YUYV"),
1013 Arc::new(decoder::raw::YuyvToRgbDecoder::new(max_width, max_height)),
1014 );
1015 self.register(
1016 FourCc::new(*b"YUYV"),
1017 Arc::new(decoder::raw::YuyvToLumaDecoder::new(max_width, max_height)),
1018 );
1019 self.register(
1020 FourCc::new(*b"NV12"),
1021 Arc::new(decoder::raw::Nv12ToRgbDecoder::new(max_width, max_height)),
1022 );
1023 self.register(
1024 FourCc::new(*b"I420"),
1025 Arc::new(decoder::raw::I420ToRgbDecoder::new(max_width, max_height)),
1026 );
1027 self.register(
1028 FourCc::new(*b"YU12"),
1029 Arc::new(decoder::raw::Yuv420pToRgbDecoder::new(
1030 FourCc::new(*b"YU12"),
1031 "yu12-cpu",
1032 true,
1033 max_width,
1034 max_height,
1035 )),
1036 );
1037 self.register(
1038 FourCc::new(*b"YV12"),
1039 Arc::new(decoder::raw::Yuv420pToRgbDecoder::new(
1040 FourCc::new(*b"YV12"),
1041 "yv12-cpu",
1042 false,
1043 max_width,
1044 max_height,
1045 )),
1046 );
1047 self.register(
1048 FourCc::new(*b"R8 "),
1049 Arc::new(decoder::raw::Mono8ToRgbDecoder::new(max_width, max_height)),
1050 );
1051 self.register(
1052 FourCc::new(*b"R16 "),
1053 Arc::new(decoder::raw::Mono16ToRgbDecoder::new(max_width, max_height)),
1054 );
1055
1056 self.register(
1058 FourCc::new(*b"NV21"),
1059 Arc::new(decoder::raw::NvToRgbDecoder::new(
1060 FourCc::new(*b"NV21"),
1061 "nv21-cpu",
1062 2,
1063 2,
1064 false,
1065 max_width,
1066 max_height,
1067 )),
1068 );
1069 self.register(
1070 FourCc::new(*b"NV16"),
1071 Arc::new(decoder::raw::NvToRgbDecoder::new(
1072 FourCc::new(*b"NV16"),
1073 "nv16-cpu",
1074 2,
1075 1,
1076 true,
1077 max_width,
1078 max_height,
1079 )),
1080 );
1081 self.register(
1082 FourCc::new(*b"NV61"),
1083 Arc::new(decoder::raw::NvToRgbDecoder::new(
1084 FourCc::new(*b"NV61"),
1085 "nv61-cpu",
1086 2,
1087 1,
1088 false,
1089 max_width,
1090 max_height,
1091 )),
1092 );
1093 self.register(
1094 FourCc::new(*b"NV24"),
1095 Arc::new(decoder::raw::NvToRgbDecoder::new(
1096 FourCc::new(*b"NV24"),
1097 "nv24-cpu",
1098 1,
1099 1,
1100 true,
1101 max_width,
1102 max_height,
1103 )),
1104 );
1105 self.register(
1106 FourCc::new(*b"NV42"),
1107 Arc::new(decoder::raw::NvToRgbDecoder::new(
1108 FourCc::new(*b"NV42"),
1109 "nv42-cpu",
1110 1,
1111 1,
1112 false,
1113 max_width,
1114 max_height,
1115 )),
1116 );
1117
1118 for code in [*b"RG24", *b"RGB3", *b"RGB6"] {
1120 self.register(
1121 FourCc::new(code),
1122 Arc::new(decoder::raw::PassthroughDecoder::new(FourCc::new(code))),
1123 );
1124 }
1125
1126 let bayer_codes = [
1128 *b"BA81", *b"BA10", *b"BA12", *b"BA14", *b"BG10", *b"BG12", *b"BG14", *b"BG16",
1129 *b"GB10", *b"GB12", *b"GB14", *b"GB16", *b"RG10", *b"RG12", *b"RG14", *b"RG16",
1130 *b"GR10", *b"GR12", *b"GR14", *b"GR16", *b"BYR2", *b"RGGB", *b"GRBG", *b"GBRG",
1131 *b"BGGR", *b"pBAA", *b"pGAA", *b"pgAA", *b"pRAA", *b"pBCC", *b"pGCC", *b"pgCC", *b"pRCC",
1133 ];
1134 for code in bayer_codes {
1135 let fcc = FourCc::new(code);
1136 if let Some(info) = decoder::raw::bayer_info(fcc) {
1137 self.register(
1138 fcc,
1139 decoder::raw::bayer_decoder_for(fcc, info, max_width, max_height),
1140 );
1141 }
1142 }
1143
1144 self.register(
1145 FourCc::new(*b"YV12"),
1146 Arc::new(decoder::raw::PlanarYuvToRgbDecoder::new(
1147 FourCc::new(*b"YV12"),
1148 "yv12-cpu",
1149 2,
1150 2,
1151 false,
1152 max_width,
1153 max_height,
1154 )),
1155 );
1156 self.register(
1157 FourCc::new(*b"YU16"),
1158 Arc::new(decoder::raw::PlanarYuvToRgbDecoder::new(
1159 FourCc::new(*b"YU16"),
1160 "yu16-cpu",
1161 2,
1162 1,
1163 true,
1164 max_width,
1165 max_height,
1166 )),
1167 );
1168 self.register(
1169 FourCc::new(*b"YV16"),
1170 Arc::new(decoder::raw::PlanarYuvToRgbDecoder::new(
1171 FourCc::new(*b"YV16"),
1172 "yv16-cpu",
1173 2,
1174 1,
1175 false,
1176 max_width,
1177 max_height,
1178 )),
1179 );
1180 self.register(
1181 FourCc::new(*b"YU24"),
1182 Arc::new(decoder::raw::PlanarYuvToRgbDecoder::new(
1183 FourCc::new(*b"YU24"),
1184 "yu24-cpu",
1185 1,
1186 1,
1187 true,
1188 max_width,
1189 max_height,
1190 )),
1191 );
1192 self.register(
1193 FourCc::new(*b"YV24"),
1194 Arc::new(decoder::raw::PlanarYuvToRgbDecoder::new(
1195 FourCc::new(*b"YV24"),
1196 "yv24-cpu",
1197 1,
1198 1,
1199 false,
1200 max_width,
1201 max_height,
1202 )),
1203 );
1204
1205 self.register(
1206 FourCc::new(*b"YVYU"),
1207 Arc::new(decoder::raw::Packed422ToRgbDecoder::new(
1208 FourCc::new(*b"YVYU"),
1209 "yvyu-cpu",
1210 [0, 3, 2, 1],
1211 max_width,
1212 max_height,
1213 )),
1214 );
1215 self.register(
1216 FourCc::new(*b"UYVY"),
1217 Arc::new(decoder::raw::Packed422ToRgbDecoder::new(
1218 FourCc::new(*b"UYVY"),
1219 "uyvy-cpu",
1220 [1, 0, 3, 2],
1221 max_width,
1222 max_height,
1223 )),
1224 );
1225 self.register(
1226 FourCc::new(*b"VYUY"),
1227 Arc::new(decoder::raw::Packed422ToRgbDecoder::new(
1228 FourCc::new(*b"VYUY"),
1229 "vyuy-cpu",
1230 [1, 2, 3, 0],
1231 max_width,
1232 max_height,
1233 )),
1234 );
1235
1236 self.register(
1238 FourCc::new(*b"BG24"),
1239 Arc::new(decoder::raw::BgrToRgbDecoder::with_input_for_max(
1240 FourCc::new(*b"BG24"),
1241 "bg24-swap",
1242 max_width,
1243 max_height,
1244 )),
1245 );
1246 self.register(
1247 FourCc::new(*b"XB24"),
1248 Arc::new(decoder::raw::BgraToRgbDecoder::with_input_for_max(
1249 FourCc::new(*b"XB24"),
1250 "xb24-strip",
1251 max_width,
1252 max_height,
1253 )),
1254 );
1255 self.register(
1256 FourCc::new(*b"XR24"),
1257 Arc::new(decoder::raw::RgbaToRgbDecoder::with_input_for_max(
1258 FourCc::new(*b"XR24"),
1259 "xr24-strip",
1260 max_width,
1261 max_height,
1262 )),
1263 );
1264
1265 self.register(
1267 FourCc::new(*b"BG48"),
1268 Arc::new(decoder::raw::Rgb48ToRgbDecoder::new(
1269 FourCc::new(*b"BG48"),
1270 "bg48-strip",
1271 true,
1272 max_width,
1273 max_height,
1274 )),
1275 );
1276 self.register(
1277 FourCc::new(*b"RG48"),
1278 Arc::new(decoder::raw::Rgb48ToRgbDecoder::new(
1279 FourCc::new(*b"RG48"),
1280 "rg48-strip",
1281 false,
1282 max_width,
1283 max_height,
1284 )),
1285 );
1286
1287 #[cfg(feature = "image")]
1289 self.register(
1290 FourCc::new(*b"ANY "),
1291 Arc::new(image_any::ImageAnyDecoder::new(FourCc::new(*b"RGBA"))),
1292 );
1293
1294 #[cfg(feature = "codec-ffmpeg")]
1296 {
1297 use crate::ffmpeg::{
1298 FfmpegEncoderOptions, FfmpegH264Decoder, FfmpegH264Encoder, FfmpegH265Decoder,
1299 FfmpegH265Encoder, FfmpegMjpegDecoder, FfmpegMjpegEncoder,
1300 };
1301 let default_decoder_threads = std::env::var("STYX_FFMPEG_DECODER_THREADS")
1302 .ok()
1303 .and_then(|v| v.parse::<usize>().ok())
1304 .filter(|v| *v > 0);
1305 let mjpeg_mjpg = Arc::new(FfmpegMjpegDecoder::with_options_for_input(
1306 FourCc::new(*b"MJPG"),
1307 false,
1308 default_decoder_threads,
1309 None,
1310 )?);
1311 self.register(FourCc::new(*b"MJPG"), mjpeg_mjpg);
1312 let mjpeg_jpeg = Arc::new(FfmpegMjpegDecoder::with_options_for_input(
1314 FourCc::new(*b"JPEG"),
1315 false,
1316 default_decoder_threads,
1317 None,
1318 )?);
1319 self.register(FourCc::new(*b"JPEG"), mjpeg_jpeg);
1320 self.register(
1321 FourCc::new(*b"H264"),
1322 Arc::new(FfmpegH264Decoder::with_options(
1323 false,
1324 default_decoder_threads,
1325 None,
1326 )?),
1327 );
1328 if let Ok(dec) = FfmpegH264Decoder::new_v4l2request_nv12_zero_copy() {
1329 self.register(FourCc::new(*b"H264"), Arc::new(dec));
1330 }
1331 let h265 = Arc::new(FfmpegH265Decoder::with_options_for_input(
1332 FourCc::new(*b"H265"),
1333 false,
1334 default_decoder_threads,
1335 None,
1336 )?);
1337 self.register(FourCc::new(*b"H265"), h265);
1338 let hevc = Arc::new(FfmpegH265Decoder::with_options_for_input(
1340 FourCc::new(*b"HEVC"),
1341 false,
1342 default_decoder_threads,
1343 None,
1344 )?);
1345 self.register(FourCc::new(*b"HEVC"), hevc);
1346 if let Ok(dec) = FfmpegH265Decoder::new_v4l2request_nv12_zero_copy() {
1347 let dec = Arc::new(dec);
1348 self.register(FourCc::new(*b"H265"), dec.clone());
1349 }
1352 self.register(
1353 FourCc::new(*b"RG24"),
1354 Arc::new(FfmpegMjpegEncoder::new_rgb24()?),
1355 );
1356 self.register(
1357 FourCc::new(*b"NV12"),
1358 Arc::new(FfmpegMjpegEncoder::new_nv12()?),
1359 );
1360 self.register(
1361 FourCc::new(*b"YUYV"),
1362 Arc::new(FfmpegMjpegEncoder::with_options_for_input(
1363 FourCc::new(*b"YUYV"),
1364 FfmpegEncoderOptions::default(),
1365 )?),
1366 );
1367 self.register(
1368 FourCc::new(*b"RG24"),
1369 Arc::new(FfmpegH264Encoder::new_rgb24()?),
1370 );
1371 self.register(
1372 FourCc::new(*b"NV12"),
1373 Arc::new(FfmpegH264Encoder::new_nv12()?),
1374 );
1375 if let Ok(enc) = FfmpegH264Encoder::new_v4l2m2m_rgb24() {
1376 self.register(FourCc::new(*b"RG24"), Arc::new(enc));
1377 }
1378 if let Ok(enc) = FfmpegH264Encoder::new_v4l2m2m_nv12() {
1379 self.register(FourCc::new(*b"NV12"), Arc::new(enc));
1380 }
1381 self.register(
1382 FourCc::new(*b"YUYV"),
1383 Arc::new(FfmpegH264Encoder::with_options_for_input(
1384 FourCc::new(*b"YUYV"),
1385 FfmpegEncoderOptions::default(),
1386 )?),
1387 );
1388 self.register(
1389 FourCc::new(*b"RG24"),
1390 Arc::new(FfmpegH265Encoder::new_rgb24()?),
1391 );
1392 self.register(
1393 FourCc::new(*b"NV12"),
1394 Arc::new(FfmpegH265Encoder::new_nv12()?),
1395 );
1396 if let Ok(enc) = FfmpegH265Encoder::new_v4l2m2m_rgb24() {
1397 self.register(FourCc::new(*b"RG24"), Arc::new(enc));
1398 }
1399 if let Ok(enc) = FfmpegH265Encoder::new_v4l2m2m_nv12() {
1400 self.register(FourCc::new(*b"NV12"), Arc::new(enc));
1401 }
1402 self.register(
1403 FourCc::new(*b"YUYV"),
1404 Arc::new(FfmpegH265Encoder::with_options_for_input(
1405 FourCc::new(*b"YUYV"),
1406 FfmpegEncoderOptions::default(),
1407 )?),
1408 );
1409 }
1410
1411 #[cfg(feature = "codec-mozjpeg")]
1413 self.register(
1414 FourCc::new(*b"RG24"),
1415 Arc::new(jpeg_encoder::MozjpegEncoder::new(FourCc::new(*b"RG24"), 85)),
1416 );
1417
1418 #[cfg(feature = "codec-turbojpeg")]
1420 self.register(
1421 FourCc::new(*b"MJPG"),
1422 Arc::new(mjpeg_turbojpeg::TurbojpegDecoder::new(FourCc::new(
1423 *b"RG24",
1424 ))),
1425 );
1426
1427 #[cfg(feature = "codec-zune")]
1429 self.register(
1430 FourCc::new(*b"MJPG"),
1431 Arc::new(mjpeg_zune::ZuneMjpegDecoder::new(FourCc::new(*b"RG24"))),
1432 );
1433
1434 Ok(())
1435 }
1436
1437 pub fn list_enabled_codecs() -> Result<Vec<(FourCc, Vec<CodecDescriptor>)>, CodecError> {
1439 let registry = CodecRegistry::with_enabled_codecs()?;
1440 Ok(registry.handle.list_registered())
1441 }
1442
1443 pub fn list_enabled_decoders() -> Result<Vec<(FourCc, Vec<CodecDescriptor>)>, CodecError> {
1445 let registry = CodecRegistry::with_enabled_codecs()?;
1446 Ok(registry.handle.list_registered_by_kind(CodecKind::Decoder))
1447 }
1448
1449 pub fn list_enabled_encoders() -> Result<Vec<(FourCc, Vec<CodecDescriptor>)>, CodecError> {
1451 let registry = CodecRegistry::with_enabled_codecs()?;
1452 Ok(registry.handle.list_registered_by_kind(CodecKind::Encoder))
1453 }
1454}
1455
1456#[derive(Clone, Debug, Default)]
1466#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1467#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
1468pub struct Preference {
1469 pub impls: Vec<String>,
1471 pub prefer_hardware: bool,
1473}
1474
1475impl Preference {
1476 pub fn hardware_biased(impls: Vec<String>) -> Self {
1477 Self {
1478 impls,
1479 prefer_hardware: true,
1480 }
1481 }
1482}
1483
1484#[derive(Clone, Debug)]
1496#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1497#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
1498pub struct CodecPolicy {
1499 fourcc: FourCc,
1500 prefer_hardware: bool,
1501 ordered_impls: Vec<String>,
1502 priorities: std::collections::HashMap<String, i32>,
1503}
1504
1505impl CodecPolicy {
1506 pub fn builder(fourcc: FourCc) -> CodecPolicyBuilder {
1508 CodecPolicyBuilder {
1509 fourcc,
1510 prefer_hardware: true,
1511 ordered_impls: Vec::new(),
1512 priorities: std::collections::HashMap::new(),
1513 }
1514 }
1515}
1516
1517pub struct CodecPolicyBuilder {
1530 fourcc: FourCc,
1531 prefer_hardware: bool,
1532 ordered_impls: Vec<String>,
1533 priorities: std::collections::HashMap<String, i32>,
1534}
1535
1536impl CodecPolicyBuilder {
1537 pub fn prefer_hardware(mut self, prefer: bool) -> Self {
1539 self.prefer_hardware = prefer;
1540 self
1541 }
1542
1543 pub fn ordered_impls<I, S>(mut self, impls: I) -> Self
1545 where
1546 I: IntoIterator<Item = S>,
1547 S: Into<String>,
1548 {
1549 self.ordered_impls = impls.into_iter().map(|s| s.into()).collect();
1550 self
1551 }
1552
1553 pub fn priority<S: Into<String>>(mut self, impl_name: S, priority: i32) -> Self {
1555 self.priorities
1556 .insert(impl_name.into().to_ascii_lowercase(), priority);
1557 self
1558 }
1559
1560 pub fn build(self) -> CodecPolicy {
1561 CodecPolicy {
1562 fourcc: self.fourcc,
1563 prefer_hardware: self.prefer_hardware,
1564 ordered_impls: self.ordered_impls,
1565 priorities: self.priorities,
1566 }
1567 }
1568}
1569
1570pub mod decoder;
1571pub mod encoder;
1572#[cfg(feature = "codec-ffmpeg")]
1573pub mod ffmpeg;
1574#[cfg(feature = "image")]
1575pub mod image_any;
1576#[cfg(feature = "image")]
1577pub mod image_utils;
1578#[cfg(feature = "codec-mozjpeg")]
1579pub mod jpeg_encoder;
1580pub mod mjpeg;
1581#[cfg(feature = "codec-turbojpeg")]
1582pub mod mjpeg_turbojpeg;
1583#[cfg(feature = "codec-zune")]
1584pub mod mjpeg_zune;
1585
1586pub mod prelude {
1587 pub use crate::decoder::raw::{
1588 BgrToRgbDecoder, BgraToRgbDecoder, I420ToRgbDecoder, Nv12ToBgrDecoder, Nv12ToRgbDecoder,
1589 PassthroughDecoder, RgbaToRgbDecoder, YuyvToRgbDecoder,
1590 };
1591 #[cfg(feature = "codec-ffmpeg")]
1592 pub use crate::ffmpeg::{
1593 FfmpegH264Decoder, FfmpegH264Encoder, FfmpegH265Decoder, FfmpegH265Encoder,
1594 FfmpegMjpegDecoder, FfmpegMjpegEncoder,
1595 };
1596 #[cfg(feature = "image")]
1597 pub use crate::image_any::ImageAnyDecoder;
1598 #[cfg(feature = "image")]
1599 pub use crate::image_utils::{CodecImageExt, dynamic_image_to_frame};
1600 #[cfg(feature = "codec-mozjpeg")]
1601 pub use crate::jpeg_encoder::MozjpegEncoder;
1602 #[cfg(feature = "codec-turbojpeg")]
1603 pub use crate::mjpeg_turbojpeg::TurbojpegDecoder;
1604 #[cfg(feature = "codec-zune")]
1605 pub use crate::mjpeg_zune::ZuneMjpegDecoder;
1606 pub use crate::{
1607 Codec, CodecDescriptor, CodecError, CodecKind, CodecPolicy, CodecPolicyBuilder,
1608 CodecRegistry, CodecRegistryHandle, CodecStats, RegistryError, mjpeg::MjpegDecoder,
1609 };
1610 pub use styx_capture::prelude::*;
1611 #[allow(unused_imports)]
1612 pub use styx_core::prelude::*;
1613}
1614
1615#[cfg(test)]
1616mod tests {
1617 use super::*;
1618 use std::sync::Arc;
1619
1620 struct Rg24Passthrough {
1621 descriptor: CodecDescriptor,
1622 }
1623
1624 impl Default for Rg24Passthrough {
1625 fn default() -> Self {
1626 Self {
1627 descriptor: CodecDescriptor {
1628 kind: CodecKind::Encoder,
1629 input: FourCc::new(*b"RG24"),
1630 output: FourCc::new(*b"RG24"),
1631 name: "passthrough",
1632 impl_name: "test",
1633 },
1634 }
1635 }
1636 }
1637
1638 impl Codec for Rg24Passthrough {
1639 fn descriptor(&self) -> &CodecDescriptor {
1640 &self.descriptor
1641 }
1642
1643 fn process(&self, input: FrameLease) -> Result<FrameLease, CodecError> {
1644 if input.meta().format.code != self.descriptor.input {
1645 return Err(CodecError::FormatMismatch {
1646 expected: self.descriptor.input,
1647 actual: input.meta().format.code,
1648 });
1649 }
1650 Ok(input)
1651 }
1652 }
1653
1654 #[test]
1655 fn auto_converts_rgba_to_rg24_for_rg24_codecs() {
1656 let registry = CodecRegistry::with_enabled_codecs_for_max(8, 8).expect("registry");
1657 registry.register(FourCc::new(*b"RG24"), Arc::new(Rg24Passthrough::default()));
1658 let handle = registry.handle();
1659
1660 let res = Resolution::new(2, 2).unwrap();
1661 let layout = plane_layout_from_dims(res.width, res.height, 4);
1662 let pool = BufferPool::with_limits(1, layout.len, 4);
1663 let mut buf = pool.lease();
1664 buf.resize(layout.len);
1665 for (i, b) in buf.as_mut_slice().iter_mut().enumerate() {
1666 *b = i as u8;
1667 }
1668 let format = MediaFormat::new(FourCc::new(*b"RGBA"), res, ColorSpace::Srgb);
1669 let frame =
1670 FrameLease::single_plane(FrameMeta::new(format, 0), buf, layout.len, layout.stride);
1671
1672 let out = handle
1673 .process_named(FourCc::new(*b"RG24"), "test", frame)
1674 .expect("process");
1675 assert_eq!(out.meta().format.code, FourCc::new(*b"RG24"));
1676 assert_eq!(out.meta().format.resolution.width.get(), 2);
1677 assert_eq!(out.meta().format.resolution.height.get(), 2);
1678
1679 let data = out.planes().first().unwrap().data();
1680 assert_eq!(data.len(), 2 * 2 * 3);
1681
1682 let expected: Vec<u8> = (0u8..16)
1683 .collect::<Vec<_>>()
1684 .chunks_exact(4)
1685 .flat_map(|px| px[..3].iter().copied())
1686 .collect();
1687 assert_eq!(data, expected.as_slice());
1688 }
1689}