1use alloc::vec::Vec;
14
15use crate::caps::{AudioFormat, Caps, CapsSet, Dim, PassthroughFields, Rate, RawVideoFormat};
16
17#[derive(Clone, Debug, PartialEq)]
25pub enum FieldTransform<T> {
26 Identity,
29 Fixed(T),
31}
32
33impl<T: Clone> FieldTransform<T> {
34 pub fn apply(&self, input: &T) -> T {
36 match self {
37 Self::Identity => input.clone(),
38 Self::Fixed(v) => v.clone(),
39 }
40 }
41}
42
43impl<T> FieldTransform<T> {
44 pub fn is_identity(&self) -> bool {
45 matches!(self, Self::Identity)
46 }
47}
48
49#[derive(Clone, Debug, PartialEq)]
51pub struct RawVideoShape {
52 pub format: FieldTransform<RawVideoFormat>,
53 pub width: FieldTransform<Dim>,
54 pub height: FieldTransform<Dim>,
55 pub framerate: FieldTransform<Rate>,
56}
57
58impl RawVideoShape {
59 pub const PASSTHROUGH: Self = Self {
62 format: FieldTransform::Identity,
63 width: FieldTransform::Identity,
64 height: FieldTransform::Identity,
65 framerate: FieldTransform::Identity,
66 };
67
68 pub fn with_format(mut self, t: FieldTransform<RawVideoFormat>) -> Self {
69 self.format = t;
70 self
71 }
72 pub fn with_width(mut self, t: FieldTransform<Dim>) -> Self {
73 self.width = t;
74 self
75 }
76 pub fn with_height(mut self, t: FieldTransform<Dim>) -> Self {
77 self.height = t;
78 self
79 }
80 pub fn with_framerate(mut self, t: FieldTransform<Rate>) -> Self {
81 self.framerate = t;
82 self
83 }
84}
85
86#[derive(Clone, Debug, PartialEq)]
88pub struct AudioShape {
89 pub format: FieldTransform<AudioFormat>,
90 pub channels: FieldTransform<u8>,
91 pub sample_rate: FieldTransform<u32>,
92}
93
94impl AudioShape {
95 pub const PASSTHROUGH: Self = Self {
97 format: FieldTransform::Identity,
98 channels: FieldTransform::Identity,
99 sample_rate: FieldTransform::Identity,
100 };
101
102 pub fn with_format(mut self, t: FieldTransform<AudioFormat>) -> Self {
103 self.format = t;
104 self
105 }
106 pub fn with_channels(mut self, t: FieldTransform<u8>) -> Self {
107 self.channels = t;
108 self
109 }
110 pub fn with_sample_rate(mut self, t: FieldTransform<u32>) -> Self {
111 self.sample_rate = t;
112 self
113 }
114}
115
116#[derive(Clone, Debug, PartialEq)]
127pub enum CapsTransform {
128 RawVideo {
129 accept: Vec<RawVideoFormat>,
130 produce: Vec<RawVideoFormat>,
131 shapes: Vec<RawVideoShape>,
132 },
133 Audio {
134 accept: Vec<AudioFormat>,
135 produce: Vec<AudioFormat>,
136 shapes: Vec<AudioShape>,
137 },
138}
139
140impl CapsTransform {
141 pub fn derive(&self, input: &Caps) -> CapsSet {
143 let mut alts: Vec<Caps> = Vec::new();
144 match (self, input) {
145 (
146 Self::RawVideo {
147 accept,
148 produce,
149 shapes,
150 },
151 Caps::RawVideo {
152 format,
153 width,
154 height,
155 framerate,
156 interlace,
157 },
158 ) => {
159 if !accept.is_empty() && !accept.contains(format) {
160 return CapsSet::from_alternatives(Vec::new());
161 }
162 for s in shapes {
163 let f = s.format.apply(format);
164 if !produce.is_empty() && !produce.contains(&f) {
165 continue;
166 }
167 push_unique(
168 &mut alts,
169 Caps::RawVideo {
170 format: f,
171 width: s.width.apply(width),
172 height: s.height.apply(height),
173 framerate: s.framerate.apply(framerate),
174 interlace: *interlace,
176 },
177 );
178 }
179 }
180 (
181 Self::Audio {
182 accept,
183 produce,
184 shapes,
185 },
186 Caps::Audio {
187 format,
188 channels,
189 sample_rate,
190 },
191 ) => {
192 if !accept.is_empty() && !accept.contains(format) {
193 return CapsSet::from_alternatives(Vec::new());
194 }
195 for s in shapes {
196 let f = s.format.apply(format);
197 if !produce.is_empty() && !produce.contains(&f) {
198 continue;
199 }
200 push_unique(
201 &mut alts,
202 Caps::Audio {
203 format: f,
204 channels: s.channels.apply(channels),
205 sample_rate: s.sample_rate.apply(sample_rate),
206 },
207 );
208 }
209 }
210 _ => {}
211 }
212 CapsSet::from_alternatives(alts)
213 }
214
215 pub fn passthrough(&self) -> PassthroughFields {
220 match self {
221 Self::RawVideo { shapes, .. } if !shapes.is_empty() => PassthroughFields {
222 format: shapes.iter().all(|s| s.format.is_identity()),
223 width: shapes.iter().all(|s| s.width.is_identity()),
224 height: shapes.iter().all(|s| s.height.is_identity()),
225 framerate: shapes.iter().all(|s| s.framerate.is_identity()),
226 channels: false,
227 sample_rate: false,
228 },
229 Self::Audio { shapes, .. } if !shapes.is_empty() => PassthroughFields {
230 format: shapes.iter().all(|s| s.format.is_identity()),
231 width: false,
232 height: false,
233 framerate: false,
234 channels: shapes.iter().all(|s| s.channels.is_identity()),
235 sample_rate: shapes.iter().all(|s| s.sample_rate.is_identity()),
236 },
237 _ => PassthroughFields::NONE,
238 }
239 }
240}
241
242fn push_unique(alts: &mut Vec<Caps>, c: Caps) {
243 if !alts.contains(&c) {
244 alts.push(c);
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use crate::caps::ANY_SAMPLE_RATE;
252 use alloc::vec;
253
254 fn raw(format: RawVideoFormat, w: u32, h: u32, fps: u32) -> Caps {
255 Caps::RawVideo {
256 format,
257 width: Dim::Fixed(w),
258 height: Dim::Fixed(h),
259 framerate: Rate::Fixed(fps << 16),
260 interlace: crate::Interlace::Any,
261 }
262 }
263
264 fn pcm(format: AudioFormat, channels: u8, sample_rate: u32) -> Caps {
265 Caps::Audio {
266 format,
267 channels,
268 sample_rate,
269 }
270 }
271
272 fn video(accept: &[RawVideoFormat], shapes: Vec<RawVideoShape>) -> CapsTransform {
273 CapsTransform::RawVideo {
274 accept: accept.to_vec(),
275 produce: Vec::new(),
276 shapes,
277 }
278 }
279
280 #[test]
281 fn identity_shape_derives_the_input_and_couples_every_field() {
282 let t = video(&[RawVideoFormat::Nv12], vec![RawVideoShape::PASSTHROUGH]);
283 let inp = raw(RawVideoFormat::Nv12, 320, 240, 30);
284 assert_eq!(t.derive(&inp).alternatives(), core::slice::from_ref(&inp));
285 let pt = t.passthrough();
286 assert!(pt.format && pt.width && pt.height && pt.framerate);
287 }
288
289 #[test]
290 fn fixed_shape_retargets_the_field_and_drops_it_from_the_mask() {
291 let t = video(
292 &[RawVideoFormat::Nv12],
293 vec![RawVideoShape::PASSTHROUGH
294 .with_width(FieldTransform::Fixed(Dim::Fixed(64)))
295 .with_height(FieldTransform::Fixed(Dim::Fixed(32)))],
296 );
297 assert_eq!(
298 t.derive(&raw(RawVideoFormat::Nv12, 320, 240, 30))
299 .alternatives(),
300 &[raw(RawVideoFormat::Nv12, 64, 32, 30)]
301 );
302 let pt = t.passthrough();
303 assert!(
304 pt.format && pt.framerate,
305 "format + rate still pass through"
306 );
307 assert!(
308 !pt.width && !pt.height,
309 "retargeted geometry is not coupled"
310 );
311 }
312
313 #[test]
314 fn unaccepted_input_derives_nothing() {
315 let t = video(&[RawVideoFormat::Nv12], vec![RawVideoShape::PASSTHROUGH]);
316 assert!(t
317 .derive(&raw(RawVideoFormat::Rgba8, 320, 240, 30))
318 .is_empty());
319 assert!(t.derive(&pcm(AudioFormat::PcmS16Le, 2, 48_000)).is_empty());
321 let any = video(&[], vec![RawVideoShape::PASSTHROUGH]);
323 assert!(!any
324 .derive(&raw(RawVideoFormat::Rgba8, 320, 240, 30))
325 .is_empty());
326 }
327
328 #[test]
329 fn produce_gate_drops_an_input_only_passthrough() {
330 let t = CapsTransform::RawVideo {
333 accept: vec![RawVideoFormat::Yuyv, RawVideoFormat::Nv12],
334 produce: vec![RawVideoFormat::Nv12],
335 shapes: vec![
336 RawVideoShape::PASSTHROUGH,
337 RawVideoShape::PASSTHROUGH.with_format(FieldTransform::Fixed(RawVideoFormat::Nv12)),
338 ],
339 };
340 assert_eq!(
341 t.derive(&raw(RawVideoFormat::Yuyv, 320, 240, 30))
342 .alternatives(),
343 &[raw(RawVideoFormat::Nv12, 320, 240, 30)]
344 );
345 assert_eq!(
348 t.derive(&raw(RawVideoFormat::Nv12, 320, 240, 30))
349 .alternatives(),
350 &[raw(RawVideoFormat::Nv12, 320, 240, 30)]
351 );
352 }
353
354 #[test]
355 fn audio_shapes_derive_channels_and_rate() {
356 let t = CapsTransform::Audio {
357 accept: vec![AudioFormat::PcmS16Le],
358 produce: Vec::new(),
359 shapes: vec![
360 AudioShape::PASSTHROUGH,
361 AudioShape::PASSTHROUGH.with_sample_rate(FieldTransform::Fixed(ANY_SAMPLE_RATE)),
362 ],
363 };
364 assert_eq!(
365 t.derive(&pcm(AudioFormat::PcmS16Le, 2, 44_100))
366 .alternatives(),
367 &[
368 pcm(AudioFormat::PcmS16Le, 2, 44_100),
369 pcm(AudioFormat::PcmS16Le, 2, ANY_SAMPLE_RATE)
370 ]
371 );
372 let pt = t.passthrough();
373 assert!(pt.format && pt.channels);
374 assert!(!pt.sample_rate, "one alternative retargets the rate");
375 }
376
377 #[test]
378 fn no_shapes_derives_nothing_and_couples_nothing() {
379 let t = video(&[RawVideoFormat::Nv12], Vec::new());
382 assert!(t
383 .derive(&raw(RawVideoFormat::Nv12, 320, 240, 30))
384 .is_empty());
385 assert_eq!(t.passthrough(), PassthroughFields::NONE);
386 }
387}