use std::ops::{Bound::*, RangeBounds}; use gst::Caps; use glib::IntoGStr; use crate::{AudioFormat, AudioLayout}; pub struct AudioCapsBuilder { builder: gst::caps::Builder, } impl AudioCapsBuilder { // rustdoc-stripper-ignore-next /// Constructs an `AudioCapsBuilder` for the "audio/x-raw" encoding. /// /// If left unchanged, the resulting `Caps` will be initialized with: /// - "audio/x-raw" encoding. /// - maximum rate range. /// - maximum channels range. /// - both interleaved and non-interleaved layouts. /// - all available formats. /// /// Use [`AudioCapsBuilder::for_encoding`] to specify another encoding. pub fn new() -> Self { assert_initialized_main_thread!(); let builder = Caps::builder(glib::gstr!("audio/x-raw")); let builder = AudioCapsBuilder { builder }; builder .rate_range(..) .channels_range(..) .layout_list([AudioLayout::Interleaved, AudioLayout::NonInterleaved]) .format_list(AudioFormat::iter_raw()) } // rustdoc-stripper-ignore-next /// Constructs an `AudioCapsBuilder` for the "audio/x-raw" encoding /// with interleaved layout. /// /// If left unchanged, the resulting `Caps` will be initialized with: /// - "audio/x-raw" encoding. /// - maximum rate range. /// - maximum channels range. /// - interleaved layout. /// - all available formats. /// /// Use [`AudioCapsBuilder::for_encoding`] to specify another encoding. pub fn new_interleaved() -> Self { AudioCapsBuilder::new().layout(AudioLayout::Interleaved) } // rustdoc-stripper-ignore-next /// Constructs an `AudioCapsBuilder` for the specified encoding. /// /// The resulting `Caps` will use the `encoding` argument as name /// and will not contain any additional fields unless explicitly added. pub fn for_encoding(encoding: impl IntoGStr) -> Self { assert_initialized_main_thread!(); AudioCapsBuilder { builder: Caps::builder(encoding), } } pub fn any_features(self) -> AudioCapsBuilder { AudioCapsBuilder { builder: self.builder.any_features(), } } pub fn features( self, features: impl IntoIterator, ) -> AudioCapsBuilder { AudioCapsBuilder { builder: self.builder.features(features), } } } impl Default for AudioCapsBuilder { fn default() -> Self { Self::new() } } impl AudioCapsBuilder { pub fn format(self, format: AudioFormat) -> Self { Self { builder: self.builder.field(glib::gstr!("format"), format.to_str()), } } pub fn format_if_some(self, format: Option) -> Self { if let Some(format) = format { self.format(format) } else { self } } pub fn format_list(self, formats: impl IntoIterator) -> Self { Self { builder: self.builder.field( glib::gstr!("format"), gst::List::new(formats.into_iter().map(|f| f.to_str())), ), } } pub fn format_list_if_some( self, formats: Option>, ) -> Self { if let Some(formats) = formats { self.format_list(formats) } else { self } } pub fn rate(self, rate: i32) -> Self { Self { builder: self.builder.field(glib::gstr!("rate"), rate), } } pub fn rate_if_some(self, rate: Option) -> Self { if let Some(rate) = rate { self.rate(rate) } else { self } } pub fn rate_range(self, rates: impl RangeBounds) -> Self { let (start, end) = range_bounds_i32_start_end(rates); let gst_rates = gst::IntRange::::new(start, end); Self { builder: self.builder.field(glib::gstr!("rate"), gst_rates), } } pub fn rate_range_if_some(self, rates: Option>) -> Self { if let Some(rates) = rates { self.rate_range(rates) } else { self } } pub fn rate_list(self, rates: impl IntoIterator) -> Self { Self { builder: self .builder .field(glib::gstr!("rate"), gst::List::new(rates)), } } pub fn rate_list_if_some(self, rates: Option>) -> Self { if let Some(rates) = rates { self.rate_list(rates) } else { self } } pub fn channels(self, channels: i32) -> Self { Self { builder: self.builder.field(glib::gstr!("channels"), channels), } } pub fn channels_if_some(self, channels: Option) -> Self { if let Some(channels) = channels { self.channels(channels) } else { self } } pub fn channels_range(self, channels: impl RangeBounds) -> Self { let (start, end) = range_bounds_i32_start_end(channels); let gst_channels: gst::IntRange = gst::IntRange::new(start, end); Self { builder: self.builder.field(glib::gstr!("channels"), gst_channels), } } pub fn channels_range_if_some(self, channels: Option>) -> Self { if let Some(channels) = channels { self.channels_range(channels) } else { self } } pub fn channels_list(self, channels: impl IntoIterator) -> Self { Self { builder: self .builder .field(glib::gstr!("channels"), gst::List::new(channels)), } } pub fn channels_list_if_some(self, channels: Option>) -> Self { if let Some(channels) = channels { self.channels_list(channels) } else { self } } pub fn layout(self, layout: AudioLayout) -> Self { Self { builder: self .builder .field(glib::gstr!("layout"), layout_str(layout)), } } pub fn layout_if_some(self, layout: Option) -> Self { if let Some(layout) = layout { self.layout(layout) } else { self } } pub fn layout_list(self, layouts: impl IntoIterator) -> Self { Self { builder: self.builder.field( glib::gstr!("layout"), gst::List::new(layouts.into_iter().map(layout_str)), ), } } pub fn layout_list_if_some( self, layouts: Option>, ) -> Self { if let Some(layouts) = layouts { self.layout_list(layouts) } else { self } } pub fn channel_mask(self, channel_mask: u64) -> Self { Self { builder: self .builder .field("channel-mask", gst::Bitmask::new(channel_mask)), } } pub fn channel_mask_if_some(self, channel_mask: Option) -> Self { if let Some(channel_mask) = channel_mask { self.channel_mask(channel_mask) } else { self } } pub fn fallback_channel_mask(self) -> Self { let channels = self.builder.structure().get::(glib::gstr!("channels")); match channels { Ok(channels) => Self { builder: self.builder.field( glib::gstr!("channel-mask"), gst::Bitmask::new(crate::AudioChannelPosition::fallback_mask(channels as u32)), ), }, Err(e) => panic!("{e:?}"), } } pub fn field(self, name: &str, value: impl Into + Send) -> Self { Self { builder: self.builder.field(name, value), } } pub fn field_if_some(self, name: &str, value: Option + Send>) -> Self { if let Some(value) = value { self.field(name, value) } else { self } } #[must_use] pub fn build(self) -> gst::Caps { self.builder.build() } } fn range_bounds_i32_start_end(range: impl RangeBounds) -> (i32, i32) { skip_assert_initialized!(); let start = match range.start_bound() { Unbounded => 1, Excluded(n) => n + 1, Included(n) => *n, }; let end = match range.end_bound() { Unbounded => i32::MAX, Excluded(n) => n - 1, Included(n) => *n, }; (start, end) } fn layout_str(layout: AudioLayout) -> &'static glib::GStr { skip_assert_initialized!(); match layout { crate::AudioLayout::Interleaved => glib::gstr!("interleaved"), crate::AudioLayout::NonInterleaved => glib::gstr!("non-interleaved"), crate::AudioLayout::__Unknown(_) => glib::gstr!("unknown"), } } #[cfg(test)] mod tests { use super::AudioCapsBuilder; #[test] fn default_encoding() { gst::init().unwrap(); let caps = AudioCapsBuilder::new().build(); assert_eq!(caps.structure(0).unwrap().name(), "audio/x-raw"); } #[test] fn explicit_encoding() { gst::init().unwrap(); let caps = AudioCapsBuilder::for_encoding("audio/mpeg").build(); assert_eq!(caps.structure(0).unwrap().name(), "audio/mpeg"); } }