1 | use std::ops::{Bound::*, RangeBounds}; |
2 | |
3 | use gst::Caps; |
4 | |
5 | use glib::IntoGStr; |
6 | |
7 | use crate::{AudioFormat, AudioLayout}; |
8 | |
9 | pub struct AudioCapsBuilder<T> { |
10 | builder: gst::caps::Builder<T>, |
11 | } |
12 | |
13 | impl AudioCapsBuilder<gst::caps::NoFeature> { |
14 | // rustdoc-stripper-ignore-next |
15 | /// Constructs an `AudioCapsBuilder` for the "audio/x-raw" encoding. |
16 | /// |
17 | /// If left unchanged, the resulting `Caps` will be initialized with: |
18 | /// - "audio/x-raw" encoding. |
19 | /// - maximum rate range. |
20 | /// - maximum channels range. |
21 | /// - both interleaved and non-interleaved layouts. |
22 | /// - all available formats. |
23 | /// |
24 | /// Use [`AudioCapsBuilder::for_encoding`] to specify another encoding. |
25 | pub fn new() -> Self { |
26 | assert_initialized_main_thread!(); |
27 | let builder = Caps::builder(glib::gstr!("audio/x-raw" )); |
28 | let builder = AudioCapsBuilder { builder }; |
29 | builder |
30 | .rate_range(..) |
31 | .channels_range(..) |
32 | .layout_list([AudioLayout::Interleaved, AudioLayout::NonInterleaved]) |
33 | .format_list(AudioFormat::iter_raw()) |
34 | } |
35 | |
36 | // rustdoc-stripper-ignore-next |
37 | /// Constructs an `AudioCapsBuilder` for the "audio/x-raw" encoding |
38 | /// with interleaved layout. |
39 | /// |
40 | /// If left unchanged, the resulting `Caps` will be initialized with: |
41 | /// - "audio/x-raw" encoding. |
42 | /// - maximum rate range. |
43 | /// - maximum channels range. |
44 | /// - interleaved layout. |
45 | /// - all available formats. |
46 | /// |
47 | /// Use [`AudioCapsBuilder::for_encoding`] to specify another encoding. |
48 | pub fn new_interleaved() -> Self { |
49 | AudioCapsBuilder::new().layout(AudioLayout::Interleaved) |
50 | } |
51 | |
52 | // rustdoc-stripper-ignore-next |
53 | /// Constructs an `AudioCapsBuilder` for the specified encoding. |
54 | /// |
55 | /// The resulting `Caps` will use the `encoding` argument as name |
56 | /// and will not contain any additional fields unless explicitly added. |
57 | pub fn for_encoding(encoding: impl IntoGStr) -> Self { |
58 | assert_initialized_main_thread!(); |
59 | AudioCapsBuilder { |
60 | builder: Caps::builder(encoding), |
61 | } |
62 | } |
63 | |
64 | pub fn any_features(self) -> AudioCapsBuilder<gst::caps::HasFeatures> { |
65 | AudioCapsBuilder { |
66 | builder: self.builder.any_features(), |
67 | } |
68 | } |
69 | |
70 | pub fn features( |
71 | self, |
72 | features: impl IntoIterator<Item = impl IntoGStr>, |
73 | ) -> AudioCapsBuilder<gst::caps::HasFeatures> { |
74 | AudioCapsBuilder { |
75 | builder: self.builder.features(features), |
76 | } |
77 | } |
78 | } |
79 | |
80 | impl Default for AudioCapsBuilder<gst::caps::NoFeature> { |
81 | fn default() -> Self { |
82 | Self::new() |
83 | } |
84 | } |
85 | |
86 | impl<T> AudioCapsBuilder<T> { |
87 | pub fn format(self, format: AudioFormat) -> Self { |
88 | Self { |
89 | builder: self.builder.field(glib::gstr!("format" ), format.to_str()), |
90 | } |
91 | } |
92 | |
93 | pub fn format_if(self, format: AudioFormat, predicate: bool) -> Self { |
94 | if predicate { |
95 | self.format(format) |
96 | } else { |
97 | self |
98 | } |
99 | } |
100 | |
101 | pub fn format_if_some(self, format: Option<AudioFormat>) -> Self { |
102 | if let Some(format) = format { |
103 | self.format(format) |
104 | } else { |
105 | self |
106 | } |
107 | } |
108 | |
109 | pub fn format_list(self, formats: impl IntoIterator<Item = AudioFormat>) -> Self { |
110 | Self { |
111 | builder: self.builder.field( |
112 | glib::gstr!("format" ), |
113 | gst::List::new(formats.into_iter().map(|f| f.to_str())), |
114 | ), |
115 | } |
116 | } |
117 | |
118 | pub fn format_list_if( |
119 | self, |
120 | formats: impl IntoIterator<Item = AudioFormat>, |
121 | predicate: bool, |
122 | ) -> Self { |
123 | if predicate { |
124 | self.format_list(formats) |
125 | } else { |
126 | self |
127 | } |
128 | } |
129 | |
130 | pub fn format_list_if_some( |
131 | self, |
132 | formats: Option<impl IntoIterator<Item = AudioFormat>>, |
133 | ) -> Self { |
134 | if let Some(formats) = formats { |
135 | self.format_list(formats) |
136 | } else { |
137 | self |
138 | } |
139 | } |
140 | |
141 | pub fn format_list_if_not_empty(self, formats: impl IntoIterator<Item = AudioFormat>) -> Self { |
142 | let mut formats = formats.into_iter().peekable(); |
143 | if formats.peek().is_some() { |
144 | self.format_list(formats) |
145 | } else { |
146 | self |
147 | } |
148 | } |
149 | |
150 | pub fn rate(self, rate: i32) -> Self { |
151 | Self { |
152 | builder: self.builder.field(glib::gstr!("rate" ), rate), |
153 | } |
154 | } |
155 | |
156 | pub fn rate_if(self, rate: i32, predicate: bool) -> Self { |
157 | if predicate { |
158 | self.rate(rate) |
159 | } else { |
160 | self |
161 | } |
162 | } |
163 | |
164 | pub fn rate_if_some(self, rate: Option<i32>) -> Self { |
165 | if let Some(rate) = rate { |
166 | self.rate(rate) |
167 | } else { |
168 | self |
169 | } |
170 | } |
171 | |
172 | pub fn rate_range(self, rates: impl RangeBounds<i32>) -> Self { |
173 | let (start, end) = range_bounds_i32_start_end(rates); |
174 | let gst_rates = gst::IntRange::<i32>::new(start, end); |
175 | Self { |
176 | builder: self.builder.field(glib::gstr!("rate" ), gst_rates), |
177 | } |
178 | } |
179 | |
180 | pub fn rate_range_if(self, rates: impl RangeBounds<i32>, predicate: bool) -> Self { |
181 | if predicate { |
182 | self.rate_range(rates) |
183 | } else { |
184 | self |
185 | } |
186 | } |
187 | |
188 | pub fn rate_range_if_some(self, rates: Option<impl RangeBounds<i32>>) -> Self { |
189 | if let Some(rates) = rates { |
190 | self.rate_range(rates) |
191 | } else { |
192 | self |
193 | } |
194 | } |
195 | |
196 | pub fn rate_list(self, rates: impl IntoIterator<Item = i32>) -> Self { |
197 | Self { |
198 | builder: self |
199 | .builder |
200 | .field(glib::gstr!("rate" ), gst::List::new(rates)), |
201 | } |
202 | } |
203 | |
204 | pub fn rate_list_if(self, rates: impl IntoIterator<Item = i32>, predicate: bool) -> Self { |
205 | if predicate { |
206 | self.rate_list(rates) |
207 | } else { |
208 | self |
209 | } |
210 | } |
211 | |
212 | pub fn rate_list_if_some(self, rates: Option<impl IntoIterator<Item = i32>>) -> Self { |
213 | if let Some(rates) = rates { |
214 | self.rate_list(rates) |
215 | } else { |
216 | self |
217 | } |
218 | } |
219 | |
220 | pub fn rate_list_if_not_empty(self, rates: impl IntoIterator<Item = i32>) -> Self { |
221 | let mut rates = rates.into_iter().peekable(); |
222 | if rates.peek().is_some() { |
223 | self.rate_list(rates) |
224 | } else { |
225 | self |
226 | } |
227 | } |
228 | |
229 | pub fn channels(self, channels: i32) -> Self { |
230 | Self { |
231 | builder: self.builder.field(glib::gstr!("channels" ), channels), |
232 | } |
233 | } |
234 | |
235 | pub fn channels_if(self, channels: i32, predicate: bool) -> Self { |
236 | if predicate { |
237 | self.channels(channels) |
238 | } else { |
239 | self |
240 | } |
241 | } |
242 | |
243 | pub fn channels_if_some(self, channels: Option<i32>) -> Self { |
244 | if let Some(channels) = channels { |
245 | self.channels(channels) |
246 | } else { |
247 | self |
248 | } |
249 | } |
250 | |
251 | pub fn channels_range(self, channels: impl RangeBounds<i32>) -> Self { |
252 | let (start, end) = range_bounds_i32_start_end(channels); |
253 | let gst_channels: gst::IntRange<i32> = gst::IntRange::new(start, end); |
254 | Self { |
255 | builder: self.builder.field(glib::gstr!("channels" ), gst_channels), |
256 | } |
257 | } |
258 | |
259 | pub fn channels_range_if(self, channels: impl RangeBounds<i32>, predicate: bool) -> Self { |
260 | if predicate { |
261 | self.channels_range(channels) |
262 | } else { |
263 | self |
264 | } |
265 | } |
266 | |
267 | pub fn channels_range_if_some(self, channels: Option<impl RangeBounds<i32>>) -> Self { |
268 | if let Some(channels) = channels { |
269 | self.channels_range(channels) |
270 | } else { |
271 | self |
272 | } |
273 | } |
274 | |
275 | pub fn channels_list(self, channels: impl IntoIterator<Item = i32>) -> Self { |
276 | Self { |
277 | builder: self |
278 | .builder |
279 | .field(glib::gstr!("channels" ), gst::List::new(channels)), |
280 | } |
281 | } |
282 | |
283 | pub fn channels_list_if( |
284 | self, |
285 | channels: impl IntoIterator<Item = i32>, |
286 | predicate: bool, |
287 | ) -> Self { |
288 | if predicate { |
289 | self.channels_list(channels) |
290 | } else { |
291 | self |
292 | } |
293 | } |
294 | |
295 | pub fn channels_list_if_some(self, channels: Option<impl IntoIterator<Item = i32>>) -> Self { |
296 | if let Some(channels) = channels { |
297 | self.channels_list(channels) |
298 | } else { |
299 | self |
300 | } |
301 | } |
302 | |
303 | pub fn channels_list_if_not_empty(self, channels: impl IntoIterator<Item = i32>) -> Self { |
304 | let mut channels = channels.into_iter().peekable(); |
305 | if channels.peek().is_some() { |
306 | self.channels_list(channels) |
307 | } else { |
308 | self |
309 | } |
310 | } |
311 | |
312 | pub fn layout(self, layout: AudioLayout) -> Self { |
313 | Self { |
314 | builder: self |
315 | .builder |
316 | .field(glib::gstr!("layout" ), layout_str(layout)), |
317 | } |
318 | } |
319 | |
320 | pub fn layout_if(self, layout: AudioLayout, predicate: bool) -> Self { |
321 | if predicate { |
322 | self.layout(layout) |
323 | } else { |
324 | self |
325 | } |
326 | } |
327 | |
328 | pub fn layout_if_some(self, layout: Option<AudioLayout>) -> Self { |
329 | if let Some(layout) = layout { |
330 | self.layout(layout) |
331 | } else { |
332 | self |
333 | } |
334 | } |
335 | |
336 | pub fn layout_list(self, layouts: impl IntoIterator<Item = AudioLayout>) -> Self { |
337 | Self { |
338 | builder: self.builder.field( |
339 | glib::gstr!("layout" ), |
340 | gst::List::new(layouts.into_iter().map(layout_str)), |
341 | ), |
342 | } |
343 | } |
344 | |
345 | pub fn layout_list_if( |
346 | self, |
347 | layouts: impl IntoIterator<Item = AudioLayout>, |
348 | predicate: bool, |
349 | ) -> Self { |
350 | if predicate { |
351 | self.layout_list(layouts) |
352 | } else { |
353 | self |
354 | } |
355 | } |
356 | |
357 | pub fn layout_list_if_some( |
358 | self, |
359 | layouts: Option<impl IntoIterator<Item = AudioLayout>>, |
360 | ) -> Self { |
361 | if let Some(layouts) = layouts { |
362 | self.layout_list(layouts) |
363 | } else { |
364 | self |
365 | } |
366 | } |
367 | |
368 | pub fn layout_list_if_not_empty(self, layouts: impl IntoIterator<Item = AudioLayout>) -> Self { |
369 | let mut layouts = layouts.into_iter().peekable(); |
370 | if layouts.peek().is_some() { |
371 | self.layout_list(layouts) |
372 | } else { |
373 | self |
374 | } |
375 | } |
376 | |
377 | pub fn channel_mask(self, channel_mask: u64) -> Self { |
378 | Self { |
379 | builder: self |
380 | .builder |
381 | .field("channel-mask" , gst::Bitmask::new(channel_mask)), |
382 | } |
383 | } |
384 | |
385 | pub fn channel_mask_if(self, channel_mask: u64, predicate: bool) -> Self { |
386 | if predicate { |
387 | self.channel_mask(channel_mask) |
388 | } else { |
389 | self |
390 | } |
391 | } |
392 | |
393 | pub fn channel_mask_if_some(self, channel_mask: Option<u64>) -> Self { |
394 | if let Some(channel_mask) = channel_mask { |
395 | self.channel_mask(channel_mask) |
396 | } else { |
397 | self |
398 | } |
399 | } |
400 | |
401 | pub fn fallback_channel_mask(self) -> Self { |
402 | let channels = self.builder.structure().get::<i32>(glib::gstr!("channels" )); |
403 | match channels { |
404 | Ok(channels) => Self { |
405 | builder: self.builder.field( |
406 | glib::gstr!("channel-mask" ), |
407 | gst::Bitmask::new(crate::AudioChannelPosition::fallback_mask(channels as u32)), |
408 | ), |
409 | }, |
410 | Err(e) => panic!(" {e:?}" ), |
411 | } |
412 | } |
413 | |
414 | // rustdoc-stripper-ignore-next |
415 | /// Sets field `name` to the given value `value`. |
416 | /// |
417 | /// Overrides any default or previously defined value for `name`. |
418 | #[inline ] |
419 | pub fn field(self, name: impl IntoGStr, value: impl Into<glib::Value> + Send) -> Self { |
420 | Self { |
421 | builder: self.builder.field(name, value), |
422 | } |
423 | } |
424 | |
425 | gst::impl_builder_gvalue_extra_setters!(field); |
426 | |
427 | #[must_use ] |
428 | pub fn build(self) -> gst::Caps { |
429 | self.builder.build() |
430 | } |
431 | } |
432 | |
433 | fn range_bounds_i32_start_end(range: impl RangeBounds<i32>) -> (i32, i32) { |
434 | skip_assert_initialized!(); |
435 | let start: i32 = match range.start_bound() { |
436 | Unbounded => 1, |
437 | Excluded(n: &i32) => n + 1, |
438 | Included(n: &i32) => *n, |
439 | }; |
440 | let end: i32 = match range.end_bound() { |
441 | Unbounded => i32::MAX, |
442 | Excluded(n: &i32) => n - 1, |
443 | Included(n: &i32) => *n, |
444 | }; |
445 | (start, end) |
446 | } |
447 | |
448 | fn layout_str(layout: AudioLayout) -> &'static glib::GStr { |
449 | skip_assert_initialized!(); |
450 | match layout { |
451 | crate::AudioLayout::Interleaved => glib::gstr!("interleaved" ), |
452 | crate::AudioLayout::NonInterleaved => glib::gstr!("non-interleaved" ), |
453 | crate::AudioLayout::__Unknown(_) => glib::gstr!("unknown" ), |
454 | } |
455 | } |
456 | |
457 | #[cfg (test)] |
458 | mod tests { |
459 | use super::{AudioCapsBuilder, AudioFormat}; |
460 | |
461 | #[test ] |
462 | fn default_encoding() { |
463 | gst::init().unwrap(); |
464 | let caps = AudioCapsBuilder::new().build(); |
465 | assert_eq!(caps.structure(0).unwrap().name(), "audio/x-raw" ); |
466 | } |
467 | |
468 | #[test ] |
469 | fn explicit_encoding() { |
470 | gst::init().unwrap(); |
471 | let caps = AudioCapsBuilder::for_encoding("audio/mpeg" ).build(); |
472 | assert_eq!(caps.structure(0).unwrap().name(), "audio/mpeg" ); |
473 | } |
474 | |
475 | #[test ] |
476 | fn format_if() { |
477 | gst::init().unwrap(); |
478 | |
479 | let formats = [AudioFormat::S24be, AudioFormat::S16be, AudioFormat::U8]; |
480 | let caps_with_format = AudioCapsBuilder::for_encoding("audio/x-raw" ) |
481 | .format_list(formats) |
482 | .build(); |
483 | assert!(caps_with_format |
484 | .structure(0) |
485 | .unwrap() |
486 | .get::<gst::List>("format" ) |
487 | .unwrap() |
488 | .iter() |
489 | .map(|f| f.get::<String>().unwrap()) |
490 | .eq(formats.iter().map(|f| f.to_string()))); |
491 | |
492 | let caps = AudioCapsBuilder::for_encoding("audio/x-raw" ) |
493 | .format_list_if_some(Some(formats)) |
494 | .build(); |
495 | assert_eq!(caps, caps_with_format); |
496 | |
497 | let caps = AudioCapsBuilder::for_encoding("audio/x-raw" ) |
498 | .format_list_if_some(Option::<Vec<AudioFormat>>::None) |
499 | .build(); |
500 | assert!(!caps.structure(0).unwrap().has_field("format" )); |
501 | |
502 | let caps = AudioCapsBuilder::for_encoding("audio/x-raw" ) |
503 | .format_list_if_not_empty(formats) |
504 | .build(); |
505 | assert_eq!(caps, caps_with_format); |
506 | |
507 | let caps = AudioCapsBuilder::for_encoding("audio/x-raw" ) |
508 | .format_list_if_not_empty(Vec::<AudioFormat>::new()) |
509 | .build(); |
510 | assert!(!caps.structure(0).unwrap().has_field("format" )); |
511 | } |
512 | } |
513 | |