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_list(self, formats: impl IntoIterator<Item = AudioFormat>) -> Self {
94 Self {
95 builder: self.builder.field(
96 glib::gstr!("format"),
97 gst::List::new(formats.into_iter().map(|f| f.to_str())),
98 ),
99 }
100 }
101
102 pub fn rate(self, rate: i32) -> Self {
103 Self {
104 builder: self.builder.field(glib::gstr!("rate"), rate),
105 }
106 }
107
108 pub fn rate_range(self, rates: impl RangeBounds<i32>) -> Self {
109 let (start, end) = range_bounds_i32_start_end(rates);
110 let gst_rates = gst::IntRange::<i32>::new(start, end);
111 Self {
112 builder: self.builder.field(glib::gstr!("rate"), gst_rates),
113 }
114 }
115
116 pub fn rate_list(self, rates: impl IntoIterator<Item = i32>) -> Self {
117 Self {
118 builder: self
119 .builder
120 .field(glib::gstr!("rate"), gst::List::new(rates)),
121 }
122 }
123
124 pub fn channels(self, channels: i32) -> Self {
125 Self {
126 builder: self.builder.field(glib::gstr!("channels"), channels),
127 }
128 }
129
130 pub fn channels_range(self, channels: impl RangeBounds<i32>) -> Self {
131 let (start, end) = range_bounds_i32_start_end(channels);
132 let gst_channels: gst::IntRange<i32> = gst::IntRange::new(start, end);
133 Self {
134 builder: self.builder.field(glib::gstr!("channels"), gst_channels),
135 }
136 }
137
138 pub fn channels_list(self, channels: impl IntoIterator<Item = i32>) -> Self {
139 Self {
140 builder: self
141 .builder
142 .field(glib::gstr!("channels"), gst::List::new(channels)),
143 }
144 }
145
146 pub fn layout(self, layout: AudioLayout) -> Self {
147 Self {
148 builder: self
149 .builder
150 .field(glib::gstr!("layout"), layout_str(layout)),
151 }
152 }
153
154 pub fn layout_list(self, layouts: impl IntoIterator<Item = AudioLayout>) -> Self {
155 Self {
156 builder: self.builder.field(
157 glib::gstr!("layout"),
158 gst::List::new(layouts.into_iter().map(layout_str)),
159 ),
160 }
161 }
162
163 pub fn channel_mask(self, channel_mask: u64) -> Self {
164 Self {
165 builder: self
166 .builder
167 .field("channel-mask", gst::Bitmask::new(channel_mask)),
168 }
169 }
170
171 pub fn fallback_channel_mask(self) -> Self {
172 let channels = self.builder.structure().get::<i32>(glib::gstr!("channels"));
173 match channels {
174 Ok(channels) => Self {
175 builder: self.builder.field(
176 glib::gstr!("channel-mask"),
177 gst::Bitmask::new(crate::AudioChannelPosition::fallback_mask(channels as u32)),
178 ),
179 },
180 Err(e) => panic!("{e:?}"),
181 }
182 }
183
184 pub fn field(self, name: &str, value: impl Into<glib::Value> + Send) -> Self {
185 Self {
186 builder: self.builder.field(name, value),
187 }
188 }
189
190 #[must_use]
191 pub fn build(self) -> gst::Caps {
192 self.builder.build()
193 }
194}
195
196fn range_bounds_i32_start_end(range: impl RangeBounds<i32>) -> (i32, i32) {
197 skip_assert_initialized!();
198 let start: i32 = match range.start_bound() {
199 Unbounded => 1,
200 Excluded(n: &i32) => n + 1,
201 Included(n: &i32) => *n,
202 };
203 let end: i32 = match range.end_bound() {
204 Unbounded => i32::MAX,
205 Excluded(n: &i32) => n - 1,
206 Included(n: &i32) => *n,
207 };
208 (start, end)
209}
210
211fn layout_str(layout: AudioLayout) -> &'static glib::GStr {
212 skip_assert_initialized!();
213 match layout {
214 crate::AudioLayout::Interleaved => glib::gstr!("interleaved"),
215 crate::AudioLayout::NonInterleaved => glib::gstr!("non-interleaved"),
216 crate::AudioLayout::__Unknown(_) => glib::gstr!("unknown"),
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::AudioCapsBuilder;
223
224 #[test]
225 fn default_encoding() {
226 gst::init().unwrap();
227 let caps = AudioCapsBuilder::new().build();
228 assert_eq!(caps.structure(0).unwrap().name(), "audio/x-raw");
229 }
230
231 #[test]
232 fn explicit_encoding() {
233 gst::init().unwrap();
234 let caps = AudioCapsBuilder::for_encoding("audio/mpeg").build();
235 assert_eq!(caps.structure(0).unwrap().name(), "audio/mpeg");
236 }
237}
238