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 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 pub fn new_interleaved() -> Self {
49 AudioCapsBuilder::new().layout(AudioLayout::Interleaved)
50 }
51
52 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 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 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 #[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 #[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 #[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}