gstreamer_audio/
caps.rs

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