1 | use std::ops::{Bound::*, RangeBounds}; |
2 | |
3 | use glib::IntoGStr; |
4 | use gst::Caps; |
5 | |
6 | use crate::VideoFormat; |
7 | |
8 | pub struct VideoCapsBuilder<T> { |
9 | builder: gst::caps::Builder<T>, |
10 | } |
11 | |
12 | impl VideoCapsBuilder<gst::caps::NoFeature> { |
13 | // rustdoc-stripper-ignore-next |
14 | /// Constructs an `VideoCapsBuilder` for the "video/x-raw" encoding. |
15 | /// |
16 | /// If left unchanged, the resulting `Caps` will be initialized with: |
17 | /// - "video/x-raw" encoding. |
18 | /// - all available formats. |
19 | /// - maximum width range. |
20 | /// - maximum height range. |
21 | /// |
22 | /// Use [`VideoCapsBuilder::for_encoding`] to specify another encoding. |
23 | pub fn new() -> Self { |
24 | assert_initialized_main_thread!(); |
25 | let builder = Caps::builder(glib::gstr!("video/x-raw" )); |
26 | let builder = VideoCapsBuilder { builder }; |
27 | builder |
28 | .format_list(VideoFormat::iter_raw()) |
29 | .width_range(..) |
30 | .height_range(..) |
31 | .framerate_range(..) |
32 | } |
33 | |
34 | // rustdoc-stripper-ignore-next |
35 | /// Constructs an `VideoCapsBuilder` for the specified encoding. |
36 | /// |
37 | /// The resulting `Caps` will use the `encoding` argument as name |
38 | /// and will not contain any additional fields unless explicitly added. |
39 | pub fn for_encoding(encoding: impl IntoGStr) -> Self { |
40 | assert_initialized_main_thread!(); |
41 | VideoCapsBuilder { |
42 | builder: Caps::builder(encoding), |
43 | } |
44 | } |
45 | |
46 | pub fn any_features(self) -> VideoCapsBuilder<gst::caps::HasFeatures> { |
47 | VideoCapsBuilder { |
48 | builder: self.builder.any_features(), |
49 | } |
50 | } |
51 | |
52 | pub fn features( |
53 | self, |
54 | features: impl IntoIterator<Item = impl IntoGStr>, |
55 | ) -> VideoCapsBuilder<gst::caps::HasFeatures> { |
56 | VideoCapsBuilder { |
57 | builder: self.builder.features(features), |
58 | } |
59 | } |
60 | } |
61 | |
62 | impl Default for VideoCapsBuilder<gst::caps::NoFeature> { |
63 | fn default() -> Self { |
64 | Self::new() |
65 | } |
66 | } |
67 | |
68 | impl<T> VideoCapsBuilder<T> { |
69 | pub fn format(self, format: VideoFormat) -> Self { |
70 | Self { |
71 | builder: self.builder.field(glib::gstr!("format" ), format.to_str()), |
72 | } |
73 | } |
74 | |
75 | pub fn format_list(self, formats: impl IntoIterator<Item = VideoFormat>) -> Self { |
76 | Self { |
77 | builder: self.builder.field( |
78 | glib::gstr!("format" ), |
79 | gst::List::new(formats.into_iter().map(|f| f.to_str())), |
80 | ), |
81 | } |
82 | } |
83 | |
84 | pub fn width(self, width: i32) -> Self { |
85 | Self { |
86 | builder: self.builder.field(glib::gstr!("width" ), width), |
87 | } |
88 | } |
89 | |
90 | pub fn width_range(self, widths: impl RangeBounds<i32>) -> Self { |
91 | let (start, end) = range_bounds_i32_start_end(widths); |
92 | let gst_widths: gst::IntRange<i32> = gst::IntRange::new(start, end); |
93 | Self { |
94 | builder: self.builder.field(glib::gstr!("width" ), gst_widths), |
95 | } |
96 | } |
97 | |
98 | pub fn width_list(self, widths: impl IntoIterator<Item = i32>) -> Self { |
99 | Self { |
100 | builder: self |
101 | .builder |
102 | .field(glib::gstr!("width" ), gst::List::new(widths)), |
103 | } |
104 | } |
105 | |
106 | pub fn height(self, height: i32) -> Self { |
107 | Self { |
108 | builder: self.builder.field(glib::gstr!("height" ), height), |
109 | } |
110 | } |
111 | |
112 | pub fn height_range(self, heights: impl RangeBounds<i32>) -> Self { |
113 | let (start, end) = range_bounds_i32_start_end(heights); |
114 | let gst_heights: gst::IntRange<i32> = gst::IntRange::new(start, end); |
115 | Self { |
116 | builder: self.builder.field(glib::gstr!("height" ), gst_heights), |
117 | } |
118 | } |
119 | |
120 | pub fn height_list(self, heights: impl IntoIterator<Item = i32>) -> Self { |
121 | Self { |
122 | builder: self |
123 | .builder |
124 | .field(glib::gstr!("height" ), gst::List::new(heights)), |
125 | } |
126 | } |
127 | |
128 | pub fn framerate(self, framerate: gst::Fraction) -> Self { |
129 | Self { |
130 | builder: self.builder.field(glib::gstr!("framerate" ), framerate), |
131 | } |
132 | } |
133 | |
134 | pub fn framerate_range(self, framerates: impl RangeBounds<gst::Fraction>) -> Self { |
135 | let start = match framerates.start_bound() { |
136 | Unbounded => gst::Fraction::new(0, 1), |
137 | Excluded(n) => next_fraction(*n), |
138 | Included(n) => { |
139 | assert!(n.numer() >= 0); |
140 | *n |
141 | } |
142 | }; |
143 | let end = match framerates.end_bound() { |
144 | Unbounded => gst::Fraction::new(i32::MAX, 1), |
145 | Excluded(n) => previous_fraction(*n), |
146 | Included(n) => { |
147 | assert!(n.numer() >= 0); |
148 | *n |
149 | } |
150 | }; |
151 | assert!(start <= end); |
152 | let framerates: gst::FractionRange = gst::FractionRange::new(start, end); |
153 | Self { |
154 | builder: self.builder.field(glib::gstr!("framerate" ), framerates), |
155 | } |
156 | } |
157 | |
158 | pub fn framerate_list(self, framerates: impl IntoIterator<Item = gst::Fraction>) -> Self { |
159 | Self { |
160 | builder: self |
161 | .builder |
162 | .field(glib::gstr!("framerate" ), gst::List::new(framerates)), |
163 | } |
164 | } |
165 | |
166 | pub fn pixel_aspect_ratio(self, pixel_aspect_ratio: gst::Fraction) -> Self { |
167 | Self { |
168 | builder: self.builder.field("pixel-aspect-ratio" , pixel_aspect_ratio), |
169 | } |
170 | } |
171 | |
172 | pub fn pixel_aspect_ratio_range( |
173 | self, |
174 | pixel_aspect_ratios: impl RangeBounds<gst::Fraction>, |
175 | ) -> Self { |
176 | let start = match pixel_aspect_ratios.start_bound() { |
177 | Unbounded => gst::Fraction::new(1, i32::MAX), |
178 | Excluded(n) => next_fraction(*n), |
179 | Included(n) => { |
180 | assert!(n.numer() >= 0); |
181 | *n |
182 | } |
183 | }; |
184 | let end = match pixel_aspect_ratios.end_bound() { |
185 | Unbounded => gst::Fraction::new(i32::MAX, 1), |
186 | Excluded(n) => previous_fraction(*n), |
187 | Included(n) => { |
188 | assert!(n.numer() >= 0); |
189 | *n |
190 | } |
191 | }; |
192 | assert!(start <= end); |
193 | let pixel_aspect_ratios: gst::FractionRange = gst::FractionRange::new(start, end); |
194 | Self { |
195 | builder: self |
196 | .builder |
197 | .field("pixel-aspect-ratio" , pixel_aspect_ratios), |
198 | } |
199 | } |
200 | |
201 | pub fn pixel_aspect_ratio_list( |
202 | self, |
203 | pixel_aspect_ratios: impl IntoIterator<Item = gst::Fraction>, |
204 | ) -> Self { |
205 | Self { |
206 | builder: self |
207 | .builder |
208 | .field("pixel-aspect-ratio" , gst::List::new(pixel_aspect_ratios)), |
209 | } |
210 | } |
211 | |
212 | pub fn field(self, name: &str, value: impl Into<glib::Value> + Send) -> Self { |
213 | Self { |
214 | builder: self.builder.field(name, value), |
215 | } |
216 | } |
217 | |
218 | #[must_use ] |
219 | pub fn build(self) -> gst::Caps { |
220 | self.builder.build() |
221 | } |
222 | } |
223 | |
224 | fn range_bounds_i32_start_end(range: impl RangeBounds<i32>) -> (i32, i32) { |
225 | skip_assert_initialized!(); |
226 | let start: i32 = match range.start_bound() { |
227 | Unbounded => 1, |
228 | Excluded(n: &i32) => n + 1, |
229 | Included(n: &i32) => *n, |
230 | }; |
231 | let end: i32 = match range.end_bound() { |
232 | Unbounded => i32::MAX, |
233 | Excluded(n: &i32) => n - 1, |
234 | Included(n: &i32) => *n, |
235 | }; |
236 | (start, end) |
237 | } |
238 | |
239 | // https://math.stackexchange.com/questions/39582/how-to-compute-next-previous-representable-rational-number/3798608#3798608 |
240 | |
241 | /* Extended Euclidean Algorithm: computes (g, x, y), |
242 | * such that a*x + b*y = g = gcd(a, b) >= 0. */ |
243 | fn xgcd(mut a: i64, mut b: i64) -> (i64, i64, i64) { |
244 | skip_assert_initialized!(); |
245 | let mut x0: i64 = 0i64; |
246 | let mut x1: i64 = 1i64; |
247 | let mut y0: i64 = 1i64; |
248 | let mut y1: i64 = 0i64; |
249 | while a != 0 { |
250 | let q: i64; |
251 | (q, a, b) = (b / a, b % a, a); |
252 | (y0, y1) = (y1, y0 - q * y1); |
253 | (x0, x1) = (x1, x0 - q * x1); |
254 | } |
255 | if b >= 0 { |
256 | (b, x0, y0) |
257 | } else { |
258 | (-b, -x0, -y0) |
259 | } |
260 | } |
261 | |
262 | /* Computes the neighbours of p/q in the Farey sequence of order n. */ |
263 | fn farey_neighbours(p: i32, q: i32) -> (i32, i32, i32, i32) { |
264 | skip_assert_initialized!(); |
265 | let n: i64 = i32::MAX as i64; |
266 | assert!(q != 0); |
267 | let mut p: i64 = p as i64; |
268 | let mut q: i64 = q as i64; |
269 | if q < 0 { |
270 | p = -p; |
271 | q = -q; |
272 | } |
273 | let (g: i64, r: i64, _) = xgcd(a:p, b:q); |
274 | p /= g; |
275 | q /= g; |
276 | let b: i64 = ((n - r) / q) * q + r; |
277 | let a: i64 = (b * p - 1) / q; |
278 | let d: i64 = ((n + r) / q) * q - r; |
279 | let c: i64 = (d * p + 1) / q; |
280 | (a as i32, b as i32, c as i32, d as i32) |
281 | } |
282 | |
283 | fn previous_fraction(fraction: gst::Fraction) -> gst::Fraction { |
284 | skip_assert_initialized!(); |
285 | let num: i32 = fraction.numer(); |
286 | let den: i32 = fraction.denom(); |
287 | let (new_num: i32, new_den: i32); |
288 | if num < den { |
289 | (new_num, new_den, _, _) = farey_neighbours(p:num, q:den); |
290 | } else { |
291 | (_, _, new_den, new_num) = farey_neighbours(p:den, q:num); |
292 | } |
293 | gst::Fraction::new(new_num, new_den) |
294 | } |
295 | |
296 | fn next_fraction(fraction: gst::Fraction) -> gst::Fraction { |
297 | skip_assert_initialized!(); |
298 | let num: i32 = fraction.numer(); |
299 | let den: i32 = fraction.denom(); |
300 | let (new_num: i32, new_den: i32); |
301 | if num < den { |
302 | (_, _, new_num, new_den) = farey_neighbours(p:num, q:den); |
303 | } else { |
304 | (new_den, new_num, _, _) = farey_neighbours(p:den, q:num); |
305 | } |
306 | gst::Fraction::new(new_num, new_den) |
307 | } |
308 | |
309 | #[cfg (test)] |
310 | mod tests { |
311 | use super::{next_fraction, previous_fraction, VideoCapsBuilder}; |
312 | |
313 | #[test ] |
314 | fn default_encoding() { |
315 | gst::init().unwrap(); |
316 | let caps = VideoCapsBuilder::new().build(); |
317 | assert_eq!(caps.structure(0).unwrap().name(), "video/x-raw" ); |
318 | } |
319 | |
320 | #[test ] |
321 | fn explicit_encoding() { |
322 | gst::init().unwrap(); |
323 | let caps = VideoCapsBuilder::for_encoding("video/mpeg" ).build(); |
324 | assert_eq!(caps.structure(0).unwrap().name(), "video/mpeg" ); |
325 | } |
326 | |
327 | #[test ] |
328 | fn test_0_1_fraction() { |
329 | gst::init().unwrap(); |
330 | let zero_over_one = gst::Fraction::new(0, 1); |
331 | let prev = previous_fraction(zero_over_one); |
332 | assert_eq!(prev.numer(), -1); |
333 | assert_eq!(prev.denom(), i32::MAX); |
334 | let next = next_fraction(zero_over_one); |
335 | assert_eq!(next.numer(), 1); |
336 | assert_eq!(next.denom(), i32::MAX); |
337 | } |
338 | |
339 | #[test ] |
340 | fn test_25_1() { |
341 | gst::init().unwrap(); |
342 | let twentyfive = gst::Fraction::new(25, 1); |
343 | let next = next_fraction(twentyfive); |
344 | //25.000000011641532 |
345 | assert_eq!(next.numer(), 2147483626); |
346 | assert_eq!(next.denom(), 85899345); |
347 | let prev = previous_fraction(twentyfive); |
348 | //24.999999988358468 |
349 | assert_eq!(prev.numer(), 2147483624); |
350 | assert_eq!(prev.denom(), 85899345); |
351 | } |
352 | #[test ] |
353 | fn test_1_25() { |
354 | gst::init().unwrap(); |
355 | let twentyfive = gst::Fraction::new(1, 25); |
356 | let next = next_fraction(twentyfive); |
357 | //0.040000000018626 |
358 | assert_eq!(next.numer(), 85899345); |
359 | assert_eq!(next.denom(), 2147483624); |
360 | let prev = previous_fraction(twentyfive); |
361 | //0.039999999981374 |
362 | assert_eq!(prev.numer(), 85899345); |
363 | assert_eq!(prev.denom(), 2147483626); |
364 | } |
365 | } |
366 | |