Skip to main content

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