gstreamer_audio/
caps.rs

1use std::ops::{Bound::*, RangeBounds};
2
3use gst::Caps;
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    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
80impl Default for AudioCapsBuilder<gst::caps::NoFeature> {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86impl<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
433fn range_bounds_i32_start_end(range: impl RangeBounds<i32>) -> (i32, i32) {
434    skip_assert_initialized!();
435    let start = match range.start_bound() {
436        Unbounded => 1,
437        Excluded(n) => n + 1,
438        Included(n) => *n,
439    };
440    let end = match range.end_bound() {
441        Unbounded => i32::MAX,
442        Excluded(n) => n - 1,
443        Included(n) => *n,
444    };
445    (start, end)
446}
447
448fn 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)]
458mod 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}