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 { 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 #[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 #[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 #[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}