1 | use std::ops::{Bound::*, RangeBounds}; |
2 | |
3 | use gst::Caps; |
4 | |
5 | use glib::IntoGStr; |
6 | |
7 | use crate::{AudioFormat, AudioLayout}; |
8 | |
9 | pub struct AudioCapsBuilder<T> { |
10 | builder: gst::caps::Builder<T>, |
11 | } |
12 | |
13 | impl 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 | |
80 | impl Default for AudioCapsBuilder<gst::caps::NoFeature> { |
81 | fn default() -> Self { |
82 | Self::new() |
83 | } |
84 | } |
85 | |
86 | impl<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 | |
196 | fn 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 | |
211 | fn 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)] |
221 | mod 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 | |